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:
Oliver Kieselbach
2026-09-04 02:18:20 +02:00
parent 186c151046
commit db020789e1
58 changed files with 125 additions and 220 deletions
+53 -10
View File
@@ -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
-136
View File
@@ -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
View File
@@ -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
}
+2 -2
View File
@@ -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
View File
@@ -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
View File
@@ -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
Submodule docs deleted from dd053d1068
Submodule
+1
Submodule internal added at aa32b61c50
+1 -1
View File
@@ -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",
+1 -1
View File
@@ -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",
+1 -1
View File
@@ -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",
+1 -1
View File
@@ -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
View File
@@ -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",
@@ -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
@@ -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>
@@ -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.
@@ -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
@@ -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/&lt;version&gt;/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>
@@ -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|";
@@ -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>()
@@ -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)
@@ -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(
@@ -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.
@@ -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" })
@@ -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>
@@ -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:&lt;eventType&gt;"</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[];