Skip to content

telemetry: remove asset_id, rename correlation_id to installation_id, add persistent servicing_id - #779

Draft
bfjelds (bfjelds) wants to merge 6 commits into
user/bfjelds/mjolnir/command-error-metricfrom
user/bfjelds/mjolnir/installation-servicing-id
Draft

telemetry: remove asset_id, rename correlation_id to installation_id, add persistent servicing_id#779
bfjelds (bfjelds) wants to merge 6 commits into
user/bfjelds/mjolnir/command-error-metricfrom
user/bfjelds/mjolnir/installation-servicing-id

Conversation

@bfjelds

@bfjelds bfjelds (bfjelds) commented Sep 4, 2026

Copy link
Copy Markdown
Member
  • Remove asset_id from telemetry (the DMI product UUID)
  • Rename correlation_id -> installation_id; and change when trident install starts.
  • Add new persistent servicing_id (like installation_id, stored in the datastore); and change when a servicing operation (install, update, or manual rollback) begins staging.

Related PRs in stack:

@azure-pipelines

Copy link
Copy Markdown
Azure Pipelines:
1 pipeline(s) were filtered out due to trigger conditions.
There may be pipelines that require an authorized user to comment /azp run to run.

@bfjelds bfjelds (bfjelds) changed the title telemetry: remove asset_id, rename correlation_id to installation_id (install-time creation), add persistent servicing_id telemetry: remove asset_id, rename correlation_id to installation_id, add persistent servicing_id Sep 4, 2026
--filepath "$METRICS_FILE" \
--mapping "$KUSTO_TABLE_MAPPING"
--mapping "$KUSTO_TABLE_MAPPING" \
--ignore-schema

@bfjelds bfjelds (bfjelds) Sep 5, 2026

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.

adding ignore-schema because we removed asset_id which is a required field in the telemetry schema.

other options would be:

  • remove asset_id from required list in schema
  • use asset_id as installation_id

@bfjelds
bfjelds (bfjelds) force-pushed the user/bfjelds/mjolnir/installation-servicing-id branch from be44dde to a67b099 Compare September 5, 2026 00:50
Drops the DMI product UUID (/sys/class/dmi/id/product_uuid) from every
telemetry event. It was the one genuinely hardware-derived identifier in
PLATFORM_INFO; installation_id (next commit) already gives a stable,
random, non-hardware-derived per-host identifier for correlation, making
asset_id redundant from a correlation standpoint while being needlessly
more identifying.

Removes read_product_uuid/PRODUCT_UUID_FILE and their tests entirely.
…ll start, not datastore-creation); add persistent servicing_id

installation_id (renamed from correlation_id):
- DataStore::correlation_id() (get-or-create, called from Trident::new
  on every invocation) split into two: DataStore::installation_id()
  (read-only) and DataStore::create_installation_id() (get-or-create).
- Trident::new() now only reads (Trident::installation_id()); it never
  creates one. Trident::install() is the only caller of
  create_installation_id(), at the start of staging (after any multiboot
  datastore swap, so a multiboot install's own new datastore gets its own
  ID). Every other command/gRPC request now simply attaches whatever was
  already stamped at install time, instead of the previous behavior where
  literally the first-ever Trident::new() call on a fresh datastore
  (regardless of which command triggered it) would mint the ID.
- Added a `tracestream: TraceStream` field to the `Trident` struct so
  `install()` can attach the newly-created ID to telemetry (previously
  `Trident::new()` only used its `tracestream` parameter transiently).
- TraceStream::set_correlation_id/correlation_id_handle ->
  set_installation_id/installation_id_handle; AppInsightsSender's
  correlation_id field/param renamed to match. JSON field renamed
  correlation_id -> installation_id.
- New test: installation_id's read-only getter returns None before
  create_installation_id is called, Some after.

