docs(telemetry): document the sandbox duplex transport contract

The duplex transport adds spans, one event, counters, and dimensions in
duplex-telemetry.ts. The generated telemetry contract does not cover them, so
the telemetry README is their canonical contract. Add a section for the span,
event, and counter names, the closed dimension-key set with its enum values, and
the opt-in and no-user-content rules.

Co-authored-by: Paperclip <noreply@paperclip.ing>
This commit is contained in:
Priya RamanandPaperclip committed 2026-08-20 06:19:27 +00:00
1 parent d0c4968e97
commit d68ef2a41a
1 file changed
+62
+62
View File
@@ -304,6 +304,68 @@ record, so a worker can never forge a parent. The host validates the
trace context the worker sends no span, so the whole provider-span path is a
no-op.
## Sandbox Duplex Transport Telemetry
Paperclip opens a fixed observability surface for the sandbox duplex transport.
This surface is separate from the first-party events and from the startup spans
above. The generated telemetry contract does not cover it, so this section is its
canonical contract. The code owner is
`packages/adapter-utils/src/duplex-telemetry.ts`. That module holds each name and
each enum value as a literal constant, so the surface never drifts.
The surface is opt-in. The host injects a recorder that binds the span to the
OTel tracer, the counter to the guarded counter store, and the event to the
run-events bridge. The default recorder is a no-op, so the whole surface stays
inert until the host binds a real recorder. Every recorder call sits inside an
error swallow, so a telemetry failure never breaks the request path.
The surface carries no user content. No route, no query, no request body, no
token, and no raw identifier rides a span, a counter, or an event. Each record
carries only the closed dimension keys below and, for the request span, a
latency. The `provider` dimension carries only the allowlisted public value
`daytona`. Any other plugin key maps to `other` before the record reaches a sink,
so a raw plugin key never reaches a span attribute, a counter label, or an event
field.
### Spans
| Span | Scope | Latency |
| --- | --- | --- |
| `sandbox.duplex.channel_open` | One duplex channel-open attempt. The `outcome` dimension is `ok` when the channel opened and readiness passed, or `error` when the open or readiness failed. | none |
| `sandbox.duplex.request` | One duplex request the broker forwarded to the host. | The request latency in milliseconds. |
### Event
| Event | Scope |
| --- | --- |
| `sandbox.duplex.transport` | The host emits it at each transport boundary: a ready duplex channel, a fallback to the file bridge, and a terminal channel loss. Its dimensions record the boundary. |
### Counters
| Counter | Scope |
| --- | --- |
| `sandbox_duplex_channel_open_total` | One successful duplex channel open. |
| `sandbox_duplex_fallback_total` | One fallback to the file bridge. The `fallback_reason` dimension records the cause. |
| `sandbox_duplex_loss_total` | One terminal duplex channel loss. The `loss_class` dimension records the phase. |
| `sandbox_duplex_session_leak_total` | One leaked provider session at teardown. |
### Dimension keys
Every span attribute, counter label, and event field uses only these closed keys.
A test asserts the exact set, so a new key never reaches a sink by accident.
| Key | Type | Optional | Value set |
| --- | --- | --- | --- |
| `provider` | string | no | `daytona`, or `other` for any other plugin key. |
| `transport` | string | no | `duplex` or `file`. A fallback record uses `file`; every other record uses `duplex`. |
| `outcome` | string | yes | `ok` or `error`. |
| `fallback_reason` | string | yes | `gate_off`, `capability_absent`, `open_failed`, `ready_invalid`, `ready_nonce_mismatch`, `ready_timeout`, or `contaminated`. It rides only a fallback record. |
| `loss_class` | string | yes | `pre_dispatch` or `post_dispatch`, relative to the first request dispatch. It rides only a loss record. |
To add a name or an enum value, extend the literal constant in
`duplex-telemetry.ts` first, then update the test that asserts the closed set.
Keep every dimension low-cardinality and free of user content.
## Sandbox Startup Run-Log Event
Paperclip writes one `run.startup.step` event to the run log for each bring-up