Commit Graph

3008 Commits

Author SHA1 Message Date
Oliver Kieselbach 08455cc5f9 OOBE quality update: install events from the System log, servicing steps, a bounded engine phase in Shadow mode (D-310)
Report 3a2207978e4c: a session was failed 30 min after the ESP exit while Windows installed its
OOBE quality update. The update watcher had been blind since July and the engine had no notion
of the phase.

- Windows Update client: 19/20/43/44 come from the System log (provider-filtered XPath), as the
  provider manifest routes them; 25/26/31/41 from Operational via their own config list.
  Updates are classified os/defender/store/other: only Windows/.NET updates become rows, the
  rest is counted into windows_update_activity_summary. Per-channel watermarks persist a record
  only after its row and engine signal were handed to the ingress.
- Servicing: CBS steps from the Setup log (windows_update_servicing), client UpdateAgentLCU.
- Engine: OsUpdateActivity and OsUpdateFacts (event-time ordered) anchor an OOBE update phase
  after the ESP exit. Each state has its own bound: the update runs at most 3 h (then Failed
  oobe_update_not_finished), after it the user has 60 min to sign in (then the engine stops
  waiting without a verdict), after the sign-in one window of grace.
- The phase acts only in OobeUpdatePhaseMode=Active. Shadow, the default, reports what it would
  decide as oobe_update_phase_shadow right before today's verdict. The agent stamps the mode on
  EnrollmentFactsObserved at every start; a later stamp can take Active away, never grant it.
- Agent: a stop without a verdict ends the session like the max-lifetime watchdog
  (agent_shutting_down reason=stopped_waiting); the backend classifies it honestly (verdict
  origin stopwait), never Failed.
- Measurement: oobe_update_page (the update page and its ExpeditedUpdate_* names; a 62407 value
  only as boolean, integer or HRESULT) and oobe_update_state (NDUP and ESP-policy registry
  values), both Debug and bounded per run.
- ANALYZE-DEV-004 1.2.0 counts install and download failures only, never scan failures.
- Event types in Constants, MCP catalog and web; shared manifests regenerated; privacy page
  names Windows updates installed during the enrollment.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
v2.0.1473
2026-10-02 15:09:50 +02:00
Oliver Kieselbach 87c5bf018c bump pointer: internal (gather-rule state released, B-hx3 field verify)
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-01 22:26:18 +02:00
Oliver Kieselbach 0d6c03d64b Public texts no longer name a customer domain or IME decompiles
- Two code comments of the gather-rule timeline feed named a customer's domain next to the
  session id; the session id stays as the regression anchor.
- The get_ime_pattern_health description and the IME Pattern Health admin text no longer send
  the reader to "the IME decompile"; the IME-TOKEN-FAILURE and IME-DO-TEL pattern descriptions
  say "verified against IME x" / "absent from the x builds" (rules/dist regenerated).
- Code comments that cited decompiled IME builds cite the IME version instead.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
v2.0.1472
2026-10-01 22:00:52 +02:00
Oliver Kieselbach b14866c9a6 bump pointer: internal (gather-rule state committed, B-hx3)
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-01 21:46:44 +02:00
Oliver Kieselbach 25b133d3d0 bump pointer: internal (gather-rule state across restarts, D-309)
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-01 21:45:29 +02:00
Oliver Kieselbach 684167292f Gather rules keep their place across agent restarts; byte-exact logparser reader; MCP filters by gather event types
- Logparser positions per (rule, file) and the on_change values persist in
  State\gather-rule-state.json, bound to the session id. A reboot, hard kill or WhiteGlove
  Part 2 no longer replays a log from byte 0 or re-emits an unchanged on_change result. A
  replaced log (fingerprint of its first 4 KB) is read from the beginning. Positions are saved
  after the events went out; restoring the file is fail-soft. --run-gather-rules stays in memory.
- LogLineReader: a maxLines stop loses no line, an unfinished last line is never split
  (interval rules wait one run, one-shot triggers read it again next run), UTF-16/UTF-32 logs
  are read by their byte order mark from any position, line ends as StreamReader.ReadLine,
  logLineNumber counts from the start of the file, lines over 32 MB are skipped. A maxLines
  value that is no positive number falls back to 1000.
