The diagnosis page is rebuilt on the server headline instead of rule
confidence: a verdict card per status and cause (culprit tiles, ESP
phases, what an unfinished enrollment still waits for), the
circumstances around it, the steps of the finding that explains it,
where the time went, all open findings grouped by role, team notes and
the deduplicated errors and warnings. It reuses the session page's hook
set, refetches the headline on live activity and opens for Failed,
Stalled, AwaitingUser, Incomplete and Succeeded with open findings, also
from read-only and cross-tenant views.
Rule text: lib/ruleText.ts prunes a finding's explanation and steps like
the notifications do (shared cases in tests/fixtures/rule-text-display,
also run by RuleTemplateInterpolator.InterpolateForNotification), then
parses the markdown and fills values into text nodes only. The session
page's analysis section, the rule card and the dry run render through it;
components/findings/EvidenceChips replaces the two evidence-link copies
and covers correlation pairs.
Vocabularies: DiagnosisPrerequisites names the engine's completion
prerequisites (manifest diagnosis.prerequisites, parity fact against
DecisionEngine.CompletionPrerequisites); the page's texts for every
diagnosis vocabulary are exhaustive over the manifest types.
Fixed on the way: useSessionSignalR held the first render's callbacks,
so ruleResultsReady was lost on an already connected hub; a deep link to
an event dropped its hash before the event loaded; the global analyze
rule update answered 500 for an invalid presentation (now 400); the
rule editor kept the presentation block on create and in JSON mode.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A typed headline built on demand from the session row and every event:
outcome = status; cause from the verdict path (derived for older rows,
flagged as a caveat), and for an explicit failure from the ESP evidence
(failed step, error code, culprit apps, AppX candidate, stuck policy
providers). Plus the ESP phase rollup, the ESP profile, what an unfinished
session still waited for, and observation caveats. Never from rule
confidence: Stalled, AwaitingUser and reconciled sessions get no terminal
rule run.
- App facts come from the session's own app events (the app table has no
intent or targeting); only required apps count, a later install clears
an app, a zero error code is none, and a 0x87d1041c ESP failure ranks
the NotDetected app first (real IME payload shape).
- The event read is strict: a storage error is a 500, never a verdict
built from no evidence. 400 for a non-GUID id, 404 for an unknown session.
- Helpers/EventDataReader replaces three private Data-string copies
(classifier, failure snapshot, time attribution) and the list parsing in
EspBlockingSets; EspBlockingSets.FromLatest replaces two backward walks.
- Six vocabularies in the shared manifest section `diagnosis`; parity facts
pin the full and the minimal shape; route in the policy catalog.
- MCP get_session_summary carries it as `headline` right after coverage,
null when the call fails.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
All 54 shipped analyze rules get role, summary, subject and key facts as
{{signal.key}} templates, each with a minor version bump so the self-seed
picks them up. Values were checked against stored findings, the rows that
receive the block at read time.
- ENRL-001 repeats the outcome only: the 0x87D1041C paragraph and the
placeholder note are gone, labels read "ESP step that failed" / "ESP
error code", remediation is "Find the cause". This changes its alert text.
- ENRL-002 says the sweep "closed" the session (it can end Incomplete).
- ID-002 and ID-004 gain evidence-only conditions (unexpected account
names; IME token failure count and codes); firing and confidence are
unchanged.
- Resolution: facts sharing a label are alternatives, an exact repeat of
label and value is left out (new shared fixture case, web module and
its generated MCP copy).
- Ratchet: every built-in presentation passes the save validator, uses
qualified tokens only, and each token names a condition of the rule and
a key its source records (RuleTemplateInterpolator.TokenRegex internal).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Analyze rules can describe how a finding is shown: a role (outcome, cause, context,
advisory), a one-line summary, the subject it is about and up to six key facts. Like the
explanation, the block is stored raw on the result and resolved where it is rendered.
- Templates: a qualified token {{signal.key}} reads one key of one condition's evidence,
so counts, durations and correlation fields are reachable. RuleEvidenceKeys (Shared,
exported to the manifest) lists the keys each condition source records; tests pin it
against the evaluators.
- Backend: PresentationJson on AnalyzeRules and RuleResults, RuleVersion on results,
ValidatePresentation on create/update, and results stored without a presentation get
the current rule's at read time.
- Web + MCP: lib/rulePresentation.ts resolves the block (unrecorded facts left out, summary
falls back to the explanation). The MCP now runs generated copies of the web modules
instead of a hand-kept mirror (npm run generate:mcp-logic, freshness tests in both
suites); the duplicate baseline shrinks by one.
- MCP output: get_session and get_session_summary return the resolved block (issues gain
ruleId, role, summary, subject, facts); test_analyze_rule previews it; validate_rule
checks qualified tokens against the key catalog and lints the subject.
- Shared case files: rule-template-interpolation gains the qualified-token cases,
rule-presentation is new.
No rule carries a presentation yet; renderers behave as before until rules gain one.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The first device on 2.0.1486 reported no bootstrap log at all. Installers up to v2.3 write
bootstrap_agent.log, and the MSI's own downloader (before the loader of 2026-09-08)
msi-bootstrap.log; the reader knew only the names from the rename of 2026-08-18 on, so the
old directly assigned installers that agent_started is meant to count read as "no bootstrap".
- The installer log is the more recently written of bootstrap-script.log and
bootstrap_agent.log; the MSI channel also covers msi-bootstrap.log, which has no version.
- Tests: the old installer name, both installer logs (the newer one counts), the old MSI
downloader.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Install-AutopilotMonitor.ps1 v2.6: Get-AgentPayloadRejections (Test-FileCatalog
-Detailed; signature Valid, signer -like the pin, files exactly as catalogued) and
Install-VerifiedAgentPayload (extract into Agent-Install, verify there, then move into
the agent folder; a rejection deletes the staging folder, so the next run starts clean).
Replaces the name-filtered Get-UnverifiedAgentBinaries; the dead "already installed"
branch behind guard 5 is gone.
- OOBE go-script: embeds the installer's marked block verbatim (EmbeddedResource) and
verifies before --install; pin and catalog name come from the Shared constants; the
golden is updated, new tests pin the embedding and the order check-before-install.
- build-agent.yml: the release verify step dot-sources the installer and runs its own
function on the final ZIP.
- Pester: every decision branch, a real unsigned catalog, the staging flow (64/64).
- Privacy page: the catalog claim; LAST_UPDATED.
Merge only after a catalog ZIP is live on stable (plan step V0): pushing this to main
publishes the installer to every loader on its next run.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
%ProgramData%\AutopilotMonitor inherited ProgramData's "Users may create files and
folders" entry, so a standard user could add files to the folder the SYSTEM agent and its
boot task start from. The agent outlives the device phase of OOBE (user phase, desktop,
reboots until the enrollment ends), and the payload catalog is checked at install time
only.
- AgentDirectoryAcl.EnsureProtected: protected DACL, SYSTEM and Administrators full
control, Users read and execute, inherited by every subfolder; idempotent, never blocks
the agent.
- Install mode applies it after the agent directories exist; every runtime start
re-checks it, which also reaches agents that arrived by self-update.
- Tests: protected shape, apply once then leave alone, existing subfolders take it over,
a widened or unprotected DACL is not taken as protected, a missing folder is not created.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- build-agent.yml: input channel (release | dev; dev writes the lab blobs only and
refuses stable/release). Every third-party PE needs its vendor's embedded Authenticode
signature (signtool verify /pa, read-only). AutopilotMonitor.Agent.cat, a signed
SHA-256 catalog over every payload file, is created after Authenticode and before the
ZIP; the device check runs under Windows PowerShell 5.1 on the final ZIP.
- Agent: SelfUpdater step 5b verifies the staged payload against the catalog before any
file is swapped (PayloadCatalogVerifier, Test-FileCatalog in-process; a rejection means
no update, skip reason catalog_rejected). agent_started reports which bootstrap
installed the agent, read from the bootstrap logs (BootstrapProvenanceReader).
- Payload: System.Management.Automation.dll (never loaded, the GAC copy binds first) and
AutopilotMonitor.Shared.xml no longer ship; PDBs stay.
- Shared: CodeSigningPublisherPattern, AgentPayloadCatalogFileName;
CodeSigningParityTests pins them against installer, loader and workflow.
- Submodules: infra (build.ps1 local only), internal (D-354, plan, docs), commands.
Installer v2.6 and the OOBE script follow once a catalog ZIP is on stable: a merged
installer change is live on the next loader run.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A caller whose token shows it is not a member of the token's tenant (acct = 1, a #EXT# user name,
or an idp naming another tenant, a personal Microsoft account or an external sign-in provider) no
longer seeds DomainName/OnboardedBy or queues the activation, is never the first-user Tenant Admin,
and cannot set the activation-page address in the roleless signup window. Positive evidence only:
a token without these signals stays a member's, and tid/idp are read under their inbound-mapped
names as well. Activation promotes OnboardedBy only; the UpdatedBy fallback is gone and a stored
#EXT# UPN is refused. Explicit grants (TenantAdmins row, app role) still apply.
The once-per-interval log gate of the client-app binding becomes Helpers/LogThrottle and also
throttles the new warning, which the polling activation page would otherwise repeat.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
search_events, search_knowledge and search_docs take a query of at most 1,000
characters; search_events takes at most 20 keywords of 100 characters and
scores at most 32 distinct keywords, caller keywords first, reporting the rest
as keywordsDropped with a note. The error-code fallback takes at most 10 codes,
search_docs.section at most 100 characters.
The event scorer compiles the query once (compileConcepts): each event field is
split into words once and its words are looked up by the key of their first
three characters, so scoring an event costs the same whatever the number of
keywords. The matching rule is unchanged; a seeded differential test pins it
against the per-keyword rule. scoreEvents yields to the event loop every 256
events and stops once the caller has gone.
Every paginated tool's continuation goes through one ContinuationSchema
(at most 4,096 characters), and buildQuery appends instead of set: both
URLSearchParams.set paths were quadratic in caller-sized input.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Every stored history entry starts unread. After a new entry the worker sets the
app badge to the unread count through applyBadge, fire and forget: inside
Safari's declarative push event the setAppBadge promise never settles, so it is
never awaited and never handed to waitUntil (pushSw.test.ts pins it). The
history page marks everything read on every visible load, clears the badge,
closes the notifications whose entries the history holds and shows "New" on
what arrived since the last visit. Unpair clears the badge; the wipe push
leaves it to the next open. The payload repeats mutable inside notification,
where Safari 18.4-26.1 reads it, and carries no app_badge.
Bumps internal (plan, D-350, D-351, concept doc).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
"Notify me when done" now also fires on the first genuine pre-provisioning
seal ("Pre-provisioning finished") on the gate of the Pre-Provisioning
Completed alert, and the watch is consumed there; watching the user phase takes
a new click. A watch placed on a sealed session lives 30 days instead of 48
hours. EventIngestProcessor.WatchOutcomeFor is the one gate, the watch text
comes from PushSessionWatchService.WatchMessage. The portal button re-reads the
watch on every status change and names what it waits for. The Success hint
says it covers a finished pre-provisioning; the receiver labels the
preprovisioning_* alerts.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
.NET's $ also matches before a final newline, so "7c9e6679\n" passed the allow-list that guards
the inbound X-Correlation-ID against CRLF and log forgery and was echoed. Such a value does not
arrive over HTTP, but the gate now holds what its comment promises; a new test case pins it
(red with $).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
PushDeliveryService sends with Parallel.ForEachAsync, and RecordingHandler appended to plain
lists without a lock: two concurrent sends could lose a request (Delivered 2, recorded 1), so
Tenant_alert_also_reaches_platform_devices_whose_owners_home_tenant_it_is failed now and then
(once in six full runs while it was traced). A request and its body are now recorded together
under the lock.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A security client that inspects TLS strips the device certificate on the way to the backend;
the platform then answers 403 "Client Certificate Required" itself, or a proxy answers with
its own page. The agent counted these like backend rejections: five in a row shut it down,
the scheduled task restarted it at every boot, and only the 48-hour emergency break removed it.
- Origin of every 401/403: only a response that echoes the request's X-Correlation-ID is the
backend's verdict and counts toward the auth shutdown; the stopped-app page still counts;
anything else is foreign (client_certificate_required or foreign_response) and never counts.
- Telemetry: a foreign answer keeps the batch and pauses uploads with a backoff from 30 s up to
15 min, without a timer; collectors and the decision engine keep running, the terminal phase
gets one upload attempt, and the agent still leaves the device when the enrollment ends.
- Distress type ClientCertificateNotReceived (9), once per process, with operation, status,
page title and the issuer of the server certificate; both handlers observe it through a
validation callback that returns exactly sslPolicyErrors == None.
- Registration: an intercepted start retries on at most InterceptedRegistrationRetryBoots
further boots (config channel, default 1, agent clamp 0..5, ConfigVersion 41), then ends
monitoring like the emergency break. The break, this give-up and the config kill signal now
share Program.EndMonitoringAtStart.
- Evidence: telemetry_upload_blocked carries reason, operation, statusCode, pageTitle,
serverCertIssuer and firstBlockedAtUtc; agent_started carries priorRegistration* after failed
starts (a local network fingerprint; only whether it changed leaves the device);
agent_metrics_snapshot counts 2xx responses without the echo.
- Backend: distress type 9, the buffer default, InterceptedRuns on the registration-failure
summary; tests pin the correlation echo on 401/403 and the middleware's place in the pipeline.
- Web: distress labels for TPM Incompatible and Cert Not Received; config report row.
- MCP: session coverage names intercepted uploads and the failed registrations before the start.
D-349
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- What is Autopilot Monitor?: the evidence of every enrollment is recorded while it runs;
analyze rules flag the patterns they know, and for the rest the AI assistant over the
built-in MCP server investigates the session (clients that work with files down to the
raw IME logs of an uploaded diagnostics package) and drafts the rule for the next one
- Key Features: "AI Investigation & Rule Authoring", full width as the seventh card
- Technology & Platform → Integrations: the MCP server (read-only)
The claims carry the same qualifiers as the landing and the docs (D-348).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Landing: section evidence_loop right after the stats ("Never debug the same failure
twice." — capture, detect, investigate, learn), hero sub-line, Compare row "Unknown
failures", capabilities tile. The terminal demo continues with the rule step: validate_rule
and both test_analyze_rule verdicts are the real results of a dry-run against the demo's
two sessions, anonymized like the rest; the window scrolls with the newest line.
- /ai: "Facts first. Raw logs when it matters." with the way back to the raw line; new
section rules ("From finding to rule") with the anonymized trace of the same dry-run;
the step card is shared with Connect.
- Portal: a failed session without any rule finding offers the AI investigation in Analysis
and Guided Diagnosis (the debug-session prompt for clients with MCP prompts, a copyable
full prompt for every other client). A failed session without a diagnostics package on a
tenant without a usable upload links to the diagnostics settings. The analyze-rule editor
tests a draft against a session on the existing dry-run route; create, save and test now
share one rule body.
- llms.txt (evidence loop, two docs links without a redirect), about FAQ, marketing guard
sections, three tracking events; jscpd baseline -1 for the removed payload clone.
D-348
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
GET /api/auth/mcp reports the caller's client-capped member role in the token's tenant
(tenantRole; reported, never a grant). The MCP server registers test_analyze_rule and
test_log_pattern only for a platform role or a Tenant Admin: their routes are
TenantAdminOrGlobalReader, so Operators and Viewers only ever got a 403. validate_rule,
author-rule and the authoring guide hand a draft to a Tenant Admin otherwise, and
investigate-failed-session and debug-session end by drafting a rule for a cause that can
recur. Catalog ratchet lowered for tenant users (51,000) and delegated callers (55,500).
D-348
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Both geo pages returned their CalculatingCard before rendering their own
ProtectedRoute. MSAL keeps the account in sessionStorage, so a new tab,
bookmark or typed URL starts signed out: the tenant scope never settled,
the fetch never started, and ProtectedRoute never mounted to fire the
login redirect. The page spun on "Calculating geographic metrics… 0s
elapsed" forever with no API call.
ProtectedRoute now wraps each page's default export, so the content only
mounts once signed in. Verified in a signed-out headless Edge: production
stays on the card, the fix redirects to login.microsoftonline.com.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The workflow gates file was missing from both paths-ignore lists, so every gate
change re-ran the full CodeQL analysis for nothing.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Both were repository variables holding the www origin; lib/msalConfig.ts uses them
only as the static-render fallback and window.location.origin in the browser.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
.github/scripts/Get-Endpoints.ps1 defines the storage account (blob and table
derived from its name), the containers, the AdminConfiguration row, the legacy
mirror, the download alias, API, MCP, portal, www, docs and the timestamp
server. Workflows load it once per job; the script publisher and the local
release build call it; the dev-render anchors are built from the alias entry.
deploy-web takes NEXT_PUBLIC_API_BASE_URL and NEXT_PUBLIC_PLATFORM_STATS_MANIFEST_URL
from the registry instead of repository variables and refuses an empty value.
A new Workflow gates job fails on any host spelled out in a workflow or CI
helper; the enforced list is derived from the registry.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>