Commit Graph

40 Commits

Author SHA1 Message Date
Oliver Kieselbach 108c52be5e updated docs - moved customer graph permission to customer docs 2026-07-13 10:28:44 +02:00
Oliver Kieselbach 0da59a640b updated docs - moved customer graph permission to customer docs 2026-07-13 10:28:32 +02:00
Oliver Kieselbach 977ad37463 fix(agent): HelloWizardStarted signal — never complete while the Hello wizard is running (session 772fe502)
A flip-flopping user-scoped WHfB CSP read "disabled" once, the arm-C
completion synthesized HelloOutcome="Skipped" and the session completed
5s later while the user sat inside the Hello wizard that had started
230ms earlier (Shell-Core 62404, previously InformationalEvent-only).

Layer 1 — engine (DecisionCore):
- New DecisionSignalKind.HelloWizardStarted + set-once fact
  DecisionState.HelloWizardStartedUtc (additive-nullable, census +
  snapshot wired).
- Prevention: centralized HelloSatisfiedForCompletion /
  HelloPolicyDisabledWithoutWizard predicates veto the policy-disabled
  stand-in at all five Skipped-synthesis sites and in the
  completion_waiting prerequisites; the AdvisoryCompletion handler gets
  a HelloWizardObservedPromote branch (its fallthrough is
  re-arm-or-fail).
- Cure: HandleHelloWizardStartedV1 retracts an engine-synthesized
  "Skipped" (exact-case discriminator vs the lowercase tracker
  vocabulary; first fact-nulling reducer, stage-agnostic because the
  RealmJoin gate-defer parks synthetic skips pre-Finalizing), cancels
  finalizing_grace, returns to AwaitingHello and arms HelloSafety.
- Agent path: ShellCoreTracker raises HelloWizardStarted before
  FinalizingSetupPhaseTriggered; 62404 included in the startup backfill
  (fire-once); coordinator timestamp mirror; adapter emit (fire-once).

Layer 2 — HelloTracker confirmation read:
- ApplyPolicyRead state machine: "disabled" commits only after a second
  confirming read (~10s poll); "enabled" commits immediately;
  disabled→enabled flags flipFlopDetected=true; pending keeps the
  policy-unknown wait cadence. Audit fields confirmedReads /
  flipFlopDetected on hello_policy_detected.

Fixtures: signal-kinds/hello-wizard-started-v1.json +
enrollment-sessions/userdriven-hello-wizard-unskip-v1.jsonl (772fe502
ordering, ends Completed with HelloOutcome=completed).
Tests: DecisionCore 462/462, V2.Core 1775/1775.
Docs: OKF concept doc docs/agent/hello-wizard-unskip.md.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-13 10:16:10 +02:00
Oliver Kieselbach ec5d5661ec docs: use relative links instead of OKF bundle-absolute links
GitHub resolves /-prefixed links against the repo root (missing /docs),
so cross-doc navigation broke. Switched all agent docs to standard
relative links and updated the bundle convention in index.md + CLAUDE.md.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-11 13:48:00 +02:00
Oliver Kieselbach 4ccb3b2317 docs(agent): OKF concept docs for the V2 agent
Three evidence-based documents under docs/agent/: runtime overview
(lifecycle, collector hosts, single-rail telemetry pipeline), decision
engine (DecisionCore reducer, signals/effects, A/B/C completion arms,
invariants, threading), and logs & persistence (artifact inventory,
signal log vs spool rationale, replay-based crash recovery, quarantine).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-11 13:32:14 +02:00
Oliver Kieselbach 923a1c793a docs: convert /docs into an OKF v0.1 knowledge bundle
Adopt the Open Knowledge Format (GoogleCloudPlatform/knowledge-catalog)
for contributor/AI-facing technical docs: YAML frontmatter on the three
existing concept documents, new index.md (progressive disclosure, root
carries okf_version) and log.md, plus the maintenance convention in
.claude/CLAUDE.md.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-11 12:47:27 +02:00
Oliver Kieselbach 12e05f2ffb docs: move design docs out of docs/design; keep customer work-files git-ignored
Reorganize docs/ tree:
- docs/design/mcp-oauth-flow.md -> docs/mcp-oauth-flow.md (no customer data)
- enrollment-status-reclassification{,-plan}.md -> tasks/ (git-ignored work
  files; they contained a customer domain that must not ship in the public repo)
