mirror of
https://github.com/okieselbach/AutopilotMonitor.git
synced 2026-10-22 19:55:35 +00:00
chore: private submodule docs/ becomes internal/ (knowledge + decisions + work files + private instructions)
- .gitmodules: docs -> internal, repo renamed to Autopilot-Monitor-internal; the OKF bundle now lives at internal/docs/ - .claude/CLAUDE.md rewritten: where knowledge lives, working-file rules, hard rules; imports the private half from internal/CLAUDE.md (silently absent without submodule access) - .claude/architecture-principles.md moved into the internal bundle (reconciled against the code) - tasks/ (gitignored) retired; working files live in internal/work/ - doc path references docs/<bundle> -> internal/docs/<bundle> in code comments, skills gitlink, codeql paths, build script - rule descriptions no longer cite an internal doc path (customer-visible text); rules/dist regenerated - public tree no longer names private operator tooling - .claude/commands gitlink: skill docs point at the new paths
This commit is contained in:
+53
-10
@@ -4,21 +4,57 @@
|
||||
- **Use subagents liberally** for research, exploration, and parallel analysis — one focus each — to keep the main context clean.
|
||||
- **Verify before claiming done.** Run the tests, check the logs, show the evidence. Diff against `main` where behaviour could regress.
|
||||
- **Fix bugs autonomously.** Given a report, a failing test, or an error, resolve it — no hand-holding.
|
||||
- **Capture corrections.** After any correction from the user, record the pattern and a preventing rule in `tasks/lessons.md`; review it at session start.
|
||||
- **Capture corrections.** After any correction from the user, add a lesson to `internal/work/lessons.md` and update the rule block at its top. Read that rule block at session start.
|
||||
- Read files over 500 LOC in chunks. Never treat one grep as proof of absence.
|
||||
|
||||
Codex reviews your output once you are done.
|
||||
|
||||
## Where knowledge lives
|
||||
|
||||
| Content | Location | Rule |
|
||||
|---|---|---|
|
||||
| Customer documentation | separate repo `autopilotmonitor-docs` (GitBook; baked into the MCP `search_docs` corpus) | Any change ⇒ MCP redeploy. Commit in that repo (`cd` first). |
|
||||
| Technical concept docs (OKF bundle) | `internal/` — private submodule, entry point `internal/docs/index.md` | Read the index first. Concept docs only for invariants, mechanisms and architecture a later change could silently break. |
|
||||
| Decisions, including "we will not build X" | `internal/decisions.md` | One entry per decision: date, decision, why, applies-to, status. A concept doc only when a mechanism needs explaining. |
|
||||
| Working files | `internal/work/` | See the next section. |
|
||||
| Agent review criteria | `internal/docs/agent/architecture-principles.md` | Check every agent change against it. |
|
||||
| Private operator instructions | `internal/CLAUDE.md` | Imported at the end of this file. Absent for contributors without submodule access; nothing in it may be copied into this public repo. |
|
||||
| Infrastructure | `infra/` — private submodule | |
|
||||
| Skills | `.claude/commands/` — private submodule | |
|
||||
| Claude auto-memory | outside the repo, per machine | Private notes only. Anything the project must keep goes into `internal/`. |
|
||||
|
||||
## Working files (`internal/work/`)
|
||||
|
||||
- `todo.md` — the running task only: checkable steps plus a Review section. Cleared when the next task starts.
|
||||
- `plans/<slug>.md` — one file per larger piece of work. The first line carries the status (AKTIV / RELEASED / VERWORFEN); the file ends with a `## Offen` section.
|
||||
- `backlog.md` — the one backlog: an "Ideen" section for raw ideas at the top, then deferred feature pieces, open questions, tech debt, user actions and the deploy/verify long tail. One line per item with what is missing, the source plan and the date. Items are deleted when done or when they get a plan file, never ticked.
|
||||
- `archive/` — closed plans whose review is worth keeping.
|
||||
- `lessons.md` — corrections from the user; rule block at the top, narrative below.
|
||||
|
||||
When → do:
|
||||
- A plan is approved → copy it to `plans/<slug>.md`, put the steps into `todo.md`.
|
||||
- A task closes → review into the plan file, leftovers as one-liners into `backlog.md`, clear `todo.md`. The commit message is the log.
|
||||
- A backlog item is picked up → back into a plan file, removed from `backlog.md`.
|
||||
- A decision is made, including a deliberate non-decision → entry in `internal/decisions.md`.
|
||||
- Working files are committed inside `internal/` when a task closes, not on every tick.
|
||||
|
||||
## Hard rules
|
||||
|
||||
- Agent projects stay on .NET Framework 4.8.
|
||||
- .NET build warnings are errors; only the NU1901–NU1904 advisories remain warnings.
|
||||
- Never put real customer domains, tenant names or tenant IDs into code, tests, commits, comments or public docs.
|
||||
- Deploy order is Backend → Web → MCP. Backend deploys are `workflow_dispatch` only. Verify the deployed version before claiming anything is live.
|
||||
- Submodules (`internal/`, `infra/`, `.claude/commands/`): commit inside the submodule first, then bump the gitlink here.
|
||||
- Stage explicit file lists; never `git add -A` or a directory. Pushing is the user's call.
|
||||
- New API responses need a typed DTO plus the parity fact. MCP vocabularies are generated from the wire types, never retyped.
|
||||
- Every route belongs in the route policy catalog; every new event type goes into the MCP resource catalog.
|
||||
|
||||
## Code Quality
|
||||
|
||||
- Simplicity first: minimal, targeted changes. No temporary fixes — find the root cause.
|
||||
- Where architecture is flawed, state is duplicated, or patterns are inconsistent, propose and implement the structural fix. Ask what a perfectionist reviewer would reject, and fix that too.
|
||||
- On non-trivial changes, ask whether there is a more elegant way before presenting. Skip it for simple fixes — don't over-engineer.
|
||||
|
||||
## Task Management
|
||||
|
||||
Keep the plan in `tasks/todo.md` as checkable items, confirm it before implementing, tick items off as you go, and add a review section when done.
|
||||
|
||||
## Customer-Facing Claims
|
||||
|
||||
`src/Web/autopilot-monitor-web/app/{about,terms,privacy}/page.tsx` and `autopilotmonitor-docs/trust/*` state facts customers and security reviewers rely on. They drift silently.
|
||||
@@ -28,11 +64,18 @@ Keep the plan in `tasks/todo.md` as checkable items, confirm it before implement
|
||||
- Describe only what customers can actually use; operator-only infrastructure is not a feature.
|
||||
- Trust pages: durable phrasing over exact figures, and bump their "Last reviewed" date.
|
||||
|
||||
## Technical Docs — OKF Bundle (`docs/`, private submodule)
|
||||
## Technical Docs — OKF Bundle (`internal/`, private submodule)
|
||||
|
||||
Contributor/AI-facing docs (customer docs live in the `autopilotmonitor-docs` repo). `docs/` is a **private** submodule (`okieselbach/Autopilot-Monitor-docs-internal`) — this repo is public, the docs are not. Start at `docs/index.md` before opening individual files.
|
||||
Contributor/AI-facing docs (customer docs live in the `autopilotmonitor-docs` repo). `internal/` is a **private** submodule (`okieselbach/Autopilot-Monitor-internal`) — this repo is public, the bundle is not. Start at `internal/docs/index.md` before opening individual files.
|
||||
|
||||
- Write a concept doc only for durable knowledge a later change could silently break: invariants, architecture decisions, non-obvious mechanisms — not for every feature. YAML frontmatter: `type` (mandatory), plus `title`, `description`, `resource`, `tags`, `timestamp`.
|
||||
- Register it in `docs/index.md`. There is no `log.md` — the commit message is the log. `index.md` is a reserved name.
|
||||
- Commit inside the submodule first, then bump the gitlink here (same rule as `.claude/commands` and the private `infra/` submodule, `okieselbach/Autopilot-Monitor-infra`).
|
||||
- Write a concept doc only for durable knowledge a later change could silently break: invariants, architecture decisions, non-obvious mechanisms — not for every feature. YAML frontmatter: `type` (mandatory, one of `Concept`, `Reference`, `Guide`), plus `title`, `description`, `resource`, `tags`, `timestamp`.
|
||||
- Register it in `internal/docs/index.md`. There is no `log.md` — the commit message is the log. `index.md` is a reserved name.
|
||||
- `internal/work/`, `internal/decisions.md` and `internal/CLAUDE.md` are not part of the bundle and are not listed in the index.
|
||||
- English, structural markdown (`# Schema`, `# Examples`, `# Citations`). Links between docs are RELATIVE — never `/`-prefixed, that breaks GitHub navigation.
|
||||
|
||||
## Language
|
||||
|
||||
- English for knowledge: concept docs, decisions, both CLAUDE files, code comments.
|
||||
- German is fine for working files under `internal/work/`.
|
||||
|
||||
Private half of these instructions (skills, private repositories, operations): @../internal/CLAUDE.md
|
||||
|
||||
@@ -1,136 +0,0 @@
|
||||
# Autopilot-Monitor — Agent Architecture Principles
|
||||
|
||||
The principles are **not nice-to-haves**, they are review criteria: any code
|
||||
change that violates a principle is either (a) a bug, (b) a reason to change the principle
|
||||
(with user sign-off), or (c) an explicitly documented exception with rationale.
|
||||
|
||||
---
|
||||
|
||||
## L.1 Truth Hierarchy (immutable)
|
||||
|
||||
`SignalLog` (input) → `Journal` (decisions) → `Snapshot` (cache) → `Projections` (derivable).
|
||||
No projection patches backwards into the journal or the signal log. On inconsistency, the
|
||||
lower layer always wins (e.g. discard the snapshot, do not "repair" the signal log).
|
||||
|
||||
## L.2 Event Sourcing as Foundation
|
||||
|
||||
`SignalLog` is the only input truth. The reducer is pure:
|
||||
`(oldState, signal) → (newState, transition, effects)`. The backend can re-derive at any
|
||||
time from `SignalLog`. Replay yields the same `Journal` content.
|
||||
|
||||
## L.3 Immutable State
|
||||
|
||||
All DTOs in the decision core are `sealed` with get-only properties. No in-place mutation.
|
||||
"Change" = new instance via `With…` method. This is the difference between "purely testable"
|
||||
and "subtle heisenbugs".
|
||||
|
||||
## L.4 Hypothesis Model instead of Flow Plugins
|
||||
|
||||
Fuzziness is constitutive (White-Glove, enrollment type, device-only). A single reducer with
|
||||
hypothesis fields (`Unknown/Weak/Strong/Confirmed/Rejected`), organized by scenario in partial
|
||||
classes. **No** plugin runtime, because clean flow boundaries do not match reality.
|
||||
|
||||
## L.5 Evidence Determinism
|
||||
|
||||
Signal-log determinism is guaranteed: same log → same outcome. Every signal carries an
|
||||
`Evidence` record with `Kind` (Raw / Derived / Synthetic) and mandatory fields. A signal
|
||||
without evidence = adapter rejection = bug.
|
||||
|
||||
## L.6 Signal Schema Versioning
|
||||
|
||||
Every `DecisionSignalKind` has a `SchemaVersion`. The reducer dispatches on
|
||||
`(Kind, SchemaVersion)` — old versions stay replay-capable. A new version or a new kind
|
||||
without a replay fixture in `tests/fixtures/signal-kinds/{kind}-v{n}.json` = merge block.
|
||||
|
||||
## L.7 Timer Monopoly
|
||||
|
||||
Only the Deadline Scheduler (the EffectRunner role) holds decision-relevant timers. Collectors,
|
||||
signal adapters and the orchestrator must not. `Task.Delay` or `new Timer(...)` in those
|
||||
layers with session-state impact = merge block. `IClock` interface + virtual clock for
|
||||
testability.
|
||||
|
||||
## L.8 Classifier as Post-Hook Kernel Service
|
||||
|
||||
`IClassifier` interface, swappable. The `ClassifierVerdict` flows back into the log as a
|
||||
synthetic `ClassifierVerdictIssued` signal. `InputHash` (SHA256 over classifier-relevant
|
||||
snapshot fields) is the anti-loop guard. Classifiers are not baked into the state machine.
|
||||
|
||||
## L.9 Clean-Agent Principle on Refactors
|
||||
|
||||
For structural refactors: build the new project in parallel to the legacy one, do not flag-
|
||||
route inside the same process. Strict separation rule: no direct reference to legacy
|
||||
projects. Code is copied, not shared. Legacy runs out, then gets removed.
|
||||
|
||||
## L.10 Generic Telemetry Transport
|
||||
|
||||
One spool for all telemetry types (events, signals, decision transitions). `TelemetryItemId`
|
||||
is the type-agnostic upload cursor; the type-specific counters (`Event.Sequence`,
|
||||
`SignalOrdinal`, `StepIndex`) remain storage / UI / reducer semantics. Drain guarantee:
|
||||
"coordinated + idempotent + resumable" — **not** cross-table atomic, which is not achievable
|
||||
with three separate tables anyway.
|
||||
|
||||
## L.11 Separation: Agent Lifecycle vs Session State
|
||||
|
||||
`DecisionState` is session-pure. Agent-process flags (crash, admin actions, boot time,
|
||||
heartbeat status) live separately in `agent-lifecycle.json`, managed by the orchestrator,
|
||||
never by the reducer. Prevents replay dilution by lifecycle events.
|
||||
|
||||
## L.12 Flush-on-Append Persistence
|
||||
|
||||
No batching of append operations. `FileOptions.WriteThrough` plus an explicit
|
||||
`FlushAsync(flushToDisk: true)` per append. Every successful `Append` return means: on disk.
|
||||
Durability > performance in the crash-prone OOBE context.
|
||||
|
||||
## L.13 UI Primacy — End-Users vs Dev Tooling
|
||||
|
||||
Product views (EventTimeline, PhaseTimeline) stay slim and stable. Debug / analysis tools
|
||||
(Inspector with DecisionGraph, SignalStream, VerifierReport) live on their own subpage behind
|
||||
an `adminMode` gate. The session-detail page does not get overloaded with dev components.
|
||||
|
||||
## L.14 Forward Compatibility by Default
|
||||
|
||||
Enums via `StringEnumConverter` + `UnknownFallbackEnumConverter` (unknown values → default,
|
||||
never crash). New schema fields are nullable. The `ReducerVersion` stamp per journal row
|
||||
allows older backends to read newer rows. Deletions only after a major version bump with
|
||||
user sign-off.
|
||||
|
||||
## L.15 Test Discipline
|
||||
|
||||
Flow / scenario tests are prioritized over structural tests. Coverage targets per component
|
||||
(DecisionCore ≥95 %, classifier 100 %, backend functions ≥85 %). A missing fixture for a
|
||||
scenario = merge block. CI threshold check is a build failure, not a warning.
|
||||
|
||||
## L.16 Fail-Safe Reducer Exceptions
|
||||
|
||||
Exception in the reducer → `DecisionTransition` with `DeadEndReason=reducer_exception`, state
|
||||
unchanged (fail-safe, never destroy state). Signal counts as processed (no retry loop). After
|
||||
five consecutive exceptions → the orchestrator emits
|
||||
`enrollment_failed(reason=reducer_instability)`.
|
||||
|
||||
## L.17 Effect Error Handling by Class
|
||||
|
||||
Three effect classes with their own strategy:
|
||||
|
||||
- **Transient** (retry with exponential backoff 100 ms / 400 ms / 1600 ms):
|
||||
`EmitEventTimelineEntry`, `PersistSnapshot`
|
||||
- **Critical** (session abort, e.g. on timer-register failure): `ScheduleDeadline`,
|
||||
`CancelDeadline`
|
||||
- **Optional** (best-effort, classifier exception → `Inconclusive` verdict, no abort):
|
||||
`RunClassifier`
|
||||
|
||||
Every effect failure is logged as a `DecisionTransition`.
|
||||
|
||||
## L.18 Implementation Discipline
|
||||
|
||||
- **R1** Pre-flight plan check before every work step
|
||||
- **R2** Delegate self-contained sub-tasks **and exploration** to subagents (Anthropic
|
||||
recommendation: preserve main-context window on large refactors — quality assurance)
|
||||
- **R3** Real verification (DB round-trip, index dual-write, agent signal emission) — no
|
||||
"it compiles and runs"
|
||||
- **R4** The plan is authority — deviations require user sign-off
|
||||
- **R5** Out-of-scope items land in `tasks/todo.md`, not in memory
|
||||
- **R6** High test coverage is a prerequisite for future refactorability, not a nice-to-have
|
||||
- **R7** Code-structure discipline: small files (guideline ≤300 LoC), clear folders, partial
|
||||
classes by scenario, check in with the user on any uncertainty (placement, naming, scope)
|
||||
|
||||
---
|
||||
+1
-1
Submodule .claude/commands updated: 1d28c43c5b...40ae3c3581
@@ -65,7 +65,7 @@ function Read-Counter {
|
||||
$resp = Invoke-WebRequest -Uri $CounterUrl -Method Get -UseBasicParsing
|
||||
} catch {
|
||||
if ((Get-HttpStatus $_) -eq 404) {
|
||||
throw "Counter blob not found at $CounterUrl. Seed it once with the last used build number: PUT the plain integer with 'x-ms-blob-type: BlockBlob' and 'If-None-Match: *'. See docs/versioning.md."
|
||||
throw "Counter blob not found at $CounterUrl. Seed it once with the last used build number: PUT the plain integer with 'x-ms-blob-type: BlockBlob' and 'If-None-Match: *'. See internal/docs/versioning.md."
|
||||
}
|
||||
throw
|
||||
}
|
||||
|
||||
@@ -8,13 +8,13 @@ on:
|
||||
push:
|
||||
branches: [main]
|
||||
paths-ignore:
|
||||
- 'docs/**'
|
||||
- 'internal/**'
|
||||
- '**/*.md'
|
||||
- 'LICENSE'
|
||||
pull_request:
|
||||
branches: [main]
|
||||
paths-ignore:
|
||||
- 'docs/**'
|
||||
- 'internal/**'
|
||||
- '**/*.md'
|
||||
- 'LICENSE'
|
||||
schedule:
|
||||
|
||||
+5
-7
@@ -15,7 +15,7 @@ bld/
|
||||
[Ll]ogs/
|
||||
|
||||
# Build counter files: the authoritative counter is the shared blob
|
||||
# (agent container, buildcounter-v{N}.txt, ETag-CAS — see docs/agent/build-counter-blob.md).
|
||||
# (agent container, buildcounter-v{N}.txt, ETag-CAS — see internal/docs/agent/build-counter-blob.md).
|
||||
# The local file is only an offline fallback for -SkipUpload/bare-dotnet smoke builds.
|
||||
buildcounter.txt
|
||||
|
||||
@@ -134,7 +134,6 @@ tests/fixtures/enrollment-sessions-local/
|
||||
TODO.txt
|
||||
tools/orphan-analysis.py
|
||||
tools/export-session-fixture.mjs
|
||||
tasks/
|
||||
|
||||
# Azure Table cleanup backups (dry-run exports + delete manifests)
|
||||
# Produced by /cleanup-table skill — never commit deletion payloads
|
||||
@@ -148,8 +147,8 @@ plans/REFACTOR_AGENT_V2.md
|
||||
# (storage account name, resource group are operator-specific) so it stays
|
||||
# out of the repo. Each operator keeps their own copy.
|
||||
scripts/Backup/setup-backup-container.ps1
|
||||
docs/Introduction-AutopilotMonitor.pptx
|
||||
docs/~$Introduction-AutopilotMonitor.pptx
|
||||
internal/docs/Introduction-AutopilotMonitor.pptx
|
||||
internal/docs/~$Introduction-AutopilotMonitor.pptx
|
||||
|
||||
# Playwright MCP browser artifacts (snapshots, screenshots, console logs)
|
||||
.playwright-mcp/
|
||||
@@ -159,7 +158,6 @@ docs/~$Introduction-AutopilotMonitor.pptx
|
||||
src/Web/autopilot-monitor-web/public/version.json
|
||||
|
||||
|
||||
# Local IME binaries scratch used by the /ime-decompile skill (MSI + msiexec-
|
||||
# extracted payload per version, for grepping in binaries). Decompilates live in
|
||||
# the private ime-decompiles repo, MSIs in the ime-archive blob - this stays local.
|
||||
# Local IME installer scratch (MSI + extracted payload per version, for grepping
|
||||
# in binaries). Operator-only, never committed.
|
||||
ime-files/
|
||||
|
||||
+2
-2
@@ -2,8 +2,8 @@
|
||||
path = .claude/commands
|
||||
url = https://github.com/okieselbach/Autopilot-Monitor-dotclaude.git
|
||||
[submodule "docs"]
|
||||
path = docs
|
||||
url = https://github.com/okieselbach/Autopilot-Monitor-docs-internal.git
|
||||
path = internal
|
||||
url = https://github.com/okieselbach/Autopilot-Monitor-internal.git
|
||||
[submodule "infra"]
|
||||
path = infra
|
||||
url = https://github.com/okieselbach/Autopilot-Monitor-infra.git
|
||||
|
||||
-1
Submodule docs deleted from dd053d1068
Submodule
+1
Submodule internal added at aa32b61c50
@@ -2,7 +2,7 @@
|
||||
"$schema": "../schema/analyze-rule.schema.json",
|
||||
"ruleId": "ANALYZE-DEV-007",
|
||||
"title": "System Clock Skew — TPM Attestation and ESP Timeout Risk",
|
||||
"description": "The device's real-time clock deviates from NTP by more than two minutes. Microsoft documents that a clock off by several minutes causes TPM attestation errors (self-deploying/pre-provisioning) and ESP timeouts. Knowledge source: docs/agent/autopilot-ztd-diagnostics.md.",
|
||||
"description": "The device's real-time clock deviates from NTP by more than two minutes. Microsoft documents that a clock off by several minutes causes TPM attestation errors (self-deploying/pre-provisioning) and ESP timeouts.",
|
||||
"severity": "warning",
|
||||
"category": "device",
|
||||
"version": "1.1.0",
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
"$schema": "../schema/analyze-rule.schema.json",
|
||||
"ruleId": "ANALYZE-ENRL-003",
|
||||
"title": "TPM Attestation Failure — Known Error Code",
|
||||
"description": "A TPM-attestation-related error code documented in the Microsoft Autopilot known-issues list was observed (0x800705B4, 0x801C03EA, 0x81039001, 0x81039023, 0x81039024, 0x80070490). These block self-deploying and pre-provisioning deployments during 'Securing your hardware'. Observability: attestation precedes MDM enrollment, so in the normal deployment model (agent installed via IME after enrollment) these codes are rarely visible — the rule primarily serves bootstrap-token deployments. See docs/agent/autopilot-ztd-diagnostics.md (Observability section).",
|
||||
"description": "A TPM-attestation-related error code documented in the Microsoft Autopilot known-issues list was observed (0x800705B4, 0x801C03EA, 0x81039001, 0x81039023, 0x81039024, 0x80070490). These block self-deploying and pre-provisioning deployments during 'Securing your hardware'. Observability: attestation precedes MDM enrollment, so in the normal deployment model (agent installed via IME after enrollment) these codes are rarely visible — the rule primarily serves bootstrap-token deployments.",
|
||||
"severity": "high",
|
||||
"category": "enrollment",
|
||||
"version": "1.1.0",
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
"$schema": "../schema/analyze-rule.schema.json",
|
||||
"ruleId": "ANALYZE-ENRL-004",
|
||||
"title": "MDM Enrollment Blocked — Known Error Code",
|
||||
"description": "An enrollment-blocking error code documented in the Microsoft Autopilot known-issues/troubleshooting pages was observed (0x80180014, 0x80180018, 0xC1036501, 0x801C03F3, 0x80070774). Each has a specific, documented cause and fix. Observability: a blocked MDM enrollment normally means no agent gets installed at all (IME install happens after enrollment) — this rule can realistically only fire for bootstrap-token deployments (pre-MDM reporting) or mixed retry scenarios. See docs/agent/autopilot-ztd-diagnostics.md (Observability section).",
|
||||
"description": "An enrollment-blocking error code documented in the Microsoft Autopilot known-issues/troubleshooting pages was observed (0x80180014, 0x80180018, 0xC1036501, 0x801C03F3, 0x80070774). Each has a specific, documented cause and fix. Observability: a blocked MDM enrollment normally means no agent gets installed at all (IME install happens after enrollment) — this rule can realistically only fire for bootstrap-token deployments (pre-MDM reporting) or mixed retry scenarios.",
|
||||
"severity": "high",
|
||||
"category": "enrollment",
|
||||
"version": "1.1.0",
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
"$schema": "../schema/analyze-rule.schema.json",
|
||||
"ruleId": "ANALYZE-ENRL-005",
|
||||
"title": "Hybrid Join Timeout 0x80004005 — Fixed by Windows Update",
|
||||
"description": "A Microsoft Entra hybrid join Autopilot deployment hit error 0x80004005 — a documented build-dependent known issue, resolved by KB5065789 (25H2, build >= 26200.6725), KB5065426 (24H2, build >= 26100.6584), and KB5070312 (23H2, build >= 22631.6276). Knowledge source: docs/agent/autopilot-ztd-diagnostics.md (error-code map).",
|
||||
"description": "A Microsoft Entra hybrid join Autopilot deployment hit error 0x80004005 — a documented build-dependent known issue, resolved by KB5065789 (25H2, build >= 26200.6725), KB5065426 (24H2, build >= 26100.6584), and KB5070312 (23H2, build >= 22631.6276).",
|
||||
"severity": "high",
|
||||
"category": "enrollment",
|
||||
"version": "1.1.0",
|
||||
|
||||
+4
-4
@@ -1541,7 +1541,7 @@
|
||||
{
|
||||
"ruleId": "ANALYZE-DEV-007",
|
||||
"title": "System Clock Skew — TPM Attestation and ESP Timeout Risk",
|
||||
"description": "The device's real-time clock deviates from NTP by more than two minutes. Microsoft documents that a clock off by several minutes causes TPM attestation errors (self-deploying/pre-provisioning) and ESP timeouts. Knowledge source: docs/agent/autopilot-ztd-diagnostics.md.",
|
||||
"description": "The device's real-time clock deviates from NTP by more than two minutes. Microsoft documents that a clock off by several minutes causes TPM attestation errors (self-deploying/pre-provisioning) and ESP timeouts.",
|
||||
"severity": "warning",
|
||||
"category": "device",
|
||||
"version": "1.1.0",
|
||||
@@ -1987,7 +1987,7 @@
|
||||
{
|
||||
"ruleId": "ANALYZE-ENRL-003",
|
||||
"title": "TPM Attestation Failure — Known Error Code",
|
||||
"description": "A TPM-attestation-related error code documented in the Microsoft Autopilot known-issues list was observed (0x800705B4, 0x801C03EA, 0x81039001, 0x81039023, 0x81039024, 0x80070490). These block self-deploying and pre-provisioning deployments during 'Securing your hardware'. Observability: attestation precedes MDM enrollment, so in the normal deployment model (agent installed via IME after enrollment) these codes are rarely visible — the rule primarily serves bootstrap-token deployments. See docs/agent/autopilot-ztd-diagnostics.md (Observability section).",
|
||||
"description": "A TPM-attestation-related error code documented in the Microsoft Autopilot known-issues list was observed (0x800705B4, 0x801C03EA, 0x81039001, 0x81039023, 0x81039024, 0x80070490). These block self-deploying and pre-provisioning deployments during 'Securing your hardware'. Observability: attestation precedes MDM enrollment, so in the normal deployment model (agent installed via IME after enrollment) these codes are rarely visible — the rule primarily serves bootstrap-token deployments.",
|
||||
"severity": "high",
|
||||
"category": "enrollment",
|
||||
"version": "1.1.0",
|
||||
@@ -2077,7 +2077,7 @@
|
||||
{
|
||||
"ruleId": "ANALYZE-ENRL-004",
|
||||
"title": "MDM Enrollment Blocked — Known Error Code",
|
||||
"description": "An enrollment-blocking error code documented in the Microsoft Autopilot known-issues/troubleshooting pages was observed (0x80180014, 0x80180018, 0xC1036501, 0x801C03F3, 0x80070774). Each has a specific, documented cause and fix. Observability: a blocked MDM enrollment normally means no agent gets installed at all (IME install happens after enrollment) — this rule can realistically only fire for bootstrap-token deployments (pre-MDM reporting) or mixed retry scenarios. See docs/agent/autopilot-ztd-diagnostics.md (Observability section).",
|
||||
"description": "An enrollment-blocking error code documented in the Microsoft Autopilot known-issues/troubleshooting pages was observed (0x80180014, 0x80180018, 0xC1036501, 0x801C03F3, 0x80070774). Each has a specific, documented cause and fix. Observability: a blocked MDM enrollment normally means no agent gets installed at all (IME install happens after enrollment) — this rule can realistically only fire for bootstrap-token deployments (pre-MDM reporting) or mixed retry scenarios.",
|
||||
"severity": "high",
|
||||
"category": "enrollment",
|
||||
"version": "1.1.0",
|
||||
@@ -2164,7 +2164,7 @@
|
||||
{
|
||||
"ruleId": "ANALYZE-ENRL-005",
|
||||
"title": "Hybrid Join Timeout 0x80004005 — Fixed by Windows Update",
|
||||
"description": "A Microsoft Entra hybrid join Autopilot deployment hit error 0x80004005 — a documented build-dependent known issue, resolved by KB5065789 (25H2, build >= 26200.6725), KB5065426 (24H2, build >= 26100.6584), and KB5070312 (23H2, build >= 22631.6276). Knowledge source: docs/agent/autopilot-ztd-diagnostics.md (error-code map).",
|
||||
"description": "A Microsoft Entra hybrid join Autopilot deployment hit error 0x80004005 — a documented build-dependent known issue, resolved by KB5065789 (25H2, build >= 26200.6725), KB5065426 (24H2, build >= 26100.6584), and KB5070312 (23H2, build >= 22631.6276).",
|
||||
"severity": "high",
|
||||
"category": "enrollment",
|
||||
"version": "1.1.0",
|
||||
|
||||
+1
-1
@@ -13,7 +13,7 @@ using Xunit;
|
||||
namespace AutopilotMonitor.Agent.V2.Core.Tests.Monitoring.SystemSignals
|
||||
{
|
||||
/// <summary>
|
||||
/// Autopilot-channel error backfill (docs/agent/autopilot-ztd-diagnostics.md,
|
||||
/// Autopilot-channel error backfill (internal/docs/agent/autopilot-ztd-diagnostics.md,
|
||||
/// Observability section): TPM/ZTD errors precede the agent's IME install, so a
|
||||
/// retry-then-success attempt leaves them in the event log with nobody watching.
|
||||
/// The backfill replays them as backfilled=true modern_deployment_error events; the
|
||||
|
||||
+1
-1
@@ -7,7 +7,7 @@ namespace AutopilotMonitor.Agent.V2.Core.Tests.Telemetry.DeviceInfo;
|
||||
/// <summary>
|
||||
/// Pins the ZTD verdict mapping (autopilot_profile_missing evidence). The event-ID semantics
|
||||
/// come from the Microsoft Autopilot troubleshooting FAQ (see
|
||||
/// docs/agent/autopilot-ztd-diagnostics.md): error IDs are authoritative and must win over
|
||||
/// internal/docs/agent/autopilot-ztd-diagnostics.md): error IDs are authoritative and must win over
|
||||
/// the Info-flow reconstruction, and the priority order within the errors is
|
||||
/// registration (807) → identity mismatch (908) → deleted (809) → not assigned (815).
|
||||
/// </summary>
|
||||
|
||||
+2
-2
@@ -22,7 +22,7 @@ namespace AutopilotMonitor.Agent.V2.Core.Monitoring.Enrollment.SystemSignals
|
||||
/// The Autopilot channel's documented event-ID semantics (100 waiting-for-profile,
|
||||
/// 153/160/161/163/164 profile-download flow, 171/172 TPM attestation, 807/809/815/908
|
||||
/// ZTD registration/assignment errors) are catalogued with sources in
|
||||
/// docs/agent/autopilot-ztd-diagnostics.md — re-check that doc's sources periodically.
|
||||
/// internal/docs/agent/autopilot-ztd-diagnostics.md — re-check that doc's sources periodically.
|
||||
/// The continuous watcher here filters to Level ≤ 3; <see cref="Telemetry.DeviceInfo.ZtdEvidence"/>
|
||||
/// runs a targeted all-level one-shot query on the profile-missing path instead.
|
||||
/// </summary>
|
||||
@@ -50,7 +50,7 @@ namespace AutopilotMonitor.Agent.V2.Core.Monitoring.Enrollment.SystemSignals
|
||||
/// registration/assignment errors (171/172, 807/809/815/908, all Level 1-2) happen
|
||||
/// BEFORE the agent is installed in the normal IME deployment model — a failed
|
||||
/// attempt that was resolved by a retry would stay invisible without replaying the
|
||||
/// channel history at startup. See docs/agent/autopilot-ztd-diagnostics.md
|
||||
/// channel history at startup. See internal/docs/agent/autopilot-ztd-diagnostics.md
|
||||
/// (Observability section). Fixed 4h lookback: OOBE start → agent install is
|
||||
/// normally well under that, and the RecordId watermark below prevents replays
|
||||
/// from re-emitting on every agent restart.
|
||||
|
||||
+2
-2
@@ -292,7 +292,7 @@ namespace AutopilotMonitor.Agent.V2.Core.Monitoring.Telemetry.DeviceInfo
|
||||
{
|
||||
// Second diagnostic surface (independent of AutopilotPolicyCache): Microsoft's
|
||||
// documented Diagnostics\Autopilot key (IsAutopilotDisabled, TenantMatched,
|
||||
// CloudAssignedTenantDomain/Id, ...). See docs/agent/autopilot-ztd-diagnostics.md.
|
||||
// CloudAssignedTenantDomain/Id, ...). See internal/docs/agent/autopilot-ztd-diagnostics.md.
|
||||
diagnosticsRegistry = ZtdEvidence.ReadDiagnosticsRegistry(_logger);
|
||||
if (diagnosticsRegistry != null)
|
||||
data["diagnosticsRegistry"] = diagnosticsRegistry;
|
||||
@@ -312,7 +312,7 @@ namespace AutopilotMonitor.Agent.V2.Core.Monitoring.Telemetry.DeviceInfo
|
||||
// runs as a standard Entra join and would otherwise look like a regular
|
||||
// Autopilot session — surface the edge case explicitly, with a ZTD verdict
|
||||
// backed by the ModernDeployment Autopilot event log plus an endpoint probe
|
||||
// (see docs/agent/autopilot-ztd-diagnostics.md for the evidence model).
|
||||
// (see internal/docs/agent/autopilot-ztd-diagnostics.md for the evidence model).
|
||||
if (!isDevicePreparation && IsAutopilotProfileMissing(data))
|
||||
{
|
||||
// On Windows 365 Cloud PCs a missing profile is the expected state — Cloud PCs
|
||||
|
||||
+1
-1
@@ -26,7 +26,7 @@ namespace AutopilotMonitor.Agent.V2.Core.Monitoring.Telemetry.DeviceInfo
|
||||
/// authoritative witness for reachability at check time.</item>
|
||||
/// </list>
|
||||
/// Event-ID meanings, registry values, and endpoint list are documented (with sources and
|
||||
/// a re-check RSS feed) in docs/agent/autopilot-ztd-diagnostics.md — keep both in sync.
|
||||
/// a re-check RSS feed) in internal/docs/agent/autopilot-ztd-diagnostics.md — keep both in sync.
|
||||
/// </summary>
|
||||
internal static class ZtdEvidence
|
||||
{
|
||||
|
||||
@@ -154,7 +154,7 @@ public class MsiProductVersionReaderTests
|
||||
|
||||
/// <summary>
|
||||
/// Sweeps every <c>ime-files/<version>/IntuneWindowsAgent.msi</c> in the repo checkout
|
||||
/// (gitignored decompile scratch, populated by /ime-decompile) and asserts the reader
|
||||
/// (gitignored local scratch, populated by an operator tool) and asserts the reader
|
||||
/// returns exactly the folder's version. Silently passes when the folder is absent (CI)
|
||||
/// — the synthetic tests above carry the contract there; this one pins the string-pool
|
||||
/// id convention against Microsoft's real packages.
|
||||
|
||||
@@ -7,7 +7,7 @@ using Moq;
|
||||
namespace AutopilotMonitor.Functions.Tests;
|
||||
|
||||
/// <summary>
|
||||
/// evaluateOn interim-trigger semantics (docs/rules/analyze-rule-triggers.md):
|
||||
/// evaluateOn interim-trigger semantics (internal/docs/rules/analyze-rule-triggers.md):
|
||||
/// trigger grammar + matching helpers, the engine's interim rule filter, the
|
||||
/// RuleResult update/finalize/resolve lifecycle replacing the permanent dedupe
|
||||
/// freeze, KO suppression on interim runs, and the ingest-side trigger registry.
|
||||
|
||||
@@ -9,7 +9,7 @@ using AutopilotMonitor.Shared.Models;
|
||||
namespace AutopilotMonitor.Functions.Tests;
|
||||
|
||||
/// <summary>
|
||||
/// Verdict-calibration drift radar (docs/backend/verdict-calibration.md): the three finding
|
||||
/// Verdict-calibration drift radar (internal/docs/backend/verdict-calibration.md): the three finding
|
||||
/// kinds, the small-n / lift / Wilson gates, re-arm, and the tracker round-trip.
|
||||
/// </summary>
|
||||
public class VerdictCalibrationRadarTests
|
||||
|
||||
@@ -9,7 +9,7 @@ using AutopilotMonitor.Shared.Models;
|
||||
namespace AutopilotMonitor.Functions.Tests;
|
||||
|
||||
/// <summary>
|
||||
/// Verdict calibration aggregate (docs/backend/verdict-calibration.md): the pure bucketing core,
|
||||
/// Verdict calibration aggregate (internal/docs/backend/verdict-calibration.md): the pure bucketing core,
|
||||
/// the persistence round-trip and the matrix arithmetic of the read endpoint.
|
||||
/// </summary>
|
||||
public class VerdictCalibrationTests
|
||||
|
||||
@@ -7,7 +7,7 @@ using AutopilotMonitor.Shared.Models;
|
||||
namespace AutopilotMonitor.Functions.Tests;
|
||||
|
||||
/// <summary>
|
||||
/// Verdict calibration instrumentation (docs/backend/verdict-calibration.md): every status write
|
||||
/// Verdict calibration instrumentation (internal/docs/backend/verdict-calibration.md): every status write
|
||||
/// declares its origin, overriding a prior verdict preserves it, the classifier names the rule
|
||||
/// behind each verdict, and pre-instrumentation rows derive an honest path from their literals.
|
||||
/// </summary>
|
||||
|
||||
+1
-1
@@ -422,7 +422,7 @@ namespace AutopilotMonitor.Functions.DataAccess.TableStorage
|
||||
};
|
||||
}
|
||||
|
||||
// ===== VERDICT CALIBRATION DRIFT (docs/backend/verdict-calibration.md) =====
|
||||
// ===== VERDICT CALIBRATION DRIFT (internal/docs/backend/verdict-calibration.md) =====
|
||||
|
||||
internal const string VerdictCalibrationRowKeyPrefix = "verdictcalibration|";
|
||||
|
||||
|
||||
+1
-1
@@ -10,7 +10,7 @@ using Microsoft.Extensions.Logging;
|
||||
namespace AutopilotMonitor.Functions.Functions.Metrics
|
||||
{
|
||||
/// <summary>
|
||||
/// Operator-only verdict calibration matrix (docs/backend/verdict-calibration.md): per verdict
|
||||
/// Operator-only verdict calibration matrix (internal/docs/backend/verdict-calibration.md): per verdict
|
||||
/// path, how many sessions it produced in the window, its share, how often it was overridden
|
||||
/// (admin / late completion / other), the 7-day re-enrollment proxy, and a 7d-vs-28d trend.
|
||||
/// Platform scope ("global" partition) or one tenant via ?tenantId=. Serves the admin
|
||||
|
||||
@@ -27,7 +27,7 @@ public class VerdictCalibrationFinding
|
||||
|
||||
/// <summary>
|
||||
/// Pure drift detection over one tenant's verdict-calibration daily rows
|
||||
/// (docs/backend/verdict-calibration.md) — the same statistics as the rule-regression radar
|
||||
/// (internal/docs/backend/verdict-calibration.md) — the same statistics as the rule-regression radar
|
||||
/// (7-day window vs the prior 28 days, lift ≥2×, Wilson-separated via
|
||||
/// <see cref="MetricsMath.RateIncreaseSeparated"/>, small-n gates) applied to three questions:
|
||||
/// <list type="bullet">
|
||||
|
||||
@@ -71,7 +71,7 @@ builder.Services
|
||||
// 2026-08-23: solved differently — RequestTelemetryMiddleware sets SamplingPercentage=100 on its
|
||||
// own item (per-item sampling bypass), and the host duplicate is removed by a workspace DCR
|
||||
// transformation on AppRequests (keeps rows with Properties.Source=='WorkerMiddleware' plus
|
||||
// non-HTTP host rows with empty Url, i.e. timer/queue invocations). See docs/backend/telemetry-ingest-shaping.md.
|
||||
// non-HTTP host rows with empty Url, i.e. timer/queue invocations). See internal/docs/backend/telemetry-ingest-shaping.md.
|
||||
|
||||
// Drop successful Azure Storage dependencies (Table/Queue/Blob) from the worker telemetry
|
||||
// pipeline to curb AppDependencies ingestion cost — this backend is storage-I/O heavy and those
|
||||
@@ -267,7 +267,7 @@ builder.Services.AddSingleton<BackendBuildInfo>();
|
||||
builder.Services.AddSingleton<GatherRuleService>();
|
||||
builder.Services.AddSingleton<AnalyzeRuleService>();
|
||||
// Cached per-tenant evaluateOn trigger sets for the ingest-side interim analyze enqueue
|
||||
// (docs/rules/analyze-rule-triggers.md). Singleton so the 5-min TTL cache is shared.
|
||||
// (internal/docs/rules/analyze-rule-triggers.md). Singleton so the 5-min TTL cache is shared.
|
||||
builder.Services.AddSingleton<AutopilotMonitor.Functions.Services.Analyze.InterimTriggerRegistry>();
|
||||
builder.Services.AddSingleton<ImeLogPatternService>();
|
||||
builder.Services.AddHttpClient<GitHubRuleRepository>()
|
||||
|
||||
+1
-1
@@ -25,7 +25,7 @@ namespace AutopilotMonitor.Functions.Services.Analyze
|
||||
/// to match the legacy <c>ReanalyzeAfterVulnerabilityEmitAsync</c> behavior (avoids
|
||||
/// double-counting eval rows on already-stat'd rules).</item>
|
||||
/// <item><c>whiteglove_sealed</c> / <c>interim_trigger</c> — evaluateOn-filtered interim
|
||||
/// runs before the session is terminal (docs/rules/analyze-rule-triggers.md): persist
|
||||
/// runs before the session is terminal (internal/docs/rules/analyze-rule-triggers.md): persist
|
||||
/// results + resolved rows, SignalR + channel notifications (NotifiedAt-deduped), skip
|
||||
/// platform-stat and rule-fire stats; the engine suppresses the KO escalation.</item>
|
||||
/// </list>
|
||||
|
||||
@@ -17,7 +17,7 @@ namespace AutopilotMonitor.Functions.Services.Analyze
|
||||
/// (no interim enqueue — behavior degrades to the terminal-only baseline, never throws
|
||||
/// into the ingest path). The engine re-checks rule scope anyway, so registry staleness
|
||||
/// can only cost a wasted (cheap, rule-less) queue round-trip, never a wrong evaluation.
|
||||
/// See docs/rules/analyze-rule-triggers.md.
|
||||
/// See internal/docs/rules/analyze-rule-triggers.md.
|
||||
/// </summary>
|
||||
public class InterimTriggerRegistry
|
||||
{
|
||||
|
||||
@@ -7,7 +7,7 @@ namespace AutopilotMonitor.Functions.Services
|
||||
{
|
||||
/// <summary>
|
||||
/// Regression tripwire for the agent's CMTrace time resolution (per-line self-anchoring,
|
||||
/// docs/agent/cmtrace-time-resolution.md). Runs once per session on the terminal ingest
|
||||
/// internal/docs/agent/cmtrace-time-resolution.md). Runs once per session on the terminal ingest
|
||||
/// batch, over samples the counter reconcile collected in its existing partition scan.
|
||||
///
|
||||
/// Detection: diff = median(Δ_IME) − median(Δ_other), where Δ = ReceivedAt − OccurredUtc.
|
||||
@@ -39,7 +39,7 @@ namespace AutopilotMonitor.Functions.Services
|
||||
internal static class CmTraceSkewTripwire
|
||||
{
|
||||
// Same grid the agent anchors on and the same residual tolerance as its own grid
|
||||
// guard (docs/agent/cmtrace-time-resolution.md) — keep these in lockstep with the
|
||||
// guard (internal/docs/agent/cmtrace-time-resolution.md) — keep these in lockstep with the
|
||||
// agent so a future anchoring change stays traceable to this detector.
|
||||
internal const int GridMinutes = 15;
|
||||
internal const double ResidualToleranceMinutes = 2.0;
|
||||
|
||||
@@ -554,8 +554,8 @@ namespace AutopilotMonitor.Functions.Services
|
||||
/// </summary>
|
||||
/// <summary>
|
||||
/// Fire-and-forget enqueue of the installer-archive job for an IME version; the
|
||||
/// producer is fail-soft (a lost message means "not auto-archived", the /ime-decompile
|
||||
/// skill's blob backfill is the manual fallback).
|
||||
/// producer is fail-soft (a lost message means "not auto-archived", the operator-side
|
||||
/// blob backfill is the manual fallback).
|
||||
/// </summary>
|
||||
private Task EnqueueImeMsiArchiveAsync(
|
||||
string imeVersion, string? msiDownloadUrl, string? msiMatchedBy, IngestEventsRequest request)
|
||||
|
||||
+2
-2
@@ -17,7 +17,7 @@ namespace AutopilotMonitor.Functions.Services.Ime
|
||||
/// <para>
|
||||
/// <b>Fault tolerance:</b> queue-side exceptions are logged but never rethrown — the
|
||||
/// caller is the ingest path's fire-and-forget continuation. A missed enqueue means the
|
||||
/// version's installer is not auto-archived; the /ime-decompile skill's blob backfill
|
||||
/// version's installer is not auto-archived; the operator-side blob backfill
|
||||
/// is the manual fallback.
|
||||
/// </para>
|
||||
/// </summary>
|
||||
@@ -72,7 +72,7 @@ namespace AutopilotMonitor.Functions.Services.Ime
|
||||
{
|
||||
// Fail-soft — ingest continuation. Skill-side blob backfill is the fallback.
|
||||
_logger.LogWarning(ex,
|
||||
"ImeMsiArchive enqueue failed (version={Version}) — /ime-decompile blob backfill is the fallback",
|
||||
"ImeMsiArchive enqueue failed (version={Version}) — operator-side blob backfill is the fallback",
|
||||
envelope.Version);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -201,7 +201,7 @@ namespace AutopilotMonitor.Functions.Services.Ime
|
||||
/// (<c>msiMatchedBy=productVersion</c>, allowlisted), the version is not archived (and
|
||||
/// not permanently rejected as a bad version string), and the last archive activity is
|
||||
/// older than <see cref="RequeueBackoff"/> — or there never was any (versions sighted
|
||||
/// before the archiver existed, or archived by hand via the /ime-decompile skill; the
|
||||
/// before the archiver existed, or archived by hand by an operator; the
|
||||
/// 409 path then heals the row to Archived).
|
||||
/// </summary>
|
||||
public static bool ShouldRequeueOnSighting(
|
||||
|
||||
+1
-1
@@ -11,7 +11,7 @@ using Microsoft.Extensions.Logging;
|
||||
namespace AutopilotMonitor.Functions.Services
|
||||
{
|
||||
/// <summary>
|
||||
/// Partial: verdict-calibration daily aggregates (docs/backend/verdict-calibration.md) — the
|
||||
/// Partial: verdict-calibration daily aggregates (internal/docs/backend/verdict-calibration.md) — the
|
||||
/// operator thermometer for the rule classifier: which verdict path produced how many
|
||||
/// sessions per day, how many of those were later overridden (admin / late completion /
|
||||
/// other), and how many devices re-enrolled within 7 days.
|
||||
|
||||
+1
-1
@@ -11,7 +11,7 @@ using Microsoft.Extensions.Logging;
|
||||
namespace AutopilotMonitor.Functions.Services
|
||||
{
|
||||
/// <summary>
|
||||
/// Partial: verdict-calibration drift radar (docs/backend/verdict-calibration.md). Same
|
||||
/// Partial: verdict-calibration drift radar (internal/docs/backend/verdict-calibration.md). Same
|
||||
/// episode/tracker pattern as the rule-regression radar, over the verdict-calibration daily
|
||||
/// rows (<see cref="VerdictCalibrationRadar"/>). Operator-only: fires a VerdictCalibrationDrift
|
||||
/// ops event once per episode and surfaces the episode in the calibration endpoint's
|
||||
|
||||
@@ -737,7 +737,7 @@ namespace AutopilotMonitor.Functions.Services
|
||||
});
|
||||
|
||||
/// <summary>
|
||||
/// Verdict-calibration drift radar (docs/backend/verdict-calibration.md): a verdict
|
||||
/// Verdict-calibration drift radar (internal/docs/backend/verdict-calibration.md): a verdict
|
||||
/// path's share of sessions doubled over its 28d baseline, the silence share
|
||||
/// (sweep+maxlife — agent went quiet, backend had to decide) doubled, or the pure
|
||||
/// fallthrough rule r6 decides ≥20 % of classifier verdicts. Operator-only diagnostic —
|
||||
@@ -867,7 +867,7 @@ namespace AutopilotMonitor.Functions.Services
|
||||
/// The CMTrace time-skew tripwire fired: a terminal session's IME-derived event
|
||||
/// timestamps diverge from its other events by a clean 15-minute-grid multiple —
|
||||
/// the signature of a timezone mis-conversion the per-line self-anchoring
|
||||
/// (docs/agent/cmtrace-time-resolution.md) failed to catch. Goal state: this event
|
||||
/// (internal/docs/agent/cmtrace-time-resolution.md) failed to catch. Goal state: this event
|
||||
/// NEVER fires; any occurrence is a real anchoring regression or a detector bug,
|
||||
/// both actionable. Emitted by <see cref="EventIngestProcessor"/> once per session
|
||||
/// (gated on the terminal status transition). The message carries the full numbers
|
||||
|
||||
@@ -38,7 +38,7 @@ namespace AutopilotMonitor.Functions.Services
|
||||
|
||||
/// <summary>
|
||||
/// Evaluates analyze rules against the full session event stream under an explicit run
|
||||
/// context (see <see cref="AnalyzeRunContext"/> and docs/rules/analyze-rule-triggers.md).
|
||||
/// context (see <see cref="AnalyzeRunContext"/> and internal/docs/rules/analyze-rule-triggers.md).
|
||||
/// <para>
|
||||
/// <b>Rule scope:</b> Terminal/Reanalyze runs evaluate every active rule; Interim runs
|
||||
/// evaluate only rules whose <c>evaluateOn</c> matches the run's trigger.
|
||||
@@ -443,7 +443,7 @@ namespace AutopilotMonitor.Functions.Services
|
||||
|
||||
/// <summary>
|
||||
/// Run context for <see cref="RuleEngine.AnalyzeSessionAsync(string, string, AnalyzeRunContext)"/>.
|
||||
/// Built from the queue envelope's Reason (see docs/rules/analyze-rule-triggers.md).
|
||||
/// Built from the queue envelope's Reason (see internal/docs/rules/analyze-rule-triggers.md).
|
||||
/// </summary>
|
||||
public sealed class AnalyzeRunContext
|
||||
{
|
||||
|
||||
@@ -210,7 +210,7 @@ namespace AutopilotMonitor.Functions.Services
|
||||
if (!string.IsNullOrEmpty(adminMarkedAction))
|
||||
indexEntity["AdminMarkedAction"] = adminMarkedAction;
|
||||
|
||||
// Verdict calibration attribution (docs/backend/verdict-calibration.md): the aggregate
|
||||
// Verdict calibration attribution (internal/docs/backend/verdict-calibration.md): the aggregate
|
||||
// sweep reads these off the session row, and the index mirror must not drop them on a
|
||||
// StartedAt-shift full upsert.
|
||||
foreach (var col in new[] { "VerdictPath", "PriorStatus", "PriorVerdictPath" })
|
||||
|
||||
+1
-1
@@ -11,7 +11,7 @@ using Microsoft.Extensions.Logging;
|
||||
namespace AutopilotMonitor.Functions.Services
|
||||
{
|
||||
/// <summary>
|
||||
/// Partial: verdict-calibration daily aggregate persistence (docs/backend/verdict-calibration.md).
|
||||
/// Partial: verdict-calibration daily aggregate persistence (internal/docs/backend/verdict-calibration.md).
|
||||
/// VerdictCalibrationAggregates: PK=TenantId ("global" for the cross-tenant row),
|
||||
/// RK="{yyyy-MM-dd}" — one row per (tenant, StartedAt date) holding the per-verdict-path
|
||||
/// buckets as a JSON column. Recomputed whole (Replace) by the maintenance sweep, 180d
|
||||
|
||||
@@ -81,7 +81,7 @@ describe('first-page defaults', () => {
|
||||
|
||||
it('a FILTERED get_session_events read stays complete: no projection, no omission marker', async () => {
|
||||
// Usage telemetry: filtered reads target specific events and want their payload about half the
|
||||
// time (and the ime-decompile skill reads data.msiDownloadUrl after an eventType filter).
|
||||
// time (and operator tooling reads data.msiDownloadUrl after an eventType filter).
|
||||
const handler = handlerFor('get_session_events');
|
||||
const { urls } = stubFetchCapture();
|
||||
|
||||
|
||||
@@ -226,7 +226,7 @@ export interface AnalyzeRule {
|
||||
provenance?: string;
|
||||
/** Rule trigger type: "single" (matches individual events) or "correlation" (combines multiple event types) Both types run at the same time during analysis - this field is organizational/descriptive */
|
||||
trigger: string;
|
||||
/** When this rule is evaluated. Null/empty = ["enrollment_end"] — the historical terminal-only behavior. Additional interim triggers let a rule fire before the session is terminal: "whiteglove_sealed" (first genuine whiteglove_complete seal) and "on_event:<eventType>" (an ingest batch contained that event type). Interim runs suppress the KO path and record no stats — see AnalyzeRuleTriggers and docs/rules/analyze-rule-triggers.md. */
|
||||
/** When this rule is evaluated. Null/empty = ["enrollment_end"] — the historical terminal-only behavior. Additional interim triggers let a rule fire before the session is terminal: "whiteglove_sealed" (first genuine whiteglove_complete seal) and "on_event:<eventType>" (an ingest batch contained that event type). Interim runs suppress the KO path and record no stats — see AnalyzeRuleTriggers and internal/docs/rules/analyze-rule-triggers.md. */
|
||||
evaluateOn?: string[];
|
||||
/** Optional device-fact gates evaluated BEFORE conditions. ALL preconditions must pass; if any fails the rule is silently skipped — no result, no UI card. Used to filter out hardware/OS profiles where a rule does not apply (e.g. "skip on virtual machines"). */
|
||||
preconditions: RulePrecondition[];
|
||||
|
||||
@@ -403,7 +403,7 @@ function lintAnalyzeRule(rule: Record<string, unknown>): ValidationFinding[] {
|
||||
});
|
||||
}
|
||||
|
||||
// ── evaluateOn (interim evaluation) lints — docs/rules/analyze-rule-triggers.md ──
|
||||
// ── evaluateOn (interim evaluation) lints — internal/docs/rules/analyze-rule-triggers.md ──
|
||||
const evaluateOn = (Array.isArray(rule.evaluateOn) ? rule.evaluateOn : []).filter(
|
||||
(t): t is string => typeof t === 'string',
|
||||
);
|
||||
|
||||
@@ -982,7 +982,7 @@ export function registerAdminTools(server: McpServer, ga: boolean, strictGa: boo
|
||||
);
|
||||
|
||||
// Tool: get_verdict_calibration — platform scope (GA + Global Reader). Operator diagnostics for
|
||||
// the session-verdict classifier (docs/backend/verdict-calibration.md) — never a customer surface.
|
||||
// the session-verdict classifier (internal/docs/backend/verdict-calibration.md) — never a customer surface.
|
||||
if (ga) server.registerTool(
|
||||
'get_verdict_calibration',
|
||||
{
|
||||
|
||||
@@ -1063,7 +1063,7 @@ namespace AutopilotMonitor.Shared
|
||||
// VerdictCalibrationAggregates: PK=TenantId (or "global"), RK="{yyyy-MM-dd}" — daily
|
||||
// per-verdict-path buckets (count, derived, re-enrolled-within-7d, overridden-by-admin/
|
||||
// late-completion) the operator calibration matrix + drift radar read
|
||||
// (docs/backend/verdict-calibration.md). Additive across days; 180d retention; regenerable.
|
||||
// (internal/docs/backend/verdict-calibration.md). Additive across days; 180d retention; regenerable.
|
||||
public const string VerdictCalibrationAggregates = "VerdictCalibrationAggregates";
|
||||
|
||||
// Configuration tables
|
||||
|
||||
@@ -148,7 +148,7 @@ namespace AutopilotMonitor.Shared.DataAccess
|
||||
Task DeleteDeviceJourneyAggregateAsync(string tenantId, string dateKey);
|
||||
Task<int> DeleteDeviceJourneyAggregatesOlderThanAsync(DateTime cutoffDate);
|
||||
|
||||
// --- Verdict calibration (docs/backend/verdict-calibration.md) ---
|
||||
// --- Verdict calibration (internal/docs/backend/verdict-calibration.md) ---
|
||||
Task<bool> SaveVerdictCalibrationAggregateAsync(VerdictCalibrationDailyAggregate aggregate);
|
||||
/// <summary>Daily verdict-path rows of one tenant partition ("global" allowed), inclusive date range; counts are additive across days.</summary>
|
||||
Task<List<VerdictCalibrationDailyAggregate>> GetVerdictCalibrationAggregatesAsync(string tenantId, DateTime startDate, DateTime endDate);
|
||||
|
||||
@@ -13,7 +13,7 @@ namespace AutopilotMonitor.Shared.Models
|
||||
/// is the incremental rerun (no stats), and the interim reasons
|
||||
/// <c>whiteglove_sealed</c> / <c>interim_trigger</c> run the evaluateOn-filtered
|
||||
/// interim path (notify yes, KO + stats suppressed) — see
|
||||
/// docs/rules/analyze-rule-triggers.md.
|
||||
/// internal/docs/rules/analyze-rule-triggers.md.
|
||||
/// </para>
|
||||
/// </summary>
|
||||
public sealed class AnalyzeOnEnrollmentEndEnvelope
|
||||
|
||||
@@ -2,7 +2,7 @@ using System;
|
||||
|
||||
namespace AutopilotMonitor.Shared.Models
|
||||
{
|
||||
/// <summary>Kinds of verdict-calibration drift the radar detects (docs/backend/verdict-calibration.md).</summary>
|
||||
/// <summary>Kinds of verdict-calibration drift the radar detects (internal/docs/backend/verdict-calibration.md).</summary>
|
||||
public static class VerdictCalibrationAlertKinds
|
||||
{
|
||||
/// <summary>One verdict path's share of all sessions rose ≥2× over its 28-day baseline (Wilson-separated).</summary>
|
||||
|
||||
+1
-1
@@ -43,7 +43,7 @@ namespace AutopilotMonitor.Shared.Models
|
||||
|
||||
/// <summary>
|
||||
/// Daily verdict-calibration rollup for one (tenant, date) — the operator's thermometer for the
|
||||
/// rule classifier (docs/backend/verdict-calibration.md). A session buckets on its StartedAt
|
||||
/// rule classifier (internal/docs/backend/verdict-calibration.md). A session buckets on its StartedAt
|
||||
/// date (same convention as every other daily aggregate; the rolling sweep re-buckets
|
||||
/// late-terminating sessions idempotently). Every status is counted — non-terminal rows are
|
||||
/// part of the picture (a growing Stalled share is a signal) — and
|
||||
|
||||
@@ -81,7 +81,7 @@ namespace AutopilotMonitor.Shared.Models
|
||||
/// is terminal: <c>"whiteglove_sealed"</c> (first genuine whiteglove_complete seal) and
|
||||
/// <c>"on_event:<eventType>"</c> (an ingest batch contained that event type).
|
||||
/// Interim runs suppress the KO path and record no stats — see
|
||||
/// <see cref="AnalyzeRuleTriggers"/> and docs/rules/analyze-rule-triggers.md.
|
||||
/// <see cref="AnalyzeRuleTriggers"/> and internal/docs/rules/analyze-rule-triggers.md.
|
||||
/// </summary>
|
||||
public List<string>? EvaluateOn { get; set; }
|
||||
|
||||
|
||||
@@ -9,7 +9,7 @@ namespace AutopilotMonitor.Shared.Models
|
||||
/// consumer (engine rule filter, ingest trigger registry, CRUD validation) shares so
|
||||
/// the matching semantics cannot drift apart. An absent/empty EvaluateOn list means
|
||||
/// <see cref="EnrollmentEnd"/> only — the historical terminal-only behavior, bit-for-bit.
|
||||
/// See docs/rules/analyze-rule-triggers.md for the full design.
|
||||
/// See internal/docs/rules/analyze-rule-triggers.md for the full design.
|
||||
/// </summary>
|
||||
public static class AnalyzeRuleTriggers
|
||||
{
|
||||
|
||||
@@ -77,7 +77,7 @@ namespace AutopilotMonitor.Shared.Models
|
||||
|
||||
// ===== EVALUATION LIFECYCLE (evaluateOn interim triggers) =====
|
||||
// All nullable: rows written before the feature read as final/terminal legacy results.
|
||||
// See docs/rules/analyze-rule-triggers.md.
|
||||
// See internal/docs/rules/analyze-rule-triggers.md.
|
||||
|
||||
/// <summary>
|
||||
/// When this finding FIRST fired for the session (stable across interim refreshes and
|
||||
|
||||
@@ -6,7 +6,7 @@ namespace AutopilotMonitor.Shared.Models
|
||||
/// Machine-readable vocabulary for <see cref="SessionSummary.VerdictPath"/> — WHICH code path
|
||||
/// produced the session's current <see cref="SessionStatus"/>. Format is
|
||||
/// <c>origin:detail</c>, lower-case, stable: values are a persisted contract that the verdict
|
||||
/// calibration aggregate (docs/backend/verdict-calibration.md) counts over days and tenants, so
|
||||
/// calibration aggregate (internal/docs/backend/verdict-calibration.md) counts over days and tenants, so
|
||||
/// entries are append-only — never rename or reuse one. Unlike <c>FailureSource</c> (written
|
||||
/// only on Failed) the path is stamped for every status write, so a sweep "Incomplete", a late
|
||||
/// agent upgrade and an admin override are all countable.
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* Pure helpers for the Verdict Calibration matrix (docs/backend/verdict-calibration.md).
|
||||
* Pure helpers for the Verdict Calibration matrix (internal/docs/backend/verdict-calibration.md).
|
||||
* Wire shape mirrors GetVerdictCalibrationFunction / VerdictCalibrationResponse (camelCase).
|
||||
*/
|
||||
|
||||
|
||||
@@ -255,7 +255,7 @@ export function GlobalSidebar({ children }: { children: ReactNode }) {
|
||||
// drains it all at once (p95 ~0.9s but a tail reaching 150s+), so the navigation the user
|
||||
// actually wants is stuck behind it and the portal freezes. Every route here is a client component behind
|
||||
// ProtectedRoute that fetches its own data after mount, so the prefetched RSC payload
|
||||
// buys nothing. See docs/web/portal-navigation-prefetch.md.
|
||||
// buys nothing. See internal/docs/web/portal-navigation-prefetch.md.
|
||||
// --- Render a global nav link ---
|
||||
const renderGlobalItem = (item: NavItem, isGlobal = false) => {
|
||||
const active = isNavActive(item.href);
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
* CMTrace-derived events with how their UTC timestamp was produced — the raw local time
|
||||
* as written in the log, the applied UTC offset and its origin, the purely observational
|
||||
* per-file writer offset, and the clock-fallback markers. All values arrive as STRINGS
|
||||
* inside the event data payload (see docs/agent/cmtrace-time-resolution.md).
|
||||
* inside the event data payload (see internal/docs/agent/cmtrace-time-resolution.md).
|
||||
*
|
||||
* The timeline sorts by sequence — always, never by time (user guardrail). A displayed
|
||||
* time that steps BACKWARDS within that order is therefore information, not a rendering
|
||||
|
||||
@@ -1484,7 +1484,7 @@
|
||||
}
|
||||
},
|
||||
"optional": true,
|
||||
"doc": "When this rule is evaluated. Null/empty = [\"enrollment_end\"] — the historical terminal-only behavior. Additional interim triggers let a rule fire before the session is terminal: \"whiteglove_sealed\" (first genuine whiteglove_complete seal) and \"on_event:<eventType>\" (an ingest batch contained that event type). Interim runs suppress the KO path and record no stats — see AnalyzeRuleTriggers and docs/rules/analyze-rule-triggers.md."
|
||||
"doc": "When this rule is evaluated. Null/empty = [\"enrollment_end\"] — the historical terminal-only behavior. Additional interim triggers let a rule fire before the session is terminal: \"whiteglove_sealed\" (first genuine whiteglove_complete seal) and \"on_event:<eventType>\" (an ingest batch contained that event type). Interim runs suppress the KO path and record no stats — see AnalyzeRuleTriggers and internal/docs/rules/analyze-rule-triggers.md."
|
||||
},
|
||||
{
|
||||
"name": "preconditions",
|
||||
|
||||
@@ -226,7 +226,7 @@ export interface AnalyzeRule {
|
||||
provenance?: string;
|
||||
/** Rule trigger type: "single" (matches individual events) or "correlation" (combines multiple event types) Both types run at the same time during analysis - this field is organizational/descriptive */
|
||||
trigger: string;
|
||||
/** When this rule is evaluated. Null/empty = ["enrollment_end"] — the historical terminal-only behavior. Additional interim triggers let a rule fire before the session is terminal: "whiteglove_sealed" (first genuine whiteglove_complete seal) and "on_event:<eventType>" (an ingest batch contained that event type). Interim runs suppress the KO path and record no stats — see AnalyzeRuleTriggers and docs/rules/analyze-rule-triggers.md. */
|
||||
/** When this rule is evaluated. Null/empty = ["enrollment_end"] — the historical terminal-only behavior. Additional interim triggers let a rule fire before the session is terminal: "whiteglove_sealed" (first genuine whiteglove_complete seal) and "on_event:<eventType>" (an ingest batch contained that event type). Interim runs suppress the KO path and record no stats — see AnalyzeRuleTriggers and internal/docs/rules/analyze-rule-triggers.md. */
|
||||
evaluateOn?: string[];
|
||||
/** Optional device-fact gates evaluated BEFORE conditions. ALL preconditions must pass; if any fails the rule is silently skipped — no result, no UI card. Used to filter out hardware/OS profiles where a rule does not apply (e.g. "skip on virtual machines"). */
|
||||
preconditions: RulePrecondition[];
|
||||
|
||||
Reference in New Issue
Block a user