Oliver Kieselbach
12a75ef13d
feat(delegation): MCP quota follows the managed tenant, delegated tenant slots, self-service delegation
...
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 >
2026-09-02 21:37:36 +02:00
..
2026-07-31 18:12:25 +02:00
2026-05-19 18:45:01 +02:00
2026-09-01 16:57:41 +02:00
2026-05-28 17:15:04 +02:00
2026-05-28 12:19:54 +02:00
2026-07-28 21:42:56 +02:00
2026-07-31 14:30:57 +02:00
2026-04-21 11:48:27 +02:00
2026-04-30 22:54:30 +02:00
2026-08-18 00:20:53 +02:00
2026-05-13 01:08:04 +02:00
2026-08-03 14:30:05 +02:00
2026-07-31 19:52:23 +02:00
2026-05-15 12:59:40 +02:00
2026-04-21 10:27:54 +02:00
2026-09-02 10:03:10 +02:00
2026-08-29 18:35:47 +02:00
2026-08-29 19:21:47 +02:00
2026-08-31 15:22:16 +02:00
2026-08-20 01:24:59 +02:00
2026-08-20 01:24:59 +02:00
2026-07-18 17:48:48 +02:00
2026-08-14 15:53:02 +02:00
2026-07-29 01:44:32 +02:00
2026-08-29 17:55:50 +02:00
2026-07-08 18:22:41 +02:00
2026-04-14 21:51:57 +02:00
2026-08-31 02:30:39 +02:00
2026-08-31 02:30:39 +02:00
2026-07-31 19:52:23 +02:00
2026-09-02 16:18:45 +02:00
2026-07-07 21:33:57 +00:00
2026-08-20 01:25:28 +02:00
feat(backend,mcp): typed API contract COMPLETED — every remaining top-level success body is a manifest-exported wire DTO, and the guard now catches the bypass shapes that let them escape. The 08-31 migration's ratchet only saw inline WriteAsJsonAsync(new {...}); four bypass classes had kept ~20 endpoint families untyped: (A) Shared classes without IApiResponse — GeographicMetricsResponse, FleetHealthMetrics, ImePatternHealthResponse, SlaMetricsResponse, PlatformUsageMetrics now implement it (wire byte-identical, interface is empty); (B) Functions-local named response families moved verbatim to Shared — PlatformAgentMetricsResponse/SessionAgentMetric/DeliveryLatencyMetrics/CrashRateMetrics/CrashExceptionSummary, AgentEfficiencyMetricsResponse family (CloneAsCacheHit relocated into the service; init→set for netstandard2.0), OffboardResponse; (C) anonymous/object builders converted to DTOs with old-vs-new wire-parity facts (TypedContractCompletionWireParityTests, 14 facts incl. every vanishing-key case): rule-stats x2 (RuleStatsResponse; uniqueRules is global-only via an optional slot at its exact wire position), verdict-calibration (builder renamed VerdictCalibrationResponseBuilder, PathRow/TrendWindow to Shared), metrics/app (MetricsMath), apps list/analytics/sessions x6 routes (AppsAnalyticsHelper; legacy + paged list share ONE DTO via optional paging keys), ime-versions lean projection (ImeVersionHistoryLeanEntry [WireContract]; bare-array envelope stays, typed as IReadOnlyList<object>), device-journeys + time-attribution fleet envelopes, feature-flags, diagnostics/paths, session restore, cascade-delete Enqueued arm (error arms stay anonymous by design), feedback eligibility/list; (D) Serialize+WriteString path — ListBackupsResponse under the backup surface's own serializer options; AppInsightsQuery's Serialize is an outgoing REQUEST body (not response debt, baselined as such). TypedResponseGuardTests gains three ratchets with mutation-proven red/green: WriteAsJsonAsync(<variable assigned an anonymous object>) (empty baseline), JsonSerializer.Serialize(new{ (error-tolerant; 1 documented request-body entry), and object/Task<object>-returning Build*/Compute*/*Payload/*Response methods (1 documented union entry); the local-WriteJson-wrapper class is closed structurally (generic T : IApiResponse — an interface-typed parameter would serialize zero properties, the generic keeps the concrete type). Manifest regenerated 208→298 exported types; MCP adopts the new types (get_rule_stats typed with the trend-drop as explicit output shaping, get_platform_metrics drops its hand-declared mirror for PlatformAgentMetricsResponse, verdict-calibration-shape derives its rows via extends Omit<generated wire row> so backend renames go tsc-red). Six backend shape-test files switched from default-options serialization to the production wire options via TestWire (ApiJsonOptions; the old default-options runs only matched while payloads were anonymous with lowercase members), and three assertions that pinned null-VALUED keys now pin key ABSENCE — production always omitted them (WhenWritingNull); the parity facts prove the wire itself is unchanged. Deliberately still untyped: auth/me, CheckMcpAccess dictionary, TenantAdmins/GlobalAdmins entity-row arrays and the object-slot payloads (RuleDryRun, HealthCheck, TenantConfigFieldSchema, ...) — the documented read-model follow-up. Suites: Backend 5323, DecisionCore 577, Agent 2805, SummaryDialog 44, MCP 617, Web tsc 0 + vitest 957, generator idempotent.
2026-08-31 04:32:51 +02:00
2026-04-27 19:44:13 +02:00
2026-08-21 13:40:47 +02:00
feat(backend,mcp): typed API contract COMPLETED — every remaining top-level success body is a manifest-exported wire DTO, and the guard now catches the bypass shapes that let them escape. The 08-31 migration's ratchet only saw inline WriteAsJsonAsync(new {...}); four bypass classes had kept ~20 endpoint families untyped: (A) Shared classes without IApiResponse — GeographicMetricsResponse, FleetHealthMetrics, ImePatternHealthResponse, SlaMetricsResponse, PlatformUsageMetrics now implement it (wire byte-identical, interface is empty); (B) Functions-local named response families moved verbatim to Shared — PlatformAgentMetricsResponse/SessionAgentMetric/DeliveryLatencyMetrics/CrashRateMetrics/CrashExceptionSummary, AgentEfficiencyMetricsResponse family (CloneAsCacheHit relocated into the service; init→set for netstandard2.0), OffboardResponse; (C) anonymous/object builders converted to DTOs with old-vs-new wire-parity facts (TypedContractCompletionWireParityTests, 14 facts incl. every vanishing-key case): rule-stats x2 (RuleStatsResponse; uniqueRules is global-only via an optional slot at its exact wire position), verdict-calibration (builder renamed VerdictCalibrationResponseBuilder, PathRow/TrendWindow to Shared), metrics/app (MetricsMath), apps list/analytics/sessions x6 routes (AppsAnalyticsHelper; legacy + paged list share ONE DTO via optional paging keys), ime-versions lean projection (ImeVersionHistoryLeanEntry [WireContract]; bare-array envelope stays, typed as IReadOnlyList<object>), device-journeys + time-attribution fleet envelopes, feature-flags, diagnostics/paths, session restore, cascade-delete Enqueued arm (error arms stay anonymous by design), feedback eligibility/list; (D) Serialize+WriteString path — ListBackupsResponse under the backup surface's own serializer options; AppInsightsQuery's Serialize is an outgoing REQUEST body (not response debt, baselined as such). TypedResponseGuardTests gains three ratchets with mutation-proven red/green: WriteAsJsonAsync(<variable assigned an anonymous object>) (empty baseline), JsonSerializer.Serialize(new{ (error-tolerant; 1 documented request-body entry), and object/Task<object>-returning Build*/Compute*/*Payload/*Response methods (1 documented union entry); the local-WriteJson-wrapper class is closed structurally (generic T : IApiResponse — an interface-typed parameter would serialize zero properties, the generic keeps the concrete type). Manifest regenerated 208→298 exported types; MCP adopts the new types (get_rule_stats typed with the trend-drop as explicit output shaping, get_platform_metrics drops its hand-declared mirror for PlatformAgentMetricsResponse, verdict-calibration-shape derives its rows via extends Omit<generated wire row> so backend renames go tsc-red). Six backend shape-test files switched from default-options serialization to the production wire options via TestWire (ApiJsonOptions; the old default-options runs only matched while payloads were anonymous with lowercase members), and three assertions that pinned null-VALUED keys now pin key ABSENCE — production always omitted them (WhenWritingNull); the parity facts prove the wire itself is unchanged. Deliberately still untyped: auth/me, CheckMcpAccess dictionary, TenantAdmins/GlobalAdmins entity-row arrays and the object-slot payloads (RuleDryRun, HealthCheck, TenantConfigFieldSchema, ...) — the documented read-model follow-up. Suites: Backend 5323, DecisionCore 577, Agent 2805, SummaryDialog 44, MCP 617, Web tsc 0 + vitest 957, generator idempotent.
2026-08-31 04:32:51 +02:00
feat(backend,mcp): typed API contract COMPLETED — every remaining top-level success body is a manifest-exported wire DTO, and the guard now catches the bypass shapes that let them escape. The 08-31 migration's ratchet only saw inline WriteAsJsonAsync(new {...}); four bypass classes had kept ~20 endpoint families untyped: (A) Shared classes without IApiResponse — GeographicMetricsResponse, FleetHealthMetrics, ImePatternHealthResponse, SlaMetricsResponse, PlatformUsageMetrics now implement it (wire byte-identical, interface is empty); (B) Functions-local named response families moved verbatim to Shared — PlatformAgentMetricsResponse/SessionAgentMetric/DeliveryLatencyMetrics/CrashRateMetrics/CrashExceptionSummary, AgentEfficiencyMetricsResponse family (CloneAsCacheHit relocated into the service; init→set for netstandard2.0), OffboardResponse; (C) anonymous/object builders converted to DTOs with old-vs-new wire-parity facts (TypedContractCompletionWireParityTests, 14 facts incl. every vanishing-key case): rule-stats x2 (RuleStatsResponse; uniqueRules is global-only via an optional slot at its exact wire position), verdict-calibration (builder renamed VerdictCalibrationResponseBuilder, PathRow/TrendWindow to Shared), metrics/app (MetricsMath), apps list/analytics/sessions x6 routes (AppsAnalyticsHelper; legacy + paged list share ONE DTO via optional paging keys), ime-versions lean projection (ImeVersionHistoryLeanEntry [WireContract]; bare-array envelope stays, typed as IReadOnlyList<object>), device-journeys + time-attribution fleet envelopes, feature-flags, diagnostics/paths, session restore, cascade-delete Enqueued arm (error arms stay anonymous by design), feedback eligibility/list; (D) Serialize+WriteString path — ListBackupsResponse under the backup surface's own serializer options; AppInsightsQuery's Serialize is an outgoing REQUEST body (not response debt, baselined as such). TypedResponseGuardTests gains three ratchets with mutation-proven red/green: WriteAsJsonAsync(<variable assigned an anonymous object>) (empty baseline), JsonSerializer.Serialize(new{ (error-tolerant; 1 documented request-body entry), and object/Task<object>-returning Build*/Compute*/*Payload/*Response methods (1 documented union entry); the local-WriteJson-wrapper class is closed structurally (generic T : IApiResponse — an interface-typed parameter would serialize zero properties, the generic keeps the concrete type). Manifest regenerated 208→298 exported types; MCP adopts the new types (get_rule_stats typed with the trend-drop as explicit output shaping, get_platform_metrics drops its hand-declared mirror for PlatformAgentMetricsResponse, verdict-calibration-shape derives its rows via extends Omit<generated wire row> so backend renames go tsc-red). Six backend shape-test files switched from default-options serialization to the production wire options via TestWire (ApiJsonOptions; the old default-options runs only matched while payloads were anonymous with lowercase members), and three assertions that pinned null-VALUED keys now pin key ABSENCE — production always omitted them (WhenWritingNull); the parity facts prove the wire itself is unchanged. Deliberately still untyped: auth/me, CheckMcpAccess dictionary, TenantAdmins/GlobalAdmins entity-row arrays and the object-slot payloads (RuleDryRun, HealthCheck, TenantConfigFieldSchema, ...) — the documented read-model follow-up. Suites: Backend 5323, DecisionCore 577, Agent 2805, SummaryDialog 44, MCP 617, Web tsc 0 + vitest 957, generator idempotent.
2026-08-31 04:32:51 +02:00
2026-07-27 14:50:55 +02:00
feat(backend,mcp): typed API contract COMPLETED — every remaining top-level success body is a manifest-exported wire DTO, and the guard now catches the bypass shapes that let them escape. The 08-31 migration's ratchet only saw inline WriteAsJsonAsync(new {...}); four bypass classes had kept ~20 endpoint families untyped: (A) Shared classes without IApiResponse — GeographicMetricsResponse, FleetHealthMetrics, ImePatternHealthResponse, SlaMetricsResponse, PlatformUsageMetrics now implement it (wire byte-identical, interface is empty); (B) Functions-local named response families moved verbatim to Shared — PlatformAgentMetricsResponse/SessionAgentMetric/DeliveryLatencyMetrics/CrashRateMetrics/CrashExceptionSummary, AgentEfficiencyMetricsResponse family (CloneAsCacheHit relocated into the service; init→set for netstandard2.0), OffboardResponse; (C) anonymous/object builders converted to DTOs with old-vs-new wire-parity facts (TypedContractCompletionWireParityTests, 14 facts incl. every vanishing-key case): rule-stats x2 (RuleStatsResponse; uniqueRules is global-only via an optional slot at its exact wire position), verdict-calibration (builder renamed VerdictCalibrationResponseBuilder, PathRow/TrendWindow to Shared), metrics/app (MetricsMath), apps list/analytics/sessions x6 routes (AppsAnalyticsHelper; legacy + paged list share ONE DTO via optional paging keys), ime-versions lean projection (ImeVersionHistoryLeanEntry [WireContract]; bare-array envelope stays, typed as IReadOnlyList<object>), device-journeys + time-attribution fleet envelopes, feature-flags, diagnostics/paths, session restore, cascade-delete Enqueued arm (error arms stay anonymous by design), feedback eligibility/list; (D) Serialize+WriteString path — ListBackupsResponse under the backup surface's own serializer options; AppInsightsQuery's Serialize is an outgoing REQUEST body (not response debt, baselined as such). TypedResponseGuardTests gains three ratchets with mutation-proven red/green: WriteAsJsonAsync(<variable assigned an anonymous object>) (empty baseline), JsonSerializer.Serialize(new{ (error-tolerant; 1 documented request-body entry), and object/Task<object>-returning Build*/Compute*/*Payload/*Response methods (1 documented union entry); the local-WriteJson-wrapper class is closed structurally (generic T : IApiResponse — an interface-typed parameter would serialize zero properties, the generic keeps the concrete type). Manifest regenerated 208→298 exported types; MCP adopts the new types (get_rule_stats typed with the trend-drop as explicit output shaping, get_platform_metrics drops its hand-declared mirror for PlatformAgentMetricsResponse, verdict-calibration-shape derives its rows via extends Omit<generated wire row> so backend renames go tsc-red). Six backend shape-test files switched from default-options serialization to the production wire options via TestWire (ApiJsonOptions; the old default-options runs only matched while payloads were anonymous with lowercase members), and three assertions that pinned null-VALUED keys now pin key ABSENCE — production always omitted them (WhenWritingNull); the parity facts prove the wire itself is unchanged. Deliberately still untyped: auth/me, CheckMcpAccess dictionary, TenantAdmins/GlobalAdmins entity-row arrays and the object-slot payloads (RuleDryRun, HealthCheck, TenantConfigFieldSchema, ...) — the documented read-model follow-up. Suites: Backend 5323, DecisionCore 577, Agent 2805, SummaryDialog 44, MCP 617, Web tsc 0 + vitest 957, generator idempotent.
2026-08-31 04:32:51 +02:00
2026-06-05 21:33:34 +02:00
2026-06-06 11:36:39 +02:00
2026-05-06 20:53:46 +02:00
2026-08-15 09:42:24 +02:00
2026-07-31 18:12:25 +02:00
2026-09-02 10:03:10 +02:00
2026-08-31 15:22:16 +02:00
2026-06-18 18:41:34 +02:00
2026-09-01 23:37:42 +02:00
2026-08-13 09:38:41 +02:00
2026-07-27 18:39:22 +02:00
2026-08-29 17:23:05 +02:00
2026-04-25 12:26:34 +02:00
2026-05-26 15:19:07 +02:00
2026-05-26 15:19:07 +02:00
2026-05-26 16:33:46 +02:00
2026-08-29 22:51:13 +02:00
2026-05-12 19:13:21 +02:00
2026-05-12 23:44:20 +02:00
2026-09-01 23:37:41 +02:00
2026-09-01 23:37:41 +02:00
2026-08-29 20:04:04 +02:00
2026-08-29 20:04:04 +02:00
2026-07-28 21:42:56 +02:00
2026-09-02 02:55:18 +02:00
2026-08-29 23:15:07 +02:00
2026-08-21 14:13:46 +02:00
2026-08-25 12:27:42 +02:00
2026-07-20 11:29:53 +02:00
2026-09-01 22:35:46 +02:00
2026-07-20 11:29:53 +02:00
2026-05-13 12:08:28 +02:00
2026-05-20 19:54:43 +02:00
2026-08-25 12:45:21 +02:00
2026-09-02 00:13:59 +02:00
2026-09-01 13:53:59 +02:00
2026-09-01 16:57:41 +02:00
2026-08-03 14:30:05 +02:00
2026-08-03 14:30:05 +02:00
2026-08-03 14:30:05 +02:00
2026-09-02 21:37:36 +02:00
2026-07-28 14:25:17 +02:00
2026-04-02 01:31:47 +02:00
2026-08-06 13:51:25 +02:00
2026-09-02 00:13:59 +02:00
2026-07-08 14:02:43 +02:00
2026-08-29 18:35:47 +02:00
2026-05-26 16:33:46 +02:00
2026-08-15 09:42:24 +02:00
2026-09-02 02:55:18 +02:00
2026-05-27 12:44:45 +02:00
2026-08-29 11:07:31 +02:00
2026-05-06 20:53:46 +02:00
2026-05-04 18:36:37 +02:00
2026-09-02 21:37:36 +02:00
2026-09-02 21:37:36 +02:00
2026-06-26 19:13:36 +02:00
2026-09-02 21:37:36 +02:00
2026-09-02 21:37:36 +02:00
2026-09-02 21:37:36 +02:00
2026-09-02 21:37:36 +02:00
feat(backend,mcp): typed API contract COMPLETED — every remaining top-level success body is a manifest-exported wire DTO, and the guard now catches the bypass shapes that let them escape. The 08-31 migration's ratchet only saw inline WriteAsJsonAsync(new {...}); four bypass classes had kept ~20 endpoint families untyped: (A) Shared classes without IApiResponse — GeographicMetricsResponse, FleetHealthMetrics, ImePatternHealthResponse, SlaMetricsResponse, PlatformUsageMetrics now implement it (wire byte-identical, interface is empty); (B) Functions-local named response families moved verbatim to Shared — PlatformAgentMetricsResponse/SessionAgentMetric/DeliveryLatencyMetrics/CrashRateMetrics/CrashExceptionSummary, AgentEfficiencyMetricsResponse family (CloneAsCacheHit relocated into the service; init→set for netstandard2.0), OffboardResponse; (C) anonymous/object builders converted to DTOs with old-vs-new wire-parity facts (TypedContractCompletionWireParityTests, 14 facts incl. every vanishing-key case): rule-stats x2 (RuleStatsResponse; uniqueRules is global-only via an optional slot at its exact wire position), verdict-calibration (builder renamed VerdictCalibrationResponseBuilder, PathRow/TrendWindow to Shared), metrics/app (MetricsMath), apps list/analytics/sessions x6 routes (AppsAnalyticsHelper; legacy + paged list share ONE DTO via optional paging keys), ime-versions lean projection (ImeVersionHistoryLeanEntry [WireContract]; bare-array envelope stays, typed as IReadOnlyList<object>), device-journeys + time-attribution fleet envelopes, feature-flags, diagnostics/paths, session restore, cascade-delete Enqueued arm (error arms stay anonymous by design), feedback eligibility/list; (D) Serialize+WriteString path — ListBackupsResponse under the backup surface's own serializer options; AppInsightsQuery's Serialize is an outgoing REQUEST body (not response debt, baselined as such). TypedResponseGuardTests gains three ratchets with mutation-proven red/green: WriteAsJsonAsync(<variable assigned an anonymous object>) (empty baseline), JsonSerializer.Serialize(new{ (error-tolerant; 1 documented request-body entry), and object/Task<object>-returning Build*/Compute*/*Payload/*Response methods (1 documented union entry); the local-WriteJson-wrapper class is closed structurally (generic T : IApiResponse — an interface-typed parameter would serialize zero properties, the generic keeps the concrete type). Manifest regenerated 208→298 exported types; MCP adopts the new types (get_rule_stats typed with the trend-drop as explicit output shaping, get_platform_metrics drops its hand-declared mirror for PlatformAgentMetricsResponse, verdict-calibration-shape derives its rows via extends Omit<generated wire row> so backend renames go tsc-red). Six backend shape-test files switched from default-options serialization to the production wire options via TestWire (ApiJsonOptions; the old default-options runs only matched while payloads were anonymous with lowercase members), and three assertions that pinned null-VALUED keys now pin key ABSENCE — production always omitted them (WhenWritingNull); the parity facts prove the wire itself is unchanged. Deliberately still untyped: auth/me, CheckMcpAccess dictionary, TenantAdmins/GlobalAdmins entity-row arrays and the object-slot payloads (RuleDryRun, HealthCheck, TenantConfigFieldSchema, ...) — the documented read-model follow-up. Suites: Backend 5323, DecisionCore 577, Agent 2805, SummaryDialog 44, MCP 617, Web tsc 0 + vitest 957, generator idempotent.
2026-08-31 04:32:51 +02:00
2026-08-13 23:17:48 +02:00
2026-05-12 19:13:21 +02:00
2026-05-18 23:06:51 +02:00
2026-08-13 23:17:48 +02:00
2026-05-08 15:28:21 +02:00
2026-08-24 20:18:57 +02:00
2026-05-28 17:03:48 +02:00
feat(backend,mcp): typed API contract COMPLETED — every remaining top-level success body is a manifest-exported wire DTO, and the guard now catches the bypass shapes that let them escape. The 08-31 migration's ratchet only saw inline WriteAsJsonAsync(new {...}); four bypass classes had kept ~20 endpoint families untyped: (A) Shared classes without IApiResponse — GeographicMetricsResponse, FleetHealthMetrics, ImePatternHealthResponse, SlaMetricsResponse, PlatformUsageMetrics now implement it (wire byte-identical, interface is empty); (B) Functions-local named response families moved verbatim to Shared — PlatformAgentMetricsResponse/SessionAgentMetric/DeliveryLatencyMetrics/CrashRateMetrics/CrashExceptionSummary, AgentEfficiencyMetricsResponse family (CloneAsCacheHit relocated into the service; init→set for netstandard2.0), OffboardResponse; (C) anonymous/object builders converted to DTOs with old-vs-new wire-parity facts (TypedContractCompletionWireParityTests, 14 facts incl. every vanishing-key case): rule-stats x2 (RuleStatsResponse; uniqueRules is global-only via an optional slot at its exact wire position), verdict-calibration (builder renamed VerdictCalibrationResponseBuilder, PathRow/TrendWindow to Shared), metrics/app (MetricsMath), apps list/analytics/sessions x6 routes (AppsAnalyticsHelper; legacy + paged list share ONE DTO via optional paging keys), ime-versions lean projection (ImeVersionHistoryLeanEntry [WireContract]; bare-array envelope stays, typed as IReadOnlyList<object>), device-journeys + time-attribution fleet envelopes, feature-flags, diagnostics/paths, session restore, cascade-delete Enqueued arm (error arms stay anonymous by design), feedback eligibility/list; (D) Serialize+WriteString path — ListBackupsResponse under the backup surface's own serializer options; AppInsightsQuery's Serialize is an outgoing REQUEST body (not response debt, baselined as such). TypedResponseGuardTests gains three ratchets with mutation-proven red/green: WriteAsJsonAsync(<variable assigned an anonymous object>) (empty baseline), JsonSerializer.Serialize(new{ (error-tolerant; 1 documented request-body entry), and object/Task<object>-returning Build*/Compute*/*Payload/*Response methods (1 documented union entry); the local-WriteJson-wrapper class is closed structurally (generic T : IApiResponse — an interface-typed parameter would serialize zero properties, the generic keeps the concrete type). Manifest regenerated 208→298 exported types; MCP adopts the new types (get_rule_stats typed with the trend-drop as explicit output shaping, get_platform_metrics drops its hand-declared mirror for PlatformAgentMetricsResponse, verdict-calibration-shape derives its rows via extends Omit<generated wire row> so backend renames go tsc-red). Six backend shape-test files switched from default-options serialization to the production wire options via TestWire (ApiJsonOptions; the old default-options runs only matched while payloads were anonymous with lowercase members), and three assertions that pinned null-VALUED keys now pin key ABSENCE — production always omitted them (WhenWritingNull); the parity facts prove the wire itself is unchanged. Deliberately still untyped: auth/me, CheckMcpAccess dictionary, TenantAdmins/GlobalAdmins entity-row arrays and the object-slot payloads (RuleDryRun, HealthCheck, TenantConfigFieldSchema, ...) — the documented read-model follow-up. Suites: Backend 5323, DecisionCore 577, Agent 2805, SummaryDialog 44, MCP 617, Web tsc 0 + vitest 957, generator idempotent.
2026-08-31 04:32:51 +02:00
2026-05-29 16:57:54 +02:00
2026-07-24 18:05:39 +02:00
2026-08-23 04:09:50 +02:00
2026-05-19 18:45:01 +02:00
2026-06-22 21:29:29 +02:00
2026-08-29 19:30:20 +02:00
2026-08-22 21:09:12 +02:00
2026-03-31 17:10:59 +02:00
2026-07-29 10:30:47 +02:00
2026-08-29 22:33:52 +02:00
2026-08-29 22:33:52 +02:00
2026-09-02 21:37:36 +02:00
2026-08-27 18:10:19 +02:00
2026-07-31 18:12:25 +02:00
2026-06-17 18:54:39 +02:00
2026-08-29 11:07:31 +02:00
2026-06-16 17:30:02 +02:00
2026-09-02 02:55:18 +02:00
2026-08-30 12:13:00 +02:00
2026-08-21 13:40:47 +02:00
2026-08-20 01:24:59 +02:00
2026-07-10 10:51:57 +02:00
2026-07-03 16:06:39 +02:00
2026-09-02 21:37:36 +02:00
2026-08-21 12:49:42 +02:00
2026-08-10 13:41:14 +02:00
2026-08-29 23:54:44 +02:00
2026-07-08 18:22:41 +02:00
2026-08-31 02:30:39 +02:00
2026-08-10 13:41:14 +02:00
2026-06-19 14:34:53 +02:00
2026-05-31 15:22:22 +02:00
2026-04-15 00:52:44 +02:00
2026-08-15 14:59:42 +02:00
2026-07-24 09:47:31 +02:00
2026-07-28 11:58:53 +02:00
2026-08-23 04:09:50 +02:00
2026-05-31 14:54:15 +02:00
2026-07-20 13:13:26 +02:00
2026-07-29 01:44:32 +02:00
2026-08-31 02:30:39 +02:00
2026-08-23 04:09:50 +02:00
2026-05-19 18:45:01 +02:00
2026-08-31 02:30:39 +02:00
2026-08-30 11:38:27 +02:00
2026-06-26 23:21:39 +02:00
2026-04-21 10:37:27 +02:00
2026-09-02 21:37:36 +02:00
2026-08-31 02:30:39 +02:00
2026-04-08 13:40:02 +02:00
2026-08-24 18:26:33 +02:00
2026-07-31 19:52:23 +02:00
2026-08-31 02:30:39 +02:00
2026-07-28 21:42:56 +02:00
2026-07-27 09:10:52 +02:00
2026-07-24 20:47:06 +02:00
2026-04-05 13:36:02 +02:00
2026-08-29 22:51:55 +02:00
2026-09-02 03:57:10 +02:00
2026-07-19 11:42:40 +02:00
2026-07-31 18:59:21 +02:00
2026-08-29 23:13:00 +02:00
2026-08-29 17:23:05 +02:00
2026-08-30 11:38:27 +02:00
feat(observability): IME pattern-drift loop — nothing the log tracker skips or stops matching stays silent: the agent keeps cumulative tracker health counters and a per-pattern match histogram (restart-safe in ImeTrackerState), reports ime_* keys in agent_metrics_snapshot (files tailed, backlog bytes, lines read, matches, oversized/timeouts/budget breaks/held tails/unanchored), raises a one-shot immediate-upload ime_tracker_degraded Warning on the first pass that skipped work, and emits ime_pattern_hits at termination (every enabled pattern, zeros included, IME version); the backend folds the histogram into the permanent per-IME-version ImePatternStats table (built-in IDs only, plausible version only, one partition read + one transactional batch) and evaluates drift without a hard-coded list — baseline = the version with the most reporting sessions (≥100), expected = ≥80 % hit rate there, drift = an expected pattern matched in none of ≥25 sessions on a newer version — raising ImePatternDriftSuspected once per version×pattern (ETag-stamped, dual-registered in the web ops-event list with a new guard test); GET metrics/ime-pattern-health (GlobalReadOrAdmin) + Global Admin → Metrics → IME Pattern Health matrix (drift/silent/low/few cells, open alerts, retired patterns) + session-page "IME Tracker" rows (latest snapshot by max sequence) + MCP get_ime_pattern_health (GA/Reader)
2026-08-30 15:12:38 +02:00
2026-04-21 12:29:12 +02:00
2026-04-21 11:48:27 +02:00
2026-04-21 11:48:27 +02:00
2026-04-21 12:03:06 +02:00
2026-04-21 12:29:12 +02:00
2026-04-21 11:29:35 +02:00
2026-09-02 21:37:36 +02:00
2026-07-28 11:44:46 +02:00
2026-07-28 14:25:17 +02:00
2026-08-21 00:44:29 +02:00
2026-07-23 23:43:59 +02:00
2026-07-16 13:24:21 +02:00
2026-09-02 00:13:59 +02:00
2026-06-09 22:43:39 +02:00
2026-07-07 21:33:57 +00:00
2026-08-21 00:44:29 +02:00
2026-07-07 21:33:57 +00:00
2026-08-24 19:18:37 +02:00
2026-09-01 23:37:41 +02:00
2026-04-09 19:50:13 +02:00
2026-07-22 10:31:48 +02:00
2026-08-20 13:12:49 +02:00
2026-05-08 16:16:12 +02:00
2026-07-22 14:43:00 +02:00
2026-07-31 18:59:21 +02:00
2026-05-31 18:09:39 +02:00
2026-07-18 16:38:15 +02:00
2026-08-23 12:19:41 +02:00
2026-09-02 21:37:36 +02:00
2026-09-02 21:37:36 +02:00
2026-09-02 21:37:36 +02:00
2026-09-02 10:03:10 +02:00
2026-09-02 10:03:10 +02:00
2026-09-02 10:03:10 +02:00
2026-06-06 15:39:49 +02:00
2026-04-30 08:45:55 +02:00
2026-05-27 16:52:52 +02:00
feat(backend,mcp): typed API contract COMPLETED — every remaining top-level success body is a manifest-exported wire DTO, and the guard now catches the bypass shapes that let them escape. The 08-31 migration's ratchet only saw inline WriteAsJsonAsync(new {...}); four bypass classes had kept ~20 endpoint families untyped: (A) Shared classes without IApiResponse — GeographicMetricsResponse, FleetHealthMetrics, ImePatternHealthResponse, SlaMetricsResponse, PlatformUsageMetrics now implement it (wire byte-identical, interface is empty); (B) Functions-local named response families moved verbatim to Shared — PlatformAgentMetricsResponse/SessionAgentMetric/DeliveryLatencyMetrics/CrashRateMetrics/CrashExceptionSummary, AgentEfficiencyMetricsResponse family (CloneAsCacheHit relocated into the service; init→set for netstandard2.0), OffboardResponse; (C) anonymous/object builders converted to DTOs with old-vs-new wire-parity facts (TypedContractCompletionWireParityTests, 14 facts incl. every vanishing-key case): rule-stats x2 (RuleStatsResponse; uniqueRules is global-only via an optional slot at its exact wire position), verdict-calibration (builder renamed VerdictCalibrationResponseBuilder, PathRow/TrendWindow to Shared), metrics/app (MetricsMath), apps list/analytics/sessions x6 routes (AppsAnalyticsHelper; legacy + paged list share ONE DTO via optional paging keys), ime-versions lean projection (ImeVersionHistoryLeanEntry [WireContract]; bare-array envelope stays, typed as IReadOnlyList<object>), device-journeys + time-attribution fleet envelopes, feature-flags, diagnostics/paths, session restore, cascade-delete Enqueued arm (error arms stay anonymous by design), feedback eligibility/list; (D) Serialize+WriteString path — ListBackupsResponse under the backup surface's own serializer options; AppInsightsQuery's Serialize is an outgoing REQUEST body (not response debt, baselined as such). TypedResponseGuardTests gains three ratchets with mutation-proven red/green: WriteAsJsonAsync(<variable assigned an anonymous object>) (empty baseline), JsonSerializer.Serialize(new{ (error-tolerant; 1 documented request-body entry), and object/Task<object>-returning Build*/Compute*/*Payload/*Response methods (1 documented union entry); the local-WriteJson-wrapper class is closed structurally (generic T : IApiResponse — an interface-typed parameter would serialize zero properties, the generic keeps the concrete type). Manifest regenerated 208→298 exported types; MCP adopts the new types (get_rule_stats typed with the trend-drop as explicit output shaping, get_platform_metrics drops its hand-declared mirror for PlatformAgentMetricsResponse, verdict-calibration-shape derives its rows via extends Omit<generated wire row> so backend renames go tsc-red). Six backend shape-test files switched from default-options serialization to the production wire options via TestWire (ApiJsonOptions; the old default-options runs only matched while payloads were anonymous with lowercase members), and three assertions that pinned null-VALUED keys now pin key ABSENCE — production always omitted them (WhenWritingNull); the parity facts prove the wire itself is unchanged. Deliberately still untyped: auth/me, CheckMcpAccess dictionary, TenantAdmins/GlobalAdmins entity-row arrays and the object-slot payloads (RuleDryRun, HealthCheck, TenantConfigFieldSchema, ...) — the documented read-model follow-up. Suites: Backend 5323, DecisionCore 577, Agent 2805, SummaryDialog 44, MCP 617, Web tsc 0 + vitest 957, generator idempotent.
2026-08-31 04:32:51 +02:00
2026-07-01 15:35:51 +02:00
2026-09-02 21:37:36 +02:00
2026-08-29 17:23:05 +02:00
2026-07-02 08:53:20 +02:00
2026-05-11 19:02:59 +02:00
2026-08-22 21:09:12 +02:00
2026-06-24 17:11:28 +02:00
2026-07-20 11:29:53 +02:00
2026-04-01 02:39:11 +02:00
2026-05-18 23:06:51 +02:00
2026-08-13 19:29:27 +02:00
2026-09-02 02:55:18 +02:00
2026-07-28 21:42:56 +02:00
2026-09-01 16:57:41 +02:00
2026-09-01 16:57:41 +02:00
2026-09-01 16:57:41 +02:00
2026-09-01 16:57:41 +02:00
2026-09-01 16:57:41 +02:00
2026-05-07 09:58:53 +02:00
2026-09-02 21:37:36 +02:00
2026-09-02 12:52:08 +02:00
2026-08-20 01:24:59 +02:00
2026-07-11 12:12:00 +02:00
2026-09-02 21:37:36 +02:00
2026-08-21 15:24:41 +02:00
2026-08-31 15:22:16 +02:00
2026-07-31 14:30:57 +02:00
2026-09-01 23:37:42 +02:00
2026-08-31 02:30:39 +02:00
2026-05-06 20:53:46 +02:00
2026-07-01 15:35:51 +02:00
2026-07-08 14:02:43 +02:00
2026-07-07 11:26:29 +02:00
2026-08-23 16:26:44 +02:00
2026-05-30 13:23:23 +02:00
2026-09-01 22:35:46 +02:00
2026-09-01 22:35:46 +02:00
2026-05-07 10:55:39 +02:00
2026-09-01 22:35:46 +02:00
2026-06-08 13:11:52 +02:00
2026-08-30 11:38:27 +02:00
fix(core,backend): every payload-derived string stored into DecisionState is bounded (FactStringBounds, 256) and the reducer-verification replay streams signals — RealmJoinFacts.WithPackageStarted/Completed left PackageId/Version/Scope unbounded and deduped via a linear ordinal compare over up to 200 rows, so a forged 5000-signal stream with ~400 KB near-identical package ids cost minutes of CPU per GET sessions/{id}/reducer-verification while ReducerVerifier held all decoded payloads (~GBs) live; keys are now bounded BEFORE the dedupe scan (forged near-duplicates collapse to one row), Scope normalised to machine/user, and the same bound applied to failed app ids, HelloOutcome, ImeMatchedPatternId, EspAdvisoryFailureCategory and RJ product version/channel incl. WithVersionOverride — never truncating real telemetry (registry key names ≤ 255, GUIDs, enum-like strings); the agent adapter bounds packageId/version at the source; ReducerVerifier replays decode-fold-discard (head-signal check before the journal so stubbed streams stay silent, mid-stream truncation and count-imbalance semantics unchanged, pinned by tests); ISignalRepository.QueryBySessionAsync carries a cumulative PayloadJson budget (SignalQueryLimits, 32 M chars) reflected in the truncated flag of sessions/{id}/signals and reducer-verification; no ingest cap — stored signals stay verbatim
2026-08-30 20:06:51 +02:00
2026-04-24 13:39:47 +02:00
2026-04-21 10:56:18 +02:00
2026-05-13 12:08:28 +02:00
2026-08-30 17:03:05 +02:00
2026-09-01 16:57:41 +02:00
2026-08-31 02:30:39 +02:00
2026-05-14 11:38:34 +02:00
2026-05-26 17:04:33 +02:00
2026-09-02 21:37:36 +02:00
2026-08-13 23:17:48 +02:00
2026-09-02 02:55:18 +02:00
2026-07-06 12:32:32 +02:00
2026-08-21 00:44:29 +02:00
2026-06-09 22:43:39 +02:00
2026-08-14 16:50:29 +02:00
2026-08-14 19:29:45 +02:00
2026-08-20 22:30:10 +02:00
2026-06-30 00:47:11 +02:00
2026-08-14 16:07:39 +02:00
2026-07-06 12:32:32 +02:00
2026-06-09 22:43:39 +02:00
2026-07-23 14:07:01 +02:00
2026-08-13 13:38:52 +02:00
2026-08-06 14:15:43 +02:00
2026-08-01 00:40:29 +02:00
2026-07-27 09:10:52 +02:00
2026-08-31 15:22:16 +02:00
2026-08-13 19:29:27 +02:00
2026-05-19 18:45:01 +02:00
2026-05-27 20:49:01 +02:00
2026-05-07 10:55:39 +02:00
2026-05-06 20:53:46 +02:00
2026-05-07 11:31:50 +02:00
2026-05-21 14:26:12 +02:00
2026-09-02 00:13:59 +02:00
2026-07-16 13:24:21 +02:00
2026-07-24 18:05:39 +02:00
2026-09-01 22:49:35 +02:00
2026-09-02 00:13:59 +02:00
2026-04-29 19:00:39 +02:00
2026-06-09 22:43:39 +02:00
2026-07-03 16:06:39 +02:00
2026-05-19 18:45:01 +02:00
2026-09-01 16:57:41 +02:00
2026-05-12 21:34:42 +02:00
2026-05-12 21:18:40 +02:00
2026-09-01 16:57:41 +02:00
2026-05-13 01:08:04 +02:00
2026-05-12 20:41:32 +02:00
2026-09-01 16:57:41 +02:00
2026-08-13 19:29:27 +02:00
2026-08-13 19:29:27 +02:00
2026-08-23 11:06:37 +02:00
2026-08-07 12:31:08 +02:00
2026-05-06 20:53:46 +02:00
2026-09-01 16:57:41 +02:00
2026-08-29 21:32:24 +02:00
2026-08-29 22:51:13 +02:00
2026-05-06 20:53:46 +02:00
2026-07-29 14:13:33 +02:00
2026-08-13 23:17:48 +02:00
2026-08-22 21:00:53 +02:00
fix(backend): SessionTenantLookup is claimed first-writer-wins at registration — the sessionId→tenantId row that every Global Admin/Reader cross-tenant session resolve trusts was an unconditional last-writer-wins upsert AFTER the session write keyed by the client-supplied sessionId, so any tenant's device (self-service onboarding) that knew a victim session GUID (portal deep links, Teams/Slack notifications, progress-portal URLs) could re-point the mapping at its own tenant and have every no-tenantId GA read and GA write (globaladmin annotation lane, restore fallback) silently served from a forged session; StoreSessionAsync now claims the row create-only BEFORE any Sessions/SessionsIndex write (409 → read owner: same tenant = re-registration, foreign tenant = SessionTenantConflictException → 409 session_owner_mismatch without disclosing the owner + SessionTenantConflict ops event, nothing written, not fail-soft), the legacy self-heal write is Add-only, ResolveSessionScopeAsync(requireGlobalAdmin) corroborates the resolved tenant against its Sessions row (silent, no operator confirmation), and scripts/Migration/Backfill-SessionTenantLookup.ps1 closed the pre-table window (13509 sessions, 8795 rows added, verify dry-run added=0); no tombstone on deletion by design
2026-08-30 20:34:50 +02:00
2026-08-23 11:06:37 +02:00
2026-08-21 12:49:42 +02:00
2026-08-21 12:49:42 +02:00
2026-07-11 10:59:31 +02:00
2026-08-06 15:13:19 +02:00
2026-09-02 21:37:36 +02:00
2026-06-09 22:43:39 +02:00
2026-09-01 16:57:41 +02:00
2026-08-15 09:42:24 +02:00
2026-09-02 02:55:18 +02:00
2026-09-02 02:55:18 +02:00
2026-09-02 02:55:18 +02:00
2026-08-21 19:41:56 +02:00
2026-05-12 19:59:28 +02:00
2026-05-20 19:54:43 +02:00
2026-09-02 02:55:18 +02:00
2026-08-23 18:31:32 +02:00
fix(backend): SessionTenantLookup is claimed first-writer-wins at registration — the sessionId→tenantId row that every Global Admin/Reader cross-tenant session resolve trusts was an unconditional last-writer-wins upsert AFTER the session write keyed by the client-supplied sessionId, so any tenant's device (self-service onboarding) that knew a victim session GUID (portal deep links, Teams/Slack notifications, progress-portal URLs) could re-point the mapping at its own tenant and have every no-tenantId GA read and GA write (globaladmin annotation lane, restore fallback) silently served from a forged session; StoreSessionAsync now claims the row create-only BEFORE any Sessions/SessionsIndex write (409 → read owner: same tenant = re-registration, foreign tenant = SessionTenantConflictException → 409 session_owner_mismatch without disclosing the owner + SessionTenantConflict ops event, nothing written, not fail-soft), the legacy self-heal write is Add-only, ResolveSessionScopeAsync(requireGlobalAdmin) corroborates the resolved tenant against its Sessions row (silent, no operator confirmation), and scripts/Migration/Backfill-SessionTenantLookup.ps1 closed the pre-table window (13509 sessions, 8795 rows added, verify dry-run added=0); no tombstone on deletion by design
2026-08-30 20:34:50 +02:00
fix(backend): SessionTenantLookup is claimed first-writer-wins at registration — the sessionId→tenantId row that every Global Admin/Reader cross-tenant session resolve trusts was an unconditional last-writer-wins upsert AFTER the session write keyed by the client-supplied sessionId, so any tenant's device (self-service onboarding) that knew a victim session GUID (portal deep links, Teams/Slack notifications, progress-portal URLs) could re-point the mapping at its own tenant and have every no-tenantId GA read and GA write (globaladmin annotation lane, restore fallback) silently served from a forged session; StoreSessionAsync now claims the row create-only BEFORE any Sessions/SessionsIndex write (409 → read owner: same tenant = re-registration, foreign tenant = SessionTenantConflictException → 409 session_owner_mismatch without disclosing the owner + SessionTenantConflict ops event, nothing written, not fail-soft), the legacy self-heal write is Add-only, ResolveSessionScopeAsync(requireGlobalAdmin) corroborates the resolved tenant against its Sessions row (silent, no operator confirmation), and scripts/Migration/Backfill-SessionTenantLookup.ps1 closed the pre-table window (13509 sessions, 8795 rows added, verify dry-run added=0); no tombstone on deletion by design
2026-08-30 20:34:50 +02:00
2026-08-30 12:13:03 +02:00
2026-07-07 13:06:04 +02:00
2026-09-02 10:03:10 +02:00
2026-09-01 16:57:41 +02:00
2026-06-08 17:09:46 +02:00
2026-08-30 16:59:32 +02:00
2026-04-21 10:50:14 +02:00
2026-09-02 21:37:36 +02:00
2026-09-01 23:37:41 +02:00
2026-05-19 12:44:36 +02:00
2026-04-21 11:29:35 +02:00
2026-09-02 21:37:36 +02:00
2026-05-19 12:44:36 +02:00
2026-08-23 15:33:45 +02:00
2026-09-01 22:49:35 +02:00
2026-04-21 10:37:27 +02:00
2026-05-11 18:47:58 +02:00
2026-04-11 11:17:48 +02:00
2026-05-12 19:59:28 +02:00
2026-05-12 19:59:28 +02:00
2026-05-12 23:44:20 +02:00
2026-05-13 10:44:54 +02:00
2026-09-01 16:57:41 +02:00
2026-05-04 18:36:37 +02:00
2026-04-21 14:31:28 +02:00
2026-08-31 02:30:39 +02:00
2026-09-01 16:57:41 +02:00
2026-09-01 16:57:41 +02:00
2026-09-02 12:52:08 +02:00
2026-05-27 19:39:02 +02:00
2026-09-02 12:52:08 +02:00
2026-09-02 21:37:36 +02:00
2026-09-02 21:37:36 +02:00
2026-08-03 14:30:05 +02:00
2026-08-29 22:33:52 +02:00
2026-09-02 21:37:36 +02:00
2026-08-29 23:55:15 +02:00
2026-09-02 10:03:10 +02:00
2026-08-13 13:39:03 +02:00
2026-05-08 18:32:22 +02:00
2026-05-08 18:32:22 +02:00
2026-08-03 14:30:05 +02:00
2026-09-02 12:52:08 +02:00
2026-07-02 08:53:20 +02:00
2026-08-30 00:48:29 +02:00
2026-09-01 16:57:41 +02:00
2026-06-22 21:29:29 +02:00
2026-07-07 13:06:04 +02:00
feat(backend,mcp): typed API contract COMPLETED — every remaining top-level success body is a manifest-exported wire DTO, and the guard now catches the bypass shapes that let them escape. The 08-31 migration's ratchet only saw inline WriteAsJsonAsync(new {...}); four bypass classes had kept ~20 endpoint families untyped: (A) Shared classes without IApiResponse — GeographicMetricsResponse, FleetHealthMetrics, ImePatternHealthResponse, SlaMetricsResponse, PlatformUsageMetrics now implement it (wire byte-identical, interface is empty); (B) Functions-local named response families moved verbatim to Shared — PlatformAgentMetricsResponse/SessionAgentMetric/DeliveryLatencyMetrics/CrashRateMetrics/CrashExceptionSummary, AgentEfficiencyMetricsResponse family (CloneAsCacheHit relocated into the service; init→set for netstandard2.0), OffboardResponse; (C) anonymous/object builders converted to DTOs with old-vs-new wire-parity facts (TypedContractCompletionWireParityTests, 14 facts incl. every vanishing-key case): rule-stats x2 (RuleStatsResponse; uniqueRules is global-only via an optional slot at its exact wire position), verdict-calibration (builder renamed VerdictCalibrationResponseBuilder, PathRow/TrendWindow to Shared), metrics/app (MetricsMath), apps list/analytics/sessions x6 routes (AppsAnalyticsHelper; legacy + paged list share ONE DTO via optional paging keys), ime-versions lean projection (ImeVersionHistoryLeanEntry [WireContract]; bare-array envelope stays, typed as IReadOnlyList<object>), device-journeys + time-attribution fleet envelopes, feature-flags, diagnostics/paths, session restore, cascade-delete Enqueued arm (error arms stay anonymous by design), feedback eligibility/list; (D) Serialize+WriteString path — ListBackupsResponse under the backup surface's own serializer options; AppInsightsQuery's Serialize is an outgoing REQUEST body (not response debt, baselined as such). TypedResponseGuardTests gains three ratchets with mutation-proven red/green: WriteAsJsonAsync(<variable assigned an anonymous object>) (empty baseline), JsonSerializer.Serialize(new{ (error-tolerant; 1 documented request-body entry), and object/Task<object>-returning Build*/Compute*/*Payload/*Response methods (1 documented union entry); the local-WriteJson-wrapper class is closed structurally (generic T : IApiResponse — an interface-typed parameter would serialize zero properties, the generic keeps the concrete type). Manifest regenerated 208→298 exported types; MCP adopts the new types (get_rule_stats typed with the trend-drop as explicit output shaping, get_platform_metrics drops its hand-declared mirror for PlatformAgentMetricsResponse, verdict-calibration-shape derives its rows via extends Omit<generated wire row> so backend renames go tsc-red). Six backend shape-test files switched from default-options serialization to the production wire options via TestWire (ApiJsonOptions; the old default-options runs only matched while payloads were anonymous with lowercase members), and three assertions that pinned null-VALUED keys now pin key ABSENCE — production always omitted them (WhenWritingNull); the parity facts prove the wire itself is unchanged. Deliberately still untyped: auth/me, CheckMcpAccess dictionary, TenantAdmins/GlobalAdmins entity-row arrays and the object-slot payloads (RuleDryRun, HealthCheck, TenantConfigFieldSchema, ...) — the documented read-model follow-up. Suites: Backend 5323, DecisionCore 577, Agent 2805, SummaryDialog 44, MCP 617, Web tsc 0 + vitest 957, generator idempotent.
2026-08-31 04:32:51 +02:00
2026-08-21 19:41:56 +02:00
feat(backend,mcp): typed API contract COMPLETED — every remaining top-level success body is a manifest-exported wire DTO, and the guard now catches the bypass shapes that let them escape. The 08-31 migration's ratchet only saw inline WriteAsJsonAsync(new {...}); four bypass classes had kept ~20 endpoint families untyped: (A) Shared classes without IApiResponse — GeographicMetricsResponse, FleetHealthMetrics, ImePatternHealthResponse, SlaMetricsResponse, PlatformUsageMetrics now implement it (wire byte-identical, interface is empty); (B) Functions-local named response families moved verbatim to Shared — PlatformAgentMetricsResponse/SessionAgentMetric/DeliveryLatencyMetrics/CrashRateMetrics/CrashExceptionSummary, AgentEfficiencyMetricsResponse family (CloneAsCacheHit relocated into the service; init→set for netstandard2.0), OffboardResponse; (C) anonymous/object builders converted to DTOs with old-vs-new wire-parity facts (TypedContractCompletionWireParityTests, 14 facts incl. every vanishing-key case): rule-stats x2 (RuleStatsResponse; uniqueRules is global-only via an optional slot at its exact wire position), verdict-calibration (builder renamed VerdictCalibrationResponseBuilder, PathRow/TrendWindow to Shared), metrics/app (MetricsMath), apps list/analytics/sessions x6 routes (AppsAnalyticsHelper; legacy + paged list share ONE DTO via optional paging keys), ime-versions lean projection (ImeVersionHistoryLeanEntry [WireContract]; bare-array envelope stays, typed as IReadOnlyList<object>), device-journeys + time-attribution fleet envelopes, feature-flags, diagnostics/paths, session restore, cascade-delete Enqueued arm (error arms stay anonymous by design), feedback eligibility/list; (D) Serialize+WriteString path — ListBackupsResponse under the backup surface's own serializer options; AppInsightsQuery's Serialize is an outgoing REQUEST body (not response debt, baselined as such). TypedResponseGuardTests gains three ratchets with mutation-proven red/green: WriteAsJsonAsync(<variable assigned an anonymous object>) (empty baseline), JsonSerializer.Serialize(new{ (error-tolerant; 1 documented request-body entry), and object/Task<object>-returning Build*/Compute*/*Payload/*Response methods (1 documented union entry); the local-WriteJson-wrapper class is closed structurally (generic T : IApiResponse — an interface-typed parameter would serialize zero properties, the generic keeps the concrete type). Manifest regenerated 208→298 exported types; MCP adopts the new types (get_rule_stats typed with the trend-drop as explicit output shaping, get_platform_metrics drops its hand-declared mirror for PlatformAgentMetricsResponse, verdict-calibration-shape derives its rows via extends Omit<generated wire row> so backend renames go tsc-red). Six backend shape-test files switched from default-options serialization to the production wire options via TestWire (ApiJsonOptions; the old default-options runs only matched while payloads were anonymous with lowercase members), and three assertions that pinned null-VALUED keys now pin key ABSENCE — production always omitted them (WhenWritingNull); the parity facts prove the wire itself is unchanged. Deliberately still untyped: auth/me, CheckMcpAccess dictionary, TenantAdmins/GlobalAdmins entity-row arrays and the object-slot payloads (RuleDryRun, HealthCheck, TenantConfigFieldSchema, ...) — the documented read-model follow-up. Suites: Backend 5323, DecisionCore 577, Agent 2805, SummaryDialog 44, MCP 617, Web tsc 0 + vitest 957, generator idempotent.
2026-08-31 04:32:51 +02:00
2026-09-01 16:57:41 +02:00
2026-09-02 21:37:36 +02:00
2026-08-31 15:22:16 +02:00
2026-08-30 11:38:27 +02:00
2026-08-30 17:38:16 +02:00
2026-07-11 12:10:56 +02:00
2026-07-11 12:10:56 +02:00
2026-07-11 12:10:56 +02:00
2026-07-11 12:10:56 +02:00
2026-06-23 19:44:30 +02:00
2026-07-23 08:37:42 +02:00
2026-09-02 01:01:49 +02:00
feat(backend,mcp): typed API contract COMPLETED — every remaining top-level success body is a manifest-exported wire DTO, and the guard now catches the bypass shapes that let them escape. The 08-31 migration's ratchet only saw inline WriteAsJsonAsync(new {...}); four bypass classes had kept ~20 endpoint families untyped: (A) Shared classes without IApiResponse — GeographicMetricsResponse, FleetHealthMetrics, ImePatternHealthResponse, SlaMetricsResponse, PlatformUsageMetrics now implement it (wire byte-identical, interface is empty); (B) Functions-local named response families moved verbatim to Shared — PlatformAgentMetricsResponse/SessionAgentMetric/DeliveryLatencyMetrics/CrashRateMetrics/CrashExceptionSummary, AgentEfficiencyMetricsResponse family (CloneAsCacheHit relocated into the service; init→set for netstandard2.0), OffboardResponse; (C) anonymous/object builders converted to DTOs with old-vs-new wire-parity facts (TypedContractCompletionWireParityTests, 14 facts incl. every vanishing-key case): rule-stats x2 (RuleStatsResponse; uniqueRules is global-only via an optional slot at its exact wire position), verdict-calibration (builder renamed VerdictCalibrationResponseBuilder, PathRow/TrendWindow to Shared), metrics/app (MetricsMath), apps list/analytics/sessions x6 routes (AppsAnalyticsHelper; legacy + paged list share ONE DTO via optional paging keys), ime-versions lean projection (ImeVersionHistoryLeanEntry [WireContract]; bare-array envelope stays, typed as IReadOnlyList<object>), device-journeys + time-attribution fleet envelopes, feature-flags, diagnostics/paths, session restore, cascade-delete Enqueued arm (error arms stay anonymous by design), feedback eligibility/list; (D) Serialize+WriteString path — ListBackupsResponse under the backup surface's own serializer options; AppInsightsQuery's Serialize is an outgoing REQUEST body (not response debt, baselined as such). TypedResponseGuardTests gains three ratchets with mutation-proven red/green: WriteAsJsonAsync(<variable assigned an anonymous object>) (empty baseline), JsonSerializer.Serialize(new{ (error-tolerant; 1 documented request-body entry), and object/Task<object>-returning Build*/Compute*/*Payload/*Response methods (1 documented union entry); the local-WriteJson-wrapper class is closed structurally (generic T : IApiResponse — an interface-typed parameter would serialize zero properties, the generic keeps the concrete type). Manifest regenerated 208→298 exported types; MCP adopts the new types (get_rule_stats typed with the trend-drop as explicit output shaping, get_platform_metrics drops its hand-declared mirror for PlatformAgentMetricsResponse, verdict-calibration-shape derives its rows via extends Omit<generated wire row> so backend renames go tsc-red). Six backend shape-test files switched from default-options serialization to the production wire options via TestWire (ApiJsonOptions; the old default-options runs only matched while payloads were anonymous with lowercase members), and three assertions that pinned null-VALUED keys now pin key ABSENCE — production always omitted them (WhenWritingNull); the parity facts prove the wire itself is unchanged. Deliberately still untyped: auth/me, CheckMcpAccess dictionary, TenantAdmins/GlobalAdmins entity-row arrays and the object-slot payloads (RuleDryRun, HealthCheck, TenantConfigFieldSchema, ...) — the documented read-model follow-up. Suites: Backend 5323, DecisionCore 577, Agent 2805, SummaryDialog 44, MCP 617, Web tsc 0 + vitest 957, generator idempotent.
2026-08-31 04:32:51 +02:00
2026-09-02 01:01:49 +02:00
2026-07-08 14:02:43 +02:00
2026-04-30 15:25:28 +02:00
2026-04-30 15:25:28 +02:00
2026-05-12 19:59:28 +02:00
2026-04-30 15:25:28 +02:00
2026-06-09 22:43:39 +02:00
2026-08-26 13:54:58 +02:00
2026-08-29 11:07:31 +02:00
2026-08-31 02:30:39 +02:00
2026-08-22 21:09:12 +02:00
2026-07-10 18:42:48 +02:00
2026-08-31 02:30:39 +02:00