Skip to content

Integrate stable telemetry into MXC runtime - #820

Open
RamonArjona4 wants to merge 3 commits into
user/ramonarjona4/telemetry-02-consent-policyfrom
user/ramonarjona4/telemetry-03-runtime
Open

Integrate stable telemetry into MXC runtime#820
RamonArjona4 wants to merge 3 commits into
user/ramonarjona4/telemetry-02-consent-policyfrom
user/ramonarjona4/telemetry-03-runtime

Conversation

@RamonArjona4

@RamonArjona4 RamonArjona4 commented Aug 12, 2026

Copy link
Copy Markdown
Member

Summary

Promotes telemetry to the stable top-level configuration and integrates the reviewed consent and policy foundation with the engine and executors. Adds typed maintenance requests, live authorization checks, generated schemas, migration handling, and Windows consent/ETW smoke coverage.

This is PR 3 of 5 and depends on the consent/policy foundation in PR 2.

Stack

Order Change PR
1 Normative privacy and telemetry documentation #818
2 Consent and policy foundation #819
3 Stable config and executor integration #820
4 Rust SDK and C ABI #821
5 Node.js and .NET SDKs #822

Review only this PR's diff; prerequisite behavior is in the PR above.

Promote telemetry to the stable configuration, add executor consent maintenance and live authorization, update generated schemas, and cover Windows consent and ETW behavior.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@azure-pipelines

Copy link
Copy Markdown
Azure Pipelines:
There may be pipelines that require an authorized user to comment /azp run to run.

@azure-pipelines

Copy link
Copy Markdown
Azure Pipelines:
There may be pipelines that require an authorized user to comment /azp run to run.

