LogParserCollector and EventLogCollector called no guard at all, while
File/Json/Xml did — and rules/gather-rules.md already promised customers
that log-parser paths were allowlisted. A tenant admin could author a
logparser rule against C:\Users via POST /api/rules/gather and have up to
1000 lines uploaded as telemetry, bypassing the hard C:\Users block the
same page guarantees; eventlog could read any channel, Security included.
The backend validates no collectorType, target, or parameters, so the
agent is the only place this can be enforced.
Both now guard. logparser cannot check rule.Target directly (GetFullPath
throws on a wildcard), so it checks the directory for wildcard targets and
re-checks each resolved path. Event log channels get an allowlist matched
on a "/" boundary. Hard-blocked channels live in a C# constant rather than
guardrails.json: a parse error drops the allowlists to empty, which is
fail-closed, but would have lifted a block sourced from the same file.
Also fixes an inert timeout in CommandCollector: ReadToEnd() blocks until
the pipe closes, so WaitForExit(30000) was only reached once the process
had already exited and a hung command pinned its worker indefinitely.
Adds the first real CI gate. No workflow ran tests on a pull request — the
deploy workflows build without testing and build-agent.yml covered only
DecisionCore.Tests on manual dispatch — while trust/security-faq.md
already told customers automated tests run in CI. ci.yml now runs all six
suites on every PR and merge, plus a job that fails on generated-guardrail
drift, alongside CodeQL and Dependabot. OfficeBinaryWatcherTests is marked
SerialThreading; its bounded re-probe races the shared ThreadPool and
failed once under full-suite load.
Verified: 3101 backend, 1911 agent V2, 474 decision core, 44 dialog, 572
web, 447 MCP. The timeout fix is verified by runtime, not assertion — a
Start-Sleep 120 command now returns after ~30s.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The MCP server could answer telemetry questions ("why did this session fail")
but not product ones ("how do I deploy the agent", "where is my data stored").
Those answers already exist in the autopilotmonitor-docs bundle; search_docs
makes them semantically searchable.
Deliberately a separate index from search_knowledge: rules and prose answer
different questions, and sharing one topK would dilute both. Registered for
every role — published documentation carries no tenant data — and skipped
entirely when no corpus was baked in, so a doc-less build advertises no
broken tool.
Corpus handling:
- Chunked at h2/h3 and <details>/<summary>, each chunk prefixed with a
"Page > Section > Subsection" breadcrumb so a mid-page hit is self-describing.
- Newlines are normalized before any parsing. This is a correctness
precondition, not cosmetics: the bundle ships CRLF and JS `.` does not match
\r, so /^(#{2,3})\s+(.*)$/ matched zero headings — silently, degrading every
page to unlabelled paragraph splits.
- Two size exceptions: tables are never split (a split table leaves `| --- |`
rows with no header), and unbroken changelog lists split at line level (left
whole, their tails fall outside the model window and become unsearchable).
- Short sections are kept — a one-line answer is often the best hit, and the
breadcrumb supplies the context a short body lacks.
Build and delivery:
- Docs are embedded at image build time into an optional third
PrecomputedSection. Optional so a checkout without the docs repo does not
invalidate the rules and event-type vectors; the reverse (server has docs,
file does not) is staleness and is rejected.
- The deploy workflow checks the public docs repo into the build context and
passes its SHA as DOCS_COMMIT, which /health reports alongside the chunk
count — editing the docs does not update a running server, so staleness is
made checkable rather than memorable.
- Embeddings are rounded to 6 decimals on serialize: 55% smaller index (2.63
vs 5.88 MB) for a ~1e-5 norm error against a 1e-3 tolerance. indexPrecomputed
now asserts unit norm, since the hydrate path never checked it.
Infra: the Bicep template was two changes behind the live deployment (EU
cutover and ACR hardening, both applied directly in Azure). Reconciled against
the running resources — managed-identity image pull replaces the admin
credentials and the acr-password secret — and infra/ is no longer gitignored so
it stops drifting. Container raised to 0.5 vCPU / 1.0 GiB for the cold-start
query-embedder load. The template does not model the mcp.autopilotmonitor.com
custom domain, so a full deployment would remove it; the header warns about it.
Verified: 428 tests pass; 45 pages produce 324 chunks; sample queries hit the
right page top-1/2; all 8 derived URLs return 200 live; the precompute output
is accepted by the boot validator and a mutated corpus is rejected.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The brave-grass filename and secret name referred to the pre-migration
Static Web App hostname. Workflow now matches the deploy-backend/-mcp
naming scheme and uses AZURE_SWA_DEPLOY_TOKEN (already set to the new
SWA's token). The old secret is removed after the first green run.
Nothing required net8.0 -- the test runner just predated the .NET 10
migration. Agent projects stay net48 (Intune/OOBE constraint).
474/474 tests green on net10.0.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The AdminConfiguration table SAS is issued for autopilotmonitoreu; the
MERGE URL still pointed at the legacy account and failed auth.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Cross-tenant re-claim of recently deleted global names is denied by
Azure by design (dangling-DNS/subdomain-takeover protection). New
tenant resources therefore use eu-suffixed names; the old names live
on in the old subscription (storage 'autopilotmonitor' recreated as
legacy bootstrap-URL keepalive, FuncApp/ACR kept as tombstones).
- API base URL -> autopilotmonitor-api-eu.azurewebsites.net
(Shared Constants, web CSP + /go fallback, MCP default)
- Agent blob container + build counter -> autopilotmonitoreu
- deploy-backend app-name, deploy-mcp ACR -> eu names
- Bootstrap script URLs stay on the legacy storage (customers
migrate once to a domain alias later)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
gh attestation verify only passes with the repo's current name
(okieselbach/AutopilotMonitor); the signed workflow identity carries it,
and the old dashed name fails identity matching even though git-remote
redirects still work. Verified against the first attested dry-run ZIP.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
First real run of build-agent.yml failed the counter CAS write with
"AuthenticationFailed ... sp is mandatory": the AZURE_BLOB_AGENT_SAS_TOKEN
secret stores the query with its leading '?', and the workflow prepends
another one, so Azure saw '?sp' as the first parameter name. The pre-existing
upload step had the same latent bug -- it had just never run. Normalize with
TrimStart('?') at all three usage sites (counter, blob upload, table merge).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Build numbers now come from a public counter blob (buildcounter-v2.txt,
reserve-before-build with ETag-CAS) shared by local build.ps1 and CI, so the
two paths can never mint the same number. The CI step that committed
buildcounter.txt bumps to main is gone; the repo file is now a gitignored
offline fallback for -SkipUpload smoke builds only.
CI parity fixes vs the local build:
- ship SummaryDialog.exe.config in the ZIP (WPF AppContextSwitchOverrides;
without it the dialog dies with 0x8007045A when spawned from SYSTEM)
- verify EXE version == manifest == reserved build number before zipping
- update the AdminConfiguration hash-oracle only on publish_as_stable
CI-built release ZIPs now carry a GitHub Artifact Attestation (Sigstore
keyless provenance, actions/attest-build-provenance@v4).
Doc: docs/agent/build-counter-blob.md
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
deploy-backend + deploy-mcp now authenticate via azure/login OIDC (client-id/tenant-id/subscription-id + id-token permission) instead of the AZURE_CREDENTIALS service-principal secret. Adds temporary oidc-login-test workflow to smoke-test the federated login. Federated credential 'github-main' (repo:okieselbach/Autopilot-Monitor:ref:refs/heads/main) created on the deploy app registration.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
GitHub forces all JS actions onto Node 24 starting 2026-06-16 and
removes Node 20 from runners on 2026-09-16. Bump every affected action
to the first major that officially declares node24, avoiding majors
with functional changes (checkout v6, upload-artifact v7):
actions/checkout v4 -> v5
azure/login v2 -> v3
actions/setup-dotnet v4 -> v5
actions/setup-node v4 -> v5
actions/upload-artifact v4 -> v6 (v5 still defaulted to node20)
softprops/action-gh-release v2 -> v3
stefanzweifel/git-auto-commit-action v5 -> v7 (v7 reverted the v6
input removals; only standard inputs used here anyway)
Azure/functions-action@v1 (floating tag, node24 since v1.5.4) and
Azure/static-web-apps-deploy@v1 (Docker action) need no change.
All target tags verified to exist via git ls-remote.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Replaces V1/V2-hardcoded build + bootstrap with a single parametric
pipeline that scales to V3+ without rewrites. Full architecture in
.claude/plans/v2-cutover.md.
Stable namespace (Bootstrap-side, never renamed): generic Bootstrap
script, agent owns its --install defaults so it works for any line.
Versioned namespace (per-line, SelfUpdater-side): isolated *-v{N}.*
blobs; SelfUpdater never crosses lines or touches stable.
Backend GetAgentConfig dispatches per X-Agent-Version-Major to
AdminConfiguration.GetAgentLine(int) -> LatestAgentV{N}* fields.
Future V3 = add field set + switch arm. TableConfigRepository.Map
falls back to legacy unsuffixed columns for transparent migration.
V1 SelfUpdater now reads version-v1.json (would have been clobbered
by the V2 cutover otherwise). V2 agent owns its own 600s TenantId-wait
default so the Bootstrap calls --install plain.
Tests: 3289 across 6 suites, all green.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Reverts:
- 8dacce5c "chore(backend): bump optional NuGet patches + fix SYSLIB0057"
- 2f68fe69 "chore(backend): upgrade Functions from .NET 8 to .NET 10 isolated"
After deploy succeeded and the Flex Consumption stack was switched to
.NET 10 Isolated in the portal, the dotnet-isolated worker died on every
boot with: dotnet exited with code 150 (FrameworkMissingFailure) and
"Exceeded language worker restart retry count for runtime:dotnet-isolated".
The deployed package was correct (TFM net10.0, FrameworkReference 10.0.0,
all package bumps applied) and the runtime stack property was set to
dotnet-isolated 10.0, but the .NET 10 runtime is not present on the
Flex Consumption stamp in West Europe even though Microsoft lists
DOTNET-ISOLATED|10.0 as supported. Restart and stop+start did not help.
Recovery sequence:
1. Stack switched back to .NET 8 Isolated (manual portal action).
2. Re-deployed last known-good net8.0 commit 637bd9a1 via temp branch
rollback-net8 (now safe to delete).
3. /api/health back to 200.
Follow-ups:
- Re-apply security fixes (DataProtection 10.0.7 pin GHSA-9mv3-2cwr-p262,
SYSLIB0057 X509CertificateLoader migration) on net8.0 in a separate PR.
- Open Microsoft support: ".NET 10 Isolated worker on Flex Consumption
WestEurope fails with FrameworkMissingFailure despite stack listed as
supported."
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The build_and_deploy_job's if-guard accepted push + non-closed PR
events only, so manually triggering the Static Web Apps workflow via
`gh workflow run` ran but skipped both jobs (conclusion=skipped).
After the V2-refactor merge to main (commit 62c0c990), the automatic
push-triggered run did not materialise on the merge commit — likely
a path-filter + merge-commit edge case on GitHub's side — leaving
no way to ship the web changes without either another path-touching
push or hand-editing the workflow.
Adding `github.event_name == 'workflow_dispatch'` to the if-guard
keeps the close-PR job untouched while making manual runs a reliable
escape hatch for future merge-commit oddities.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The post-config binary integrity check was comparing the running EXE's
SHA-256 against LatestAgentSha256 which is the hash of the ZIP package —
these can never match. Introduces LatestAgentExeSha256 as a separate hash
computed from AutopilotMonitor.Agent.exe at build time, flowing through
AdminConfiguration → AgentConfigResponse → MonitoringService.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Build SummaryDialog (WPF) alongside Agent and bundle into same ZIP
- Upload AutopilotMonitor-Agent.zip + version.json to Azure Blob Storage
(matches bootstrap script download URL pattern)
- Compute Content-MD5 header for ZIP integrity validation
- New workflow input to toggle blob upload independently from release
Requires AZURE_BLOB_AGENT_SAS_TOKEN secret for blob storage access.
https://claude.ai/code/session_01QiuThUkeSEbiapFpTY9yRp
On-demand workflow (workflow_dispatch) that:
- Builds the .NET Framework 4.8 Agent on Windows runner
- Auto-increments build counter or accepts manual override
- Creates a ZIP artifact with all build outputs + version.json
- Optionally creates a GitHub Release with the artifacts
- Commits updated buildcounter.txt back to the repo
https://claude.ai/code/session_01QiuThUkeSEbiapFpTY9yRp
The private submodule (.claude/commands) is not needed for builds
and was causing CI failures since the runner has no access to it.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
## Rules as GitHub single source of truth
- Add 75 individual JSON rule files (2 gather, 18 analyze, 55 IME patterns)
under rules/gather/, rules/analyze/, rules/ime-log-patterns/
- Add JSON Schemas for all three rule types with VS Code IntelliSense support
- Add CI combine script (rules/scripts/combine.js) + GitHub Action that
validates schemas with ajv-cli and generates rules/dist/ combined files
- Convert BuiltIn*.cs from hardcoded C# objects to embedded JSON resource loaders
- Add GitHubRuleRepository service to fetch dist/ files from raw.githubusercontent.com
- Add ReseedFromGitHubFunction: POST /api/rules/reseed-from-github?type=all|gather|analyze|ime
## IME Log Patterns
- Extend ImeLogPattern model with Description and IsBuiltIn properties
- Add ImeLogPatterns table + API constants
- Add TableStorageService CRUD methods for IME log patterns
- Add ImeLogPatternService (CRUD, merge, auto-seed from embedded JSON)
- Add ImeLogPatternsFunction: GET/PUT /api/rules/ime-log-patterns (Phase 1: read-only
for tenants, Galactic Admin can enable/disable/edit globally)
- Add IME Log Patterns management page (/ime-log-patterns)
- Wire GetAgentConfigFunction to use ImeLogPatternService instead of static class
## API route consolidation
- All rule endpoints now under /api/rules/* for single certificate exclusion path:
/api/rules/gather, /api/rules/analyze, /api/rules/ime-log-patterns,
/api/rules/reseed-from-github
## Export buttons
- Add per-rule and export-all JSON download buttons to gather-rules,
analyze-rules, and ime-log-patterns pages
## Admin configuration
- Replace old individual reseed buttons with unified "Fetch & Reseed from GitHub"
(calls new GitHub endpoint) plus compact individual-type reseed buttons
- Add CDN cache hint: GitHub raw.githubusercontent.com caches up to 5 minutes
## Preview mode
- ?preview=1 URL param seeds localStorage.previewMode (persists across navigation)
- ?preview=0 clears it; toggle in Navbar settings dropdown (indigo, eye icon)
- IME Log Patterns nav link gated behind galacticAdminMode OR previewMode
- Preview badge shown on nav link when active for normal users
## Tests
- Add xUnit test project (net8.0) with 5 tests for embedded resource loading,
known IME actions validation, and valid categories check
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>