- remove now-empty docs/design/ folder

Repoint the 22 source files whose comments referenced
docs/design/enrollment-status-reclassification.md to the new tasks/ path.
Comment/docstring text only; no functional code changed.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-11 10:59:31 +02:00
Oliver Kieselbach 6a61ec5360 docs(design): MCP OAuth flow — who authenticates where
Sequence diagram and explanation of the dual-identity connect flow (AI-product
account vs. Entra work account), including the frequently-missed requirement
that the default browser must hold an active vendor session (claude.ai) for
the callback in step 6 to be accepted.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-11 10:36:34 +02:00
Oliver Kieselbach 170e66133d fix(backend): honor Hello-disabled + SkipUserEsp in the user-completed-setup reconcile rule
Follow-up to 049b6e5b: tenants running with Windows Hello disabled AND
the User ESP skipped can never produce a Hello terminal event, so the
reconcile rule would never fire for them and silent post-desktop
sessions stayed mislabeled AwaitingUser -> Incomplete. Mirror the
agent's Hello-disabled fast-path (HandleDesktopArrivedV1: policy
disabled + SkipUserEsp + desktop => complete):

- ExtractRollup reads hello_policy_detected Data.helloEnabled and
  esp_config_detected Data.skipUserStatusPage (contradicting
  observations resolve pessimistically to enabled/required).
- Rule 4 accepts desktop + (Hello terminal OR policy-disabled +
  SkipUserEsp). Hello disabled WITHOUT SkipUserEsp keeps waiting —
  the agent's strong post-AccountSetup gate (session 08c99638) blocks
  its fast-path there too, and rule 2 covers the rollup-complete case.
- esp_exiting stays rejected as success evidence (page-lifecycle event,
  fires with errorCode=0 while apps are unfinished — lesson 08c99638).
- Snapshot v3 + Web block additionally carry skipUserEsp.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-10 10:51:57 +02:00
Oliver Kieselbach 049b6e5b4a fix(backend): reconcile silent sessions to Succeeded when user provably finished setup (session 294ab5b4)
The timeout classifier only read the ESP registry rollup, so a session
where the user reached the desktop and completed Windows Hello — with
only the RealmJoin completion gate still pending when the agent went
silent — was labeled AwaitingUser (and would have graduated to
Incomplete at grace expiry). Desktop arrival + a positive Hello terminal
are the agent's own Classic completion AND-gate; the RJ gate
self-releases after 60 min, so such a session's enrollment succeeded and
only the completion report never left the device.

- EnrollmentTimeoutClassifier: rollup now carries DesktopArrived,
  HelloResolved (hello_provisioning_completed/hello_skipped only) and
  RealmJoinDetected/Resolved (realmjoin_resolved/realmjoin_timeout);
  new rule 4 reconciles desktop+Hello sessions to Succeeded with an
  RJ-aware reason. Explicit failure and agent_emergency_break still win.
- FailureSnapshotBuilder: schema v3 embeds the new evidence
  (helloResolved, realmJoinDetected, realmJoinResolved).
- Web FailureSnapshotBlock: renders Hello completed + RealmJoin rows.
- Design doc: decision table updated; desktop_arrived ALONE stays
  rejected as a completion signal.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-10 10:26:37 +02:00
Oliver Kieselbach 7e68f9d965 fix(metrics): surface terminal Incomplete bucket in usage stats
The 2026-07-08 timeout reclassification (Incomplete = terminal, non-failure)
migrated SessionStats, FleetHealthStats and SessionStatusBuckets, but two
reporting surfaces were overlooked: the live UsageMetricsService.SessionMetrics
(both platform + tenant paths, feeding get_usage_metrics and the usage
dashboards) and the persisted daily UsageMetricsSnapshot. Terminal Incomplete
sessions were counted in Total but in no breakdown bucket, so
Succeeded+Failed+InProgress no longer reconciled to Total and operators lost
sight of those devices.

- SessionMetrics + UsageMetricsSnapshot gain an Incomplete / SessionsIncomplete
  count, mirroring the migrated peers (Incomplete only, not AwaitingUser).
- Snapshot column stored + mapped in TableStorageService.Metrics; legacy rows
  deserialize to 0 (no seed bump needed).
- Success rate unchanged everywhere: denominator was already Succeeded+Failed,
  correctly excluding Incomplete/AwaitingUser. SLA paths verified already-correct
  (BuildSnapshot + breach evaluator receive terminal-filtered sessions).
