Commit Graph

2857 Commits

Author SHA1 Message Date
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
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
Oliver Kieselbach b938a03367 chore(submodule): backlog item for the published-DPA deploy
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-11 23:36:09 +02:00
Oliver Kieselbach d474c2be9f fix(web): privacy and terms point to the published DPA instead of "available on request"
The DPA is published in the customer docs and accepted on the Get started
page before the first sign-in (D-251); the onboarding records the accepted
version on the tenant (D-252). The legal pages still described it as
available on request and as a Pro-only signed document.

- privacy: the Roles section says the DPA is published, applies to both
  plans, is accepted at sign-up with the version recorded, and that an
  individually signed DPA prevails; External Services links the DPA and its
  sub-processor list; Changes no longer hinges on "if you hold a signed
  DPA"; Contact no longer offers to send one.
- terms: the DPA is no longer listed as a Pro differentiator; Security and
  Transparency links the DPA as applying to both plans. Last updated
  11 September 2026.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-11 23:35:59 +02:00
Oliver Kieselbach 96390beeb9 chore(submodule): drop the separate todo file from the working-file rules
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-11 23:29:23 +02:00
Oliver Kieselbach fe63a0a352 chore(submodule): park the zone-anchoring plan behind B-ugv
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-11 22:02:00 +02:00
Oliver Kieselbach 2e36e9c234 fix(web): Fleet Health model rows without failures link to all sessions of the model — the "Health by Device Model" deep link carried status=Failed unconditionally, so a 100 % row landed on a list that is empty by definition; the Failed filter now travels only when the bucket has failed sessions, and the link title says which list opens
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-11 22:00:47 +02:00
Oliver Kieselbach b83aa6d97d chore(submodule): plan for the AgentExecutor.log zone-anchoring defect
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-11 21:52:17 +02:00
Oliver Kieselbach f42fcbcb3c fix(web): the app/script panel divider names the ESP phase, not the assignment — sections read "Device Setup" / "Account Setup" from the phase timeline's table with a tooltip that device-assigned work runs under Account Setup too (a Cloud PC runs its device scripts after the user signed in), Device Preparation sessions get no divider, and user-assigned app and download rows carry a User pill from the extension's own targeting
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-11 21:48:20 +02:00
Oliver Kieselbach f70ba354f8 feat(web): additional managed tenants are add-ons — the Pro plan card reads "2 managed tenants included, additional tenants as add-ons" and a full slot count in Delegated Access suggests buying add-ons instead of asking for a larger package
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-11 20:03:51 +02:00
Oliver Kieselbach fc17047290 feat(web): plan cards — the Pro MSP line says 2 managed tenants are included and more are available on request
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-11 19:54:21 +02:00
Oliver Kieselbach 7bbc2cbddf feat(backend): the tenant row records the accepted DPA version — HandleNewTenantDomainAsync writes Constants.CurrentDpaVersion (2026-09-1.7) into TenantConfiguration.DpaVersion in the same first write as OnboardedBy, once, and later logins never touch it; the field is system-owned (patch deny-list, PUT preserve, revert-protected), projected by config/all, the MCP tenant list and the GA tenant report; no backfill (D-252)
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-11 19:30:16 +02:00
Oliver Kieselbach 9a78daa880 chore(submodule): work files — D-251 settled (DPA accepted with the onboarding)
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-11 19:05:10 +02:00
Oliver Kieselbach 5d532d3dbc feat(web): Pro is available — /buy hands off to Microsoft Marketplace and Cleverbridge (no price on the page), the plan cards show the list price of €149 / month, the self-service 30-day trial opens in the portal Plan section, and the get-started sign-in waits for a Terms of Use + DPA tick (D-251)
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-11 18:45:56 +02:00
Oliver Kieselbach a22c26eb9d feat(web): session detail — the Download, Install and Script panels mark where the user phase begins: a quiet "Device phase" caption over the first row and a "User phase" divider before the first row that started after the device entered Account Setup, so user-targeted apps and user-context scripts stand out; sessions without a user phase render unchanged
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-11 17:28:41 +02:00
Oliver Kieselbach b84c467255 chore(web): admin settings nav — Config Reseed and Usage Plans move up behind Diagnostics Log Paths so the MCP-related entries (Usage Plans, MCP Users, Delegated Admins, Tenant Groups) sit together
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-11 17:02:10 +02:00
Oliver Kieselbach 53124e94a6 feat(web): Optional Graph capabilities — tick features in the table to build the grant command (all ticked ⇒ -Features All, ScriptDisplayNames pre-ticked), Copy permissions follows the selection, and the grant card names Azure Cloud Shell as a run location
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-11 16:48:16 +02:00
Oliver Kieselbach 838068fd31 chore(submodule): work files for the MCP quota build (D-250) — commit hashes and the staging-list lesson
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-11 16:46:56 +02:00
Oliver Kieselbach 71282f3556 feat(backend,web,mcp): the MCP quota follows the delegating tenant — every request is checked and counted on the caller's home tenant, a managed tenant is never charged or blocked by its manager's reads, and every delegation slot bought beyond the edition's included ones grows both the account and the organization windows by operator-editable per-slot values (D-250)
Backend/Shared:
- McpQuotaService: ResolveHomeAsync reads definitions, entitlements, plan name and purchased slots once per check; Grow adds slots × per-slot value to every window (a lifted window stays unlimited); the decision and McpPlanLimits carry the breakdown; CheckManyAsync, the charge scope and TargetTenantId are gone
- McpQuotaEnforcementMiddleware: one home charge for every caller; IsExempt = Global Admin only (counted, never blocked; the Global Reader stays quota'd); two 429 texts (organization / user)
- TenantEntitlementService.GetPurchasedDelegatedSlots = max(0, effective slot limit − catalog count), 0 without the delegation right; FeatureEntitlementCatalog gains McpSlot*RequestLimit (Pro 300/6000 per account, 900/18000 per organization = 30 % of the Pro windows; Community and conferred Pro 0)
- PlanTierDefinition gains four nullable slot fields; GET/PUT global/config/plan-tiers return the catalog (UsagePlanCatalogDefaults per edition) so a blank field is explained
- McpUsageQuotaNode / McpOrganizationQuotaNode carry purchasedDelegatedSlots and the slot values (absent without purchased slots)
- Removed: the Tenant Group flag ChargeHomeTenantQuota with its scope plumbing (HomeChargedTenantIds, IsDelegatedAggregate, CatalogDecisionResult), Narrow/quotaExcludedTenants on the session envelopes and the X-MCP-Quota-Excluded-Tenants header, targetTenantId on the 429 body, the foreign-home attribution on McpTenantUsage (HomeTenantId, delegated/homeTenantId on the organization report) and the managed tenant's budget on the manager's managed-tenant list; PATCH global/tenant-groups/{id} is a rename

Web: SectionUsagePlans with four slot inputs, catalog placeholders and a built-in-defaults line; SectionMcpUsage renders "base + slots × per-slot" on both cards and drops the delegated badge; SectionTenantGroups without the charge toggle; SectionDelegatedAccess, the accept page and the privacy page state the new model (privacy last updated 2026-09-11).
MCP: get_fleet_overview no longer merges an exclusion list, the 429 renderer names only the caller's organization, the delegated-scope instruction states the home-charge model; wire types regenerated.
Tests: quota service slot-growth suite, entitlement purchased-slot matrix, conferred-Pro self-service writes (403/409), parity facts for the new and the removed fields; the radar analytics fixture is dated off the real clock (it had aged out of the 30-day window).

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-11 16:46:27 +02:00
Oliver Kieselbach 7080dcc45a fix(backend): rule catalogs — a GitHub reseed newer than the deployed build closes the embedded self-seed (RuleCatalogStamp), the IME self-seed sunsets retired patterns, and IME pattern health reads the global partition instead of the embedded catalog (D-249)
The self-seed of every fresh backend instance treated the embedded catalog as authoritative and wrote it over a newer GitHub reseed: on 2026-09-11 it re-inserted PS-SCRIPT-EXITCODE three hours after the reseed had deleted it (D-248), so agents ran 83 patterns. Every explicit reseed now stamps its kind in AdminConfiguration (PK RuleCatalog); the three self-seeds do nothing while a github stamp is newer than BackendBuildInfo.BuildUtc, and an empty table is still seeded from the binary. The IME self-seed deletes built-in rows the embedded catalog no longer ships.

ImePatternHealthService (health matrix and the ime_pattern_hits ingest filter) takes the pattern list from the seeded global partition via ImeLogPatternService.GetGlobalCatalogAsync, never from BuiltInImeLogPatterns, so the health page and the drift statistics follow a reseed without a deploy; the response carries catalog { source, stampedAt, patternCount } (manifests regenerated, web line, MCP description).

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-11 12:09:18 +02:00
Oliver Kieselbach 5a62714dc2 chore(submodule): fleet baseline for the IME overwrite fix (40.7 % fallback before, 0 % after) and the P6 todo
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
v2.0.1461
2026-09-11 03:46:37 +02:00
Oliver Kieselbach c930d18f1e feat(agent,mcp): the IME log tracker's poll-loop pauses become measurable — longest pass and longest gap between passes as session maxima in agent_metrics_snapshot, ime_pattern_hits and the MCP coverage block (B-wr4)
Test-VM session c3ecb568 (2.0.1460): the tracker loop was not scheduled for 26 s at 100 %
VM CPU while the rest of the agent kept running; five results were read before their
executor end blocks — held and completed correctly (D-247) — and nothing in the fleet
telemetry could have shown the pause (ime_backlog_bytes is an instantaneous value).

- ImeLogTracker.RunPollPassAsync hosts the loop body (file reads, the pending-completion
  flush, the token-failure window) and times it on a monotonic clock (MonotonicMillisProvider,
  Stopwatch in production): PassMaxMs = longest single pass (time INSIDE — reads, ledger
  verification, regex), PassGapMaxMs = longest pause between the end of one pass and the
  start of the next (the 100 ms delay, SaveState and any scheduling delay). Restart-safe
  like the other health counters (TrackerHealthData); ImeTrackerHealth carries both.
- agent_metrics_snapshot: ime_pass_max_ms / ime_pass_gap_max_ms; ime_pattern_hits:
  passMaxMs / passGapMaxMs.
- MCP session coverage: sessionTotals.passMaxMs / passGapMaxMs, and from 5 s on a note that
  says the lines were read late, not lost — every event keeps its log line's timestamp; the
  overwrite note and the pause note join when both apply.
- Tests: ImeLogTrackerPollTimingTests (maxima over three passes with a fake clock, fresh
  tracker at zero, restart keeps and never lowers them); MCP coverage tests for the note
  threshold. Agent suite 2941 green, solution build 0 warnings, MCP tsc clean.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-11 03:41:43 +02:00
Oliver Kieselbach 583c81c9a8 chore(submodule): IME overwrite fix verified in the field (2.0.1460, five held results), B-pd2/B-2rs closed, B-wr4 opened
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-11 03:26:40 +02:00
Oliver Kieselbach 483ec8b8e0 Merge branch 'main' of https://github.com/okieselbach/AutopilotMonitor v2.0.1460 2026-09-11 02:55:29 +02:00
Oliver Kieselbach 328446864a chore(submodule): todo for P5 of the IME overwrite fix
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-11 02:53:02 +02:00
Oliver Kieselbach 106480ef52 fix(agent,rules): health-script lines never feed a platform-script slot — PS-SCRIPT-EXITCODE deleted, HS-INVOCATION opens an unowned invocation in the IME log, the no-marker fallback stays within files platform runs open in (D-248)
Test-VM session 24dc69d1 (2.0.1459) had the overwrite geometry the result hold (D-247)
was built for, yet the hold never fired: "Powershell execution is done, exitCode = 0" —
written by IME's health-script worker (AgentCommon ScriptWorker) to both
IntuneManagementExtension.log and HealthScripts.log — was read as PS-SCRIPT-EXITCODE and
attributed to the pending platform script c81b8053 1.2 s before its executor even started
(IME-log marker: the last PS-SCRIPT-GENERATED; HealthScripts.log: the no-marker fallback to
the last platform start). The completion went out at the result line with the foreign exit
code, and the run's own end block, recovered 150 ms later by the rewind, was dropped as
already emitted. Both codes were 0 by luck; a failing health script would have turned a
succeeded platform script into script_failed. The worker's launch line flipped the pending
script's context the same way (338d9434, twice in one run).

- rules: PS-SCRIPT-EXITCODE deleted — the line is health-script-only (3 hits = 3 cycles,
  platform scripts never produce it), HS-EXITCODE already carries the value, and a line that
  lands inside a parallel platform run's window cannot be attributed by position.
  HS-INVOCATION (new): the worker's command line `"…\agentexecutor.exe"  -remediationScript`
  (identical from IME 1.104.102 to 1.105.152) as agentInvocationArgument of a non-powershell
  kind — the launch line it writes next stays with that invocation. Both take effect on the
  next reseed for every agent from 2.0.1458 on.
- tracker: a file without markers resolves to the platform run in flight only when a
  platform run has ever opened in that file (IntuneManagementExtension.log after a rollover,
  AgentExecutor.log); HealthScripts.log and the app logs never qualify. Test seams:
  ProcessLogMessageForTest takes the source file, ClearInvocationMarkersForTest.
- tests: ImeLogTrackerPlatformScriptContaminationTests +5 with the session's real lines —
  the worker's exit line pre-fills nothing, its launch line flips no context, HealthScripts.log
  lines resolve to no platform script, the context line after an IME-log rollover still finds
  the run in flight, and no shipped pattern reads the worker's exit line.
- rules/dist regenerated (node rules/scripts/combine.js); analyze-rules.json catches up with
  ANALYZE-OFFICE-001 1.1.1 (6657c6b9 was committed without its dist).

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-11 02:52:43 +02:00
github-actions[bot] d3a45f958d chore: regenerate rules/dist + generated TS from individual files 2026-09-11 00:29:54 +00:00
Oliver Kieselbach 6657c6b9c0 fix(rules): ANALYZE-OFFICE-001 explains what the detector actually reports
office_install_failed is emitted only for an error code in the C2R INSTALL scenario; a window without on-disk proof ends silently. The old text described a StreamingFinished-based failure and attributed the early "done" of the built-in app type to IME, which never sees Office CSP installs. Text only, version 1.1.1.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-11 02:28:12 +02:00
Oliver Kieselbach 6c5e2fd921 chore(submodule): IME overwrite plan closed as RELEASED after the 2.0.1459 test-VM run
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-11 02:04:14 +02:00
Oliver Kieselbach 65842b87bf chore(submodule): todo for P4 of the IME overwrite fix
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
v2.0.1459
2026-09-11 01:34:01 +02:00
Oliver Kieselbach b323c52364 fix(agent): a platform script's IME result waits for its executor end block before the completion is emitted; a start line older than the emitted result never opens a new run (D-247)
The first agent with the ledger (2.0.1458, session e7f3c910) lost nothing — 16/16 results,
20/20 end blocks, 20 rewinds — yet 3 of 16 completions carried no exit code and one policy
got an agentexecutor_fallback duplicate. The IME result line reaches the tracker before the
AgentExecutor.log end block of the same run: always when the block was overwritten (the check
the result triggers recovers it one pass later), often without any overwrite (the
AgentExecutor.log read of a pass precedes the IME log read by tens of milliseconds while the
exit line and the result line are 8–30 ms apart), and whenever the poll loop stalls (2 s at
100 % VM CPU). Emitting at the result line dropped every recovered end block, and the late
start line reopened the slot, cleared the emitted-marker and the fallback emitted the same
run again 15 s later.

- CompletePlatformScriptFromImeResult (one core for the handler and the test seam) emits at
  the result line only when the slot has its exit code; otherwise the result stays on the
  slot (ResultObservedAtUtc, ResultPatternId, ResultHeldSinceUtc) and
  FlushPendingPlatformScriptResults — the former FlushStalePlatformScriptResults, still the
  home of the 15 s exit-code fallback — emits it at the end of the pass in which the exit
  code arrived (the block's output lines come with it), at shutdown, or after
  PlatformScriptEndBlockGrace (5 s, wall clock).
- The emitted-marker is a Dictionary keyed by policy id with the run's source timestamp
  (state file: PlatformScriptResultEmittedAt; the old list is read as MinValue). A start
  line strictly older than the emitted result is the late start line of that run: it owns
  the lines behind it in its file, opens no slot, emits no script_started. A start line at
  or after a held result closes the hold as the next run; a start line that meets a slot
  the result opened stamps StartedAtUtc.
- The adapter binds the completion to the result line (ResultObservedAtUtc, patternId =
  ResultPatternId) the way the fallback binds to the exit line, so a held emit keeps its
  timestamp, pattern and duration.
- Exit-code lines whose owner has no pending slot any more log why they are dropped;
  ApplyExitCode is shared by the handler and the test seam.
- Tests: ImeLogTrackerResultHoldTests (11) and ImeLogTrackerAdapterResultHoldTests (1); six
  existing cases that encoded the immediate emit without an exit code now flush explicitly.
  Agent suite 2933 green, solution build 0 warnings.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-11 01:33:34 +02:00
Oliver Kieselbach 321a6efc81 chore(submodule): P2 of the IME overwrite fix — plan, concept doc, todo
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-11 00:14:56 +02:00
Oliver Kieselbach 5ecb2a998b feat(web,mcp): the IME overwrite counters surface — session page row "Overwritten log blocks recovered"; get_session_summary coverage carries overwriteRewinds/overwriteBytesReprocessed and a recovered-not-missing note, never a gap (D-246, P2)
The agent (v2.0.1458) counts the log blocks it re-read after a concurrent IME process had overwritten bytes behind its bookmark. The IME Tracker rows on the session page show the count and bytes as their own row, apart from Skipped work; the MCP coverage block adds both counters to sessionTotals and, when rewinds > 0, an imeTracker.note saying the script results and exit codes in those blocks are complete — no coverage.gaps line, no degraded flag, so an assistant does not read recovered data as a blind spot. Sessions from older agents carry neither key and show neither.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-11 00:14:55 +02:00
Oliver Kieselbach bb6df93b51 chore(submodule): agent v2.0.1458 released
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-11 00:02:19 +02:00
Oliver Kieselbach 096a049deb chore(submodule): todo for the IME overwrite fix
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
v2.0.1458
2026-09-10 23:40:58 +02:00
Oliver Kieselbach b9caf524c3 chore(submodules): concept doc, D-246, plan and skill helper for the IME multi-writer overwrite fix
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-10 23:40:21 +02:00
Oliver Kieselbach 197de1d718 fix(agent): the IME log tracker verifies and rewinds when a concurrent IME process overwrote bytes behind its bookmark; executor lines belong to the invocation at their file position (D-246)
IME's trace listener opens every log once per process with FileMode.Append and FileShare.ReadWrite
and writes at its own stream position afterwards. AgentExecutor.exe (two processes per user-context
script, one per detection script) has such a listener on AgentExecutor.log and on
IntuneManagementExtension.log, so a process that resumes logging after another one appended
overwrites those bytes: the final file holds the last writer, a byte-position tailer has read the
first version. Session 46749560 lost 6 of 16 platform-script results and 4 of 16 executor end
blocks with every tracker health counter at zero — results fell back to the exit code (one Success
where IME said Failed), one finished script stayed Incomplete. The agentexecutor_fallback results
in the fleet are this race, not late IME logging.

The tracker now keeps a fingerprint ledger of every entry it processed from the two multi-writer
files (FNV-1a 64 over the decoded bytes, one CmTraceEntryAssembler shared by the read loop and the
verification so entry boundaries are identical by construction), verifies it on every script end
signal, on a byte-budgeted cadence while an invocation is in flight (256 KB/s, at least 1 s
apart), in a 4 KB guard zone on every growth pass and on a fragment at the bookmark, and rewinds
to the first changed entry; entries whose (offset, hash) still read unchanged are skipped,
everything else is processed as backlog (never fresh, never a calibration anchor). The
policyId-less executor lines are owned by the nearest preceding invocation marker in their file
(PS-AGENT-ARG opens, PS-AGENT-SCRIPT-START upgrades to the platform script, PS-AGENT-INVOCATION and
PS-AGENT-COMPLETED close) instead of a last-started pointer, so an end block a rewind uncovers
reaches its own script. Restart seeds the ledger from the 64 KB behind the persisted bookmark;
a rollover clears it. Four restart-safe counters (ime_overwrite_rewinds,
ime_overwrite_bytes_reprocessed, ime_verify_passes, ime_verified_bytes) ride agent_metrics_snapshot
and ime_pattern_hits — recovered data, not skipped work, no new event type.

Tests: ImeLogTrackerOverwriteRewindTests (15) drive the positional-overwrite write pattern against
both files, including the two field scenarios with the shipped patterns; agent suite 2921 green,
backend known-actions list extended for the two new patterns.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-10 23:40:20 +02:00
Oliver Kieselbach 18d4f08f2b chore(submodule): todo closed for the request-row marker (D-244)
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-10 21:42:15 +02:00
Oliver Kieselbach ddf450d87d chore(submodule): plan request-row marker RELEASED (D-244)
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-10 21:41:46 +02:00
Oliver Kieselbach e3b2113365 chore(submodules): import-session time shift, token, note and scrub semantics; demo import work files (D-242, D-245)
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-10 21:13:11 +02:00
Oliver Kieselbach 6a85f45604 backend: agent request rows carry the admitting device validator and the proven tenant (D-244)
SecurityValidator stamps ValidatedTenantId once the certificate tenant binding
(or the bootstrap token) proves the tenant, and DeviceValidation once the
device-validation chain has run: the admitting ValidatorType name, None for a
tenant without a validator, Transient for the 503, Rejected for the 403 of
stage device. RequestTelemetryMiddleware resolves the row's TenantId in one
place (policy tenant, proven tenant, then the X-Tenant-Id header only on the
routes that validate it) and copies DeviceValidation as a dimension.

Register, config and upload-url rows carried no TenantId before, so
tenant-filtered KQL missed exactly the calls that run the validation, and the
validators' success line is Information-level and never reaches App Insights.
Proving which validator admitted a device is now one query.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-10 19:48:58 +02:00
Oliver Kieselbach a2d1aedcad fix(web,mcp): a script still running when observation ends is Incomplete, not running forever (D-243)
Session 2ebcd480: the last platform script started 27 s after enrollment_complete and its
start line was the session's last event; two days later the Script Executions panel still
ticked "Running (175306s — stuck?)" in amber with "1 running" in the header. Same blind
spot as D-237, one panel over.

Web: applyObservationEnd in lib/scriptExecutions.ts marks a Running placeholder on a
terminal session as Incomplete (bounded by session.lastEventAt); the row stops ticking and
shows "Incomplete" in slate with the watched span as a lower bound, the header counts
"N incomplete" apart from running/succeeded/failed, and the card keeps its React key across
the live→terminal flip. No backend change: there is no per-script row, and the session
counters count finals only.

MCP: get_session_summary adds coverage.scripts.stillRunning and a coverage.gaps line for
terminal sessions, folded from script_started without a later result — the same rule as the
web placeholder.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-10 19:10:59 +02:00