- UserProfileResolver no longer caches "no user signed in yet" (detection retried at most
  every 30 s); guarded callers expand the token with the profile they checked.
- MCP get_session_events, search_sessions_by_event and query_raw_events accept a gather
  rule's own outputEventType; an empty first-page result for an uncatalogued type carries
  eventTypeNote with the closest built-in types instead of being rejected up front.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-01 21:45:10 +02:00
Oliver Kieselbach 6474860a57 bump pointer: internal (lesson from the vulnerabilities feedback)
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-01 20:15:56 +02:00
Oliver Kieselbach d23f2eae3e bump pointer: internal (vulnerabilities feedback fixes, D-308)
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-01 20:14:40 +02:00
Oliver Kieselbach fae6aa0b92 Vulnerabilities band filters always find entries, list controls stay put, first load shows the progress dialog
The summary endpoint takes perBand: above 0 it adds bandCves and bandSoftware, each severity and
priority band's top entries outside the lists. The portal asks for 10 per band and shows them only
under a filter, so Attend lists its 7 CVEs instead of nothing; the MCP tool never asks and keeps its
size. The Most exposed controls sit below the title in every state, bands with 0 entries are
disabled, and the first load shows CalculatingDialog (the Geographic Performance dialog without its
page) with separate estimates for all tenants and one tenant. D-308.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-01 20:13:47 +02:00
Oliver Kieselbach 43dfb731f1 bump pointer: internal (vulnerabilities tab redesign, D-307)
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-01 19:38:45 +02:00
Oliver Kieselbach dd5534b0c9 Vulnerabilities tab shows a severity donut, the priority ladder and the most exposed software
The exposure summary groups CVE index rows by product: every new row carries the finding's
CPE vendor:product as Product, and a row without it borrows the product of a row with the
same title. The summary adds topSoftware, distinctSoftware and kevAffectedSessions, and
known-exploited CVEs and products stay listed below the topN cut (capped so the MCP result
stays under its 50k cap). The shared panel is rebuilt: KPI tiles, a severity donut and the
Act/Attend/Track ladder as list filters, and a Most exposed card with Software and CVEs
lenses. get_vulnerability_summary describes the new fields. D-307.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-01 19:35:09 +02:00
Oliver Kieselbach 54334de378 Rule pages point to AI-assisted rule authoring
The community box on the gather and analyze rule pages gets a line that an
assistant connected via MCP can draft, validate and test a rule, linking the
AI-assisted rule authoring docs page.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-01 19:11:56 +02:00
Oliver Kieselbach 9671191eef bump pointer: internal (/plans nav marking, slash links, MCP docs, D-306)
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-01 18:43:41 +02:00
Oliver Kieselbach c5a4feb762 Internal links on the public pages end with a slash
The host answers /plans, /about, /privacy and the other page paths with a
301 to the slash path first. The navbar, the footer, the landing and
get-started calls to action, the terms page and two portal links to
/settings now point at the slash path directly. A guard renders every
public page with the build's trailing-slash setting and fails on an
internal link without it.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-01 18:41:22 +02:00
Oliver Kieselbach 2253fca533 bump pointers: internal, .claude/commands (AI page and marketing click counts, D-305)
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-01 18:16:42 +02:00
Oliver Kieselbach ff9e79dcc7 Landing gets an AI page (/ai) and counts clicks and section views
New public page /ai, linked from the navbar (AI), the Then just ask section
and the footer. It shows what an AI client can answer from the structured
enrollment data: a question gallery whose answer panel switches between chat
app, VS Code, terminal and self-hosted, the data path (where the model runs,
what reaches us) and how to connect. A test keeps every MCP tool and argument
on the page in line with the server's registrations for tenant users.

