Commit Graph

2754 Commits

Author SHA1 Message Date
Oliver Kieselbach e33d85c01d Agent Host changes for agents/gather-rule-exitcode-info-update (#151) v2.0.1452 2026-09-08 14:57:30 +02:00
Oliver Kieselbach 4e73ba8900 Agent Host changes for agents/improve-changelog-promotion-ui (#152) 2026-09-08 14:57:11 +02:00
Oliver Kieselbach 4df52b3da9 Agent Host changes for agents/timeline-search-enhancement (#150) 2026-09-08 14:47:30 +02:00
Oliver Kieselbach 844d86410c Agent Host changes for agents/mcp-server-beschreibung-lieferung (#149) 2026-09-08 14:46:15 +02:00
dependabot[bot] 9514e1033d chore(deps): bump the web group (#148)
Bumps the web group in /src/Web/autopilot-monitor-web with 5 updates:

| Package | From | To |
| --- | --- | --- |
| [next](https://github.com/vercel/next.js) | `16.3.3` | `16.3.4` |
| [@next/bundle-analyzer](https://github.com/vercel/next.js/tree/HEAD/packages/next-bundle-analyzer) | `16.3.3` | `16.3.4` |
| [@types/react-dom](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/react-dom) | `19.2.5` | `19.2.7` |
| [autoprefixer](https://github.com/postcss/autoprefixer) | `10.5.4` | `10.5.5` |
| [eslint-config-next](https://github.com/vercel/next.js/tree/HEAD/packages/eslint-config-next) | `16.3.3` | `16.3.4` |


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

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

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

Updates `autoprefixer` from 10.5.4 to 10.5.5
- [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.4...10.5.5)

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

---
updated-dependencies:
- dependency-name: next
  dependency-version: 16.3.4
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: web
- dependency-name: "@next/bundle-analyzer"
  dependency-version: 16.3.4
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: web
- dependency-name: "@types/react-dom"
  dependency-version: 19.2.7
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: web
- dependency-name: autoprefixer
  dependency-version: 10.5.5
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: web
- dependency-name: eslint-config-next
  dependency-version: 16.3.4
  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-08 13:07:51 +02:00
Oliver Kieselbach b2236bf65f web: card and page headers stack below sm so their action buttons stay on screen at phone width
Title and trailing controls shared one non-wrapping flex row with the controls
marked flex-shrink-0. At 440px (iPhone 16 Pro Max) that squeezed the Operational
Events title into an 86px column and pushed the Refresh buttons of Agent Metrics
and the Tenant Config Report past the viewport, scrolling the whole page sideways.
SectionCardHeader now stacks below sm, which covers all 32 cards that use it.

A width sweep over the portal routes at 440px found two more real overflows, fixed
here as well: the audit From/To date pair, and Alert Rules, where the unbreakable
event-type names and a channel-name input without min-w-0 pushed the severity
selects 99px off screen.

Verified: every swept route now measures zero horizontal overflow at 440px, desktop
is unchanged at 1440px. The header test asserted "no trailing group" via the absence
of gap-3, which the wrapper itself now carries; it checks flex-wrap instead and pins
the stacking classes.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-08 01:35:50 +02:00
Oliver Kieselbach 31535b07e0 web+backend: a warming MCP health card says only "Starting instance... (attempt n of 12)" and re-probes for ~60s before it warns (D-220)
The card composed its line from the backend's explanation ("did not answer within
3s - it scales to zero when idle ... ~20-30s"): probe mechanics nobody waiting on a
starting instance needs. It now writes its own one-liner and ignores check.message,
so the server's "warming" and a client-side probe timeout read identically and the
wording does not depend on a backend deploy. The backend message is shortened but
still carries the budget for direct API callers.

The re-poll budget of 8 x 5s (~40s) expired on a real cold start - Container Apps
activation has no upper bound we control - so it is now 12 x 5s (~60s), with a test
pinning that the stated seconds come from cadence x attempts.
2026-09-07 21:27:07 +02:00
Oliver Kieselbach 066b6e6f04 backend+web: the MCP health probe answers a cold container in 3s with status "warming" instead of holding the request for 30s, and the System Health card re-probes until it is up (D-220)
Measured over 14 days, GET health/mcp answered in 4..148ms when warm and timed out at 30s
in 50 of 83 calls — the median call took 27,972ms. A cold start is 20-30s of Container Apps
activation (~98% KEDA plus image mount, ~500ms application), so no UI wait covers it and the
generous timeout only bought a long wall in front of the same answer. The browser's own
budget is the same 30s, so the backend's carefully worded warning never reached the user at
all: they saw a raw DOMException.

Backend: the budget drops to 3s, which sits in the widest observed latency gap
(2347ms..3930ms) so jitter cannot flip the verdict. Our own cancellation now reports the new
status "warming" at Information level (with minReplicas=0 a cold probe is the expected path,
and logging it as a warning poisons the ops signal); HttpRequestException is split out of
that catch and stays a real warning, still without the host in the message. The warm-hit
threshold drops to 750ms — ~5x the warm maximum, deliberately generous because every
measurement comes from a near-idle single-user system and warm latency will rise under
concurrency — and moves into McpReachableMessage so a test can pin the boundary. The XML doc
now describes the probe that exists rather than the wake-signal design the measurements
disproved.

"warming" is a new status value rather than an added DTO boolean because the backend deploys
before the web: an unknown value degrades to a grey card with a clean banner, whereas a
boolean would leave old web showing yellow and "Warnings detected" on every cold start.
HealthCheck.Status now documents the five-value vocabulary, which rides into both generated
TypeScript mirrors as JSDoc.

Web: resolveMcpCardState owns display, banner rating and the poll decision as a pure
function (the suite runs on node, so testable logic cannot live in JSX). A warming server
re-probes every 5s for up to 8 attempts and stays out of the overall banner exactly as
"unknown" already does — scaling from zero is expected behaviour, not a degraded platform —
and only becomes a real warning once the re-polls are spent. The card keeps showing
"warming" while the next probe is in flight so it cannot flicker, carries the blue
in-progress look instead of yellow, and was hoisted out of the healthResult guard: a failing
detailed check would otherwise hide a card that is still polling.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-07 18:25:56 +02:00
Oliver Kieselbach a1e2b76a9f web: the session Vulnerability Report names its inventory badge "Identification Confidence" (header and legend, Inventory and Delta tab); the Software Inventory analyzer toggle uses the rose accent like the other Agent Analyzer toggles
The column showed high/medium/low under the header "Identification", which reads as a method rather than a rating. The wire field is `confidence` and the tooltip already says "identification confidence"; the header and legend now match. The analyzer toggle and its hover border were still emerald from before the section moved.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-07 13:13:31 +02:00
Oliver Kieselbach 29e5b8251d web(rules): analyze and gather rule cards get a two-row header on every breakpoint — badge row with fixed-width toggle, severity and rule-id columns on top, the full title below with threshold, sparkline and hit rate right-aligned; gather badges ordered category, type, collector like the analyze card
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-07 13:11:44 +02:00
Oliver Kieselbach 02168a5c55 chore: bump internal gitlink (geo-statistics-explained plan, D-219, backlog B-fpu/B-csp)
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-07 12:12:12 +02:00
Oliver Kieselbach a6b6661320 web(geo): the Location Performance table states what Avg Duration, P95 and vs Global are computed over (footnote with docs links, header and benchmark tooltips); a location without a succeeded session shows no duration instead of 0 min / -100% in the fastest bucket
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-07 12:10:25 +02:00
Oliver Kieselbach 0c72666a7a chore: bump skills gitlink (CAS marker row corrected to the pre-fix code path)
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-07 10:35:34 +02:00
Oliver Kieselbach 9023321db9 chore: bump skills gitlink (platform-health KQL fix + 09-07 marker baselines)
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-07 07:48:08 +02:00
Oliver Kieselbach 7ef097529c backend: deleting an owned tenant group stamps the retention grace anchor on every member (conferred Pro ended without ProDowngradedUtc)
The portal hides Delete for owned (msp-{home}) groups, but DELETE global/tenant-groups/{groupId} accepts any id: a Global Admin deleting one ended Pro (MSP) for every member with no grace anchor, so a member that raised its retention under Pro was clamped to the Community cap at the next sweep. DeleteGroupCoreAsync now snapshots the owner and calls ProConferralService.RecordLossAsync(member, owner, "group-deleted") per member, owner excluded, independent of assignees. Two tests in RevokeEnforcementTests; internal docs list the fourth writer.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-07 00:15:21 +02:00
Oliver Kieselbach e7df69efe4 backend+web: vulnerability summary carries a priority breakdown (act/attend/track); the exposure panel shows Act and Attend tiles and every severity or priority chip filters the top-CVE list (D-218)
Act equals the KEV set by construction (CvePriority: KEV => act), so the KEV tile becomes the Act tile and Attend joins it instead of a duplicate count. The breakdown is counted in the backend over every CVE in the window, like the severity breakdown, because the list is top-N. Both panel consumers read the generated CveExposureSummary type; the MCP tool description names the new block. Wire types regenerated for web and MCP.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-06 22:30:36 +02:00
Oliver Kieselbach 2c7efa2733 backend: key-safe X-MCP-Tool-Name prefix for the usage row key (CodeQL cs/log-forging #91)
The caller-supplied X-MCP-Tool-Name header went verbatim into the UserUsageLog
row key ({yyyyMMdd}_{tool}:{endpoint}) and from there into the CAS-conflict
warning of TableCasRetry. EndpointNormalizer.ToolNameKey now strips CR/LF and
every other control character or line separator, replaces the characters Table
Storage rejects in keys (/ \ # ?) and caps the length at 64; an empty result
skips the prefix. Real tool names pass through unchanged, so existing usage
rows keep their keys. A header with an illegal key character previously made
every usage increment fail with 400 (fail-soft, counter never advanced).

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-06 22:19:56 +02:00
Oliver Kieselbach 2742549827 chore: bump skills gitlink (platform-health marker baselines + GetBootstrapScript trap)
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-06 22:07:46 +02:00
Oliver Kieselbach 3c45f5cb09 chore: regenerate the shared manifest and wire types for the NtpServer summary text (D-216 follow-up)
The manifest committed with d5d1d7bf carried the types section of the previous HEAD, so
the ntpServer doc string still read "null = use agent default"; SharedManifestParityTests
compares byte-for-byte. Generated output only, no wire change. Bumps the internal gitlink
(plan review addendum).

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-06 22:02:10 +02:00
Oliver Kieselbach d5d1d7bfbd backend+web: one agent-config derivation (AgentConfigResolver) served on agent/config and GET config/{tenantId}/effective-agent-config; wire-type initializer defaults in the shared manifest; the Tenant Config Report reads both (D-216)
The report still carried two hand copies after D-215: a typed map of C#
initializer defaults for the "custom" highlight, and a TypeScript mirror of
GetAgentConfigFunction's resolution rules for the runtime column.

Backend: the derivation moves verbatim into Services/AgentConfigResolver
(Build = pure, ResolveAsync loads tenant + admin config and the two rule
catalogs). GetAgentConfigFunction keeps the kill verdict, the migrate logging
and the device fields on top; ParseAgentMajor, ResolveDiagnosticsUploadEnabled,
MergeDiagnosticsLogPaths and ResolveMigrateTarget move with it (call sites and
tests retargeted; the QueryParams ratchet baseline shrinks by the file). New
GlobalAdminOnly route config/{tenantId}/effective-agent-config returns the same
AgentConfigResponse without a device (optional ?agentVersion=, default the V2
line). AgentConfigResolverTests pin the rules that used to live in the web:
upload 10, Hosted destination without SAS, SAS regardless of mode, unrestricted
only with entitlement + gate + toggle, operator knobs from AdminConfiguration,
analyzer defaults, hash line per major, migrate override and rejection.

Manifest: SharedManifestParityTests emits "defaults" next to "fields" for
TenantConfiguration and AdminConfiguration and an "agentConfig" section with
the AgentConfigResponse / CollectorConfiguration / AnalyzerConfiguration class
defaults (fresh-instance values; nested objects null). TenantConfiguration.
NtpServer gets the initializer "time.windows.com" the table read already
applies, so the manifest default and a stored row agree.

Web: the catalog rows lose value() and default; the runtime column renders
the endpoint's response, the highlight compares against the manifest defaults,
informational rows (reasons, timestamps, identities, secrets) never read as
custom. Previously excluded fields are visible (configVersion, catalog counts,
sealing pattern ids, hash oracle, migrate target); the collector class
defaults sit in a collapsed "Collector Internals" section. Fail-soft: while the
backend is not deployed the runtime column shows a notice.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-06 21:59:34 +02:00
Oliver Kieselbach f4e2088b69 chore: bump internal gitlink (lessons: shared index) 2026-09-06 21:57:49 +02:00
Oliver Kieselbach 55425058a8 chore: bump internal gitlink (dashboard-search plan review) 2026-09-06 21:57:26 +02:00
Oliver Kieselbach eec0d17607 chore: bump internal + skills gitlinks (D-217 plan/review, lessons, ux-insights marker row) 2026-09-06 21:54:47 +02:00
Oliver Kieselbach 43f5304ba4 web+backend: dashboard search gets field qualifiers (model=, manufacturer=), AND-ed terms and exclusions, parsed identically on both sides; Fleet Health model rows link with that search (D-217)
The "Health by Device Model" link searched "{Manufacturer} {Model}" as one substring; the server's
free-text predicate tested it per field, so once the failed sessions were outside the loaded first
page the sweep returned nothing.

- lib/searchQueryTokens.ts: tokenizer shared by the timeline and the session search (terms, quotes,
  leading minus, key=value / key:value qualifier when the separator precedes any quote)
- app/dashboard/utils/sessionSearchQuery.ts: 15 qualifiers = exactly the fields both sides search,
  matcher over session fields + client-derived extra text, suggestion field lookup, link builder
- useDashboardFilters / SessionTable: filter and suggestions use the grammar; fuzzy only for a
  single free term; session_searched gains a `qualifiers` count
- Helpers/SessionSearchQuery.cs: the same grammar for q= on /api/search/sessions (+ global), parsed
  once per request; MatchesFreeText is a thin wrapper
- utils/session-search-syntax.cases.json: one case file run by vitest and by
  SessionFreeTextSearchTests (parity proof)
- FleetModelHealth/FleetSlowModel/FleetFailingModel: manufacturer + model as stored plus label,
  grouped on the pair; useFleetHealth uses the generated wire types; "Unknown" is not a link
- shared manifest + wire types regenerated for the three DTOs

Backend suite 6158/6159 (the one failure is SharedManifestParityTests against uncommitted WIP of a
parallel task), web vitest 1177/1177, build 0 warnings.
2026-09-06 21:54:10 +02:00
Oliver Kieselbach b21472dd8c web(progress): pre-provisioned device parked after the technician part sits on Account setup instead of a forever-running device apps step
The progress portal placed the active step purely by the stored phase. After
whiteglove_complete the backend caps that phase at Apps (Device), so the end
user saw "Waiting for you to sign in" above a step list that still showed
"Installing apps (device)" as running at 43 % (customer report).

The seal detection the headline already used is now one helper,
isParkedAfterTechnicianPart (pre-provisioned, whiteglove_complete, no resumedAt
and no whiteglove_resumed), and resolveActiveStepIndex places such a session on
the first user step: every device step done, 57 %, Account setup current. A
stale Complete phase on older sessions can no longer push it past the sign-in;
once resumed the placement is phase-driven again.

Vitest 24/24, tsc and eslint clean. Backlog B-ydz carries the post-deploy check.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-06 21:24:32 +02:00
Oliver Kieselbach e3991d55be web(admin): Tenant Config Report renders from an exhaustive catalog over the generated wire types (D-215)
The report typed its own TenantConfig interface and drifted for five months: 31 stored
fields were missing (payingCustomer, the MSP overrides, three validation gates, the SLA
block, four analyzer flags, app-homing and trial bookkeeping) and three runtime values
were wrong (upload interval 30 instead of 10, diagnostics-enabled without the Hosted
destination, unrestricted mode without the entitlement).

tenantConfigReportCatalog.ts is Record<keyof TenantConfiguration> and
Record<keyof AgentConfigResponse | CollectorConfiguration | AnalyzerConfiguration>:
every key is rendered or excluded with a reason, so a new C# property fails tsc once the
wire type is regenerated. Runtime rows carry the GetAgentConfigFunction rule and a source
tag; entitlement values come from feature-flags, operator knobs from global/config.
A vitest pins the same parity against SHARED_MANIFEST.tenantConfiguration.fields and the
resolution rules (upload 10, Hosted destination, flags-driven unrestricted mode, admin
idle timeout, analyzer defaults). Empty strings count as unset for the custom highlight.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-06 21:08:03 +02:00
Oliver Kieselbach 864d1ed31e backend: MCP usage counters go through TableCasRetry with an 8-attempt jittered budget instead of three immediate retries (B-c92)
Parallel tool calls of one user hit the same (user, day, endpoint) row in the same instant; without backoff the losers stayed synchronised and 3 of 6 increments were lost. Both increments now use the one CAS helper (merge patch, AddEntity on 404, StorageCasConflict marker) with UsageCounterRetries = 8; the tenant counter's attribution columns travel in the merge patch. Tests pin the budget, the patch shape and the create path.
2026-09-06 19:04:58 +02:00
Oliver Kieselbach 76ef1e53ca chore: bump internal gitlink (final check 2026-09-06: plans released, backlog long-tail rewritten, todo cleared) 2026-09-06 18:48:46 +02:00
Oliver Kieselbach 7dd298f04e chore: bump skills gitlink (session skills: exit-code class, packaging counters, WU symbols)
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-06 18:21:14 +02:00
Oliver Kieselbach 66452621c6 exit-code class per app + reboot rule: IME-EXITCODE-CLASS, ANALYZE-APP-018, exitCode on every terminal app event (D-214)
- IME-EXITCODE-CLASS captures the return-code class of the installer exit code ("lpExitCode is defined as <Success|SoftReboot|HardReboot|Retry|Failed>", verified against the IME string literal) as exitCodeClass on AppPackageState; new action captureExitCodeClass, persisted across the agent restart a reboot causes, last value wins across a Retry loop. IME-EXITCODE-MAPPED-FAILED keeps the error path; both match the same line.
- Adapter: exitCodeClass whenever known, exitCode on every terminal state (Installed/Postponed/Skipped/Error) instead of Error only.
- ANALYZE-APP-018 (warning, apps): app_install_completed.exitCodeClass in SoftReboot,HardReboot; +25 HardReboot, +15 observed system_reboot_detected; never fails the session.
- Web: completed-app exit badge amber (failed stays red), "mapped as SoftReboot/HardReboot/Retry" on timeline and Install Progress.
- Tests: pack end to end (phase, current app, class lines, negatives, restart round trip), adapter payloads, linearity samples, backend rule engine + known actions.
- internal gitlink: plan, D-214, backlog.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
v2.0.1451
2026-09-06 18:08:49 +02:00
Oliver Kieselbach 0b23d93957 updated last modified 2026-09-06 17:49:25 +02:00
Oliver Kieselbach 4281a6d81b chore: bump internal gitlink (rules category groups plan, Paket 3) 2026-09-06 17:39:10 +02:00
Oliver Kieselbach fe919a51aa web rules pages: group analyze and gather rules by category with collapsible headers, counters and Expand/Collapse All (state per tab in localStorage); reveal a rule inside its group from telemetry, template copy and view-copy
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-06 17:37:44 +02:00
Oliver Kieselbach 0dcf0eb503 Observation coverage in get_session_summary, absence audit with coverage lint, diagnostics packaging counters (D-213)
MCP: coverage block (window, IME tracker, collectors, upload, diagnostics, gaps) built from the
health events the summary already fetches (session-coverage.ts, data.<key> slices); validate_rule
warns on a required not_exists of an IME-log-derived event without the ime_tracker_degraded
precondition; authoring guide 'absence'; instructions and prompts point at coverage.gaps.
Rules: guardrails.json imeLogDerivedEventTypes (schema + combine). Backend: catalog invariant test —
absence rules carry the coverage gate or a documented exception (ANALYZE-ID-004).
Agent: diagnostics_uploaded/_upload_failed carry packaging counters (truncated, includedFiles,
skippedFiles, skippedByReason, problemsByKind) — counts only, never paths.
Also: shared-manifests.json regenerated (was stale at HEAD; McpAccessPolicy doc string) + codegen.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-06 17:35:52 +02:00
Oliver Kieselbach e2fb99d5d8 mcp access-guard: surface the suspension text from the policy envelope; AdminConfiguration.McpAccessPolicy comment matches the enforced AllMembers semantics; internal gitlink (MCP-Nebenbefunde, B-4s2)
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-06 17:12:19 +02:00
Oliver Kieselbach a3ee613b60 scripts/error-codes: decode entities in one pass and strip tags until none remain (CodeQL 89/90)
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-06 16:33:36 +02:00
Oliver Kieselbach d2197c6a57 error-code catalog v2: generated from primary sources, symbols and families, MCP lookup_error_code (D-211/D-212)
Shared: error-codes.json schemaVersion 2 (607 entries, 50 IME enforcement states) generated
by scripts/error-codes/generate.mjs from MS Learn tables and the IME enums of one build;
ErrorCodeEntry gains symbol/category/imeRetriesDuringEsp, ErrorCodeCatalog gains
TryLookupDetailed (HRESULT_FROM_WIN32 derivation as data), TryLookupBySymbol,
TryLookupEnforcementState. Backend enricher siblings carry symbol/category/derivedFromWin32
plus enforcementStateInfo. Web: validating, minifying sync; formatErrorCodeSource for every
tooltip; windows_update_failed badge; script error codes decoded; bundle budget test.
Agent: WindowsUpdateTracker reads the catalog symbol (WU_E_UNKNOWN sentinel, load failure
never costs an event). MCP: error-code-catalog.ts (lexical-only), lookup_error_code for every
role, search_knowledge merges catalog hits, get_session_summary key events carry
errorCode/errorText, Dockerfile COPY + ERROR_CODES_PATH. ANALYZE-APP-014 1.2.1 corrects the
0x80073cf0 gloss. Gitlinks: internal (D-211/D-212, plan review, backlog), .claude/commands.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-06 16:24:35 +02:00
Oliver Kieselbach e5997bc50e chore: bump internal gitlink (error-code catalog v2 plan)
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-06 15:25:13 +02:00
Oliver Kieselbach 75138016ce chore: bump internal gitlink (analysis notes revision 2)
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-06 14:46:44 +02:00
Oliver Kieselbach fc4772efb0 chore: bump internal gitlink (analysis notes: detection-coverage ideas)
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-06 14:42:56 +02:00
Oliver Kieselbach 0bde428fa0 web: GA tenant modal — MCP switch and suspension moved down next to offboarding
Order at the end of the modal: MCP Access, Tenant Suspension, Offboard Tenant. Rarely used
levers sit together; the frequently edited plan, contact and limit sections come first.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-06 14:13:40 +02:00
Oliver Kieselbach 16ca3a059c backend/web/mcp: per-tenant MCP switch as an operator control (D-210)
TenantConfiguration.McpDisabled + McpDisabledReason (GA-only, same deny-list and PUT guard as Disabled).
Front door: McpUserService denies every caller of a closed home tenant below the platform roles.
Per request: PolicyEnforcementMiddleware denies a closed home or target tenant for X-Client-Source: mcp
(403 McpDisabled) and narrows the delegated fleet bound, never to null. ClientSourceHeader is the one
reader of the header. The MCP access guard surfaces the backend reason verbatim. GA tenant modal,
list badge and config report; wire types regenerated; internal doc, health marker, gitlinks bumped.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-06 14:01:01 +02:00
Oliver Kieselbach 1bfc80e6db chore: bump internal gitlink (release backfill plan RELEASED, D-209)
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-06 13:26:49 +02:00
Oliver Kieselbach fa705a2ef6 updated Readme.md 2026-09-06 13:13:42 +02:00
Oliver Kieselbach 81f807e124 web: modal overlays render through ModalPortal into <body> (D-208)
A fixed overlay still receives its parent's box-model margins: the tenants page's
space-y-8 wrapper gave the edit dialog's backdrop a 32 px margin-top, leaving the
navbar un-dimmed. All 21 full-screen overlays now portal to document.body.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-06 12:27:01 +02:00
Oliver Kieselbach a4f7c88785 chore: bump internal gitlink (request-wire-contract RELEASED, live verify) 2026-09-06 12:02:36 +02:00
Oliver Kieselbach 54259f070f chore: bump internal gitlink (request-wire-contract GEBAUT, D-207, B-r95/B-cme) 2026-09-06 11:42:37 +02:00
Oliver Kieselbach 26cef3ab06 web: request bodies as jsonBody<T> against the generated wire types (D-207)
Every request body in app/, components/, contexts/, hooks/ and lib/ is encoded
as jsonBody<SomeRequest>(…) from lib/apiClient.ts; the apiClient ratchet gains
the `body[:=] JSON.stringify(` pattern (74 → 10 sites) and refuses a bare
jsonBody(. The hand-written RestoreRowRequestBody/RestoreRowMode are the
generated RestoreRowRequest/RestoreRowMode now; scriptDisplayNames posts through
fetchJson; the tenant-group and delegation mutate helpers take a pre-encoded body.

What the typing surfaced and fixes here: canManageBootstrapTokens and `all` are
sent explicitly, AdminConfiguration goes back on the wire through
toWireAdminConfiguration (the outbound twin of fromWire…), TenantManagementSection
uses the wire TenantConfiguration instead of a local subset, nameless CPE entries
are filtered before they are sent, the session action posts
QueueSessionActionRequest, create-from-template sends { variables }.

Remaining baseline (with reason): the three rule/pattern pages, whose local rule
types diverge from the wire — B-cme.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-06 11:42:24 +02:00
Oliver Kieselbach 00ad230ffe mcp: request bodies typed against the generated wire types (jsonBody<T>, D-207)
The eight backend POST/PUT/PATCH bodies name their request DTO through
jsonBody<T> from client.ts — the one place a body is encoded — so a renamed
or retyped backend field fails tsc instead of surfacing as a 400 at runtime.
The apiFetch guard additionally refuses `body: JSON.stringify(` and a bare
`jsonBody(` with an empty baseline.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-06 11:42:22 +02:00
Oliver Kieselbach 3d920a8113 shared+backend: IApiRequest marker, one RequestBody reader, every request body a Shared wire type (D-207)
Every HTTP request body the web, the MCP or the agent sends is now a flat class in
AutopilotMonitor.Shared implementing IApiRequest — the request twin of IApiResponse.
WireTypeManifestBuilder exports the implementers as a third root, so the request DTOs
reach wire-types.generated.ts on both clients; for request types a nullable property
keeps `| null` (a PATCH may send an explicit null to clear an override).

Functions read a body only through RequestBody.ReadAsync<T>/ReadOptionalAsync<T>
(System.Text.Json, ApiJsonOptions.Read = wire options + case-insensitive; empty or
malformed body = 400 envelope, never a 500) whose generic constrains on IApiRequest.
ReadNewtonsoftAsync<T> stays for the Newtonsoft-bound cluster (tenant/admin config via
the patch service's PopulateObject, rule and pattern documents) as a frozen per-file
baseline. The tenant-plan PATCH keeps its presence-sensitive JsonDocument walk with the
keys derived from PatchTenantPlanRequest by nameof (PlanPatchKeyParityTests).

35 nested request classes, 7 JObject/JsonDocument hand-walks and the anonymous
DeserializeAnonymousType bodies moved to Shared; the two identical TicketRequests are
one DownloadTicketRequest; ServerAction is no longer the request (QueueSessionActionRequest,
the stored row is built server-side); create-from-template takes { "variables": … }
instead of a bare map. Fields the endpoints default server-side are nullable now
(CPE mapping, report attachments, feedback rating on dismissal, upn for applications).

TypedRequestGuardTests: no raw deserializer under Functions/ (named exemptions: agent
routes frozen by D-200, Azure Monitor webhook, upstream/stored-column parses), the
Newtonsoft branch only in its baseline, no request type in the Functions assembly, every
DTO flat and in Shared. RequestBodyTests cover both readers, the cap and the envelope.
QueryParamGuardTests baseline shrinks by 14 (the Content-Length TryParse lives in the reader).

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-06 11:42:03 +02:00