- Web: Incomplete card added to both usage views (slate, matches the badge).
- MCP: query_raw_sessions status enum learns AwaitingUser/Incomplete so callers
  can filter by the new states.
- Test: UsageMetricsServiceWindowTests pins the Incomplete bucket + 50% rate on
  the crcins 1/1/8 shape.

Backend build clean, full Functions suite 2825 pass, web + MCP tsc clean.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-08 21:24:52 +02:00
Oliver Kieselbach 419d93a2d8 fix(reclassification): review hardening for timeout status states
Addresses four review rounds on the enrollment-status reclassification
(timeout != failure; new AwaitingUser/Incomplete states).

Backend correctness:
- UpdateSessionStatusAsync ETag force-fallback now mirrors the normal path:
  uses IsTerminalTransitionAllowed + admin-marked guard, stamps CompletedAt
  for Incomplete, and persists FailureReason/FailureSnapshotJson for
  Incomplete/AwaitingUser (not just Failed) so a contended write cannot
  regress a terminal or drop the classification reason/snapshot.
- StoreSessionAsync CAS loop re-reads the terminal tuple in the fresh-read
  window and preserves a just-landed terminal (Succeeded/Failed/Incomplete)
  verbatim, so re-registration can no longer revert it via the Replace.
- SessionGraceHours + AbsoluteMaxSessionHours now roundtrip through the
  tenant-config Store/Map pair (overrides were silently lost).
- ResolveGraceHours clamps the assumed agent cap to >= the real runtime
  default (48h) until the agent honors the override, so a low override can
  never terminalize a still-running session early.
- Incomplete now funnels through the terminal counter-reconcile seam, and
  the maintenance sweep re-reconciles after the synthetic session_timeout
  event (also fixes the pre-existing off-by-one on the Failed path).

Web:
- SessionInfoCard delegates its status pill to the shared SessionStatusBadge
  (removes a divergent local map; picks up AwaitingUser/Incomplete).
- SessionTable search color map + status filter chips learn the new states.
- useSessionEvents uses isTerminalStatus() instead of a hard-coded
  Succeeded/Failed check (no needless refetch on Incomplete).
- DataManagementSection copy no longer says "Failed - Timed Out".

Docs/comments:
- Plan doc + TenantConfiguration comments corrected (+3h/51h buffer, and the
  timeout now reclassifies to AwaitingUser/Incomplete, not "Failed").

Tests: +ResolveGraceHours clamp case, +grace/abs-cap config roundtrip +
legacy-row defaults. Full backend suite green (2712), web tsc green.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-08 19:26:41 +02:00
Claude ff8a442195 docs: correct plan — Fleet Health Incomplete done in PR5 2026-07-08 15:39:27 +00:00
Claude 824ef9a19c feat(agent): Part C — best-effort emergency-break report [net48, CI-verify]
Make the agent's silent 48h absolute session-age break visible: on break, before
cleanup/exit, fire a SessionAgeEmergencyBreak AgentErrorReport over the resilient
emergency channel; the backend (Part B) materializes it as an agent_emergency_break
timeline event and the timeout classifier terminalizes the session instead of
waiting out grace.

Decoupled so the guard stays pure and its unit tests are untouched:
- CheckSessionAgeEmergencyBreak gains optional Action onBreakFired = null, invoked
  once when the break is confirmed, before marker/cleanup, fully swallowed.
- New EmergencyBreakReporter.TrySend builds a throwaway auth bundle
  (BackendClientFactory) and sends, bounded to a 5s wait. AgentBootstrap wires it;
  GetAgentVersion made internal.

NOTE: agent targets net48 and was NOT compiled in this environment (only the .NET 8
SDK is available) — to be built/verified locally. The Shared AgentErrorType value is
verified (compiled in the backend build).
2026-07-08 15:36:53 +00:00
Claude a2a5d2fc0e feat(web): PR5 — surface Incomplete/AwaitingUser in the UI
- sessionStatus.ts: isTerminalStatus includes Incomplete.
- SessionStatusBadge: Incomplete = neutral slate (clearly not a red failure),
  AwaitingUser = sky/blue 'still going'; clock affordance extended to Incomplete.