The landing, /get-started and /ai send anonymous click and section-view
counts (marketing_cta_clicked, marketing_section_viewed); every link and
button carries a data-track id. Layouts with their own OpenGraph metadata
keep the shared image and the large Twitter card again. The navbar tightens
between lg and xl so the new entry fits at 1024 px. The FAQ names the
documented MCP clients, and the terminal demo uses the real search_sessions
argument (deviceName).

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-01 18:15:15 +02:00
Oliver Kieselbach e54b4585b8 bump pointers: internal, .claude/commands (event follow-up page live, D-304)
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-01 17:17:41 +02:00
Oliver Kieselbach 7894d61e0b Event follow-up page for the Workplace Ninja Summit 2026
Unlisted page /events/<slug>/ for the link in the post-event mail: try it,
request a walkthrough, read the docs, plus the free community editions of
SCEPman and MyWorkID. It is noindex and kept out of the sitemap, robots.txt
and llms.txt. Every call to action sends one anonymous click event
(event_followup_clicked). The customer quote is now shared with the landing.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-01 17:11:48 +02:00
Oliver Kieselbach 090f47d4fb Merge branch 'main' of https://github.com/okieselbach/AutopilotMonitor 2026-10-01 13:14:44 +02:00
Oliver Kieselbach c2d68b6537 bump pointer: internal (landing GitHub stars, D-303)
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-01 13:13:35 +02:00
Oliver Kieselbach 5b0dfaad99 Landing shows the GitHub star count and a customer quote
The navbar gets a "Star" pill with the repository's star count from sm up;
phones keep the bare GitHub mark. A new section before the closing CTA
shows one customer quote with a star ask underneath. The footer's
"Star us on GitHub" line is gone, so the page asks twice, not three times.

The count is read once per web deploy (deploy-web.yml, own step, job token
only there) and inlined by next build as NEXT_PUBLIC_GITHUB_STARS, so
visitors' browsers never contact GitHub and the CSP stays as it is.
Without a value the pill renders without a counter (D-303).

