- .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
A domain typed into the cross-tenant filter only became a tenant ID when a suggestion was
picked; submitted raw it was dropped by the fetch (not a GUID) while the client scope compared
it against session tenant IDs, leaving zero rows and "No sessions found for this tenant yet".
Submit now resolves GUID / exact domain / unique substring, updates the box to the ID, fetches
with the resolved value, and the empty state says "No tenant matches" for unresolvable input.
The search box never matched the tenant column: tenant ID and domain are now part of the
searchable text and the suggestion dropdown (cross-tenant only), sourced from one
tenantId-to-domain map shared by table and filter.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Two tenants clicked the migration banner, consented the new app, and were refused by the
role-superset probe without ever learning why: the previous app's service principal holds a
tenant-side add-on grant (DeviceManagementScripts.Read.All) that no re-consent can produce, and
the grant command on the Optional Graph capabilities page pre-fills the homed (previous) app.
Backend: the probe now separates manifest roles (still propagating after consent ⇒ transient,
the retry converges) from add-on roles (permanent ⇒ MissingRoles names them). consent-status and
access-check carry appHomingPending + appHomingMissingRoles; the manual flip's probe payload
carries missingRoles. The access-check invalidates the Graph caches BEFORE the homing probe so
"Detect existing access" judges a fresh primary token after the admin's grant.
Web: TenantConfigContext learns the blocking roles from either response and the post-consent
poll keeps going while the switch is still converging (previously it stopped on the first
legacy-side reconcile and left the tenant un-switched). Settings → Autopilot Validation shows a
"One more step" banner with the roles, the grant command pre-filled with the NEW app's client id
and -Permissions, and the re-check button; the admin flip error names the roles too.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
The agent's 48h emergency break is wall-clock, so devices powered on after a shelf
period fire it days after the sweep already terminalized the session (2026-09-03
audit: 224 of 269 breaks in three days were such late cleanups from one kiosk tenant,
all Warning, burying the real ones).
- ReportAgentErrorFunction.ClassifyEmergencyBreak: session already terminal
(Succeeded/Failed/Incomplete) => Info + lateCleanup=true; open, unknown or
unreadable => Warning (historical semantics). Row read is best-effort.
- OpsEventService.RecordAgentEmergencyBreakAsync takes the severity and stamps
sessionStatusAtBreak/lateCleanup into message and details.
- Timeline event agent_emergency_break stays Warning (classifier rule 3 keys on type).
- SessionOwnerBindingObserver: document that the ops-event throttle is per Function
instance (IMemoryCache) and deliberately so.
- Tests for both branches, the fail-safe path and the pure rule; docs submodule bumped.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
The Performance Map gets a "Color by" selector (enrollment duration, success rate,
API latency, DO peer efficiency, App-Load-Score) and a legend under the map. One
bucket definition per mode (mapColorModes.ts) feeds both the Leaflet markers and
the Location Performance table badges, so map, legend and table share thresholds.
The map's duration scale moves to the table's 0.8/1.0/1.2/1.5 ladder.
Also: page.tsx uses the generated wire types instead of drifted local interfaces,
formatThroughput comes from lib/formatting, marker tooltips show latency and DO share.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
The four README screenshots were still from the private preview (old blue
logo, preview banner). Retaken in the light theme with the current logo,
tenant view only, no banner. Fleet Health uses the 90-day window; the geo
map has too few locations in the own tenant to read as a world map.
Also stores the GitHub social preview as source (HTML composition plus the
anonymized dashboard capture it embeds) and the rendered 1280x640 PNG.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
- Root LICENSE is now the verbatim AGPL-3.0 text so GitHub detects the license
instead of showing "Other"; the split model (MIT for Agent + Shared) is
explained in the README and each component keeps its own license file.
- README: MCP server in the component list, an "AI Integration (MCP)" section
with example prompts and the server URL, and a neutral Feedback section.
- SECURITY.md routes reports to private GitHub Security Advisories.
- CONTRIBUTING.md is a feedback routing page (issues, discussions, security,
Report Session button), CODE_OF_CONDUCT.md is short and neutral.
- Issue forms for bug reports and feature requests; blank issues disabled.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
- /about: "Common Questions" section (15 user problems → feature answers) from one source array, mirrored as FAQPage JSON-LD
- root layout: SoftwareApplication JSON-LD becomes an @graph with Person (author/publisher), Organization glueckkanja AG (provider), WebSite, featureList, softwareHelp
- public/llms.txt with portal, docs, and source links
- robots: explicit allow block for GPTBot, OAI-SearchBot, ChatGPT-User, ClaudeBot, Claude-SearchBot, Claude-User, PerplexityBot, Perplexity-User, Google-Extended
- drop the unverified "policy conflicts" claim from the analyze-rules feature card
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
- TenantConfiguration.McpUsagePlanOverride: a SectionUsagePlans plan name pinned to a whole
tenant (members' default user plan + organization windows); precedence per-user override >
tenant override > edition default; never changes the edition
- PATCH config/{tenantId}/plan accepts mcpUsagePlan (validated against the definitions,
400 on unknown names; null/blank clears), audited, echoed as mcpUsagePlan/mcpUsagePlanOverride
- Tenant Management: "MCP usage plan (override)" select fed by global/config/plan-tiers
- Quota 429: every Community block now says Community is sized for occasional use and Pro
lifts the window; managed-tenant block names whose plan governs it
- Field in BaseDeniedFields/RevertProtectedFields/projection, Store/Map, manifests regenerated
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Phase A — the budget follows the data
- A delegated (MSP) read is charged to the MANAGED tenant's organization MCP budget and checked against
its plan; the caller's own budget still follows the home tenant. 429 names the managed tenant (domain)
and the Pro upgrade path; new McpQuotaExceededResponse.targetTenantId.
- Bounded fleet aggregate (global/sessions, global/stats/sessions): exhausted managed tenants are excluded
softly — RequestContext.AllowedTenantIds is narrowed (never widened, never null; pinned) and echoed as
quotaExcludedTenants. Global Admins stay exempt and are tracked on their home tenant only.
- Tenant Group flag ChargeHomeTenantQuota (PATCH global/tenant-groups/{id}, audited per tenant): reads
through the group are charged to the assignee's home tenant (operator-managed service groups).
- McpTenantUsage rows carry UserPrincipalName / HomeTenantId / LastRequestAt; new
GET metrics/mcp-usage/organization (budget by account incl. delegated readers) rendered on MCP Usage.
- MCP: get_fleet_overview (delegated + platform) over the two subset-tier routes; error handler names
the managed tenant. Other delegated tools stay tenant-mandatory.
Phase B — delegated tenant slots
- EditionEntitlements.MaxDelegatedTenants (Community 0 / Pro 2), TenantConfiguration.MaxDelegatedTenantsOverride
(GA-only via PATCH config/{tenantId}/plan { maxDelegatedTenants }), DelegatedSlotService counting distinct
managed tenants per home tenant through identity bindings (+ pending invitations + 24 h holds), enforced
fresh on grant / group assign / add-tenant with 409 DelegatedSlotLimitReached; GA UI offers
"raise to N & retry"; GET global/delegated-slots/{tenantId}; feature-flags entitlements.maxDelegatedTenants.
Phase C — self-service delegation
- One implicit Tenant Group msp-{tenantId} per managing tenant (OwnerTenantId on the meta row);
DelegationInvitations table (invitations + slot release holds); DelegationInviteTicket (HMAC, 7 days,
locates the row only — the accepting tenant is always the JWT tenant; one-shot via ETag).
- DelegationSelfService: ordered accept chain (distinct codes), remove/revoke with 24-hour slot hold, dual
audit and live-stream disconnect, assignees bound to tenant membership, "who manages my tenant" view;
GA release-hold escape hatch. No delegations/* route carries {tenantId} (pinned).
- Web: Settings → Tenant → Delegated Access, /delegations/accept?token=, AuthGate keeps the deep link
across the activation detour, Tenant Management holds + release, owned groups read-only in GA UI.
- Offboarding wipes both sides (documented two-bucket membership); 90-day sweep of terminal rows.
- Trust pages (terms, privacy) and generated wire types updated; docs submodule bumped.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
CodeQL flagged the re-save in AuthGate (js/clear-text-storage-of-sensitive-data,
alert #82): the value flowed back into sessionStorage from the consumed
return URL. The link only has to survive one more sign-in round trip, so the
re-home path now reads it without clearing and never writes it again.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Session details, diagnosis, annotations, fleet health, geographic performance,
SLA, software, usage metrics, audit log, system health, IME log patterns and the
rules pages link to their guide from the end of the header control group.
Navbar help links use the DOCS_PATHS catalog (service announcements no longer
goes through a redirect).
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
- Backend: the app-homing consent probe now requires the primary app's Graph
roles to be a superset of the legacy app's, so a mere sign-in consent
(User.Read, no application role) can never auto-flip a tenant onto an app
without the device-validation permission.
- Web: dashboard banner for tenants still on the previous app (per-tab
dismiss, docs link); docs links on the settings funnel banner, the flipped
banner and the failed-sign-in screen; immediate re-homing after a redirect
sign-in on the other app (one hop per tab, never after a cross-app
fallback); the www -> portal handover passes the learned app as ?authapp=.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
The dashboard selection could grow across pages without limit and a bulk delete was one click on the red button. The effective selection is now cut to BULK_MAX_TARGETS (100, the largest page size) and every pick path (row, Shift range, page checkbox) fills only up to the remaining room. The delete dialog lists every target and, above BULK_TYPED_CONFIRM_THRESHOLD (10), asks for the word DELETE before the button arms. Block stays one click because it expires after 24 hours.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
CompanyName joins ContactEmail as the tenant contact profile: optional on Community,
both required by the self-service trial start (one 409 ContactProfileRequired verdict
naming every missing part). GA plan assignment stays soft (admin warning). Feature
flags expose companyNameSet as a boolean only; TenantTrialStarted carries companyName.
Web: Contact section field, Plan gate and dashboard banner name what is missing,
admin panel/report/search know the company. Privacy page lists the new datum.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
The organization-quota card called toLocaleString on undefined when the
backend predates the tenant quota (deploy window, local dev against prod),
tripping the error boundary. Tenant fields are optional on the client now;
the card renders only when they are present.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
- SectionCardHeader replaces 33 hand-copied card headers across settings, rules and admin pages
- DocsLink + lib/docsPaths: every tenant-facing section links its documentation page (new tab)
- docsPaths guard test encodes the GitBook URL rule (group slug, not folder name)
- delete dead MaintenanceSection duplicate
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Every X-Client-Source: mcp request is now charged against two budgets: the
caller's own daily/monthly windows (per-user override or tenant edition plan)
and the organization-wide daily/monthly windows of the token's tenant, which
always follow the tenant's edition plan so a per-user override never widens
them. Ten accounts no longer mean ten times the budget.
Tenant counters live in the new McpTenantUsage table (PK = tenantId,
RK = {yyyyMMdd}_{oid}: one partition range per check, no hot row, never a
property scan over UserUsageLog); exact-PK offboarding bucket and 90-day
retention included. PlanTierDefinition gains nullable tenant limits (null =
catalog fallback: Community 300/9000, Pro 3000/60000; 0 = unlimited). The 429
body carries level=user|tenant and a message naming whose budget it is,
X-MCP-Quota-Tenant-* headers ride along, and mcp-usage/me exposes both
budgets (wire types regenerated).
MCP server: the quota 429 body was rendered as Error: undefined because it
has no error key; it now gets a dedicated block with the reset time and a
level-specific suggestion. Web: Usage Plans edits the tenant limits; the
self-service MCP Usage page renders both budgets (the quota node was never
displayed before).
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
McpAccessPolicy.AllMembers returned Allowed for any valid JWT, so a Progress
Portal end-user without a role could open an MCP session. The AllMembers path
now requires an effective role (Admin / Operator / Viewer) in the token's
tenant, resolved by the new TenantMemberRoleResolver that the policy
middleware shares (table first, Entra app-role claim when the tenant opted
in). Explicit grants (platform role, delegated scope, enabled McpUsers row)
and the Disabled-row kill-switch keep working in a policy-independent order;
auth/mcp and auth/me hand the token's app roles through.
The MCP Users settings section shows the user list under AllMembers too, as
the per-user override list (individual usage plan, disable = block).
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
The fixture waited 5 s per posted signal and the scenarios 10 s for a
terminal stage. Those are deadlock guards, not performance assertions:
every applied step performs 5-6 WriteThrough + Flush(true) writes and 2-3
atomic renames synchronously on the ingress worker, and on a cold GitHub
runner volume the first scenarios after the parallel phase still blew the
5 s budget even though the collection is already serialised (run
33581374985: the first four scenarios in run order failed, the other nine
passed; the rerun of the same commit was green). One constant,
EnrollmentOrchestratorFixture.SettleTimeoutMs = 30 s, now covers the
per-signal, terminal-stage and uploader waits.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
`dotnet test --verbosity quiet` also sets the console logger to quiet, which
drops the "Error Message" block of a failed test — run 33581374985 showed
only four "[FAIL]" lines for the V2 Core scenario flake and nothing about
which fixture budget blew. An explicit `--logger "console;verbosity=minimal"`
keeps the build quiet but prints the failed tests with message and stack.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
The server already strips SignalR Quota, Poison Queues and every endpoint URL
for non-Global-Admins, but the page mirrored the Global-Admin VIEW only for the
build blocks, so demo mode still showed the full operator report. The new
lib/healthCheckView.ts applies the same rule presentation-side (cards + URL
detail rows), and both card render paths share one HealthDetails component
that puts URL values on their own line under the label instead of wrapping
them right-aligned next to it.
The healthy agent-binaries message no longer advertises the legacy blob; that
account stays in the Global-Admin-only details.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
The per-client opt-in stays available on the server; this repo's .mcp.json no longer sets it,
so operator sessions run compact too and pay no indentation tokens. Set the header in your own
client config when you need to read raw results.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>