@@ -0,0 +1,79 @@
{
"$schema": "http://json-schema.org/draft-07/schema#",

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

$schema

Not sure this file belongs here. these schemas are effectively public api surface.

Your schema here is an implementation detail of an internal file format that no caller should need to care about - at least, I think right?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a detail I think I'm unclear about. Do we expect that we'll ever have to support external callers trying to invoke telemetry config via the JSON config file with the EXE? If we don't then this is probably not needed.

return Err(WxcError::ConfigParse(
"'experimental.telemetry' has moved to the stable section; \
use top-level 'telemetry' instead."
.to_string(),

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should we only reject legacy telemetry if the passed config's version is < 0.8.0?

],
"description": "macOS Seatbelt backend configuration. Used when containment is `seatbelt`."
},
"telemetry": {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

telemetry

is it weird that telemetry is part of the schema, as opposed to an ancillary property?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In this context telemetry is a gate that the invocation is requesting telemetry. We're trying to keep execution policy "enable telemetry gate" separate from state mutation ("I consent to telemetry, I withdraw consent, I would like status.") But as noted earlier exposing the surface of telemetry in the JSON schema might be overkill.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

:shipit:

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Copilot-Session: 2da373b9-0a51-4aa4-a968-14f05f27b5c7
Copilot AI balanced review requested due to automatic review settings August 13, 2026 04:06
@RamonArjona4
RamonArjona4 requested a review from a team August 13, 2026 04:06

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Promotes telemetry into MXC’s stable runtime configuration and integrates consent-aware emission across executors and the engine.

Changes:

  • Adds stable telemetry parsing, authorization gates, and state-aware correlation.
  • Introduces typed consent-maintenance requests and generated schemas.
  • Adds Windows consent/ETW smoke tests and updates documentation.

Reviewed changes

Copilot reviewed 27 out of 27 changed files in this pull request and generated 6 comments.

Show a summary per file
File Description
.github/workflows/Build.Windows.Job.yml Adds telemetry tests and smoke coverage.
docs/schema.md Documents stable telemetry configuration.
docs/state-aware-lifecycle/mxc-state-aware-sandbox-api.md Updates telemetry correlation guidance.
docs/telemetry/telemetry-consent-design.md Marks consent design as implemented.
docs/telemetry/telemetry-consent-privacy-review.md Updates implementation status.
docs/telemetry/telemetry-policy.md Marks policy integration as implemented.
docs/telemetry/telemetry.md Updates telemetry implementation status.
schemas/dev/mxc-config.schema.0.8.0-dev.json Promotes telemetry to the schema root.
schemas/dev/mxc-telemetry-consent.schema.1.json Adds the maintenance-request schema.
src/core/lxc/src/main.rs Adds consent maintenance and stable telemetry wiring.
src/core/mxc_darwin/src/main.rs Adds equivalent macOS maintenance handling.
src/core/mxc_engine/src/lib.rs Adds telemetry-aware streaming wrappers.
src/core/mxc_engine/src/policy.rs Adds per-request telemetry enablement.
src/core/mxc_engine/src/state_aware.rs Adds state-aware telemetry and correlation.
src/core/wxc/src/main.rs Integrates maintenance commands and runtime emission.
src/core/wxc_common/Cargo.toml Clarifies telemetry test-support behavior.
src/core/wxc_common/src/config_parser.rs Parses stable telemetry and migration cases.
src/core/wxc_common/src/models.rs Moves telemetry into ExecutionRequest.
src/core/wxc_common/src/telemetry/consent_cli.rs Implements shared consent maintenance handling.
src/core/wxc_common/src/telemetry/events.rs Updates ETW event documentation.
src/core/wxc_common/src/telemetry/mod.rs Adds live authorization and SDK emission paths.
src/core/wxc_common/src/wire.rs Defines telemetry maintenance wire contracts.
src/testing/wxc_e2e_tests/tests/e2e_windows.rs Updates telemetry E2E invocation.
src/tools/mxc_schema_gen/src/main.rs Adds consent schema/type generation modes.
tests/examples/28_telemetry_enabled.json Uses stable top-level telemetry.
tests/scripts/run_telemetry_consent_smoke_test.ps1 Adds isolated consent smoke coverage.
tests/scripts/run_telemetry_etw_smoke_test.ps1 Strengthens isolated ETW smoke coverage.
Suppressed comments (1)

src/core/wxc_common/src/telemetry/events.rs:106

  • The provider emits MXC.Error, not Error; preserving this exact identity is part of the ETW smoke assertion. Keep the public function documentation aligned with the actual event contract.
/// Log an Error ETW event.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread docs/telemetry/telemetry-consent-design.md
Comment thread docs/telemetry/telemetry-policy.md
Comment thread src/core/wxc_common/src/telemetry/events.rs
Comment on lines +182 to +186
if config
.get("experimental")
.and_then(serde_json::Value::as_object)
.is_some_and(|object| object.contains_key("telemetry"))
{
Comment on lines +4 to +5
"title": "MXC Telemetry Consent Maintenance Contract",
"description": "Public JSON contract for telemetry-consent maintenance. It is separate from the execution configuration and uses an explicit command discriminator.",
Comment thread docs/schema.md
"telemetry": { // Telemetry (experimental, Windows only)
"enabled": true // Emit TraceLogging ETW events via pure Rust tracelogging crate
}
},
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Copilot-Session: 2da373b9-0a51-4aa4-a968-14f05f27b5c7
Copilot AI review requested due to automatic review settings August 13, 2026 05:28

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 28 out of 28 changed files in this pull request and generated no new comments.

Suppressed comments (3)

docs/telemetry/telemetry-consent-design.md:2

  • Removing the implementation-status notice makes this document claim that the Rust/C ABI and Node/.NET consent APIs described later are available now, but this PR's own stack assigns those surfaces to follow-up PRs #821 and #822. Keep a partial-implementation notice until those PRs land so readers are not directed to APIs absent from this branch.
# Telemetry consent design

src/core/mxc_engine/src/lib.rs:195

  • A timed-out state-aware streaming exec is converted to a generic BackendError, so classify_mxc_error reports process_error instead of the existing timeout telemetry category. Preserve ErrorKind::TimedOut when constructing the state-aware telemetry outcome (or add a state-aware emit path that accepts FailureReason::Timeout) so one-shot and state-aware timeout events agree.
                let outcome = match result {
                    Ok(exit_code) => Ok(
                        wxc_common::state_aware_dispatch::DispatchOutcome::ExecCompleted {
                            exit_code: *exit_code,
                        },
                    ),
                    Err(error) => Err(wxc_common::mxc_error::MxcError::backend_error(
                        error.to_string(),
                    )),

src/core/mxc_engine/src/policy.rs:787

  • SandboxRequest is re-exported by mxc-sdk, so this method immediately adds a public Rust SDK API. The repository's SDK-change convention requires updating the Rust SDK crate docs and src/core/mxc-sdk/README.md in the same PR; neither currently documents this method. Please add those updates, or keep the method non-public until the SDK-exposure PR.
    /// Enable or disable telemetry for this invocation.
    ///
    /// Enabling this per-request switch is necessary but not sufficient:
    /// telemetry still requires persisted user consent and an administrative
    /// policy that permits collection. It is independent of experimental mode.
    pub fn set_telemetry_enabled(&mut self, enabled: bool) -> &mut Self {

.inner
.telemetry
.as_ref()
.map(|config| telemetry::init(config, &mut logger))

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

High (security) — Registering ETW from mxc_engine invalidates the provider's executable-only unsafe precondition.

This newly moves provider registration into a library used by mxc-sdk and the existing mxc_ffi cdylib. mxc_telemetry still justifies the unsafe registration by stating MXC is an executable, not a DLL, so unload ordering is irrelevant. A leaked streaming handle or explicit native-library unload can therefore leave ETW holding callbacks into an unmapped module.

Fix: Keep registration in process-lifetime executables, or add an explicit FFI/global teardown contract that prevents unloading until all provider references are released, and update the unsafe preconditions accordingly.


/// Handle a typed consent-maintenance envelope, if present.
pub fn handle_maintenance_input(input: &str, is_base64: bool) -> Option<ConsentCliOutcome> {
let json = match crate::config_parser::decode_request_input(input, is_base64) {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

High (reliability/cross-platform) — The maintenance probe consumes ordinary config streams before normal parsing reads them again.

Every executor calls this probe first. For a non-maintenance request, decode_request_input reads the supplied path here, then the normal loader reads the same path again. Regular files pay duplicate I/O/parsing; named pipes, /dev/stdin, and process-substitution paths are drained by the first read and fail or block on the second.

Fix: Decode the input once in the executor and pass the loaded JSON to both maintenance discrimination and ordinary request parsing.

}

impl Drop for TelemetryProcess {
fn drop(&mut self) {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Medium (reliability) — Dropping a live telemetry wrapper releases the provider without emitting the promised terminal event.

wait() emits, but Drop only shuts down. kill() likewise only forwards, and try_wait() ignores its error branch. Kill-then-free, drop-without-wait, or a failed poll can therefore lose the invocation's Execution/Error record while releasing its provider reference, contradicting the wrapper's “exactly one terminal event” contract.

Fix: Use an explicit terminal-state machine and emit a bounded killed/cancelled/error outcome on kill, drop, and try_wait failure before releasing the provider exactly once.

);
let json = serde_json::to_string(&presentation)
.map_err(|error| format!("failed to serialize consent presentation: {error}"))?;
println!("{json}");

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Medium (reliability) — A closed presenter pipe causes a Rust panic.

println! panics on stdout write failure. If the SDK host times out, crashes, or closes its pipe before the child emits the presentation envelope, this turns a routine IPC disconnect into an uncontrolled panic before normal executor setup.

Fix: Write through stdout().lock()/writeln! and map BrokenPipe or other write failures to a clean transport error and nonzero outcome.

// cross-phase lifecycle to join.
correlation_vector: "",
});
if invocation_can_emit(active) {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Medium (performance/reliability) — Reuse one authorization result per logical emission.

The outer completion path checks authorization, then this check rereads the consent file and policy registry before Execution, and line 223 does it again before the paired Error. Similar repetition exists in early-exit, crash/cancellation, and state-aware paths. This adds blocking retry-capable I/O and can split an intended event pair if authorization changes between reads.

Fix: Perform one live check immediately before each logical emission and pass the result through to both event writes.

telemetry::emit_sdk_early_exit(
telemetry_active,
&containment,
telemetry::FailureReason::InitError,

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Medium (correctness) — Preserve the actual error category instead of reporting every spawn failure as initialization.

dispatch::spawn_runner returns typed MxcError codes, but this path emits every failure as InitError. Malformed requests, unsupported containment, and policy validation failures are therefore recorded under the wrong bounded telemetry category.

Fix: Share the exhaustive MxcErrorCodeFailureReason mapping used by the state-aware path and emit the mapped reason here.

let phase = parsed.phase;
let phase_name = phase.as_str();
let incoming_correlation = parsed.correlation_vector.clone();
let mut logger = Logger::new(Mode::Buffer);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Medium (correctness/observability) — State-aware telemetry initialization warnings are discarded.

Both state-aware entry points initialize telemetry with a buffered Logger, but never surface logger.take_warnings(). Ordinary SDK spawn attaches those warnings to its process wrapper, while state-aware envelope and streaming calls silently lose provider-registration diagnostics.

Fix: Include warnings in envelope results and wrap streaming state-aware processes with the same warning propagation used by spawn.

if phase == Phase::Provision && telemetry_active {
inject_correlation_vector(&mut outcome, &correlation);
}
telemetry::emit_sdk_state_aware(

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Medium (test coverage) — Add an engine-level state-aware telemetry execution test.

The shared crate now tests event planning/emission, and engine tests cover correlation helpers, but no test drives this integration end to end. Phase attribution, provision-vector injection, engine error mapping, and provider release can still regress independently while both component suites stay green.

Fix: Add an engine/integration test with a capture sink that executes representative provision and exec outcomes and asserts phase, shared correlation base, error mapping, and final provider release.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants