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>
Every job that runs third-party code (restore, build, tests, npm ci, next build,
wix) now holds only a read-only token and hands its output on as one archive.
Signing, publishing and deploying happen in separate jobs on a fresh runner that
run only this repository's scripts and pinned actions, unpack artifacts outside
the checkout and take no build-job output into a script.
- build-agent: reserve -> build -> release
- build-msi: sign-loader -> build -> release
- deploy-backend: reserve -> build -> deploy (publish output as one tar.gz)
- deploy-web: build -> deploy (SWA token only, no id-token) -> record
- combine-rules: validate (read-only, PRs too) -> publish (push to main only)
- deploy-mcp: same shape; documents docker build as its boundary
Deploy-only jobs run in the production environment. The jobs that sign stay
without an environment for now: a job carries one OIDC subject, and the signing
identity still trusts the ref. oidc-login-test.yml is removed; combine-rules no
longer triggers on the dev branch.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
build-agent.yml tested only DecisionCore (648 of 4,154 agent tests). It now runs
the DecisionCore, Agent.V2.Core and SummaryDialog suites before building the
release binaries, with the reserved build number.
The order matters: Agent.V2.Core.Tests references the agent exe, so its build
compiles the agent. Run after the release build or without BUILD_NUMBER, it
rewrites the exe with another version (a fresh checkout stamped 2.0.1 in a local
simulation). Run first with the number, it stamps the release version and the
Build Agent step stays an up-to-date no-op (same FileVersion, same write time).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Names the helper homes (web lib/, backend Functions/Helpers/) and asks for a
search by what a helper does before writing a new one, which no tool can judge.
Lists the local commands for the new gates, since CI only reports.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>