From 0af16387ae1c244d2160c2408a3e6c61a39a1a2b Mon Sep 17 00:00:00 2001 From: Tianyi Cui <53024+tianyicui@users.noreply.github.com> Date: Mon, 27 Jul 2026 21:30:28 +0800 Subject: [PATCH] fix(telemetry): rename record waterfall --- ...23-session-telemetry-otel-revival.i18n.yaml | 6 +++--- ...026-07-23-session-telemetry-otel-revival.md | 4 ++-- ...-07-23-session-telemetry-otel-revival.zh.md | 4 ++-- docs/cordis-catalog/events.md | 13 +++++++------ docs/cordis-catalog/services.md | 2 +- docs/event-producer-consumer.md | 2 +- .../tests/fixtures/telemetry-otel.cordis.yml | 2 +- .../tests/fixtures/telemetry-redact-rule.ts | 4 ++-- packages/cordis/tool-cordis/src/api-catalog.ts | 8 ++++---- packages/telemetry/README.i18n.yaml | 6 +++--- packages/telemetry/README.md | 2 +- packages/telemetry/README.zh.md | 2 +- .../session-telemetry-otel/README.i18n.yaml | 6 +++--- .../telemetry/session-telemetry-otel/README.md | 2 +- .../session-telemetry-otel/README.zh.md | 2 +- .../session-telemetry/README.i18n.yaml | 6 +++--- packages/telemetry/session-telemetry/README.md | 4 ++-- .../telemetry/session-telemetry/README.zh.md | 4 ++-- .../session-telemetry/src/coordinator.ts | 8 ++++---- .../telemetry/session-telemetry/src/index.ts | 7 ++++--- .../session-telemetry/tests/redact.spec.ts | 18 +++++++++--------- 21 files changed, 57 insertions(+), 55 deletions(-) diff --git a/.agents/notes/implemented/feature/2026-07-23-session-telemetry-otel-revival.i18n.yaml b/.agents/notes/implemented/feature/2026-07-23-session-telemetry-otel-revival.i18n.yaml index 8c4c025c86..cd9e4f7e9f 100644 --- a/.agents/notes/implemented/feature/2026-07-23-session-telemetry-otel-revival.i18n.yaml +++ b/.agents/notes/implemented/feature/2026-07-23-session-telemetry-otel-revival.i18n.yaml @@ -1,6 +1,6 @@ # Bilingual-pair consistency record (docs/i18n/README.md): the git blob hash of each # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: -# pnpm run verify-translation-pairing --write -2026-07-23-session-telemetry-otel-revival.md: 1c855ad8d3263f5ff3343b480983f7cf14d9d4c6 -2026-07-23-session-telemetry-otel-revival.zh.md: 371e8f9fbfbdcc7c74fa5ae39406ea4bf159bc81 +# pnpm run verify-translation-pairing --write .agents/notes/implemented/feature/2026-07-23-session-telemetry-otel-revival.md +2026-07-23-session-telemetry-otel-revival.md: a58598d8a956d47cb0cf6aa3e659f38314bc4b17 +2026-07-23-session-telemetry-otel-revival.zh.md: cc09717e349d5ae2ab5157bf46de30b1823c775f diff --git a/.agents/notes/implemented/feature/2026-07-23-session-telemetry-otel-revival.md b/.agents/notes/implemented/feature/2026-07-23-session-telemetry-otel-revival.md index 1c855ad8d3..a58598d8a9 100644 --- a/.agents/notes/implemented/feature/2026-07-23-session-telemetry-otel-revival.md +++ b/.agents/notes/implemented/feature/2026-07-23-session-telemetry-otel-revival.md @@ -13,7 +13,7 @@ Every deployment that wants harness sessions in an observability stack must hand `packages/telemetry/` revives the two reviewed packages under the SDK stance — the harness provides the capability, the deployment configures where records go and owns what leaves in them: - **`@deepseek-ai/dsh-session-telemetry`** — the seam. `TelemetryBackend` (`emit`/`flush?`/`shutdown`), the service-registered `Telemetry` form, and `TelemetryCoordinator` owning capture: adoption with cursor read-back, the per-append firehose (project → `structuredClone` → redact → `emit`, zero I/O), the fixed first-chunk-per-(turn, step) projection, the `agent/error` relay, and dispose-time `shutdown` records. -- **The `telemetry/redact` waterfall** — the delta over the branch version. Every record passes it before reaching any backend; the seam ships NO rules of its own — the innermost `next()` is a pass-through, deployments mount their rules as listeners (stacking by transforming `next()`'s return value), and a throwing rule withholds the record fail-closed. Redaction applies to the exported copy only; the canonical log is never rewritten. +- **The `telemetry/record` waterfall** — the delta over the branch version and the seam's redaction extension point. Every record passes it before reaching any backend; the seam ships NO rules of its own — the innermost `next()` is a pass-through, deployments mount their rules as listeners (stacking by transforming `next()`'s return value), and a throwing rule withholds the record fail-closed. Redaction applies to the exported copy only; the canonical log is never rewritten. - **`@deepseek-ai/dsh-session-telemetry-otel`** — the reference backend: OTel JS SDK log pipeline (`LoggerProvider` → `BatchLogRecordProcessor` → OTLP/HTTP exporter), configured verbatim through `exporter`/`processor` passthroughs. `exporter.url` is required and validated at load; unmounted or unconfigured, nothing leaves the process. The boundary axiom holds: the harness's aspect ends at `emit()`. Batching, retry, queueing, and loss policy are the reporting SDK's, configured through passthroughs — delivery is best-effort (at-most-once across a crash), which the READMEs state plainly. @@ -34,4 +34,4 @@ The boundary axiom holds: the harness's aspect ends at `emit()`. Batching, retry ## Consequences -A deployment adds one `cordis.yml` entry with an OTLP endpoint and gets its session stream in any OTel-compatible stack; removing the entry is the opt-out, with no residual state. A rule-free deployment exports records exactly as captured — including any credentials embedded in file contents or command output — so a deployment crossing a trust boundary must mount `telemetry/redact` listeners, and both READMEs state this plainly. Where rules are mounted, exported bodies can differ from canonical log bytes, so receivers must not treat telemetry as a byte-exact replica; the log remains the source of truth. Crash durability is explicitly out of scope until the outbox decision above is revisited. +A deployment adds one `cordis.yml` entry with an OTLP endpoint and gets its session stream in any OTel-compatible stack; removing the entry is the opt-out, with no residual state. A rule-free deployment exports records exactly as captured — including any credentials embedded in file contents or command output — so a deployment crossing a trust boundary must mount `telemetry/record` listeners, and both READMEs state this plainly. Where rules are mounted, exported bodies can differ from canonical log bytes, so receivers must not treat telemetry as a byte-exact replica; the log remains the source of truth. Crash durability is explicitly out of scope until the outbox decision above is revisited. diff --git a/.agents/notes/implemented/feature/2026-07-23-session-telemetry-otel-revival.zh.md b/.agents/notes/implemented/feature/2026-07-23-session-telemetry-otel-revival.zh.md index 371e8f9fbf..cc09717e34 100644 --- a/.agents/notes/implemented/feature/2026-07-23-session-telemetry-otel-revival.zh.md +++ b/.agents/notes/implemented/feature/2026-07-23-session-telemetry-otel-revival.zh.md @@ -13,7 +13,7 @@ Status: implemented `packages/telemetry/` 以 SDK 立场复活这两个经过评审的包——harness 提供能力,部署方配置上报去向并对导出内容负责: - **`@deepseek-ai/dsh-session-telemetry`** —— seam 本体。`TelemetryBackend`(`emit`/`flush?`/`shutdown`)、服务注册形态的 `Telemetry`、以及拥有捕获侧的 `TelemetryCoordinator`:带游标回读的收养、逐 append 的 firehose(投影 → `structuredClone` → 脱敏 → `emit`,零 I/O)、固定的每 (turn, step) 首 chunk 投影、`agent/error` 转发、以及 dispose 时的 `shutdown` 记录。 -- **`telemetry/redact` waterfall** —— 相对分支版本的增量。每条记录抵达任何 backend 前必经此处;seam 自身不带任何规则——最内层 `next()` 原样透传,部署方以监听器挂载自己的规则(通过变换 `next()` 的返回值堆叠),抛异常的规则将该记录 fail-closed 扣下。脱敏只作用于导出副本;canonical log 永不改写。 +- **`telemetry/record` waterfall** —— 相对分支版本的增量,也是该 seam 的脱敏扩展点。每条记录抵达任何 backend 前必经此处;seam 自身不带任何规则——最内层 `next()` 原样透传,部署方以监听器挂载自己的规则(通过变换 `next()` 的返回值堆叠),抛异常的规则将该记录 fail-closed 扣下。脱敏只作用于导出副本;canonical log 永不改写。 - **`@deepseek-ai/dsh-session-telemetry-otel`** —— 参考 backend:OTel JS SDK 日志管线(`LoggerProvider` → `BatchLogRecordProcessor` → OTLP/HTTP exporter),经 `exporter`/`processor` passthrough 原样配置。`exporter.url` 必填且加载时校验;未挂载或未配置时,任何数据都不会离开进程。 边界公理保持不变:harness 的职责止于 `emit()`。批处理、重试、排队与丢失策略属于 reporting SDK,经 passthrough 配置——投递是尽力而为(崩溃时至多一次),README 对此如实陈述。 @@ -34,4 +34,4 @@ Status: implemented ## Consequences -部署方在 `cordis.yml` 加一个带 OTLP endpoint 的条目即可把会话流接入任何 OTel 兼容体系;删除条目即退出,无残留状态。未挂载规则的部署导出的记录与捕获时完全一致——包括文件内容与命令输出中内嵌的任何凭据——因此跨信任边界的部署必须挂载 `telemetry/redact` 监听器,两个 README 对此如实陈述。挂载规则后,导出的 body 可能与 canonical log 字节不同,接收端不得把遥测当作字节精确副本;日志仍是唯一事实源。崩溃持久性在上述 outbox 决定重启前明确不在范围内。 +部署方在 `cordis.yml` 加一个带 OTLP endpoint 的条目即可把会话流接入任何 OTel 兼容体系;删除条目即退出,无残留状态。未挂载规则的部署导出的记录与捕获时完全一致——包括文件内容与命令输出中内嵌的任何凭据——因此跨信任边界的部署必须挂载 `telemetry/record` 监听器,两个 README 对此如实陈述。挂载规则后,导出的 body 可能与 canonical log 字节不同,接收端不得把遥测当作字节精确副本;日志仍是唯一事实源。崩溃持久性在上述 outbox 决定重启前明确不在范围内。 diff --git a/docs/cordis-catalog/events.md b/docs/cordis-catalog/events.md index 29cb51b717..aa53b02ff1 100644 --- a/docs/cordis-catalog/events.md +++ b/docs/cordis-catalog/events.md @@ -894,14 +894,15 @@ Source: [`packages/core/system-prompt/src/index.ts:35`](../../packages/core/syst ## `telemetry/*` -### `telemetry/redact` — waterfall +### `telemetry/record` — waterfall -Redact one outbound record before it reaches the backend — the seam's scrubbing extension point. The seam ships NO rules of its own: the innermost `next()` passes the record through unchanged, and with no listener mounted records reach the backend as captured, so exported data is exactly as clean as the rules a deployment mounts. Listeners stack by transforming `next()`'s return value; returning without `next()` replaces everything beneath. Dispatched synchronously on the capture hot path inside the coordinator's containment: a throwing listener withholds that one record (fail-closed) and never reaches the agent loop. Redaction applies to the exported copy only; the canonical session log is never rewritten. +Transform one outbound record before it reaches the backend. This waterfall is the seam's redaction extension point. It ships NO rules of its own: the innermost `next()` passes the record through unchanged, and with no listener mounted records reach the backend as captured, so exported data is exactly as clean as the rules a deployment mounts. Listeners stack by transforming `next()`'s return value; returning without `next()` replaces everything beneath. Dispatched synchronously on the capture hot path inside the coordinator's containment: a throwing listener withholds that one record (fail-closed) and never reaches the agent loop. Redaction applies to the exported copy only; the canonical session log is never rewritten. ```ts cordis-catalog /** - * Redact one outbound record before it reaches the backend — the seam's - * scrubbing extension point. The seam ships NO rules of its own: the + * Transform one outbound record before it reaches the backend. This + * waterfall is the seam's redaction extension point. It ships NO rules + * of its own: the * innermost `next()` passes the record through unchanged, and with no * listener mounted records reach the backend as captured, so exported * data is exactly as clean as the rules a deployment mounts. Listeners @@ -915,10 +916,10 @@ Redact one outbound record before it reaches the backend — the seam's scrubbin * copy; listeners return a (possibly new) record and must not mutate it. * @mode waterfall */ -'telemetry/redact'(record: TelemetryRecord, next: () => TelemetryRecord): TelemetryRecord +'telemetry/record'(record: TelemetryRecord, next: () => TelemetryRecord): TelemetryRecord ``` -Source: [`packages/telemetry/session-telemetry/src/index.ts:40`](../../packages/telemetry/session-telemetry/src/index.ts) +Source: [`packages/telemetry/session-telemetry/src/index.ts:41`](../../packages/telemetry/session-telemetry/src/index.ts) ## `tools/*` diff --git a/docs/cordis-catalog/services.md b/docs/cordis-catalog/services.md index fff219ece6..8b3326142b 100644 --- a/docs/cordis-catalog/services.md +++ b/docs/cordis-catalog/services.md @@ -1772,7 +1772,7 @@ flush?(): void abstract shutdown(): Promise ``` -Source: [`packages/telemetry/session-telemetry/src/index.ts:134`](../../packages/telemetry/session-telemetry/src/index.ts) +Source: [`packages/telemetry/session-telemetry/src/index.ts:135`](../../packages/telemetry/session-telemetry/src/index.ts) ## `ctx.tokenMeter` — `TokenMeterService` diff --git a/docs/event-producer-consumer.md b/docs/event-producer-consumer.md index a3c5387097..2fa8767acf 100644 --- a/docs/event-producer-consumer.md +++ b/docs/event-producer-consumer.md @@ -48,7 +48,7 @@ This matrix shows which packages dispatch each harness-owned event and which pac | `subagent/start` | `emit` | [`packages/subagent/subagent/src/index.ts:130`](../packages/subagent/subagent/src/index.ts) | [`subagent`](../packages/subagent/subagent) (`events.dispatch`) | [`hooks-claude`](../packages/hooks/hooks-claude), [`subagent`](../packages/subagent/subagent) | | `system-prompt/assemble` | `waterfall` | [`packages/core/system-prompt/src/index.ts:29`](../packages/core/system-prompt/src/index.ts) | [`system-prompt`](../packages/core/system-prompt) (`waterfall`) | [`agent`](../packages/core/agent), [`system-prompt`](../packages/core/system-prompt) | | `system-prompt/change` | `emit` | [`packages/core/system-prompt/src/index.ts:35`](../packages/core/system-prompt/src/index.ts) | [`system-prompt`](../packages/core/system-prompt) (`emit`) | - | -| `telemetry/redact` | `waterfall` | [`packages/telemetry/session-telemetry/src/index.ts:40`](../packages/telemetry/session-telemetry/src/index.ts) | [`session-telemetry`](../packages/telemetry/session-telemetry) (`waterfall`) | - | +| `telemetry/record` | `waterfall` | [`packages/telemetry/session-telemetry/src/index.ts:41`](../packages/telemetry/session-telemetry/src/index.ts) | [`session-telemetry`](../packages/telemetry/session-telemetry) (`waterfall`) | - | | `tools/change` | `emit` | [`packages/core/tools/src/index.ts:156`](../packages/core/tools/src/index.ts) | [`tools`](../packages/core/tools) (`emit`) | - | | `tools/code-dispatch-log` | `waterfall` | [`packages/core/tools/src/index.ts:138`](../packages/core/tools/src/index.ts) | [`tools`](../packages/core/tools) (`waterfall`) | [`spill-policy`](../packages/spill/spill-policy) | | `tools/execute` | `waterfall` | [`packages/core/tools/src/index.ts:113`](../packages/core/tools/src/index.ts) | [`tools`](../packages/core/tools) (`waterfall`) | [`session-checkpoint-policy`](../packages/session-persistence/session-checkpoint-policy), [`timeout-policy`](../packages/timeout/timeout-policy) | diff --git a/examples/headless-agent/tests/fixtures/telemetry-otel.cordis.yml b/examples/headless-agent/tests/fixtures/telemetry-otel.cordis.yml index ff81d0116c..34e23b828e 100644 --- a/examples/headless-agent/tests/fixtures/telemetry-otel.cordis.yml +++ b/examples/headless-agent/tests/fixtures/telemetry-otel.cordis.yml @@ -1,7 +1,7 @@ # Test-only composition: session-telemetry-otel through the real Loader/app # path, exporting to the mock OTLP collector the driver starts (url via env). # The redact-rule entry models a deployment mounting its own scrub rule on the -# telemetry/redact waterfall — the seam itself ships no rules. +# telemetry/record waterfall — the seam itself ships no rules. - id: cli-mock-llm name: './cli-mock-llm.ts' diff --git a/examples/headless-agent/tests/fixtures/telemetry-redact-rule.ts b/examples/headless-agent/tests/fixtures/telemetry-redact-rule.ts index 10b7081dd3..7a2aa7958a 100644 --- a/examples/headless-agent/tests/fixtures/telemetry-redact-rule.ts +++ b/examples/headless-agent/tests/fixtures/telemetry-redact-rule.ts @@ -3,7 +3,7 @@ import type { Context } from 'cordis' /** * Deployment-style redaction rule for the telemetry e2e: scrubs the fixture * credential from body strings, exactly as a real deployment would mount its - * own rules on the `telemetry/redact` waterfall. + * own rules on the `telemetry/record` waterfall. */ const SECRET = /sk-e2efixture[0-9]+/g @@ -22,7 +22,7 @@ export const name = 'telemetry-redact-rule' /** Mount the fixture scrub rule onto the redact waterfall. */ export function apply(ctx: Context): void { - ctx.on('telemetry/redact', (_record, next) => { + ctx.on('telemetry/record', (_record, next) => { const record = next() return { ...record, body: scrub(record.body) } }) diff --git a/packages/cordis/tool-cordis/src/api-catalog.ts b/packages/cordis/tool-cordis/src/api-catalog.ts index 193743fbaa..4cecb115ea 100644 --- a/packages/cordis/tool-cordis/src/api-catalog.ts +++ b/packages/cordis/tool-cordis/src/api-catalog.ts @@ -1280,11 +1280,11 @@ export const EVENT_API: readonly EventApiEntry[] = [ summary: 'Emitted when any prompt provider changes.', }, { - name: 'telemetry/redact', + name: 'telemetry/record', mode: 'waterfall', - signature: '\'telemetry/redact\'(record: TelemetryRecord, next: () => TelemetryRecord): TelemetryRecord', - jsDoc: '/**\n * Redact one outbound record before it reaches the backend — the seam\'s\n * scrubbing extension point. The seam ships NO rules of its own: the\n * innermost `next()` passes the record through unchanged, and with no\n * listener mounted records reach the backend as captured, so exported\n * data is exactly as clean as the rules a deployment mounts. Listeners\n * stack by transforming `next()`\'s return value; returning without\n * `next()` replaces everything beneath. Dispatched synchronously on the\n * capture hot path inside the coordinator\'s containment: a throwing\n * listener withholds that one record (fail-closed) and never reaches the\n * agent loop. Redaction applies to the exported copy only; the canonical\n * session log is never rewritten.\n * @param record - the candidate record, already the coordinator\'s own deep\n * copy; listeners return a (possibly new) record and must not mutate it.\n * @mode waterfall\n */', - summary: 'Redact one outbound record before it reaches the backend — the seam\'s scrubbing extension point.', + signature: '\'telemetry/record\'(record: TelemetryRecord, next: () => TelemetryRecord): TelemetryRecord', + jsDoc: '/**\n * Transform one outbound record before it reaches the backend. This\n * waterfall is the seam\'s redaction extension point. It ships NO rules\n * of its own: the\n * innermost `next()` passes the record through unchanged, and with no\n * listener mounted records reach the backend as captured, so exported\n * data is exactly as clean as the rules a deployment mounts. Listeners\n * stack by transforming `next()`\'s return value; returning without\n * `next()` replaces everything beneath. Dispatched synchronously on the\n * capture hot path inside the coordinator\'s containment: a throwing\n * listener withholds that one record (fail-closed) and never reaches the\n * agent loop. Redaction applies to the exported copy only; the canonical\n * session log is never rewritten.\n * @param record - the candidate record, already the coordinator\'s own deep\n * copy; listeners return a (possibly new) record and must not mutate it.\n * @mode waterfall\n */', + summary: 'Transform one outbound record before it reaches the backend.', }, { name: 'tools/change', diff --git a/packages/telemetry/README.i18n.yaml b/packages/telemetry/README.i18n.yaml index 87f24d005e..b10509cb56 100644 --- a/packages/telemetry/README.i18n.yaml +++ b/packages/telemetry/README.i18n.yaml @@ -1,6 +1,6 @@ # Bilingual-pair consistency record (docs/i18n/README.md): the git blob hash of each # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: -# pnpm run verify-translation-pairing --write -README.md: 3e20e3ce31de36311529eb5d91382b9bb0049ddd -README.zh.md: a7036f4f119b144aa61de1c25836c980256d4db7 +# pnpm run verify-translation-pairing --write packages/telemetry/README.md +README.md: 944cb3f9bac6169feddf8b49bc481cfbe7c6fa9d +README.zh.md: 7be8af93654565cd9fb4d8b4376e0960fd7eb72b diff --git a/packages/telemetry/README.md b/packages/telemetry/README.md index 3e20e3ce31..944cb3f9ba 100644 --- a/packages/telemetry/README.md +++ b/packages/telemetry/README.md @@ -2,7 +2,7 @@ English | [中文](README.zh.md) -Outbound session reporting: the telemetry seam plus its OpenTelemetry backend. The design — the boundary axiom (the harness's aspect ends at `emit()`; delivery is the reporting SDK's), the `telemetry/redact` waterfall (deployment-mounted rules; the seam ships none), the fixed chunk projection, the handoff cursor, and the operational-record channel — is pinned in [the revival Agent Note](../../.agents/notes/implemented/feature/2026-07-23-session-telemetry-otel-revival.md). +Outbound session reporting: the telemetry seam plus its OpenTelemetry backend. The design — the boundary axiom (the harness's aspect ends at `emit()`; delivery is the reporting SDK's), the `telemetry/record` waterfall (deployment-mounted redaction rules; the seam ships none), the fixed chunk projection, the handoff cursor, and the operational-record channel — is pinned in [the revival Agent Note](../../.agents/notes/implemented/feature/2026-07-23-session-telemetry-otel-revival.md). | Package | Role | |---|---| diff --git a/packages/telemetry/README.zh.md b/packages/telemetry/README.zh.md index a7036f4f11..7be8af9365 100644 --- a/packages/telemetry/README.zh.md +++ b/packages/telemetry/README.zh.md @@ -2,7 +2,7 @@ [English](README.md) | 中文 -面向外部的会话上报:遥测(telemetry)seam 及其 OpenTelemetry 后端。整套设计归档于[复活 Agent Note(agent 决策记录)](../../.agents/notes/implemented/feature/2026-07-23-session-telemetry-otel-revival.md):边界公理(harness 的职责止于 `emit()`,投递由上报 SDK 负责)、`telemetry/redact` waterfall(瀑布式事件;脱敏规则由部署方挂载,seam 自身不带任何规则)、固定分片投影、handoff 游标,以及运维记录通道。 +面向外部的会话上报:遥测(telemetry)seam 及其 OpenTelemetry 后端。整套设计归档于[复活 Agent Note(agent 决策记录)](../../.agents/notes/implemented/feature/2026-07-23-session-telemetry-otel-revival.md):边界公理(harness 的职责止于 `emit()`,投递由上报 SDK 负责)、`telemetry/record` waterfall(瀑布式事件;脱敏规则由部署方挂载,seam 自身不带任何规则)、固定分片投影、handoff 游标,以及运维记录通道。 | 包 | 职责 | |---|---| diff --git a/packages/telemetry/session-telemetry-otel/README.i18n.yaml b/packages/telemetry/session-telemetry-otel/README.i18n.yaml index df98e01d2e..e8cafdc470 100644 --- a/packages/telemetry/session-telemetry-otel/README.i18n.yaml +++ b/packages/telemetry/session-telemetry-otel/README.i18n.yaml @@ -1,6 +1,6 @@ # Bilingual-pair consistency record (docs/i18n/README.md): the git blob hash of each # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: -# pnpm run verify-translation-pairing --write -README.md: e345449033b066103fb14e546e8d9909d68fe696 -README.zh.md: 6cad58ebc1ad9bab6dc722fcf0d702f500891d32 +# pnpm run verify-translation-pairing --write packages/telemetry/session-telemetry-otel/README.md +README.md: 9b208e291e77bee50d9d4fd14808268dca75f2db +README.zh.md: f36cfe74146b779c4ddb1101227cb45a06f0968c diff --git a/packages/telemetry/session-telemetry-otel/README.md b/packages/telemetry/session-telemetry-otel/README.md index e345449033..9b208e291e 100644 --- a/packages/telemetry/session-telemetry-otel/README.md +++ b/packages/telemetry/session-telemetry-otel/README.md @@ -21,7 +21,7 @@ The OpenTelemetry backend for [the telemetry seam](../session-telemetry/) — th ## What leaves the machine -Records carry the complete `event.data` as the seam's `telemetry/redact` waterfall returns it — user and assistant message content, tool arguments and results (command output, file contents), the full system prompt and tool schemas (`request/header`), todo text, compaction summaries, hook `stderrSummary`, and the session `cwd` (a local path). The seam ships no redaction rules: with no `telemetry/redact` listener mounted, that is the raw captured copy, so a deployment exporting beyond a trusted boundary mounts its own rules (see [the seam README](../session-telemetry/README.md#the-redact-waterfall)). Provider credentials never appear regardless: adapter API keys are constructor parameters, not session events, so they are structurally absent from the log and therefore from telemetry. +Records carry the complete `event.data` as the seam's `telemetry/record` waterfall returns it — user and assistant message content, tool arguments and results (command output, file contents), the full system prompt and tool schemas (`request/header`), todo text, compaction summaries, hook `stderrSummary`, and the session `cwd` (a local path). The seam ships no redaction rules: with no `telemetry/record` listener mounted, that is the raw captured copy, so a deployment exporting beyond a trusted boundary mounts its own rules (see [the seam README](../session-telemetry/README.md#the-redact-waterfall)). Provider credentials never appear regardless: adapter API keys are constructor parameters, not session events, so they are structurally absent from the log and therefore from telemetry. ## Field mapping diff --git a/packages/telemetry/session-telemetry-otel/README.zh.md b/packages/telemetry/session-telemetry-otel/README.zh.md index 6cad58ebc1..f36cfe7414 100644 --- a/packages/telemetry/session-telemetry-otel/README.zh.md +++ b/packages/telemetry/session-telemetry-otel/README.zh.md @@ -21,7 +21,7 @@ ## 哪些数据会离开本机 -记录携带完整的 `event.data`,内容以 seam 的 `telemetry/redact` waterfall(瀑布式事件)返回的结果为准:用户与 assistant 消息内容、工具参数与工具结果(命令输出、文件内容)、完整的系统提示词与工具 schema(`request/header`)、todo 文本、压缩(compaction)摘要、钩子的 `stderrSummary`,以及会话 `cwd`(一个本地路径)。seam 不带任何脱敏规则:未挂载 `telemetry/redact` 监听器时,导出的就是捕获原样的副本,因此向可信边界之外导出的部署方要挂载自己的规则(见 [seam README](../session-telemetry/README.md#the-redact-waterfall))。无论如何,提供方凭据都不会出现:适配器的 API key 是构造函数参数而非会话事件,因此它们在结构上就不存在于日志中,也就不存在于遥测中。 +记录携带完整的 `event.data`,内容以 seam 的 `telemetry/record` waterfall(瀑布式事件)返回的结果为准:用户与 assistant 消息内容、工具参数与工具结果(命令输出、文件内容)、完整的系统提示词与工具 schema(`request/header`)、todo 文本、压缩(compaction)摘要、钩子的 `stderrSummary`,以及会话 `cwd`(一个本地路径)。seam 不带任何脱敏规则:未挂载 `telemetry/record` 监听器时,导出的就是捕获原样的副本,因此向可信边界之外导出的部署方要挂载自己的规则(见 [seam README](../session-telemetry/README.md#the-redact-waterfall))。无论如何,提供方凭据都不会出现:适配器的 API key 是构造函数参数而非会话事件,因此它们在结构上就不存在于日志中,也就不存在于遥测中。 ## 字段映射 diff --git a/packages/telemetry/session-telemetry/README.i18n.yaml b/packages/telemetry/session-telemetry/README.i18n.yaml index 6c905a5ea1..71475fc8fe 100644 --- a/packages/telemetry/session-telemetry/README.i18n.yaml +++ b/packages/telemetry/session-telemetry/README.i18n.yaml @@ -1,6 +1,6 @@ # Bilingual-pair consistency record (docs/i18n/README.md): the git blob hash of each # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: -# pnpm run verify-translation-pairing --write -README.md: 76f9b242ca0b4779a9ab10e5ae93bab1ffb5e581 -README.zh.md: d930dc92e41e85c597aaeff840e6e6b03712b06d +# pnpm run verify-translation-pairing --write packages/telemetry/session-telemetry/README.md +README.md: a3820ee8e91a5c513c08df76b149cf3a670ee2ca +README.zh.md: d607a4ab4d953242ede53ff0b5e50bdf69e29ea4 diff --git a/packages/telemetry/session-telemetry/README.md b/packages/telemetry/session-telemetry/README.md index 76f9b242ca..a3820ee8e9 100644 --- a/packages/telemetry/session-telemetry/README.md +++ b/packages/telemetry/session-telemetry/README.md @@ -14,7 +14,7 @@ The coordinator registers, all through the composing fiber's effects: `session/c ## The redact waterfall -Every record passes the `telemetry/redact` waterfall between projection and `emit()` — the seam's scrubbing extension point. The seam ships NO rules of its own: the innermost `next()` passes the record through unchanged, so with no listener mounted records reach the backend exactly as captured, and exported data is precisely as clean as the rules a deployment mounts. Listeners stack by transforming `next()`'s return value; returning without `next()` replaces everything beneath, and a throwing listener withholds that one record fail-closed inside the coordinator's containment. Redaction applies to the exported copy only; the canonical session log is never rewritten. +Every record passes the `telemetry/record` waterfall between projection and `emit()` — the seam's scrubbing extension point. The seam ships NO rules of its own: the innermost `next()` passes the record through unchanged, so with no listener mounted records reach the backend exactly as captured, and exported data is precisely as clean as the rules a deployment mounts. Listeners stack by transforming `next()`'s return value; returning without `next()` replaces everything beneath, and a throwing listener withholds that one record fail-closed inside the coordinator's containment. Redaction applies to the exported copy only; the canonical session log is never rewritten. ## The handoff cursor @@ -39,4 +39,4 @@ None; this package neither assembles nor sends a provider request. ## Known Limitations and Deferred Work - **Best-effort delivery** — the cursor marks handed-off, not delivered; a session torn down inside a reload window cannot be re-adopted; whatever sits in a backend queue at crash time is lost. A durable outbox (spool, per-sink cursors, at-least-once) is deferred until a deployment states a crash-loss requirement — see [the revival Agent Note](../../../.agents/notes/implemented/feature/2026-07-23-session-telemetry-otel-revival.md). -- **No built-in redaction rules** — with no `telemetry/redact` listener mounted, records leave the process exactly as captured, including any credentials embedded in file contents or command output; a deployment exporting to a shared collector owns its rule set. +- **No built-in redaction rules** — with no `telemetry/record` listener mounted, records leave the process exactly as captured, including any credentials embedded in file contents or command output; a deployment exporting to a shared collector owns its rule set. diff --git a/packages/telemetry/session-telemetry/README.zh.md b/packages/telemetry/session-telemetry/README.zh.md index d930dc92e4..d607a4ab4d 100644 --- a/packages/telemetry/session-telemetry/README.zh.md +++ b/packages/telemetry/session-telemetry/README.zh.md @@ -14,7 +14,7 @@ ## 脱敏 waterfall -每条记录在投影与 `emit()` 之间都要经过 `telemetry/redact` waterfall(瀑布式事件),这是该 seam 的擦除扩展点。seam 自身不带任何规则:最内层的 `next()` 原样透传记录,因此未挂载监听器时,记录以捕获时的原样到达后端;导出数据能干净到什么程度,恰恰取决于部署方挂载了什么规则。监听器通过变换 `next()` 的返回值来堆叠;不调用 `next()` 就返回,即替换其下方的全部逻辑;抛出异常的监听器会在协调器的隔离范围内以 fail-closed 方式扣下这一条记录。脱敏只作用于导出副本;权威会话日志永不改写。 +每条记录在投影与 `emit()` 之间都要经过 `telemetry/record` waterfall(瀑布式事件),这是该 seam 的擦除扩展点。seam 自身不带任何规则:最内层的 `next()` 原样透传记录,因此未挂载监听器时,记录以捕获时的原样到达后端;导出数据能干净到什么程度,恰恰取决于部署方挂载了什么规则。监听器通过变换 `next()` 的返回值来堆叠;不调用 `next()` 就返回,即替换其下方的全部逻辑;抛出异常的监听器会在协调器的隔离范围内以 fail-closed 方式扣下这一条记录。脱敏只作用于导出副本;权威会话日志永不改写。 ## handoff 游标 @@ -39,4 +39,4 @@ ## 已知限制与延期工作 - **尽力而为的投递**:游标标记的是已交接而非已投递;在重载窗口内被拆除的会话无法重新收养;崩溃时留在后端队列中的内容会丢失。持久 outbox(spool、每 sink 游标、at-least-once)推迟到有部署方提出明确的崩溃丢失要求时再实现;见[复活 Agent Note](../../../.agents/notes/implemented/feature/2026-07-23-session-telemetry-otel-revival.md)。 -- **不内置脱敏规则**:未挂载 `telemetry/redact` 监听器时,记录以捕获时的原样离开进程,包括文件内容或命令输出中内嵌的任何凭据;向共享 collector 导出的部署方自行负责其规则集。 +- **不内置脱敏规则**:未挂载 `telemetry/record` 监听器时,记录以捕获时的原样离开进程,包括文件内容或命令输出中内嵌的任何凭据;向共享 collector 导出的部署方自行负责其规则集。 diff --git a/packages/telemetry/session-telemetry/src/coordinator.ts b/packages/telemetry/session-telemetry/src/coordinator.ts index a5b552c765..76f3fb1f16 100644 --- a/packages/telemetry/session-telemetry/src/coordinator.ts +++ b/packages/telemetry/session-telemetry/src/coordinator.ts @@ -2,8 +2,8 @@ * Capture coordinator: the seam's upstream half. Subscribes to the session * firehose plus the one live-bus relay (`agent/error`), applies the fixed * chunk projection, builds logical records, runs each through the - * `telemetry/redact` waterfall (deployment-mounted rules; pass-through when - * none), and hands the result to the backend — synchronously, with every + * `telemetry/record` waterfall (deployment-mounted redaction rules; + * pass-through when none), and hands the result to the backend — synchronously, with every * handler self-contained so a failing backend can never starve other * subscribers (cordis `emit` is stop-on-throw) or touch the agent loop. * Composed by a backend in its constructor. @@ -178,14 +178,14 @@ export class TelemetryCoordinator { } /** - * Run the `telemetry/redact` waterfall over one record and hand the result + * Run the `telemetry/record` waterfall over one record and hand the result * to the backend. The innermost `next` passes the record through unchanged * — the seam ships no rules; exported data is as clean as the listeners a * deployment mounts. Callers run inside {@link contain}, so a throwing * rule withholds the record instead of reaching the loop (fail-closed). */ private handOff(record: TelemetryRecord): void { - this.backend.emit(this.ctx.waterfall('telemetry/redact', record, () => record)) + this.backend.emit(this.ctx.waterfall('telemetry/record', record, () => record)) } /** Forward the turn-end boundary to the backend's optional flush hint. */ diff --git a/packages/telemetry/session-telemetry/src/index.ts b/packages/telemetry/session-telemetry/src/index.ts index b52f80fbbe..ca0f504647 100644 --- a/packages/telemetry/session-telemetry/src/index.ts +++ b/packages/telemetry/session-telemetry/src/index.ts @@ -22,8 +22,9 @@ declare module 'cordis' { interface Events { /** - * Redact one outbound record before it reaches the backend — the seam's - * scrubbing extension point. The seam ships NO rules of its own: the + * Transform one outbound record before it reaches the backend. This + * waterfall is the seam's redaction extension point. It ships NO rules + * of its own: the * innermost `next()` passes the record through unchanged, and with no * listener mounted records reach the backend as captured, so exported * data is exactly as clean as the rules a deployment mounts. Listeners @@ -37,7 +38,7 @@ declare module 'cordis' { * copy; listeners return a (possibly new) record and must not mutate it. * @mode waterfall */ - 'telemetry/redact'(record: TelemetryRecord, next: () => TelemetryRecord): TelemetryRecord + 'telemetry/record'(record: TelemetryRecord, next: () => TelemetryRecord): TelemetryRecord } } diff --git a/packages/telemetry/session-telemetry/tests/redact.spec.ts b/packages/telemetry/session-telemetry/tests/redact.spec.ts index c4b9d4d5bb..6e71d61627 100644 --- a/packages/telemetry/session-telemetry/tests/redact.spec.ts +++ b/packages/telemetry/session-telemetry/tests/redact.spec.ts @@ -1,5 +1,5 @@ /** - * The `telemetry/redact` waterfall contract: pass-through when no listener is + * The `telemetry/record` waterfall contract: pass-through when no listener is * mounted, listener stacking and replacement, ops-record coverage, the * untouched canonical log, and the fail-closed containment of a throwing rule. */ @@ -35,7 +35,7 @@ async function setup() { return { ctx, backend, fiber } } -describe('telemetry/redact waterfall', () => { +describe('telemetry/record waterfall', () => { it('passes records through unchanged when no listener is mounted', async () => { const { ctx, backend } = await setup() const session = ctx.sessions.create(SessionId('w')) @@ -46,7 +46,7 @@ describe('telemetry/redact waterfall', () => { it('applies a mounted rule to every outbound record, ops records included', async () => { const { ctx, backend, fiber } = await setup() - ctx.on('telemetry/redact', (_record, next) => { + ctx.on('telemetry/record', (_record, next) => { const record = next() return { ...record, body: { scrubbed: true } } }) @@ -62,7 +62,7 @@ describe('telemetry/redact waterfall', () => { it('keeps the canonical log untouched by a mounted rule', async () => { const { ctx } = await setup() - ctx.on('telemetry/redact', (_record, next) => ({ ...next(), body: null })) + ctx.on('telemetry/record', (_record, next) => ({ ...next(), body: null })) const session = ctx.sessions.create(SessionId('log')) session.append('user/message', { content: [{ type: 'text', text: FIXTURE_SECRET }], source: { kind: 'user' } }, { surfaceOp: 'append' }) const logged = session.events[0]!.data as { content: { text: string }[] } @@ -72,13 +72,13 @@ describe('telemetry/redact waterfall', () => { it('stacks listeners outermost-first around next()', async () => { const { ctx, backend } = await setup() const order: string[] = [] - ctx.on('telemetry/redact', (_record, next) => { + ctx.on('telemetry/record', (_record, next) => { order.push('outer-before') const record = next() order.push('outer-after') return { ...record, attributes: { ...record.attributes, outer: 1 } } }) - ctx.on('telemetry/redact', (_record, next) => { + ctx.on('telemetry/record', (_record, next) => { order.push('inner') const record = next() return { ...record, attributes: { ...record.attributes, inner: 1 } } @@ -92,8 +92,8 @@ describe('telemetry/redact waterfall', () => { it('a listener that skips next() replaces everything beneath it', async () => { const { ctx, backend } = await setup() const inner = { called: false } - ctx.on('telemetry/redact', () => ({ channel: 'ops', time: 0, severity: 'info', attributes: {}, body: 'replaced' } satisfies TelemetryRecord)) - ctx.on('telemetry/redact', (_record, next) => { + ctx.on('telemetry/record', () => ({ channel: 'ops', time: 0, severity: 'info', attributes: {}, body: 'replaced' } satisfies TelemetryRecord)) + ctx.on('telemetry/record', (_record, next) => { inner.called = true return next() }) @@ -105,7 +105,7 @@ describe('telemetry/redact waterfall', () => { it('a throwing rule withholds the record fail-closed without disturbing the log', async () => { const { ctx, backend } = await setup() - ctx.on('telemetry/redact', () => { + ctx.on('telemetry/record', () => { throw new Error('rule exploded') }) const session = ctx.sessions.create(SessionId('closed'))