- Fleet Health: FleetHealthStats.Incomplete (model + BuildFleetHealthPayload) +
  web interface + a dedicated slate Incomplete stat card (FleetStatCard gained a
  'slate' color), grid widened to 5.
- Main dashboard KPI row unchanged (its Success Rate is already terminal-only/honest).

Verified: web tsc --noEmit clean; backend Functions.Tests 2709 pass.
2026-07-08 15:30:47 +00:00
Claude 96ad65f715 feat(backend): PR4 — surface Incomplete as a third state, honest failure rate
- SessionStatusBuckets: explicit AwaitingUser + Incomplete buckets (out of Other),
  still reconciling to Total.
- Metrics summary: expose awaitingUser + incomplete; failure rate is now
  Failed/(Succeeded+Failed) — Incomplete and non-terminal states are out of the
  denominator (crcins drops from failed/total to the real terminal rate).
- SessionStats.IncompleteLastNDays; dashboard SuccessRatePct was already terminal-only.
- Daily aggregation already used Succeeded+Failed denominator — unchanged.
- Tests: SessionStatusBucketsTests. Suite: 2705 pass. Fleet Health Incomplete surfacing
  deferred to PR5 (web) with its documented over-all-sessions semantics.
2026-07-08 15:17:35 +00:00
Claude c0585133be feat(backend): PR3 — reconcile late completions to Succeeded
A device that reaches the desktop is enrolled regardless of an earlier sweep
timeout guess, so a genuine completion must be able to heal a prior verdict.

- Replace the terminal idempotency guard with pure IsTerminalTransitionAllowed:
  Succeeded UPGRADES any non-Succeeded state (Failed/Incomplete/AwaitingUser →
  Succeeded); Failed/Incomplete and AwaitingUser never overwrite an existing
  terminal. Ingest already calls UpdateSessionStatusAsync(Succeeded) on a late
  enrollment_complete without a pre-terminal skip, so the relaxed guard wires the
  reconcile end-to-end.
- Clear stale FailureReason + FailureSnapshotJson on Succeeded (reconcile hygiene).
- Preserve admin-marked terminals: a late completion never auto-overrides a
  hand-marked Failed/Incomplete.
- Tests: SessionStatusTransitionTests (full matrix, 20). Suite: 2705 pass.
2026-07-08 15:11:52 +00:00
Claude 4749c21d4e feat(backend): Part B — materialize agent_emergency_break timeline event
ReportAgentErrorFunction now injects ISessionRepository and, on a
SessionAgeEmergencyBreak report, writes an agent_emergency_break EnrollmentEvent
into the session stream (Sequence one past the last event, idempotent, best-effort
— never turns the always-200 emergency channel into a retry loop). This is what
makes the agent's otherwise-silent 48h break visible in the timeline and lets the
timeout classifier see it. Pure BuildAgentEmergencyBreakEvent helper (Warning,
non-terminal) + tests. Functions.Tests: 2685 pass.
2026-07-08 15:03:42 +00:00
Claude ebc9a19857 docs: plan the agent emergency-break event (backend materialize + agent emit) 2026-07-08 14:56:16 +00:00
Claude deb28d1b49 fix(backend): shrink timeout grace buffer 12h→3h (48h cap → 51h grace)
A completion can only arrive <= the agent's absolute cap (it self-destructs and
sends nothing after), so the grace buffer needn't wait hours for stragglers — it
only has to span one 2h maintenance-sweep cycle + minor clock skew, and reconcile
heals any premature Incomplete. Buffer 12→3h, default grace 60→51h.
2026-07-08 14:49:47 +00:00
Claude 576fc2ff84 fix(backend): derive timeout grace from the agent's absolute session cap
The 5h→terminal grace was a magic 72h. Anchor it to the agent instead: the 6h
per-run brake emits enrollment_failed (→ Failed at ~6h), but the 48h absolute
emergency break (AbsoluteMaxSessionHours) is SILENT to the backend — it writes a
local marker, cleans up and exits with no terminal event. So a session only stays
non-terminal past 48h when the agent hit that silent cap and is provably gone.

Grace must therefore be >= the agent absolute cap (never mark Incomplete while the
agent could still legitimately enroll) and only slightly beyond it:
  effectiveGrace = max(AbsoluteMaxSessionHours + 12h buffer, SessionGraceHours) = 60h default.

- EnrollmentTimeoutClassifier.ResolveGraceHours (pure, tested): floors grace at
  agentCap + buffer; a SessionGraceHours override can only raise it.
