Commit Graph

2897 Commits

Author SHA1 Message Date
Oliver Kieselbach 3fdcbabd48 chore: bump internal (backlog B-az5, B-z9d)
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-22 20:11:31 +02:00
Oliver Kieselbach b2e8c7627f chore: bump internal and .claude/commands (context diet)
internal: operations facts as reference doc, leaner CLAUDE.md, context-diet plan. .claude/commands: shorter skill descriptions.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-22 20:02:27 +02:00
Oliver Kieselbach bd08d13011 chore: bump internal (mcp catalog sight check in Claude Code done)
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-22 19:55:11 +02:00
Oliver Kieselbach 10c69f592e chore: bump internal (mcp-catalog-context-audit released)
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-22 18:41:06 +02:00
Oliver Kieselbach fe0b2dc7fe refactor(mcp): catalog diet — shared argument texts, scope-first instructions, $schema stripped (D-269)
Every tool description and both role instructions now fit the 2048-char host cap
(Claude Code truncated the Global Admin instructions and two descriptions silently).
Pagination, projection and scope mechanics are said once in the instructions and
the shared pageSize/continuation/tenantId/days texts; role markers left tools only
that role can see; get_resource lists its sections on the argument. The SDK $schema
URL is stripped from tools/list.

Measured (compact tools/list): Global Admin 111.4 KB -> 89.1 KB, Reader 94.7 -> 74.3,
tenant user 64.5 -> 51.3, delegated 75.1 -> 55.6 KB. catalog-budget.test.ts pins the
new ceilings, the variant counts and the empty over-cap list.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-22 18:14:52 +02:00
Oliver Kieselbach a0f007fd43 fix(mcp): synthetic tenant examples, catalog budget test and audit script (D-269)
list_tenants named a real customer domain as its example; the filterTenantList test
carried three real tenants. Both now use synthetic names and GUIDs.

catalog-budget.test.ts pins the per-role wire catalog as a ratchet (size, repeated
argument texts, no foreign domains or GUIDs, the 2048-char host truncation cap with
the four known over-cap texts listed). scripts/audit-catalog.ts dumps and sizes the
catalog per role for before/after measurements.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-22 14:02:35 +02:00
Oliver Kieselbach 1b27babd17 ci: zizmor gate for the workflow files plus a clean baseline (D-268)
zizmor (static analysis for GitHub Actions) now runs twice: locally before a
workflow commit (hard rule in .claude/CLAUDE.md) and as the `zizmor` job next to
the pin gate in action-pins.yml (zizmorcore/zizmor-action pinned by SHA, zizmor
1.30.1 pinned, SARIF into Code scanning, severity low and above). Like the pin
gate it reports and does not block. zizmor.yml holds the single reasoned ignore.

Baseline fixes so the gate starts green:
- persist-credentials: false on every checkout except combine-rules.yml, the one
  job that pushes with the checkout credentials (ignored in zizmor.yml, with reason)
- publish-scripts.yml: id-token: write moves from the workflow to the publish job;
  indexnow-notify.yml: permissions: {}
- deploy-mcp.yml: env values referenced as shell variables inside bash steps
  instead of template expansion
- pester.yml / publish-scripts.yml: reusable workflow via the self-repository form
  $/ (GitHub changelog 2026-07-30); the pin gate treats $/ like ./
- combine-rules.yml: ajv-cli installed with npm ci from rules/scripts/package-lock.json
  (no ts-node peer, no lifecycle scripts) instead of an ad-hoc global install;
  Dependabot watches /rules/scripts
- azure/login pin comment corrected to the tag the SHA actually is (v3.0.2; v3
  moved on to v3.1.0), no runtime change
