Commit Graph

8 Commits

Author SHA1 Message Date
Oliver Kieselbach e453b96395 feat(backend): persist and expose IsSelfDeployingProfile session flag
Persists the agent-sent self-deploying/kiosk profile marker (template:
IsHybridJoin, write-once): Sessions column + SessionsIndex full-mirror
projection + SessionSummary mapping. Re-registration preserve is a
sticky-true OR - a boot with an unreadable Autopilot policy cache must
not pin the flag to false once it was observed true.

Filterable end-to-end: SessionSearchFilter + continuation-token
fingerprint + search/raw query params + index/basic scan filters, plus
MCP search_sessions / query_raw_sessions inputs and the get_session
overview field.

Display/analytics only - the completion fix itself ships in the agent
(DecisionCore gates on the EnrollmentFactsObserved seed, not this flag).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-02 12:59:30 +02:00
Oliver Kieselbach 6344a8b061 feat(sessions): per-session RebootCount, stored + MCP-queryable
Surface "how many reboots did this enrollment have" as a stored,
filterable per-session metric so admins can ask e.g. "machines with
many reboots" via MCP search_sessions.

No agent change: V2 already emits one system_reboot_detected per reboot
(EventLog 6009 boot-time vs session start). The backend counts and stores
it; V1 stays 0 (never emits it).

Hybrid counting:
- Incremental per ingest batch in ClassifyEvents (V2 + legacy V1, shared
  EventClassification) -> rebootIncrement on IncrementSessionEventCountAsync,
  for a live in-flight value (passed 0 on terminal batches).
- Authoritative reconcile (ReconcileSessionRebootCountAsync): distinct count
  of system_reboot_detected from the Events table, idempotent + fail-soft.
  Runs centrally in TableSessionRepository.UpdateSessionStatusAsync after any
  successful terminal transition (ingest, admin mark, maintenance timeout,
  rule-engine fail), and again post-increment in the ingest path to cover
  already-terminal batch replays. Self-corrects at-least-once batch double-counts.

Storage/correctness:
- RebootCount preserved across re-registration in StoreSessionAsync (a reboot
  IS the restart trigger) and projected in BuildSessionIndexEntity (superset rule).

Search/MCP:
- rebootCountMin/Max pushed to OData in BuildSearchScanFilter (sparse-subset),
  decoded in SearchSessionsFunction + QueryRawSessionsFunction, added to the
  continuation fingerprint and the fields= projection. Client-side
  MatchesRebootCountBounds backstops the device-snapshot + legacy scan paths.
- search_sessions MCP tool gets rebootCountMin/Max + rebootCount projection.

Tests: SessionIndexProjection (superset), StoreSessionReregistrationPreserve,
RebootCountFilterBounds. Suite 2228/2228 green; MCP 220 green.

Inert until backend redeploy; legacy index rows lack the column until next
upsert (excluded by min-filter, intended). Web UI badge deferred.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-08 13:11:52 +02:00
Oliver Kieselbach b215602737 feat(backend,mcp): server-side fields= projection for paginated config/all
Follow-up to opt-in pagination: fields= was a client-side trim in the MCP tool,
so it did not round-trip across pages — following nextLink returned the full
safe-field set again, and the shape differed page 1 vs 2+.

Move the projection server-side (matching query_raw_sessions):
- TenantConfigProjection: keep-list intersection — only the 9 non-sensitive
  fields can ever be emitted; requested keys are intersected (fields=teamsWebhookUrl
  yields nothing), tenantId always included. ParseFields is case-insensitive.
- TenantConfigPagination: parse fields=; BuildNextLink echoes it so the projection
  carries across every page. fields is NOT in the continuation fingerprint (it is a
  projection, not a filter — changing it mid-stream keeps the cursor valid).
- GetAllTenantConfigurationsFunction projects via TenantConfigProjection.ProjectAll.
- MCP list_tenants passes fields to the backend (followNextLink params) and drops the
  client-side projectTenantFields; extractTenantList stays as defense-in-depth.

Now every page returns exactly the requested columns and the backend sends less over
the wire. Tests: TenantConfigProjection (incl. secret-cannot-be-requested) +
pagination fields-echo; backend 24/24, MCP vitest 52/52.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-27 19:39:02 +02:00
Oliver Kieselbach f9d22216b6 feat(backend,mcp): opt-in pagination on config/all to fix list_tenants size-cap
The MCP list_tenants tool fetched GET /api/config/all, which returns all
tenant configs in one unpaginated array. With ~198 tenants the projected
response exceeded the tool response cap and was offloaded to a file, making
the tool unusable for the model.

