@@ -48,7 +48,7 @@ One module, `src/telemetry.rs`, owns init and teardown via `Telemetry::init(unit
4848- ** Endpoint** : none configured in code. The exporter resolves ` OTEL_EXPORTER_OTLP_ENDPOINT ` and
4949 related ` OTEL_* ` variables from the environment automatically. Unset → no SDK provider or
5050 exporter is built (R02); since PR3 the human-readable stderr ` tracing ` layer still runs so
51- diagnostics stay visible — see [ Log bridge] ( #log-bridge-pr3 ) .
51+ diagnostics stay visible — see [ Log bridge] ( #log-bridge-pr3-landed ) .
5252- ** Protocol** : HTTP JSON (` http-json ` + protobuf-free wire), batch exporter, targeting the local
5353 Alloy forwarder at ` 127.0.0.1:4318 ` by convention.
5454- ** Resource** : ` service.name ` = ` st2-<unit> ` selected per entrypoint (below; ` src/main.rs `
@@ -69,28 +69,62 @@ One module, `src/telemetry.rs`, owns init and teardown via `Telemetry::init(unit
6969| One-shot CLI invocations | ` st2-cli ` |
7070| Hook executions (` st2 driver claude-observe ` ; other hook surfaces not instrumented yet) | ` st2-hook ` |
7171
72- ## Trace roots
72+ ## Reconciliation trace hierarchy
7373
74- Instrumented via the ` tracing ` facade since PR3 (` tracing::info_span! ` , not manual SDK tracer
75- calls ), one root span per unit of work :
74+ Instrumented exclusively through the ` tracing ` facade (` tracing::info_span! ` plus
75+ ` tracing-opentelemetry ` status extension ), one bounded trace represents one reconcile pass :
7676
77- - ** Supervisor loop pass** — each iteration of the ` up_loop_until ` loop (` src/run.rs ` ,
78- ` up_loop_until ` ) wraps one reconcile pass in a span named ` st2.reconcile_pass ` with field
79- ` st2.host ` ; after the pass it records ` st2.crash_loops ` and ` st2.unparked ` counts.
80- - ** One-shot up** — ` up_once ` (` src/run.rs ` , ` up_once ` ) wraps its single pass in the same
81- ` st2.reconcile_pass ` shape.
82- - ** Single-file spec path** — ` reconcile_pass_specs_with_sessions ` emits the same root span.
83-
84- A deterministic INFO event (target ` st2 ` , message ` reconcile pass complete ` , ` result ` =
85- ` pass ` \| ` fail ` ) closes every pass so log-based assertions need no fault injection.
86-
87- Not yet instrumented (follow-ups, not PR1 scope):
88-
89- - Provider session lifecycles (claude / codex / opencode spawn, attach, teardown) beyond the
90- PR2 launch/reap counters, exec sidecars (` src/exec_backend.rs ` ).
77+ ```
78+ st2.reconcile_pass
79+ ├── st2.catalog.lock
80+ ├── st2.catalog.discover
81+ ├── st2.hooks.verify # only when a consumer requires hooks
82+ ├── st2.catalog.materialize
83+ ├── st2.runtime.observe # omitted for an externally supplied snapshot
84+ └── st2.reconcile.execute
85+ ```
9186
92- Span names follow the central ` 01-conventions ` rules (` span.label ` discipline included). Names are
93- registered st2-side; this list plus PR2's metric set is that registry's seed.
87+ ` st2.reconcile_pass ` remains the compatibility root at the supervisor-loop, one-shot catalog,
88+ selected-task, and single-file-spec sites. Its ` span.label ` and ` st2.reconcile.path ` are the enum
89+ ` catalog | selected | spec ` . Root outcome attributes are ` st2.host ` , ` st2.crash_loops ` ,
90+ ` st2.unparked ` , ` st2.report.errors ` , ` st2.report.warnings ` , ` st2.reconcile.skipped ` , and
91+ ` st2.result = pass | fail ` ; non-empty errors set OTel status ` ERROR ` . A deterministic INFO event
92+ (target ` st2 ` , message ` reconcile pass complete ` , ` result = pass | fail ` ) closes every root so
93+ log-based assertions need no fault injection.
94+
95+ | Span name | Parent | ` span.label ` | Operation boundary | Attributes and status | Path applicability |
96+ | --- | --- | --- | --- | --- | --- |
97+ | ` st2.reconcile_pass ` | none | ` catalog ` \| ` selected ` \| ` spec ` | One complete reconcile pass | Root attributes above; ` ERROR ` when the pass returns/collects an error | Catalog loop/once, selected task, spec loop/once |
98+ | ` st2.catalog.lock ` | ` st2.reconcile_pass ` | ` shared ` | Shared catalog-authoring lock acquisition | ` st2.result ` ; ` ERROR ` on acquisition failure | Catalog, selected |
99+ | ` st2.catalog.discover ` | ` st2.reconcile_pass ` | ` catalog ` | Recursive desired-state snapshot | ` st2.catalog.spec_count ` , report warning/error counts, ` st2.result ` ; ` ERROR ` when discovery reports errors even though the pass may continue | Catalog, selected |
100+ | ` st2.hooks.verify ` | ` st2.reconcile_pass ` | ` lifecycle hooks ` | Required lifecycle-hook receipt/set verification | ` st2.hooks.consumer = codex \| pi \| codex+pi ` , ` st2.result ` ; ` ERROR ` on verification failure | Catalog or selected, only when required |
101+ | ` st2.catalog.materialize ` | ` st2.reconcile_pass ` | ` catalog ` \| ` selected owner ` | Aggregate catalog/selected-owner materialization call | Materialization failure and report warning/error counts, ` st2.result ` ; ` ERROR ` when materialization reports errors | Catalog, selected |
102+ | ` st2.runtime.observe ` | ` st2.reconcile_pass ` | ` all sessions ` | Authoritative ` Runner::list_sessions ` call | ` st2.runtime.session_count ` , ` st2.result ` ; ` ERROR ` on list failure | Catalog, selected, spec; omitted by ` _with_sessions ` because that snapshot is external |
103+ | ` st2.reconcile.execute ` | ` st2.reconcile_pass ` | ` apply plan ` | Aggregate mutation call around ` execute_with_presentation_cursor ` | Plan launch/GC/teardown counts, newly added report warning/error counts, ` st2.result ` ; ` ERROR ` only when execution adds errors | Catalog, selected, spec |
104+
105+ Every first-party root and child has a non-empty ` span.label ` . The exporter-enabled
106+ ` AtomicBool ` in ` src/telemetry.rs ` is the hierarchy gate; ` tracing::enabled! ` is insufficient
107+ because the stderr formatter remains installed without an endpoint. When the tracer exporter is
108+ unset, child constructors return before span construction, label handling, collection allocation,
109+ or count inspection. All children are aggregates and trace volume is bounded by the table.
110+
111+ Attribute policy follows the central ` 01-conventions ` contract:
112+
113+ | Attribute family | Value type | Cardinality | Privacy | Metric-label policy |
114+ | --- | --- | --- | --- | --- |
115+ | ` span.label ` | enum string | bounded | public | forbidden |
116+ | ` st2.reconcile.path ` , ` st2.result ` , ` st2.hooks.consumer ` | enum string | tiny/bounded | public | spanmetrics-only |
117+ | All ` *_count ` , ` st2.crash_loops ` , ` st2.unparked ` , ` st2.report.errors ` , ` st2.report.warnings ` | integer | bounded numeric | public | forbidden |
118+ | ` st2.reconcile.skipped ` | boolean | tiny | public | spanmetrics-only |
119+ | ` st2.host ` | string | bounded fleet identity | internal | forbidden |
120+
121+ No span or status description carries an id, filesystem path, selector, or error prose.
122+
123+ Explicitly rejected spans: pure reconcile planning, identity validation,
124+ ` compile_generated_tasks ` , debounce, report absorption, wait/sleep, watcher callbacks, and
125+ wrapper functions. Per-task and per-owner spans are also rejected from this hierarchy: they need
126+ a separately specified hard detail budget. Provider-session lifecycles and exec sidecars remain
127+ follow-up surfaces beyond the PR2 launch/reap counters.
94128
95129## Metrics (PR2)
96130
0 commit comments