- TenantConfiguration.SessionGraceHours default 0 (= auto-derive); mirror
  AbsoluteMaxSessionHours so the floor follows a tenant override of the agent cap.
- Sweep uses ResolveGraceHours instead of the hard-coded 72h.

Follow-up noted: wire AbsoluteMaxSessionHours down into AgentConfigResponse so the
agent and backend share one value. Functions.Tests: 2678 pass.
2026-07-08 14:41:54 +00:00
Claude 56d378f2b2 feat(backend): PR2 — reclassify timed-out sessions in the sweep
Wire EnrollmentTimeoutClassifier into MarkStalledSessionsAsTimedOutAsync so the
5h inactivity sweep stops blindly failing silent-but-provisioned sessions
(docs/design/enrollment-status-reclassification.md). Forward-only.

- TenantConfiguration.SessionGraceHours (default 72h) + default config.
- Sweep Stage 2: classify from the ESP subcategory rollup instead of hard Failed.
  Fast-path skips within-grace AwaitingUser sessions with NO event read (grace is
  time-based; late completions are caught by ingest). Per-outcome counters; audit
  + ops event only when the pass actually terminalized/transitioned something.
- session_timeout event + auto-analyze kept ONLY for Failed/Incomplete (parity with
  the agent max-lifetime path); AwaitingUser is non-terminal, Succeeded is a reconcile.
- GetStalledSessionsAsync includes AwaitingUser so it graduates to Incomplete once
  grace elapses; agent-silent (Stage 1) stays InProgress-only.
- UpdateSessionStatusAsync: Incomplete is terminal (idempotency); AwaitingUser can't
  regress a terminal; CompletedAt stamped for Incomplete (no skewing duration); reason
  + snapshot persisted for Incomplete/AwaitingUser.

Full Functions.Tests suite: 2670 pass.
2026-07-08 14:17:18 +00:00
Claude 3c254e6e03 feat(backend): PR1 — timeout classifier + AwaitingUser/Incomplete states
Foundation for reclassifying 5h-timeout sessions (docs/design/
enrollment-status-reclassification.md). No behaviour change to the sweep yet —
this only adds the pieces PR2 wires in.

- SessionStatus: append AwaitingUser (non-terminal) + Incomplete (terminal,
  non-failure). Members appended so persisted ordinals never shift.
- EnrollmentTimeoutClassifier: pure ExtractRollup (parses the esp_provisioning_status
  "N of M subcategories completed" message + the 30s all-succeeded fallback line —
  categorySucceeded is ignored, Windows never sets it) and ClassifyTimedOutSession
  implementing the decision table (explicit failure→Failed; AccountSetup all-succeeded
  / enrollment_complete→Succeeded; DeviceSetup done + within grace→AwaitingUser, else
  Incomplete; silent pre-DeviceSetup→Incomplete). Never returns Failed without an
  explicit failure signal.
- FailureSnapshotBuilder: embed the same rollup (schema v2) so the snapshot records the
  evidence behind the verdict.
- Tests: EnrollmentTimeoutClassifierTests (12). Full Functions.Tests suite 2670 pass.
2026-07-08 14:05:57 +00:00
Claude a561f56a2a docs: propose enrollment status reclassification (timeout ≠ failure)
Design note + implementation plan for splitting the 5h maintenance-sweep
"Failed" bucket into three states. Evidence (crcins.com, 90d): 90.7% of
"failures" are 5h inactivity timeouts with no explicit failure event; 99.1%
of those had DeviceSetup fully provisioned (device enrolled) and simply had
the agent go silent during the user/Account-Setup phase. True failure rate
~4-5%, not 57.2%.

Proposal (backend + stats only, no agent change / no heartbeat):
- add AwaitingUser (non-terminal) + Incomplete (terminal, non-failure) states
- classify timed-out sessions from the ESP subcategory rollup the agent
  already emits (gate on subcategories, not the unreliable categorySucceeded)
- passive grace window owned by the backend; reconcile late completions to
  Succeeded; exclude Incomplete from the failure-rate denominator
