Commit Graph

3385 Commits

Author SHA1 Message Date
Oliver Kieselbach 77fae059ed bump pointer: internal (D-401, D-402), .claude/commands
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-02 19:43:54 +01:00
Oliver Kieselbach fd309da51d Notifications: retire channel provider 40; ops channels come from the stored list
Value 40 stays reserved and a stored one is refused, like value 1. The three fixed ops
slots and their synthesis are gone. A customer submission reaches a channel through its
ops event and an alert rule, so the functions that raise one now require the service.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-02 19:43:51 +01:00
Oliver Kieselbach 9c4039b3c3 bump pointer: internal (B-vm9, B-fdd), .claude/commands
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-02 19:31:15 +01:00
Oliver Kieselbach b0fb8eb5fa bump pointer: internal (B-vm9), .claude/commands
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-02 18:48:29 +01:00
Oliver Kieselbach dd2432fa62 bump pointer: internal (D-400 live)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-02 18:41:41 +01:00
Oliver Kieselbach d9b67e444a bump pointer: internal (D-400)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
v2.0.1497
2026-11-02 18:17:48 +01:00
Oliver Kieselbach 47b91f76e7 Agent: a proxy value with credentials is replaced as a whole
Taking it apart left part of a password containing =, ; or :// in the log and the event.
A proxy string has no defined form for credentials, so there is nothing safe to parse.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-02 18:16:59 +01:00
Oliver Kieselbach 9c6b55a8bc bump pointer: internal (D-400), .claude/commands
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-02 18:05:27 +01:00
Oliver Kieselbach f00b475f59 Agent: the log names the upload route and when the proxy settings changed
The agent log had no proxy line at all, and proxy_configuration could not tell auto-detect
from direct.

ProxyObserver logs the settings and the route .NET chooses before the first request, every
change with its time, the route in each failed upload, one finding per failure series that
ties it to a change or says nothing changed, and a summary when the run stops while failing.
It compares with the start instead of judging a value.

proxy_configuration uses the same reader. It knows auto-detect and the real WinHTTP proxy,
drops credentials and PAC query strings, and is sent again on a change.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-02 18:04:37 +01:00
Oliver Kieselbach f8743dcae0 bump pointer: internal (B-3gb done)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-02 17:24:21 +01:00
Oliver Kieselbach bd94382edb Web: panel heads without nested buttons, session page fits a phone
The Download and Install heads were one button holding the skipped, pending
and no-download toggles: invalid HTML that hid them from screen readers. The
head is now a div that takes the mouse click, so the whole area still toggles,
with the chevron as the real button (ProgressPanelHeader). The tab order
changes: the panel toggle now follows the pills.

Community submissions had the same flaw with span role=button, which the
keyboard could not reach at all. Title, Show all and Pending only are sibling
buttons now.