- Dependabot: 7-day cooldown on every ecosystem (security updates are exempt)

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-22 12:56:03 +02:00
Oliver Kieselbach 100d2242fa chore: regenerate shared manifest and wire types (TenantManagerItem.Name doc)
The XML summary added on TenantManagerItem.Name in ef280b7b rides into
shared-manifests.json as the field's doc; CI run 35701895623 caught the drift.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-22 10:02:11 +02:00
Oliver Kieselbach 089b1b38fd Merge branch 'main' of https://github.com/okieselbach/AutopilotMonitor 2026-09-22 09:53:24 +02:00
Oliver Kieselbach ac465839a5 chore: bump internal (backlog B-umq for D-267)
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-22 09:52:35 +02:00
Oliver Kieselbach ef280b7bb5 fix(backend): managed tenants see operator-provisioned access as "Platform support"
GET delegations/managers no longer exposes the name of an operator-created
Tenant Group to the customer; both operator shapes (groups and direct grants)
carry the neutral label. Assignees, reader count and source stay visible.
Web badge reads "Provisioned by operators". D-267.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-22 09:52:00 +02:00
dependabot[bot] 233737f333 chore(deps-dev): bump the web group (#159)
Bumps the web group in /src/Web/autopilot-monitor-web with 2 updates: [@types/node](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/node) and [autoprefixer](https://github.com/postcss/autoprefixer).


Updates `@types/node` from 22.20.2 to 22.20.4
- [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases)
- [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/node)

Updates `autoprefixer` from 10.5.6 to 10.6.1
- [Release notes](https://github.com/postcss/autoprefixer/releases)
- [Changelog](https://github.com/postcss/autoprefixer/blob/main/CHANGELOG.md)
- [Commits](https://github.com/postcss/autoprefixer/compare/10.5.6...10.6.1)

---
updated-dependencies:
- dependency-name: "@types/node"
  dependency-version: 22.20.4
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: web
- dependency-name: autoprefixer
  dependency-version: 10.6.1
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: web
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-09-22 09:29:05 +02:00
dependabot[bot] 909904e928 chore(deps): bump the mcp group (#158)
Bumps the mcp group in /src/McpServer/autopilot-monitor-mcp with 2 updates: [zod](https://github.com/colinhacks/zod) and [@types/node](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/node).


Updates `zod` from 4.6.2 to 4.6.5
- [Release notes](https://github.com/colinhacks/zod/releases)
- [Commits](https://github.com/colinhacks/zod/compare/v4.6.2...v4.6.5)

Updates `@types/node` from 22.20.2 to 22.20.4
- [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases)
- [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/node)

---
updated-dependencies:
- dependency-name: zod
  dependency-version: 4.6.5
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: mcp
- dependency-name: "@types/node"
  dependency-version: 22.20.4
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: mcp
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-09-22 09:28:50 +02:00
Oliver Kieselbach 59ab47de8c chore: bump internal (D-266 network link changes are not activity)
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
v2.0.1463
2026-09-22 00:48:51 +02:00
Oliver Kieselbach f252a0709f fix(agent): network link changes no longer count as enrollment activity
network_state_change and network_connectivity_check join the non-activity
list next to power_state_change. A flapping dock or access point kept the
idle-stopped periodic collectors running for the whole episode and reset
the stall clock of the enrollment it was stalling. The events are emitted
unchanged; the device-info network snapshots stay activity.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-22 00:48:50 +02:00
Oliver Kieselbach d6d9638981 chore: bump internal (D-265 recurring health-script runs, backlog B-c6h)
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-22 00:38:55 +02:00
Oliver Kieselbach 020248fbda feat(agent): collapse recurring health-script runs into one summary per policy
IME re-runs remediation policies on its own schedule for as long as the
device is up, so an enrollment that waits for its user reported every run
of every policy. The tracker now emits a policy's first run and every run
whose result differs from the last reported one (compliance, exit code,
stderr, remediation status, error code); repeats are counted and surface
as one script_recurrence_summary per policy when the tracker stops.

The start signal of a known policy is held until its result is known,
because the running indicators pair a start with a later result; a start
without any result is still emitted after a grace. The counters live in
the tracker state file next to the file positions, so a hard kill rolls
both back together. Platform scripts are untouched. The summary does not
count as enrollment activity.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-22 00:38:00 +02:00
Oliver Kieselbach a06a0dbfff chore: bump internal (D-264 emergency break stays a start-time check, backlog B-drn)
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-22 00:08:22 +02:00
Oliver Kieselbach 96c24e0688 chore: bump internal (D-263 excessive-events recount, backlog B-7wp)
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-21 23:46:03 +02:00
Oliver Kieselbach c156350183 fix(backend): excessive-events sweep warns and blocks on recounted rows
The stored EventCount doubles on replayed upload batches and was only
corrected at terminal transitions, so the sweep judged running sessions on
an inflated number. It now recounts a candidate before it records the ops
event or blocks the device, skips candidates it cannot verify, and limits
the recount to candidates with a pending action. The warn text no longer
claims an agent loop bug.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-21 23:45:11 +02:00
Oliver Kieselbach 15b25ff509 chore: bump internal (D-261 typed wrapper bodies, D-262 req.Query, backlog B-t5z)
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-21 22:31:15 +02:00
Oliver Kieselbach 5163d7d6a0 refactor(backend): read the request query through req.Query only
73 sites in 68 files built the query collection with
HttpUtility.ParseQueryString(req.Url.Query), 15 files used req.Query and five mixed
both. HttpRequestData.Query is that same call, cached per request, and the ASP.NET
Core request type does not override it, so the change is behaviour-neutral.

- QueryParamGuardTests: ParseQueryString in the Functions project is limited to a
  named baseline of three sites that parse something other than the request query.
- A fact pins req.Query against HttpUtility.ParseQueryString (repeated keys, encoded
  values, caching) so a worker update that changes the implementation fails a test.
- Test mocks that set Url answer Query through CallBase; a loose mock returns null.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-21 22:28:11 +02:00
Oliver Kieselbach 9fd343d328 fix(backend): typed bodies and error envelope for backup, session-restore and delegation wrappers
Six functions wrote their responses through a private wrapper taking the body as
object, which no typed-response guard could see. Their error bodies carried the code
in the error slot, no correlation id, and RestoreRow returned the raw exception text
on a 500.

- Backup job status, row restore and backup trigger answer through ResponseHelper:
  errors are the envelope, the 500 is sanitized, the 202 body is BackupTriggerResponse.
- Same for the session-deletion maintenance trigger, RestoreSession's three error
  sites and the delegation acknowledgements (MessageResponse).
- Backup codes are constants (Constants.BackupErrorCodes) and part of apiErrorCodes;
  RunAlreadyActive and EnqueueFailed join ApiErrorCodes.
- TypedResponseGuardTests gains guard F: no HttpResponseData-returning method with an
  object parameter. BackupWireParityTests pins that the move from the backup
  serializer options to the worker pipeline leaves the wire unchanged.
- Web reads BackupTriggerResponse from the generated wire types.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-21 22:19:38 +02:00
Oliver Kieselbach aef88f3337 chore: bump internal (IME archiver pre-walk check doc, backlog B-ryg)
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-21 20:59:13 +02:00
Oliver Kieselbach 12af1d6c76 fix(backend): IME archiver accepts an existing archive entry before walking the hosts
A version whose installer was already in the archive could only turn
Archived through the 409 on the write-once upload, which needs a
distribution host that still serves the build. For a version no host
serves any more the row stayed Failed:VersionMismatch next to its own
blob and re-walked every host once a day.

The archiver now checks the archive first: an entry whose ProductVersion
is the observed version ends the job as Archived with the hash and size
of the archived bytes and no download; a missing provenance sidecar is
written without a source URL. An entry holding another build, or an
unreadable one, is not accepted and the walk runs as before.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-21 20:58:49 +02:00
Oliver Kieselbach 5433bff3db chore: bump internal (navbar profile photo plan and backlog)
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-21 17:40:00 +02:00
Oliver Kieselbach 355509aa7c feat(web): show the signed-in user's profile photo in the navbar avatar
The browser loads the photo straight from Microsoft Graph with the User.Read
scope already consented at sign-in; it never reaches the backend. Any failure
(no photo, token, network) falls back to the initials. One request per account
and tab, cached in sessionStorage including the no-photo answer.

The four copied avatar blocks in the navbar become one UserAvatar component.
Graph host added to the URL registry and to connect-src in both CSPs; the
privacy page states where the photo comes from and that it is not stored.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-21 17:39:41 +02:00
Oliver Kieselbach 7fb78a7e29 update pointer v2.0.1462 2026-09-21 15:31:59 +02:00
Oliver Kieselbach bfd8d58142 chore: bump internal (WNS deck plan)
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-21 15:27:36 +02:00
Oliver Kieselbach 8ad472d1bc fix(agent): single-instance guard counts a same-named process only in session 0
A process name is not a protected namespace: any user session can keep a
binary named like the agent resident, which made every agent start during
that session exit 1 before registration and let install mode report a
runtime launch that never happened.

The guard and install mode's launch verification now share one rule
(FindSiblingAgentPid): a name match counts only in session 0 and never for
the caller itself. Every runtime start lands there (Scheduled Task, WMI
launch from the SYSTEM install chain, self-update restart), so behaviour
for real agents is unchanged. The session id comes from the process
snapshot, so no handle is opened; a process that cannot be read does not
count, which fails open into two agents rather than none.

Two interactive --console runs in the same user session no longer block
each other; an interactive run next to the session-0 runtime still does.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-21 15:23:23 +02:00
Oliver Kieselbach fc5e2cd751 chore: bump internal (D-259 final, SLA plan and backlog)
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-21 15:10:00 +02:00
Oliver Kieselbach 9540791441 fix(web): SLA page headlines the window the alerts judge and shows "No data" for an empty period
Banner and the success-rate/duration gauges read the evaluation period
(last 30 days) instead of the current ISO week and name the window in their
labels; the app install gauge says "this week". A target whose period holds
no finished enrollment renders a neutral "No data" card and is left out of
the banner count; with nothing to judge the banner is neutral.

Banner logic moved to lib/slaStatus.ts (pure, tested).

Needs the backend of the previous commit: the page reads
evaluationPeriod / evaluationWindowDays.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-21 15:08:38 +02:00
Oliver Kieselbach 058da1627f fix(sla): alert and dashboard judge one rolling 30-day window; an empty period is no data; a lasting breach repeats only on new enrollments
The SLA breach alert judged the calendar month while the dashboard headline
showed the current ISO week, so an alert could report 78 % while the page it
links to showed 0 everywhere - and an empty week rendered as 0.0 % "Breached".

- SlaEvaluationWindow is the single definition of the SLA populations:
  success rate and P95 duration on terminal sessions of the last 30 days
  (rolling), app installs on the install attempts of the current ISO week.
  The breach evaluator and SlaMetricsService both read it, including the
  one-decimal rounding, so the value shown is the value judged.
- SlaMetricsResponse carries EvaluationPeriod and EvaluationWindowDays;
  SlaSnapshot carries Period and HasData. A period without a finished
  enrollment meets every target flag and is marked HasData = false.
- The dashboard reads from whichever starts earlier, the selected months or
  the evaluation window; trend and violators stay on the selected months.
- A lasting breach is notified again only after the cooldown AND once
  something finished since the last notification. A new breach episode after
  a resolve still notifies. Resolve notifications are unchanged.
- Alert period fact and telemetry tag follow the window constant.
- SlaMetricsService takes a nowProvider; the week-boundary workaround in its
  tests is gone.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-21 15:08:16 +02:00
Oliver Kieselbach cb9b3fe404 fix(agent): diagnostics paths collect exactly the folder the guard validated, and never the hard-blocked event logs
A configured diagnostics entry is resolved once to folder, pattern and recursion, and
that target is what gets validated and enumerated; recursion asks the guard before
entering a subfolder, and the user-profile token is expanded before validation.
Security, PowerShell and Sysmon event logs are refused per file, by name and by
resolved channel, in every mode; the portal flags such a path before it is saved.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-21 14:58:03 +02:00
Oliver Kieselbach 12315b2f2d fix(web): Monitoring nav is ordered by admin usage, Progress Portal moves to the end
Directly under Dashboard the Progress Portal collected misclicks: 418 of 1,050
admin page views in 90 days went on to the dashboard within 5 seconds, from
every origin page. It is the end-user page; regular users keep it as their only
entry via REGULAR_USER_ITEMS. Annotations showed the same walk-through pattern
and moves down with it. Fleet Health, Geographic Perf. and Software lead.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-21 13:56:38 +02:00
Oliver Kieselbach 0a4a051866 feat(web): dashboard stat cards get one descriptive icon per metric, red only when sessions failed
All five cards showed the same bar-chart glyph on a differently coloured
block, so the colour carried no meaning. Each card now has a small grey
outline icon for its metric (pulse, check, stopwatch, calendar, x) in the
top-right corner, and the label gets the freed width. Failed Today turns
its value and icon red only when the count is above zero and reads
"No failures" at zero.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-21 13:40:13 +02:00
Oliver Kieselbach f47d961947 fix(web): rule pages tell non-admins that contributing is admin-only instead of failing with 403
The analyze and gather rule pages loaded the community submissions list for
every role. rules/submissions is TenantAdminOrGA, so an Operator or Viewer got
a 403 and a red "Failed to load your community submissions" banner on every
page visit (16 denials in 24 h from two operators of one tenant).

The pages now skip the list for callers who cannot read it and the
contribution box says that only tenant admins can contribute and follow the
tenant's submissions. Tenant admins, Global Admins and a global-scope override
are unchanged; the backend policy stays as it is.

Bumps the internal and .claude/commands gitlinks (platform-health marker
catalog, backlog).

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-21 13:27:25 +02:00
Oliver Kieselbach c701d0d694 Merge branch 'main' of https://github.com/okieselbach/AutopilotMonitor 2026-09-19 14:40:22 +02:00
Oliver Kieselbach d4a3edf849 fix(offboarding): a completed run removes the tenant's superseded dead letters (D-256)
A retry after a poisoned offboarding is a new envelope with a new history
row, and nothing ever deleted from a poison queue. The dead letter of the
failed run therefore stayed until its 7-day TTL and kept the poison backlog
alert firing daily for a tenant that was already gone.

TenantOffboardingHandler.HandleAsync now reports a TenantOffboardingOutcome.
On Completed the worker receives tenant-offboarding-poison in batches and
deletes the envelopes of that tenant; other tenants' and malformed messages
reappear untouched. The sweep never throws: a failure there would keep the
completed envelope on the main queue and end with a Completed run in poison.
A re-pickup of a completed envelope sweeps again.

Other poison queues stay terminal on purpose; their failures have no later
success that supersedes the dead letter.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-19 14:09:28 +02:00
Oliver Kieselbach e164eac1eb feat(backend,web): manual maintenance trigger answers 202, the run is single-flight (D-255)
The trigger ran the whole maintenance inside the HTTP request (about three
minutes against a 230 s gateway limit): the portal reported a timeout while the
run went on, a second click started a second run, and nothing serialized those
runs against each other or against the 2h timer.

Backend
- MaintenanceRunGate: the timer and the manual run enter through one blob
  lease. A held lease writes MaintenanceSkippedLocked and runs nothing;
  MaintenanceStarted is written after the lease is held, the lease is released
  last.
- POST maintenance/trigger validates the date, probes the lease (409 while a
  run is active), enqueues on maintenance-trigger (fail-hard) and answers 202
  with the typed TriggerMaintenanceResponse. MaintenanceTriggerQueueWorker
  (self-polling) runs it; a failing or skipped run is not retried.
- The run report travels in the details of MaintenanceCompleted.
- BlobLeaseLockStore is the shared base of the lease lock stores;
  SessionDeletionMaintenanceLockStore derives from it, behaviour unchanged.
- The probe lets a failed lease release surface as 5xx: a probe lease still
  held at worker pickup would turn the queued run into a silent skip.

Web
- useOpsRunStatus: run status from ops events (latest Started newer than the
  latest Completed/Failed = active), used by the session-cleanup banner
  (rendering unchanged) and the maintenance trigger section.
- Trigger section: queued / already-active messages, status line, polls every
  15 s while a run is active, button disabled meanwhile. The 230 s fetch
  timeout is gone.

Shared manifest, generated wire types and the MCP ops-event vocabulary
regenerated (two new ops event types, reshaped response).

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-19 13:50:06 +02:00
Claude c81591b498 feat(offboarding): operator retry of a Failed tenant offboarding from Tenant Management
A Failed offboarding was a dead end: the worker returns early on a Failed
history, the offboard re-click refuses to re-enqueue on a Failed marker, and
the marker cleanup only warns. Recovery meant deleting the marker row in Table
Storage by hand, while the tenant sat in the list as a tombstone counted on the
Waitlist (its whitelist row is wiped early in the cascade).

- GET global/tenants/{tenantId}/offboarding returns the offboarding record
  (marker status, failed phase, attempts, history details) as the typed
  TenantOffboardingStatusResponse; GA + Reader, never delegated.
- POST global/tenants/{tenantId}/offboarding/retry re-drives a Failed run: the
  History row keeps its RowKey and DrainCompletedAt (the worker's resume anchor)
  and counts the retry, marker and history go back to Initiated, the ordinary
  resume path re-affirms the Disabled-gate, waits out the remaining drain
  barrier and enqueues the envelope. Completed / in-flight answer 409, no
  marker 404, a missing history row 500 before any write. Global audit row
  RETRY/TenantOffboarding. Nothing retries on its own.
- Tenant editor: an "Offboarding" card replaces the Offboard action while the
  cascade owns the row (status, started by, attempts, failed phase and error);
  Failed offers "Retry offboarding…" behind the type-to-confirm dialog in its
  retry variant.
- Tenant list: tombstone rows carry an Offboarding badge (own Status facet)
  instead of Suspended, and no longer count as Waitlist.
- Tests: retry decision (reset order, drain anchor, conflicts, not found,
  enqueue failure, remaining-barrier delay), status projection, wire parity
  facts, filter facet. Shared manifest and generated wire types regenerated.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XPG5WCmSwfNAv4xkK62n5r
2026-09-18 10:30:06 +00:00
Oliver Kieselbach d8c03d9d95 fix(web): manual maintenance trigger waits for the run instead of timing out at 30 s
The run answers when it is done (about three minutes); the default fetch
timeout reported "signal timed out" although the run completed, which invites
a second click and a second run. Wait up to the gateway limit of 230 s.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-18 11:18:50 +02:00
Oliver Kieselbach ffb133f0b9 fix(backend): platform counters no longer freeze under retention (D-254)
The landing-page counters stood still since D-198: the two-hourly recompute
was their only writer and merged Max(live figure, stored figure), while
retention prunes the live figure below the stored one - so new enrollments
never showed up again.

- Hot path increments the per-tenant counters only (CAS): enrollments at the
  fresh registration, successes at the one status seam every Succeeded writer
  passes (transitioned && Succeeded), events per ingest batch. No batch
  touches the global row.
- The maintenance run floors the tenant counters at the live figures (one
  multi-field write per tenant), sums the tenant rows and adds the growth
  since the previous run to the platform row. The previous sums live in a
  baseline row written in one transaction with the platform row. A total is
  never derived from an absolute figure again: a shrinking source (retention,
  offboarding) costs the growth of one interval instead of freezing the
  counter. Without a baseline the sum is a lower bound only.
- TotalTenants rolls up the tenant rows with enrollments, UniqueDeviceModels
  a persisted set of seen models. TotalUsers stays a high-water-mark (a
  persisted user set would outlive the user-activity retention).
- The platform row is merged with its read ETag and never carries
  IssuesDetected: the former full-row upsert undid every increment that
  landed during the run.
- Reads that feed the baseline are not fail-soft, and a floor that could not
  be written skips the rollup of that run - a swallowed zero would be added
  as growth a second time.

Shared manifest and generated wire types: doc comment of PlatformStats only.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-18 10:48:49 +02:00
Oliver Kieselbach b3fe321f0d fix(offboarding): owned-group wipe no longer poisons every tenant offboarding
The wipe of the tenant's own self-service Tenant Group handed the derived
group id ("msp-{tenantId}") to the tenant-GUID entry points of SafeWipeService.
Their GUID guard threw on every attempt, so each offboarding since the
self-service delegation change ran into max_dequeue and ended Failed after the
tenant tables were already wiped.

- SafeWipeService: WipeByOwnedGroupPartitionAsync / WipeByOwnedGroupRowKeyAsync
  take the tenant GUID and derive the group id themselves; matching filters in
  OffboardingFilters.
- The public wipe entry points are no longer virtual: they validate and hand
  over to protected virtual cores. Test fakes override the cores only, so the
  guard runs under every fake. With the old handler code 16 handler tests fail
  on the guard, which is how this shipped unnoticed.
- Tests: handler regression on the owned-group anchors, owned-group variants
  incl. verify-abort on a foreign group, filter shapes and guards.

Deploy and recovery of the stuck offboarding: B-5g6.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-18 09:41:21 +02:00
dependabot[bot] 69d210c51d chore(deps): bump the mcp group (#156)
Bumps the mcp group in /src/McpServer/autopilot-monitor-mcp with 3 updates: [compression](https://github.com/expressjs/compression), [zod](https://github.com/colinhacks/zod) and [@types/node](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/node).


Updates `compression` from 1.8.1 to 1.8.2
- [Release notes](https://github.com/expressjs/compression/releases)
- [Changelog](https://github.com/expressjs/compression/blob/master/HISTORY.md)
- [Commits](https://github.com/expressjs/compression/compare/v1.8.1...v1.8.2)

Updates `zod` from 4.5.4 to 4.6.2
- [Release notes](https://github.com/colinhacks/zod/releases)
- [Commits](https://github.com/colinhacks/zod/compare/v4.5.4...v4.6.2)

Updates `@types/node` from 22.20.1 to 22.20.2
- [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases)
- [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/node)

---
updated-dependencies:
- dependency-name: compression
  dependency-version: 1.8.2
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: mcp
- dependency-name: zod
  dependency-version: 4.6.2
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: mcp
- dependency-name: "@types/node"
  dependency-version: 22.20.2
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: mcp
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-09-15 11:33:51 +02:00
dependabot[bot] 06a88e9897 chore(deps): bump the web group (#157)
Bumps the web group in /src/Web/autopilot-monitor-web with 10 updates:

| Package | From | To |
| --- | --- | --- |
| [@microsoft/applicationinsights-web](https://github.com/microsoft/ApplicationInsights-JS) | `3.4.3` | `3.4.4` |
| [next](https://github.com/vercel/next.js) | `16.3.4` | `16.3.5` |
| [react](https://github.com/react/react/tree/HEAD/packages/react) | `19.2.8` | `19.3.0` |
| [@types/react](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/react) | `19.2.18` | `19.3.0` |
| [react-dom](https://github.com/react/react/tree/HEAD/packages/react-dom) | `19.2.8` | `19.3.0` |
| [@types/react-dom](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/react-dom) | `19.2.7` | `19.3.0` |
| [@next/bundle-analyzer](https://github.com/vercel/next.js/tree/HEAD/packages/next-bundle-analyzer) | `16.3.4` | `16.3.5` |
| [@types/node](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/node) | `22.20.1` | `22.20.2` |
| [autoprefixer](https://github.com/postcss/autoprefixer) | `10.5.5` | `10.5.6` |
| [eslint-config-next](https://github.com/vercel/next.js/tree/HEAD/packages/eslint-config-next) | `16.3.4` | `16.3.5` |


Updates `@microsoft/applicationinsights-web` from 3.4.3 to 3.4.4
- [Release notes](https://github.com/microsoft/ApplicationInsights-JS/releases)
- [Changelog](https://github.com/microsoft/ApplicationInsights-JS/blob/main/RELEASES.md)
- [Commits](https://github.com/microsoft/ApplicationInsights-JS/commits)

Updates `next` from 16.3.4 to 16.3.5
- [Release notes](https://github.com/vercel/next.js/releases)
- [Commits](https://github.com/vercel/next.js/compare/v16.3.4...v16.3.5)

Updates `react` from 19.2.8 to 19.3.0
- [Release notes](https://github.com/react/react/releases)
- [Changelog](https://github.com/react/react/blob/main/CHANGELOG.md)
- [Commits](https://github.com/react/react/commits/v19.3.0/packages/react)

Updates `@types/react` from 19.2.18 to 19.3.0
- [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases)
- [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/react)

Updates `react-dom` from 19.2.8 to 19.3.0
- [Release notes](https://github.com/react/react/releases)
- [Changelog](https://github.com/react/react/blob/main/CHANGELOG.md)
- [Commits](https://github.com/react/react/commits/v19.3.0/packages/react-dom)

Updates `@types/react-dom` from 19.2.7 to 19.3.0
- [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases)
- [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/react-dom)

Updates `@next/bundle-analyzer` from 16.3.4 to 16.3.5
- [Release notes](https://github.com/vercel/next.js/releases)
- [Commits](https://github.com/vercel/next.js/commits/v16.3.5/packages/next-bundle-analyzer)

Updates `@types/node` from 22.20.1 to 22.20.2
- [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases)
- [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/node)

Updates `@types/react` from 19.2.18 to 19.3.0
- [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases)
- [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/react)

Updates `@types/react-dom` from 19.2.7 to 19.3.0
- [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases)
- [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/react-dom)

Updates `autoprefixer` from 10.5.5 to 10.5.6
- [Release notes](https://github.com/postcss/autoprefixer/releases)
- [Changelog](https://github.com/postcss/autoprefixer/blob/main/CHANGELOG.md)
- [Commits](https://github.com/postcss/autoprefixer/compare/10.5.5...10.5.6)

Updates `eslint-config-next` from 16.3.4 to 16.3.5
- [Release notes](https://github.com/vercel/next.js/releases)
- [Commits](https://github.com/vercel/next.js/commits/v16.3.5/packages/eslint-config-next)

---
updated-dependencies:
- dependency-name: "@microsoft/applicationinsights-web"
  dependency-version: 3.4.4
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: web
- dependency-name: next
  dependency-version: 16.3.5
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: web
- dependency-name: react
  dependency-version: 19.3.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: web
- dependency-name: "@types/react"
  dependency-version: 19.3.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: web
- dependency-name: react-dom
  dependency-version: 19.3.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: web
- dependency-name: "@types/react-dom"
  dependency-version: 19.3.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: web
- dependency-name: "@next/bundle-analyzer"
  dependency-version: 16.3.5
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: web
- dependency-name: "@types/node"
  dependency-version: 22.20.2
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: web
- dependency-name: "@types/react"
  dependency-version: 19.3.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: web
- dependency-name: "@types/react-dom"
  dependency-version: 19.3.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: web
- dependency-name: autoprefixer
  dependency-version: 10.5.6
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: web
- dependency-name: eslint-config-next
  dependency-version: 16.3.5
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: web
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-09-15 11:33:35 +02:00
Oliver Kieselbach aee30f68e8 chore(submodule): tenant-management-filters plan and deploy item B-h7n
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-15 11:29:25 +02:00
Oliver Kieselbach 3db9a884de feat(web): facet filters for the Tenant Management list
Replaces the two Ready/Waitlist toggles with a Filter popover of five
facets (Plan, Paying, Trial, App registration, Status): OR within a
facet, AND across facets, each option carrying the count it would match
under the other facets, active selections as removable chips, and a
plan breakdown (Community / Pro with paying / Trial / MSP) over all
tenants in the statistics line.

The list badge, the filter and the edit modal now derive the effective
edition through one function; a trial-only tenant reads "Pro (Trial)"
in the list instead of "Community". The App facet is bound to the
dual-app parallel window (legacyConfigured), like the badge.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-15 11:28:57 +02:00
Oliver Kieselbach 0037b8cb12 chore(submodule): WDP docs push and Cloud PC automatic-mode backlog items
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-12 23:59:48 +02:00
Oliver Kieselbach 205b13dbb9 chore(submodule): backlog items for the ARM64 requirements docs push
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-12 23:24:54 +02:00