2026-07-08 13:51:48 +00:00
Oliver Kieselbach 8f7048c615 docs: trim architecture.md to overview + solution structure
The detailed per-component chapters (Agent internals through Build & Development)
are retired — product documentation lives at docs.autopilotmonitor.com now, and
the deep internals went stale faster than they earned their keep. What remains
is corrected against the actual tree: Shared/DecisionCore and SummaryDialog.Tests
added, src/McpServer and rules/guardrails.json were missing entirely, scripts/
gained Backup + CustomerSetup, Functions references DecisionCore too, and the
overview diagram no longer claims the web app serves docs.

docs/images stays — all five files are embedded in the repo README.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-06 21:39:29 +02:00
Oliver Kieselbach 3e4a1d7745 docs(readme): green brand logo in front of the title
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-06 20:27:20 +02:00
Oliver Kieselbach 9f7b070e80 chore: remove legacy V1 agent and its backend ingest path
The V1 agent line is retired: no new V1 sessions since 2026-05-09, only
orphaned straggler traffic remained on /api/agent/ingest (last seen
2026-06-29, events for long-cleaned-up sessions). Bootstrap/Intune/CI
have deployed V2-only since the v2 cutover.

Agent:
- Delete AutopilotMonitor.Agent, .Agent.Core, .Agent.Core.Tests
  (projects + sln entries). V2 trio, SummaryDialog, DecisionCore.Tests
  and V2Isolation.targets stay.

Backend:
- Relocate the shared statics/DTOs V2 reused from the V1 partials
  (StampServerFields, SanitizeEventTimestamps, IsMaxLifetimeAgentShutdown,
  RecalculateAppDurations, EventClassification, AppInstallAggregationState)
  into EventIngestProcessor; IngestEventsOutput moves next to
  IngestTelemetryFunction (same namespace, zero ripple).
- Delete IngestEventsFunction (route agent/ingest), BootstrapIngestEvents
  (route bootstrap/ingest), NdjsonParser and their DI registration.
  EventDataNormalizer stays (used by TelemetryPayloadParser).
- Drop both routes from EndpointAccessPolicyCatalog and
  RequestTelemetryMiddleware.TenantHeaderTrustedPaths.
- Retire the V1 update-hash oracle: LatestAgentV1*/LatestBootstrapV1*
  fields + GetAgentLine case 1 (default arm returns empty hashes, so
  stragglers gracefully skip the integrity check; ParseAgentMajor keeps
  its default of 1 on purpose — defaulting to 2 would feed V2 hashes to
  V1 binaries). TableConfigRepository Store+Map updated in lockstep.
- Remove ApiEndpoints.IngestEvents/.BootstrapIngestEvents constants.

Tests: NdjsonParserTests deleted; timestamp/critical-path/max-lifetime
tests repointed to EventIngestProcessor; the two NDJSON pipeline tests
removed (coverage retained by direct-list siblings); catalog/no-store
InlineData rows updated.

Docs: architecture.md (endpoint tables, ingest flow diagram, solution
layout, test matrix), web SectionAgentInternals endpoint tables,
build.ps1 help text.

Azure: no config change needed — /api/agent is not in the client-cert
exclusion paths (agent/ingest was always cert-enforced) and the
/api/bootstrap prefix exclusion remains for the surviving bootstrap
routes (verified via ARM on autopilotmonitor-api).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-02 08:53:20 +02:00
Oliver Kieselbach 6c263da945 docs(architecture,skills): document dual-destination diagnostics + bump skill submodule
- docs/architecture.md Diagnostics section rewritten: both destinations
  (CustomerSas default + Hosted opt-in), new download routing on shape,
  cascade-delete step §5b with SAS-delete-permission gating, and the V2
  remote-config retry + observability events.
- Submodule .claude/commands bumped to 17a6f5b for the dual-destination
  fetch-diag-zip.sh refactor + skill error-handling note updates.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-19 21:57:51 +02:00
Oliver Kieselbach e4c4dd57db feat(backend,web): optional Graph add-on permissions for Intune script display names
Adds opt-in resolution of Intune Platform Script + Remediation display names in
session timelines via a tenant-side appRoleAssignment grant. No app manifest
change, no impact on customers who don't enable it. Architecture is reusable for
future optional Graph permissions: catalog -> detector -> resolver -> endpoint.

Backend
- GraphFeatureDetector parses the `roles` claim from the SP access token and
  caches the (token, roles) bundle per tenant. GetSnapshotAsync surfaces a
  transient flag so admin-UI callers can render "unknown" instead of a
  misleading "0 of 0 granted" when token acquire times out.