The desktop links, bell and sign-in now start at lg instead of md: the bar
already overflowed between 768 and ~1000 px and the pill widened it further.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-01 13:13:33 +02:00
dependabot[bot] 50d29e61e8 chore(deps): bump the web-security group across 1 directory with 3 updates (#170)
Bumps the web-security group with 3 updates in the /src/Web/autopilot-monitor-web directory: [next](https://github.com/vercel/next.js), [brace-expansion](https://github.com/juliangruber/brace-expansion) and [dompurify](https://github.com/cure53/DOMPurify).


Updates `next` from 16.3.5 to 16.3.6
- [Release notes](https://github.com/vercel/next.js/releases)
- [Commits](https://github.com/vercel/next.js/compare/v16.3.5...v16.3.6)

Updates `brace-expansion` from 1.1.18 to 1.1.21
- [Release notes](https://github.com/juliangruber/brace-expansion/releases)
- [Commits](https://github.com/juliangruber/brace-expansion/compare/v1.1.18...v1.1.21)

Updates `dompurify` from 3.4.14 to 3.4.16
- [Release notes](https://github.com/cure53/DOMPurify/releases)
- [Commits](https://github.com/cure53/DOMPurify/compare/3.4.14...3.4.16)

---
updated-dependencies:
- dependency-name: next
  dependency-version: 16.3.6
  dependency-type: direct:production
  dependency-group: web-security
- dependency-name: brace-expansion
  dependency-version: 1.1.21
  dependency-type: indirect
  dependency-group: web-security
- dependency-name: dompurify
  dependency-version: 3.4.16
  dependency-type: indirect
  dependency-group: web-security
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-10-01 11:16:00 +02:00
dependabot[bot] 4b03ba4e19 chore(deps): bump fast-uri (#169)
Bumps the mcp-security group with 1 update in the /src/McpServer/autopilot-monitor-mcp directory: [fast-uri](https://github.com/fastify/fast-uri).


Updates `fast-uri` from 3.1.7 to 3.1.8
- [Release notes](https://github.com/fastify/fast-uri/releases)
- [Commits](https://github.com/fastify/fast-uri/compare/v3.1.7...v3.1.8)

---
updated-dependencies:
- dependency-name: fast-uri
  dependency-version: 3.1.8
  dependency-type: indirect
  dependency-group: mcp-security
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-10-01 11:15:42 +02:00
dependabot[bot] f0aec031f3 chore(deps): bump the actions group with 2 updates (#168)
Bumps the actions group with 2 updates: [github/codeql-action/init](https://github.com/github/codeql-action) and [github/codeql-action/analyze](https://github.com/github/codeql-action).


Updates `github/codeql-action/init` from 4.38.0 to 4.38.2
- [Release notes](https://github.com/github/codeql-action/releases)
- [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md)
- [Commits](https://github.com/github/codeql-action/compare/b96794f015dfd88f77b49b1c93e0fa7110f94c63...2892aa5e19bbd11bc0cff5427e3b750a04d9e3c2)

Updates `github/codeql-action/analyze` from 4.38.0 to 4.38.2
- [Release notes](https://github.com/github/codeql-action/releases)
- [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md)
- [Commits](https://github.com/github/codeql-action/compare/b96794f015dfd88f77b49b1c93e0fa7110f94c63...2892aa5e19bbd11bc0cff5427e3b750a04d9e3c2)

---
updated-dependencies:
- dependency-name: github/codeql-action/init
  dependency-version: 4.38.2
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: actions
- dependency-name: github/codeql-action/analyze
  dependency-version: 4.38.2
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: actions
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-10-01 09:28:08 +02:00
Oliver Kieselbach 66208b3767 bump pointer: internal (rule notification placeholders, D-302)
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-30 18:25:11 +02:00
Oliver Kieselbach c60e07dfd4 Rule notifications resolve {{token}} placeholders before they are sent
A stored rule result keeps the rule's template; only the web and the MCP
server resolved it. The notification builders sent the explanation as
stored, so a channel card showed {{reason}}, {{appName}},
{{failedSubcategory}} and {{errorCode}} although the session had recorded
all four values.

- RuleTemplateInterpolator: the backend copy of the resolution, applied in
  BuildRuleFiredAlert and AddRuleResultSections before the text is cut.
  In a notification a list item whose value was not recorded is dropped
  and a token in running text becomes an en dash.
- The cut lands on a line, sentence or word boundary instead of a
  character count; the limits are unchanged.
- tests/fixtures/rule-template-interpolation/cases.json: one case file
  run by the backend, the web and the MCP server.
- The engine's evidence auto-field list is one constant. The web, the MCP
  interpolator and the validate_rule token lint gain timeoutHours, which
  the engine already recorded.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-30 18:12:02 +02:00
Oliver Kieselbach f564ee6a06 bump pointer: internal (hello wait timeout released)
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-30 15:50:29 +02:00
Oliver Kieselbach ec025686ce bump pointer: internal (hello wait timeout plan, D-301)
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
v2.0.1471
2026-09-30 15:38:30 +02:00
Oliver Kieselbach 821c41e2ba Tenant Hello wait timeout up to 1 hour (#167)
* feat: tenant Hello wait timeout up to 1 hour

The "Hello Wait Timeout" setting (30..300 s in the UI) had no effect in the
V2 agent: its only consumer, HelloTracker.StartHelloWaitTimer, has no
production caller, while the decision engine armed a fixed 300 s
hello_safety deadline after the ESP exit and the tracker ran a fixed 300 s
completion timer after the wizard start.

The setting is now the real Hello budget, clamped to 300..3600 s
(HelloWaitTimeout in AutopilotMonitor.Shared). Every value that was valid
before (<= 300, including the old 30 s default) keeps today's 5 minutes.

- Engine: the agent stamps helloWaitTimeoutSeconds on EnrollmentFactsObserved
  (only when > 300), recorded set-once in EnrollmentScenarioObservations.
  All six hello_safety arm sites share one builder with the tenant window;
  a fact that arrives after hello_safety was armed moves it out. The WDP
  backstop is never shorter than the tenant window; the signal census
  surfaces hello_wait_extended.
- Agent: the Hello tracker's wizard completion timer uses the same budget;
  the stall probe skips session_stalled while hello_safety is pending.
- Backend: ValidateModel rejects values outside 300..3600; stored rows are
  read as their effective value, so legacy 30 s rows never block unrelated
  saves or backup reverts. Default 30 -> 300; shared manifest and wire types
  regenerated.
- Web: input 300..3600, bounds pinned against the C# constants.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01S17pipjWqjZ8ULAu1JrFyG

* fix: keep the Hello wait's 30 s minimum and default, raise only the maximum

Product decision: the setting keeps its UI contract (30 s minimum and
default, same description); only the maximum moves to 3600 s. Configured
values up to 300 s keep the built-in 5-minute Hello window, larger values
extend it.

- HelloWaitTimeout: MinSeconds/DefaultSeconds 30; BuiltInSeconds 300 is the
  floor of EffectiveSeconds.
- Backend validates only a changed value (30..3600), like the retention cap;
  the read-time normalization is gone again.
- The agent stamps the tenant window only above BuiltInSeconds.
- Web keeps the original text and default; only the maximum and its hint
  change.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01S17pipjWqjZ8ULAu1JrFyG

* fix: Hello wait default 300 s, stale-fire guard, deferred stall report

Product decision: default 300 s (what V2 has always waited), minimum 30 s
still accepted, maximum 3600 s. Values up to 300 s keep the built-in
5-minute window, which the settings hint now says.

Review follow-ups:
- Engine: a hello_safety fire whose incarnation was replaced (armed due
  later than the fire's due time) is dead-ended, same guard as RealmJoin —
  a late tenant window could otherwise be undercut by the queued fire of
  the built-in deadline. The tenant fact is recorded only when it extends
  the built-in window.
- Stall probe: session_stalled is held back while a tenant-extended Hello
  wait (hello_safety or the WDP backstop) is pending and emitted once it is
  over and the session is still idle; real activity drops it. Default
  tenants never hit the guard.
- Comments: WDP backstop wording, 5-min reference, AgentConfiguration.
- Web pins the default against the shared manifest; tracker constructor
  defaults use HelloWaitTimeout.DefaultSeconds.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01S17pipjWqjZ8ULAu1JrFyG

---------

Co-authored-by: Claude <noreply@anthropic.com>
2026-09-30 15:36:36 +02:00
Oliver Kieselbach ec3eaa6437 fix(web): sitemap and llms.txt list public pages at their canonical /page/ URL (#166) 2026-09-29 14:13:59 +02:00
Oliver Kieselbach d36c9a659d Merge branch 'main' of https://github.com/okieselbach/AutopilotMonitor 2026-09-29 12:02:55 +02:00
Oliver Kieselbach c27dbf2aa2 bump pointer: internal (client role gates plan)
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-29 11:46:31 +02:00
Oliver Kieselbach 28443d11fa fix(web): don't send SignalR joins and bootstrap reads the backend refuses by role
Every visit by a user without a tenant role joined the tenant broadcast
group before the dashboard redirected to /progress, and the settings
area loaded the bootstrap session list for every role. The backend
refused both by rule: 134 AddToGroup 403s, 19 serial-less session
joins and 27 bootstrap-list 403s in 30 days, each one also an
api_request_failed event.

- lib/signalrGroupAccess mirrors the backend's same-tenant join rules
  (broadcast and member-notify groups need an own role or platform
  scope, the admin-notify group the Admin or a GA, a session group a
  serial for roleless callers, global-admins platform scope, an
  unparseable name is a 400). Cross-tenant joins stay with the
  backend's platform/delegated admission. The gate keys on
  hasOwnTenantOrPlatformRole, not hasTenantReadScope: a delegated admin
  without an own role is refused the home tenant's groups too.
- SignalRContext.joinGroup applies it before sending: a refused join
  reaches onDenied with the server's status and is never held for the
  reconnect rejoin. joinGroup depends on the caller's role fields as
  primitives, so its identity changes only when auth/me resolves, the
  same commit that delivers the tenant id consumers join with. That
  also ends the "tenant-" join the dashboard sent before the tenant id
  was known (the 400s).
- TenantConfigContext loads bootstrap sessions only for
  canManageBootstrapSessions (mirror of BootstrapManagerOrGA: GA, Admin,
  Operator with the bootstrap grant).

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-29 11:46:05 +02:00
Oliver Kieselbach 0c41b0f449 bump pointer: .claude/commands (ux-insights rejoin baseline)
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-29 11:22:44 +02:00
Oliver Kieselbach 7799fdb725 bump pointer: .claude/commands (platform-health baselines 2026-09-29)
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-29 11:04:44 +02:00
dependabot[bot] 0c1a2a4192 chore(deps-dev): bump tsx (#165)
Bumps the mcp group in /src/McpServer/autopilot-monitor-mcp with 1 update: [tsx](https://github.com/privatenumber/tsx).


Updates `tsx` from 4.23.13 to 4.23.15
- [Release notes](https://github.com/privatenumber/tsx/releases)
- [Changelog](https://github.com/privatenumber/tsx/blob/master/release.config.cjs)
- [Commits](https://github.com/privatenumber/tsx/compare/v4.23.13...v4.23.15)

---
updated-dependencies:
- dependency-name: tsx
  dependency-version: 4.23.15
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: mcp
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-09-29 11:00:30 +02:00
Oliver Kieselbach 0356770ae8 bump pointer: internal (operator group customer label, D-300)
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-29 10:58:39 +02:00
Oliver Kieselbach 3a9a074be1 Operator groups can carry a customer label; customers still never see the group name
Under D-267 a managed tenant saw every operator-provisioned access as
"Platform support". A customer in two operator groups run by different teams
got two identical entries under "Who can read this tenant" and could not tell
them apart.

A Global Admin can now give an operator group a customer label (at most 80
characters) with the existing PATCH global/tenant-groups/{groupId}, which takes
only the fields to change: name and/or customerLabel, where an empty label
clears it. The customer sees the label as the entry name; a group without one
and direct grants keep "Platform support". Owned (self-service) groups refuse a
label with 409, because their customers see the managing tenant's name.
RenameTenantGroupAsync becomes UpdateTenantGroupAsync, one read-modify-write on
the meta row.

DelegationSelfService.CustomerNameOf is now the one source of the name a
customer sees for a group. The managers view and the Group detail of the audit
rows written under every member tenant (assign, unassign, group delete) both use
it. Those audit rows carried the internal group name to the customer's audit
page before this change.

Admin, Tenant Groups: operator groups show "Customers see: ..." and get a
"Customer label" button next to Rename. The customer page is unchanged.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-29 10:58:25 +02:00
Oliver Kieselbach a3870e7dcc bump pointer: internal (pre-provisioning seal + registration failure reason live)
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-29 01:59:09 +02:00
Oliver Kieselbach 8cb5d42edc Seal pre-provisioning despite IME's AccountSetup line; report why a session never registered
WhiteGlove seal: IME reports "EspPhase: AccountSetup" from the device session
as soon as the device apps are done when the user ESP is enabled. The sealing
classifier counted that as user activity (-40) and left pre-provisioning Part 1
unsealed, so no whiteglove.complete marker was written and the Part-2 boot hit
the 48-hour emergency break. The excluder now also needs the ESP registry to
show AccountSetup progress: the adapter stamps accountSetupProgress on
WhiteGloveShellCoreSuccess, only after the tracker has actually read the
AccountSetup subcategories (unread is never "untouched"). The new excluder is a
subset of the old one, so a verdict can only rise; signals without the key
score as before. WhiteGloveShellCoreSuccess v1 gets its replay fixture.

The Part-1 exit writes whiteglove.complete as its first step instead of after
analyzers, diagnostics upload, grace and drain, where a reseal could kill the
process before it.

Registration failures: a run that exits because register-session failed
records outcome, last error, network link at start and end, the wall-clock
attempt window and the config-fetch outcome in registration-failure.json. The
emergency break sends the record as AgentErrorReport.PriorRegistrationFailure
(null for older agents); the backend bounds it and adds it to the ops details,
the App Insights dimensions and the timeline data. Retry behaviour is unchanged.

The raw registry trigger whiteglove_signal_* is renamed save_result_step_*: the
step exists in every 25H2 enrollment and the old name read as a
pre-provisioning signal.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
v2.0.1470
2026-09-29 01:38:45 +02:00
Oliver Kieselbach fa637369e1 bump pointer: internal (emergency break session age live)
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-29 00:05:44 +02:00
Oliver Kieselbach edf30d35e0 Emergency break reports the session age; a break without a session row is Info
The agent measured the session age at its 48 h break but never sent it.
AgentErrorReport now carries SessionAgeHours (optional, null for older
agents), and the backend puts it into the ops details, the timeline data and
the App Insights dimension. It is information only: the backend cannot derive
it (no row, or a row registered seconds before the break after a hibernate
resume), and it never feeds the severity.

The backend now reads the Sessions row once for the owner binding and the
break verdict. A report for a session without a row (in practice one that
never registered) is Info and writes no timeline event and no EventType index
row: nothing can close or open that session, and the orphan sweep deleted the
event a day later with a Warning of its own. A row under cascade delete gets
nothing written past the lock. Terminal stays Info with lateCleanup; an open
status or a failed read stays Warning with the historical write. Break
materialization now requires GUID ids.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
v2.0.1469
2026-09-28 23:40:36 +02:00
Oliver Kieselbach 3b4228e3a1 bump pointer: internal (session-owner binding stage 2 live)
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-28 23:38:45 +02:00
Oliver Kieselbach a029d69d5e Session-owner binding stage 2: refuse sessions bound to another device
Each Sessions row is bound to the device identity that registered it. The
binding ran in shadow; it now refuses every foreign-identity outcome:
register-session answers 403 with session_owner_mismatch before anything is
written, agent telemetry 403 in the error envelope (SessionOwnerMismatch),
the hosted diagnostics upload URL 403. The error channel stays 200 and only
skips its write into the named session. An evaluation defect fails open.

The agent already rotates its session id on the 403 at registration; it now
does the same for the coded 409 (session id claimed by another tenant) instead
of retrying six times and exiting, and fails without retry on any other coded
409 (BackendRefusalException, never an auth failure). A snapshot restored
after a rotation adopts the current session id.

A guard test pins the four call sites and fails when a site observes the
binding without acting on the refusal. Privacy page: session binding under
how data is protected.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
v2.0.1468
2026-09-28 23:12:18 +02:00
Oliver Kieselbach daae1ce1fa bump pointer: internal (paged cross-partition merge released)
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-28 21:02:35 +02:00
Oliver Kieselbach d90cf24508 bump pointer: internal (D-294/D-295 paged cross-partition merge)
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-28 20:53:35 +02:00
Oliver Kieselbach 32f1fd4132 Click-next lists change page only after the page loaded
The audit log, ops events, rule submissions and session reports each
carried a copy of the same paging state. Next bumped the page number and
cursor stack before the request and kept them when it failed, so a failed
request showed "Page 3" over the rows of page 1.

useCursorPager (pure transitions in lib/cursorPager) owns rows, cursors
and the stack; the page number is derived from the stack. A move commits
only after its page loaded, and only the latest move commits. The fetch is
read through useLatest, which retires the per-page workarounds for the
token getter's changing identity.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-28 20:53:35 +02:00
Oliver Kieselbach ffb09e60fa Global audit and ops-event pages continue from one RowKey watermark
The cross-partition pages (audit log over all tenants, ops events over
all categories) kept one continuation entry per partition that still had
rows. On the tenant fan-out the nextLink outgrew the host's URL limit:
the follow-up request was rejected with HTTP 414 before the function ran,
so Next never loaded a second page for a Global Admin.

The continuation is now the (RowKey, Partition) of the last returned row.
Every page reads each partition through BoundedFanOut (pageSize + 1 rows
after the watermark) and merges by (RowKey, Partition), which is the
display order in both tables. Partitions after the watermark's partition
continue with `RowKey ge`, the others with `RowKey gt`, so equal ops
RowKeys across categories survive a page cut. The merge no longer sorts
by the resolved OccurredUtc while advancing by RowKey, which could skip
rows where the two disagree.

The fan-out was unbounded (Task.WhenAll over every tenant); it now runs
with the shared cross-tenant concurrency ceiling like the session list.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-28 20:53:35 +02:00
Oliver Kieselbach f920b6228e fix(web): suspended and offboarded tenants get their page again (#164) 2026-09-28 18:23:28 +02:00
Oliver Kieselbach 1765773116 bump pointer: internal (RealmJoin app install rows released)
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-28 18:21:38 +02:00