Add backward-compatible opt-in pagination to config/all (same dual-mode
pattern as GetSessionReportsFunction):
- No pageSize  -> unchanged legacy bare full-config array (web + maintenance
  aggregation depend on this shape; zero regression).
- pageSize     -> secret-stripped { count, tenants, nextLink } envelope using
  the existing ContinuationToken HMAC (scope "config-all", caller-bound),
  TenantId-ordered pages. Secrets never leave the backend.

Backend: GetTenantConfigurationsPageAsync (repo + service), TenantConfigPagination
helper, dual-mode GetAllTenantConfigurationsFunction. Route/policy unchanged.
MCP: list_tenants now follows nextLink against config/all (extractTenantList as
defense-in-depth + optional fields= trim); client-side selectTenantPage removed.

Tests: TenantConfigPaginationTests 13/13; MCP security-guards 56/56.
No /api/global/ relocation here - that convention cleanup is deferred.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-27 19:04:27 +02:00
Oliver Kieselbach 84a1aa14cb feat(backend): server-side filters for /api/sessions/{id}/events
Push eventType/severity/source filters from MCP-tool layer down into the
Function so count and nextLink stay coherent. Previously filters lived
client-side: backend returned the full page, MCP filtered → could
produce count: 0 with nextLink set, which read as a bug ("nextLink is
emitted even though there's nothing left").

With server-side filters:
- count reflects post-filter items in this page
- nextLink is still gated on NextRawToken (more raw rows ahead in this
  session — filter may or may not match further down)
- filter values echo back onto nextLink so callers can follow pagination
  without having to re-supply them

SessionEventsPagination.BuildNextLink learns an optional `extras` arg so
arbitrary query params survive the round-trip.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-07 16:27:49 +02:00
Oliver Kieselbach 00f3bf61ef feat(mcp,backend): server-driven response cap + version prefix + lean projection
Real-world test exposed two distinct LLM-context bottlenecks the pagination
rollout alone didn't solve:

  1. Anthropic's MCP client truncates tool responses at ~25-30k chars by
     default, mid-page; nextLink follows but rows in the truncated middle are
     silently lost. pageSize alone can't fix this — even pageSize=50 of full
     SessionSummary objects (~1.5KB each) blows the cap.

  2. Aggregation queries ("how many V2 enrollments?") shouldn't need 1.5MB of
     SessionSummary blobs paginated across 4 round-trips. The LLM only needs a
     handful of fields to count.

Three coordinated fixes, all server-driven so customers connecting via MCP
get them automatically without any client config:

* `_meta.anthropic/maxResultSizeChars` annotation on every tool response,
  calibrated per response density (sessions=300k, events=250k, rawTable=500k,
  small=50k). The Anthropic client respects it and stops mid-page truncation.
  New `toolResultText()` helper in shared.ts wraps the pattern.

* `fields=` projection on `search_sessions` and `query_raw_sessions` (server-
  side, prunes wire payload 5-20x) and on `query_table` (client-side, since
  TableEntity columns are dynamic). Always retains PartitionKey + RowKey for
  cursor stability. Tool descriptions now actively recommend it for counting /
  aggregation queries.

* `agentVersionPrefix=` / `imeAgentVersionPrefix=` filters on `search_sessions`
  and `query_raw_sessions`. Replaces the "one call per build" loop pattern when
  the LLM wants a whole version line. Mutually exclusive with the exact-match
  variants (exact wins). Fingerprint-bound so a token from "2.0." can't replay
  into "2.1.".

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-07 11:31:50 +02:00
Oliver Kieselbach 840ac9f21f feat(backend,mcp): paginate the last three legacy MCP diagnostic tools
query_raw_sessions, search_sessions_by_cve, and query_table were still on the
legacy `?limit=` contract (capped at 200/500/100 with no nextLink). On large
deployments this made "all devices affected by CVE-X" or "every row in
TenantConfiguration" structurally un-answerable.

Migrate to the established pageSize/continuation/nextLink contract:

- query_raw_sessions reuses SearchSessionsPagination; field projection logic
  unchanged.
- search_sessions_by_cve gets a new SearchSessionsByCvePageAsync repo method
  (analog to by-event) plus SearchSessionsByCvePagination helper with
  cveId+CVSS/risk fingerprint binding.
- query_table gets a generic RawTablePagination helper covering tableName +
  PK/RK/filter scope.

Sweep all pageable MCP tools to use followNextLink so backend-echoed query
params round-trip correctly. Raise session-list defaults 50 to 200 and unify
tool descriptions on a "pagination-first" message: pageSize is the batch size,
not a truncation point.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-07 10:55:39 +02:00
Oliver Kieselbach 70382e991b feat(backend,web,mcp,shared): MS-Graph nextLink pagination rollout
Lands the planned 6-PR pagination rollout (project_mcp_pagination_rollout)
as one tested batch covering session_events, audit+ops, session_reports,
search_sessions/list, and query_raw_events. Backend builds clean; Backend
1024 / Agent V2 1007 / Web 249 tests green.

Primitives (Shared/Pagination):
- ContinuationToken with fingerprint binding (tenantId, dateFrom/To, filters)
  so a forged or cross-tenant token fails validation on the next page.
- PaginatedResult<T> + RawPage<TEntity> as the canonical wire shapes.

Backend (AutopilotMonitor.Functions/Pagination):
- AzureTablesPaginator drives single-partition + per-partition pages.
- DateWindowPagination sweeps day-buckets for global audit/ops queries
  (the backend resolves "last 30d" once and echoes dateFrom/dateTo on
  nextLink so follow-up calls don't recompute "now" and break the token).
- PerPartitionFanOutMerge stable-merges N partition cursors for cross-
  tenant session search.
- Per-feature plans (SessionEventsPagination, SearchSessionsPagination,
  SearchSessionsByEventPagination, SessionListPagination,
  SessionReportsPagination, QueryRawEventsPagination).
- Functions adopt nextLink: GetSessionEvents, GetAuditLogs (tenant +
  global), GetOpsEvents, GetSessionReports, SearchSessions,
  SearchSessionsByEvent, GetSessions, GetAllSessions, QueryRawEvents.
- Repository contracts (ISessionRepository, IOpsEventRepository,
  INotificationRepository, IMaintenanceRepository) gain *PageAsync
  variants returning PaginatedResult; legacy GetSessionsAsync becomes
  a drain-all helper used by maintenance/feedback/SLA paths that still
  need bounded full-list semantics. SessionPage DTO removed.
- PlatformMetricsService now drains all date-bounded sessions via
  GetAllSessionsAsync (previous maxResults:500 was a silent truncation).

Web:
- lib/paginationLink.ts + ?? api.ts wrappers expose follow-nextLink in
  hooks (useSessionEvents, useDashboardSessions) and pages (audit,
  fleet-health, session detail, OpsEvents, SessionReports admin).
- Session list uses Pattern B2 (load-more append); audit + ops + reports
  use Pattern B1 (click-next replace); session detail timeline keeps
  Pattern A (eager-fetch full set).

MCP:
- followNextLink helper accepts either an opaque continuation OR the
  whole "/api/?" path the backend emitted, and follows the latter
  verbatim so server-resolved query params (tenantId fingerprint,
  resolved dateFrom/dateTo, page size) round-trip correctly. Without
  this, get_audit_logs / get_ops_events / list_session_reports / sessions
  follow-ups would silently fail token validation.
- get_audit_logs, get_ops_events, list_session_reports, and the session
  tools migrate to pageSize + continuation/nextLink, with tool docs
  spelling out the round-trip rule so external MCP clients pass the
  full nextLink instead of stripping it down to "continuation".

Tests:
- New: ContinuationTokenTests, DateWindowPaginationTests,
  PerPartitionFanOutMergeTests, AuditLogRowKeyOrderingTests,
  SessionEventsPaginationTests, SessionListPaginationTests,
  SearchSessionsPaginationTests, SearchSessionsByEventPaginationTests,
  SessionReportsPaginationTests, QueryRawEventsPaginationTests, and
  TestSetup helpers.
- Web: paginationLink.test.ts + sessionListLoadMore.test.ts.
- PlatformMetricsServiceWindowTests adjusted for the drain-all change.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-06 20:53:46 +02:00