- GraphTokenService now accepts a CancellationToken so optional-feature callers
  can apply tight budgets instead of inheriting the 5+15+30 s retry chain.
- ScriptDisplayNameResolver: cache-first, list-pull-on-cold-tenant, per-ID
  fallback. Throttle-aware abort: skip per-ID for kinds whose full-pull hit a
  transient failure; break the per-ID loop on the first HttpRequestException.
  Total budget 5 s, with a tight 1.5 s sub-budget on the token acquire.
- Azure Table `ScriptNameCache` + direct-TableClient fail-loud repository
  (mirrors TableOffboardingAuditRepository pattern). Daily
  ScriptNameCacheCleanupFunction: 7 d positive / 1 h NotFound TTL.
- POST /api/tenants/{id}/scripts/display-names (body-carried refs to dodge URL
  length limits; backend cap 200 refs/request).
- GET /api/tenants/{id}/graph-permissions/status (4 s budget; includes app
  ClientId so the UI can render an exact grant command).
- POST /api/tenants/{id}/graph-permissions/refresh (invalidates token cache).
- All three routes registered in EndpointAccessPolicyCatalog (MemberRead for the
  resolve, TenantAdminOrGA for the admin-UI surfaces).

Web
- `useScriptDisplayNames` hook with stable refset fingerprint (no spurious
  refetches on parent re-renders); clears stale map on tenant/refset change so
  a session switch never leaks names across tenants.
- Chunked POST loop: 200-ref batches with partial-success semantics. Preserves
  earlier chunks when later ones fail (non-2xx OR transport throw); only
  TokenExpiredError propagates so auth flow can react.
- ScriptExecutions renders " . {DisplayName}" alongside existing label/short-id,
  keyed by Kind + policyId so Platform/Remediation under the same id never
  collide.
- New Settings section "Optional Graph capabilities": status table with granted
  / not granted / unknown, copy-paste PS one-liner with ClientId baked in,
  refresh button gated on response.ok.

Customer-facing
- scripts/CustomerSetup/Grant-AutopilotMonitorAddOn.ps1: idempotent
  grant / verify / revoke script. -Features ScriptDisplayNames (high level) or
  -Permissions ... (admin-UI generated). Pure ASCII for PS 5.1 compat.
- docs/customer-graph-add-on-permissions.md: end-to-end customer guide
  covering prerequisites, the appRoleAssignment pattern, and revocation.

Tests
- 1718 backend xUnit / 364 web vitest. Highlights: JWT roles parsing edge
  cases; full-pull-429 suppresses per-ID; per-ID-429 breaks loop; transient
  detection via GetSnapshotAsync; chunked partial success (HTTP failure + thrown
  transport); TokenExpiredError propagation; EndpointPolicyCatalogCompleteness
  covers the three new routes by reflection.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-19 01:14:21 +02:00
Oliver Kieselbach 60eb841d37 updated known-issues, architecture.md and rawquery function 2026-04-06 15:12:07 +02:00
Oliver Kieselbach b191bd67d4 updated Readme 2026-04-04 19:13:33 +02:00
Oliver Kieselbach b62f9ee01b update architecture.md 2026-04-02 01:00:47 +02:00
Oliver Kieselbach 5f1bd1c470 bugfix bootstrapper and update architecture.md 2026-03-31 22:49:51 +02:00
Oliver Kieselbach e8903ff413 chore 2026-03-31 22:38:28 +02:00
Oliver Kieselbach d1ffac14bc hardened integrity check of agent via SHA256 from version.json and agent config (download and runtime check) 2026-03-29 22:56:11 +02:00
Oliver Kieselbach 12880a8e1d renamings 2026-03-27 20:59:05 +01:00
Oliver Kieselbach 0fd8ca22d1 renaming global admin 2026-03-23 06:28:59 +01:00
Oliver Kieselbach a37a6b82e8 restructured source code for better maintenance and added ai files 2026-03-18 01:15:46 +01:00
Oliver Kieselbach fd6ca05ebc Simplify README and remove outdated docs
Replace verbose README with a concise overview and link to the
official documentation at autopilotmonitor.com/docs. Remove
CONTRIBUTING.md, QUICKSTART.md, docs/architecture.md, and
docs/getting-started.md as the website docs supersede them.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-23 21:52:11 +01:00
Oliver Kieselbach 5690620058 Initial commit 2026-02-05 06:14:41 +01:00