At 390 px the session page ran up to 630 px wide. The event type, the event
message and the install error detail hold long words without a break point;
the event row wraps now and those texts break inside a word. Desktop rows stay
on one line. The clone between the two panel heads is gone from the baseline.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-02 17:24:21 +01:00
dependabot[bot] 8abd3a8848 chore(deps): bump the actions group with 6 updates (#195)
Bumps the actions group with 6 updates:

| Package | From | To |
| --- | --- | --- |
| [zizmorcore/zizmor-action](https://github.com/zizmorcore/zizmor-action) | `0.6.4` | `0.8.0` |
| [actions/upload-artifact](https://github.com/actions/upload-artifact) | `7.0.1` | `7.0.2` |
| [actions/download-artifact](https://github.com/actions/download-artifact) | `8.0.1` | `8.0.2` |
| [actions/setup-node](https://github.com/actions/setup-node) | `7.0.0` | `7.1.0` |
| [github/codeql-action/init](https://github.com/github/codeql-action) | `4.38.2` | `4.39.1` |
| [github/codeql-action/analyze](https://github.com/github/codeql-action) | `4.38.2` | `4.39.1` |


Updates `zizmorcore/zizmor-action` from 0.6.4 to 0.8.0
- [Release notes](https://github.com/zizmorcore/zizmor-action/releases)
- [Commits](https://github.com/zizmorcore/zizmor-action/compare/cc914d7f3750a2d13d75c7f184a1060aa0e9d482...53290badfa369cb0bf7ad16342cb62c2fa47ac7b)

Updates `actions/upload-artifact` from 7.0.1 to 7.0.2
- [Release notes](https://github.com/actions/upload-artifact/releases)
- [Commits](https://github.com/actions/upload-artifact/compare/043fb46d1a93c77aae656e7c1c64a875d1fc6a0a...cf430e030ddbb5b0abf93d22962f4752f3646cd9)

Updates `actions/download-artifact` from 8.0.1 to 8.0.2
- [Release notes](https://github.com/actions/download-artifact/releases)
- [Commits](https://github.com/actions/download-artifact/compare/3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c...9000827ccba6bdab643e8b6fd33ac0654aef8333)

Updates `actions/setup-node` from 7.0.0 to 7.1.0
- [Release notes](https://github.com/actions/setup-node/releases)
- [Commits](https://github.com/actions/setup-node/compare/820762786026740c76f36085b0efc47a31fe5020...949feb2413d6458794dcd2491c4babbbce0c15c1)

Updates `github/codeql-action/init` from 4.38.2 to 4.39.1
- [Release notes](https://github.com/github/codeql-action/releases)
- [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md)
- [Commits](https://github.com/github/codeql-action/compare/2892aa5e19bbd11bc0cff5427e3b750a04d9e3c2...9b3254085494b55a0f7c4c2b3d2d52cc712ce370)

Updates `github/codeql-action/analyze` from 4.38.2 to 4.39.1
- [Release notes](https://github.com/github/codeql-action/releases)
- [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md)
- [Commits](https://github.com/github/codeql-action/compare/2892aa5e19bbd11bc0cff5427e3b750a04d9e3c2...9b3254085494b55a0f7c4c2b3d2d52cc712ce370)

---
updated-dependencies:
- dependency-name: zizmorcore/zizmor-action
  dependency-version: 0.8.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: actions
- dependency-name: actions/upload-artifact
  dependency-version: 7.0.2
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: actions
- dependency-name: actions/download-artifact
  dependency-version: 8.0.2
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: actions
- dependency-name: actions/setup-node
  dependency-version: 7.1.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: actions
- dependency-name: github/codeql-action/init
  dependency-version: 4.39.1
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: actions
- dependency-name: github/codeql-action/analyze
  dependency-version: 4.39.1
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: actions
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-11-02 16:46:27 +01:00
Oliver Kieselbach 43b3008266 Merge origin/main: the phone layout of Install Progress and the No data row
Both changed InstallProgress; the row keeps the new detection badge and the frozen text.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
v2.0.1496
2026-11-02 16:24:48 +01:00
Oliver Kieselbach 2304436b8c bump pointer: internal (D-398, D-399), .claude/commands
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-02 16:14:32 +01:00
Oliver Kieselbach ad875d4021 Session page: a row reads No data once nothing has arrived from the agent for 20 minutes
When the uploads of an unfinished session stopped, its app and script rows kept a
ticking timer until the session timed out hours later, and read like a hanging package.
They now freeze at the agent's last report and say No data, and the next data brings
them back to live. The session status does not change.

Silence is measured on the server clock (lastIngestAt, every event's receivedAt), since
device clocks can be hours off. A Stalled session counts without a timestamp; an agent
that reported the stall itself keeps its timer until it goes quiet too. A session that
has ended keeps Incomplete and its wording.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-02 16:14:25 +01:00
Oliver Kieselbach fd6b68231e Agent: an IME log rollover keeps the read position instead of replaying the archive
IME rolls a log with File.Move at 3 MB. The tracker kept positions per path, took the
archive for a new file and read it from 0, so every line played again: finished apps
fell back to Installing, the subgraph line of an older app marked the running app as
skipped with a wrong start, and the app summary jumped.

A position now belongs to the file (volume serial plus file index). It follows the file
to its new name, only the unread tail is read, and the live name starts at 0. Invocation
markers and a held tail move along: reading the archive again used to restore the marker
of a platform script in flight by accident. A new file longer than the old position no
longer loses lines after a restart. An unreadable identity keeps the old behaviour.

OnAllAppsCompleted had no subscriber and is gone.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-02 15:48:47 +01:00
Oliver Kieselbach 88494a312f Agent: a failed upload names its cause and the endpoint it tried
The NETWORK fail line only said "An error occurred while sending the request",
so DNS, connect timeout, proxy and TLS looked the same. It now carries the
WebException status, the socket error or native code and the innermost message,
which on .NET Framework names the endpoint; a destination other than the backend
means a proxy. The distress and emergency channels get the same text.

The "same logical network" line keeps every occurrence on Debug: when it happened
is the information.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-02 15:31:53 +01:00
Oliver Kieselbach 6901f6f682 Install Progress on phones: the green detection pill moves into Details, red and amber shorten (#194) 2026-10-31 08:50:27 +01:00
Oliver Kieselbach 82c0b20be1 bump pointer: internal (D-397), .claude/commands
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-30 12:27:53 +01:00
Oliver Kieselbach 4fa439cc3c Settings: Save only; the Reset button is gone from every card
Reset sat next to Save and discarded a card's edits with one click and no
confirmation. The bar is now SaveBar; the Reset handlers go with it, as does the
Reset button on the Global Admin settings card. A failed save shows its reason
until the next Save.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-30 12:27:31 +01:00
Oliver Kieselbach b631c63c53 bump pointer: internal (D-395, D-396 web live; open checks in the backlog)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-30 11:07:09 +01:00
Oliver Kieselbach e9aaf3db53 bump pointer: internal (D-395, D-396, work files)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-30 10:59:00 +01:00
Oliver Kieselbach a37930e87a Agent: two identical Debug repeats go to Trace after their first occurrence
NetworkChangeDetector "same logical network" (16x) and the DeviceInfo "payload unchanged" line
(3x) stayed on Debug in session 080a3016, measured against the D-394 contract.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-30 10:59:00 +01:00
Oliver Kieselbach 6bc5d47afe Settings: one card, one Save; the result of a save shows at its button
Agent Settings was the only page with two cards on one Save bar, so a change in the top card
read as auto-saved and was lost. Agent Parameters and Agent Collectors are now their own
sections: Save and Reset touch only their card, unsaved edits in the other card stay (D-395).

A failed save showed a green "Saved!" on every tenant settings card, because saveConfiguration
reports a failure as false and the bar only caught exceptions; the reason was only in the banner
at the top of the page. A failure now throws and the bar shows "Save failed: <reason>" next to
the button until the next Save or Reset (D-396).

The Contact card showed "Read-only" and locked Reset on invalid input. saveBlockedReason now
blocks only Save and names the reason; canSave means a read-only viewer and nothing else.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-30 10:58:59 +01:00
Oliver Kieselbach 0ebbf87e5b bump pointer: internal (D-394 live: agent 2.0.1495; count the next session B-ztz)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-30 01:49:49 +01:00
Oliver Kieselbach fec4bcc6c5 bump pointer: internal (B-gkg, B-g22 built; ships with the D-394 agent release B-ztz)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
v2.0.1495
2026-10-30 01:23:39 +01:00
Oliver Kieselbach 6ce2176aa0 Agent logs the TenantId miss block once and no longer swallows handler faults silently
- TenantIdAwaiter re-checks silently. The bootstrap's own probe has already written
  the resolver's miss block, which used to appear twice in a row whenever the
  TenantId was missing at start (the wait is on by default). A hit re-probes with
  the logger for the source line; an internal overload makes this testable.
- Handler and callback faults are logged at Warning: HelloCompleted (it carries the
  Hello resolution to the decision engine), the upload orchestrator's five events
  (one RaiseSafely helper), the AAD user-joined callback and ConsoleBypassWatcher's
  WatcherArmFailed.
- Faults on per-signal and per-event paths are logged at Debug with a repeatKey:
  the hand-off of signals and transitions to the upload spool, the SignalPosted and
  back-pressure observers, and timeline subscribers.
- Comments that still named a level from before D-394 are corrected; two German
  comments in SignalIngress are replaced.

D-394

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-30 01:23:28 +01:00
Oliver Kieselbach 1fe4e6711e bump pointer: internal, .claude/commands (D-394 built; agent release and count B-ztz)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-30 00:54:17 +01:00
Oliver Kieselbach 61947c416c Agent log levels follow one contract: Info is the flow, Debug the why, Verbose every detail, Trace the mechanics
- AgentLogger documents the four levels and takes an optional repeatKey on
  Info/Debug/Verbose: an identical repeat under the same key goes to Trace.
- To Trace: the IME registry watcher's arm/fire cycle (the only watcher wired
  to Debug), spool enqueue and ingress per item, informational pass-through,
  classifier-tick and no-op decision steps, idle DO polls, the Hello policy
  poll and the upload send.
- Decision-stage changes are Info; other effect-bearing steps and dead ends
  stay Debug.
- To Verbose: the upload success record (now with firstId, lastId, bytes),
  adapter mappings, download progress, per-file diagnostics entries and raw
  provisioning JSON.
- To Debug: "how" lines of start and end loops, duplicate Info lines, the
  OOBE update registry snapshot, caught exceptions that were logged below
  Debug, and IME lines read from a previous enrollment's log.
- Repeats at Info (re-collections, interval gather rules, recurring health
  scripts, IME policy and session lines) carry a repeatKey.
- Stop followed by Dispose logs "Stopped" once, Hello COMPLETED is logged only
  when it completed, and the await-enrollment poll no longer warns every 5 s.
- The Shared LogLevel doc and the portal hint describe the levels the same
  way; manifests and wire types regenerated.

D-394

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-30 00:53:57 +01:00
Oliver Kieselbach 25c65f1837 bump pointer: internal (D-393 live; field verify B-8v3)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-30 00:04:36 +01:00
Oliver Kieselbach eb287248c6 bump pointer: internal (D-392 VM check clean; D-393 built, backend deploy after go)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-29 23:57:53 +01:00
Oliver Kieselbach 738a349710 Failed app rows without a code take IME's code from the registry (D-393)
When the IME log line that first marks a failure carries no code (installer
timeout and similar), the agent reports the failure without one. IME's code
follows seconds later, but the app is already Error, so no second event goes
out and FailureCode stays empty; "Top Failure Codes" skipped such rows.

At session end the backend now fills an empty FailureCode on failed IME rows
from the latest registry_app_state error of the same AppId (canonical through
ErrorCodeCatalog.NormalizeCode). Fill only: a code or failureType from the
event stays, and a registry error older than the row's last attempt start is
never used. It runs at the terminal seam, after the agent's shutdown batch and
from the sweep, next to the ESP-blocking resolution; idempotent and fail-soft.

The session row-range filter, now used three times in the file, moves into
SessionAppRowsFilter.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-29 23:57:31 +01:00
Oliver Kieselbach 73525f1d34 bump pointer: internal (D-392 live; field verify B-wpr, VM check B-qfy)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-29 22:41:10 +01:00
Oliver Kieselbach b7c6b11909 bump pointer: internal (D-392 phase 2 built; release and VM check B-qfy)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
v2.0.1494
2026-10-29 22:16:13 +01:00
Oliver Kieselbach 74d489b51e Agent reports IME's detection check after the install as app_post_install_detection
IME checks an app right after its installer; only that check's verdict reaches the ESP and
Intune. The agent now reports its result as its own informational event (D-392):

- IME-POST-INSTALL-DETECTION marks the check ("expected to have enforcement state:
  Detected", logged only for the app just installed); the app's next detection result line
  becomes app_post_install_detection (postInstallDetection, afterReboot) and detectionResult
  stays the check before the install.
- After an exit code mapped to SoftReboot/HardReboot the check right after the install is not
  reported; IME-POST-REBOOT-DETECTION (InProgressPendingReboot -> Success) marks the check
  after the restart instead.
- The mark is transient and the marker action app-mutating, so a marker from a previous
  enrollment's log or without its result line never turns a later check into this one. No
  state transition, no decision signal; historic replay is suppressed like DO telemetry.
- Install Progress, the timeline, the diagnosis tile (DiagnosisCulprit.PostInstallDetection)
  and the MCP event catalog show the check.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-29 22:16:00 +01:00
Oliver Kieselbach dff69b4d73 bump pointer: internal (D-392 app detection, phase 1 built; deploy and verify B-wpr)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-29 21:55:12 +01:00
Oliver Kieselbach 9c654983d1 App detection follows IME's two checks: 0x87D1041C is the verdict after the install, detectionResult the check before it
IME checks every app before the install (NotDetected starts it) and right after the
installer; only the second check's verdict reaches the ESP and Intune. app_install_completed
comes at IME's provisional "Success", so detectionResult on app events is the check before
the install. The apps banner, ANALYZE-APP-001 and the diagnosis read it as the result after
the install and flagged healthy installs (D-392).

- IME-ERROR-REPORT captures EnforcementErrorCode as errorCode (-2016345060 = 0x87D1041C).
- AppInstallSummaries.FailureCode is stored canonical (ErrorCodeCatalog.NormalizeCode); one
  HRESULT parser serves the catalog lookup, the diagnosis and the ingest.
- The apps banner counts failed installs IME did not detect after the installer
  (DetectionFailureCount, renamed from DetectionLiesCount), with its own advice for Store
  apps; the app page uses the generated AppAnalyticsResponse.
- ANALYZE-APP-001 v2 matches the code in any enrollment outcome; ANALYZE-APP-013 picks its
  app by the code; ANALYZE-APP-010 is retired (APP-001 reports the same event).
- Diagnosis, Install Progress and the timeline read the code: IME's verdict turns an
  installed row into a detection failure, error codes read as hex, and the timeline shows the
  check before the install.
- The summary column DetectionResult and DiagnosisCulprit.DetectionResult are gone.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-29 21:54:40 +01:00
Oliver Kieselbach 0bc82fd719 bump pointer: internal (D-390/D-391 released: backend 1.5.1306, MCP 1.6.569 @ 783b390; field verify B-w7y, alert rule B-5q5)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-29 20:17:29 +01:00
Oliver Kieselbach 783b390448 bump pointer: internal (lesson from the D-390/D-391 request-body work)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-29 18:00:39 +01:00
Oliver Kieselbach 5c75155293 bump pointer: internal, .claude/commands (D-390/D-391 counted body cap and report upload limit built; deploy and verify B-w7y, alert rule B-5q5)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-29 17:58:50 +01:00
Oliver Kieselbach 7588286787 Request bodies are capped on the bytes actually read, and report uploads run three at a time per instance
RequestBody checked its byte cap only against the Content-Length header and then buffered the
whole body. A chunked body carries no Content-Length and a gzip body's header names the compressed
size, so a route with an 8 KB cap read whatever Kestrel's unpinned default allowed once inflated.
Every read now counts the bytes the handler receives, after request decompression, and stops one
byte past the cap; a site that names no cap reads under 1 MiB, and the error stays the 400
"Request body too large". The agent routes that parse with their own reader (session
registration, error report, distress, upload URL) and the Azure Monitor alert webhook read through
the same counted buffer and keep their responses. The telemetry ingest shares the counting, so a
body refused at the worker limit gets its 413 instead of a 500.

The worker's request body limit is pinned at 20 MiB, the largest route cap. Kestrel and the
decompression stream both enforce it, and a PostConfigure keeps registration order and app
settings from lifting it. A guard test keeps every body read inside the counted reader.

Session report and diagnostic-file uploads (up to 20 MiB each) run at most three at a time per
instance. A fourth gets 429 with Retry-After, and the portal dialogs keep their input. The first
refusal per instance and hour raises the new ReportUploadLimitReached ops event, which an alert
rule can route.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-29 17:58:24 +01:00
Oliver Kieselbach 0d6797275c bump pointer: internal (B-xg7 done, B-vu8 trimmed, D-389 changelog entry)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-29 16:14:48 +01:00
Oliver Kieselbach 17f98f5c56 bump pointer: internal (D-389 usage hardware device count released, B-xg7)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-29 16:06:20 +01:00
Oliver Kieselbach f53fdf86f6 Fleet Health counts read "1 enrollment" and "1 install" in the singular
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-29 16:00:46 +01:00
Oliver Kieselbach b57f9312ce bump pointer: internal (D-389 usage hardware device count built, B-xg7)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-29 15:42:21 +01:00
Oliver Kieselbach 5136d33c2c Usage Metrics hardware lists show the devices behind their sessions; Fleet Health counts enrollments
Top Manufacturers and Top Models counted enrollment sessions without saying so, so a device
reset and enrolled again read like several devices. The lists still rank and share by sessions;
every row now also carries the distinct devices behind them (HardwareCount.DeviceCount), keyed
like device journeys: the normalized serial number per tenant, and a session without a usable
serial counts as its own device. The page groups both lists under a Hardware heading that says
so. Fleet Health's Health by Device Model labelled its session count "devices"; it now says
"enrollments". The get_usage_metrics description names the unit.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-29 15:41:45 +01:00
Oliver Kieselbach 5589ca42c7 bump pointer: internal (D-388 feedback id released: backend 1.5.1304, B-cfz)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-29 13:39:38 +01:00
Oliver Kieselbach 8f9cf6e153 bump pointer: internal (lesson: browser checks are always possible)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-29 13:13:42 +01:00
Oliver Kieselbach eebbc895c8 bump pointer: internal, infra (D-388 feedback id built; deploy, backfill and verify B-cfz)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-29 13:06:28 +01:00
Oliver Kieselbach 7d19a71230 Every feedback entry gets a 12-character id, shown on User Feedback and linked from the bell and alerts
Help-menu feedback, ratings and farewell comments each carry a FeedbackId: 12 lowercase hex
characters, the shape of a session report id. The feedback repository mints it when a row is first
written and keeps it on every later write (the rating and farewell rows are rewritten whole); a
help-menu row's RowKey ends in it. It is stored as a column in every kind, so one filter finds an
entry across all partitions.

FeedbackEntryWire.FeedbackId is that id for every kind (it was the help-menu RowKey).
FeedbackReceived, FeedbackRatingReceived and OffboardingFeedbackReceived carry feedbackId and a
portal path to the single entry, and the "New Feedback" / "New Rating" bells link it too.

The User Feedback page shows the id with a copy button on every card and follows ?feedbackId= to
the entry's tab and page, highlighting the card; an unknown id is named in the error banner.

Rows written before get their id from a one-time backfill after this backend is live.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-29 13:05:41 +01:00