New persistent servicing_id:
- DataStore::new_servicing_id() (unconditional overwrite, unlike
  installation_id's insert-if-absent) + DataStore::servicing_id()
  (read-only) using the existing set_value/get_value generic key-value
  API (set_value's overwrite semantics were previously exercised only by
  tests; this is its first real caller).
- Attached via operation_context (not threaded through every engine::*
  function signature): CURRENT_OPERATION's thread-local state extended
  from (operation_id, command) to also carry an optional servicing_id,
  set via the new operation_context::set_servicing_id(). Both
  TraceSender's and AppInsightsSender's field-merging logic updated to
  include it when present.
- Created at the start of staging in all three places that stage:
  Trident::install() (alongside installation_id), engine::update::update()
  (at the same unconditional "start" point update_start already fires
  from), and engine::manual_rollback::execute_rollback()'s has_stage()
  branch (naturally gated to genuine staging, unlike install/update).
  finalize_rollback() reads the persisted ID back (via
  DataStore::servicing_id()) at its own start, so a resumed finalize-only
  invocation (e.g. after reboot) still tags its own metrics with the same
  servicing_id the staging invocation used.
- Known simplification: install/update mint a fresh servicing_id on every
  invocation that reaches their "start" point, including a possible
  finalize-only re-invocation of update() (which, per its existing
  design, unconditionally re-runs staging logic on every call regardless
  of whether it's a true first stage or a resumed one) -- so unlike
  manual rollback, a genuinely separate stage-then-finalize update
  sequence would not currently share one servicing_id across both calls.
  Documented in code comments; not fixed here to avoid a much larger
  change to update()'s existing stage/finalize semantics.

docs/Reference/Agent-Configuration.md updated: asset_id entry removed
(handled in the prior commit), correlation_id -> installation_id with its
new creation semantics, and a new servicing_id entry.

Verified: cargo build --workspace, clippy --all-targets --features
functional-test, fmt --check all clean. cargo test -p trident --lib:
422/422 pass (new tests: installation_id read-only-getter test in
datastore.rs; set_servicing_id tests in operation_context.rs).
…ation

Trident::update() has a case my earlier servicing_id placement in
engine::update::update() completely missed: when the Host Configuration
for this invocation is unchanged from what is already persisted
(datastore.host_status().spec == host_config) and the servicing state is
AbUpdateStaged/RuntimeUpdateStaged, it calls ab_update::finalize_update()/
runtime_update::finalize_update() *directly* -- bypassing
engine::update::update() (and therefore my new_servicing_id() call)
entirely. Every other update path does go through engine::update::update(),
which is correctly gated by Trident::update() to only run when staging is
actually happening (a changed config with has_stage(), or a retry of a
failed/no-op update), so this was the one path a fresh servicing_id was
never being created *or* read back for -- meaning a genuine, separate
finalize-only continuation of a staged update (the real two-step A/B
update flow, rebooting in between) would have no servicing_id at all.

Fix: read the persisted servicing_id back (DataStore::servicing_id()) at
the top of both direct-finalize match arms in Trident::update(), mirroring
what execute_rollback's finalize_rollback already does. This is safe
specifically because reaching those arms already required
datastore.host_status().spec == host_config -- i.e. this invocation's
config is provably identical to whatever was staged, so the persisted
servicing_id is guaranteed to belong to *this* operation, not an unrelated
one.

Also corrected the inline comment on the new_servicing_id() call in
engine::update::update() itself, which had incorrectly implied that
function handles finalize-only re-invocations too.

cargo build -p trident, clippy --all-targets --features functional-test,
fmt --check all clean. cargo test -p trident --lib: 422/422 pass
(unchanged -- no test exercises the true separate-process two-step update
flow yet; this is a targeted correctness fix for a path today's test
suite doesn't cover end-to-end).
set_servicing_id() only affects events fired *after* it runs on this
thread (operation_context is read at send-time, not retroactively) --
with new_servicing_id()/set_servicing_id() coming after update_start,
that metric itself never carried the very servicing_id it establishes.
Swapped the order so update_start (and everything else this invocation
emits) is tagged with it.

cargo build, clippy --all-targets --features functional-test, fmt --check
all clean. cargo test -p trident --lib: 422/422 pass.

Note: the same ordering issue exists in
engine::manual_rollback::execute_rollback (manual_rollback_start fires
before new_servicing_id, unconditionally at the top of the function,
before has_stage()/rollback-availability are even known) -- left as a
follow-up since fixing it cleanly needs more thought: the metric fires
before we know whether a fresh ID will be minted at all, so simply
reading back any already-persisted ID first risks tagging the event with
a stale ID from a prior, unrelated, already-completed operation.
…d (same ordering bug as update_start)

manual_rollback_start fired unconditionally at the very top of
execute_rollback, before has_stage()/rollback-availability were even
known -- and the servicing_id mint (previously placed after rollback_type
was determined, deep inside the has_stage() branch) happened well after
that. Same root cause as the update_start fix: set_servicing_id only
affects events emitted after it runs.

Harder to fix than update_start though, since the metric fires before we
know whether this call will actually stage anything. Restructured so the
right servicing_id is determined first, then the metric fires:

- has_stage() requested: mint a fresh servicing_id immediately (moved up
  from after rollback_type is determined). This now also covers the rare
  "staging requested but nothing available to roll back" early-return
  case, which previously never reached the old mint call at all -- a
  fresh "this attempt" ID for that case is reasonable, and a later
  genuine stage overwrites it again.
- finalize-only (no has_stage()): read back whatever servicing_id is
  already persisted, mirroring finalize_rollback's existing read-back (a
  harmless redundant read if this same call both stages and finalizes).

Removed the now-redundant later new_servicing_id() call inside the
has_stage() branch.

cargo build --workspace, clippy --all-targets --features functional-test,
fmt --check all clean. cargo test -p trident --lib: 422/422 pass.
Removing asset_id from the metrics/telemetry payload (earlier commit in
this PR) breaks kusto_ingestor.py's schema check against the existing
Kusto table mapping, which still expects that column. --ignore-schema
skips that check so metrics upload keeps working without needing a
coordinated Kusto table-mapping update in lockstep with this PR.
@bfjelds
bfjelds (bfjelds) force-pushed the user/bfjelds/mjolnir/installation-servicing-id branch from 1599407 to c96c0af Compare September 5, 2026 01:36
@bfjelds
bfjelds (bfjelds) requested a balanced review from Copilot September 5, 2026 01:38

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.

🟡 Changes recommended

Install finalization overwrites the staged servicing ID, breaking cross-invocation correlation.

Once you've addressed the issues Copilot identified, you can request another Copilot review.

Pull request overview

Updates telemetry identifiers to improve privacy and correlate servicing operations across invocations.

Changes:

  • Removes hardware-derived asset_id and renames correlation_id to installation_id.
  • Persists and emits per-servicing-operation servicing_id values.
  • Adjusts telemetry documentation and Kusto ingestion.
File summaries
File Description
docs/Reference/Agent-Configuration.md Documents telemetry identifiers.
crates/trident/src/main.rs Passes the installation-ID handle to App Insights.
crates/trident/src/logging/tracestream.rs Emits installation and servicing IDs.
crates/trident/src/logging/operation_context.rs Tracks servicing IDs per invocation.
crates/trident/src/logging/appinsights.rs Adds identifiers to App Insights events.
crates/trident/src/lib.rs Creates and restores identifiers during servicing.
crates/trident/src/engine/update.rs Creates servicing IDs for updates.
crates/trident/src/engine/manual_rollback/mod.rs Handles rollback servicing IDs.
crates/trident/src/datastore.rs Persists installation and servicing IDs.
.pipelines/templates/stages/testing_common/scripts/upload_metrics.sh Uploads metrics without schema validation.
Review details
  • Files reviewed: 10/10 changed files
  • Comments generated: 1
  • Review effort level: Balanced

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

Comment thread crates/trident/src/lib.rs
Comment on lines +580 to +584
let servicing_id = datastore
.new_servicing_id()
.message("Failed to create servicing ID")?;
info!("Servicing ID: {servicing_id}");
set_servicing_id(servicing_id.to_string());
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.

2 participants