Commit Graph

17 Commits

Author SHA1 Message Date
Oliver Kieselbach 2c48968ec6 session annotations: role-separated human verdicts on sessions (flywheel labels)
New SessionAnnotations table (PK=tenantId, RK={sessionId}_{lane}, lanes
operator/tenantadmin/globaladmin) storing structured verdicts + notes with
server-stamped authorship and a fired-rule-id snapshot for join-free
rule-quality evaluation. Per-lane write matrix re-gated in-function with
own-tenant binding; the globaladmin lane is platform-internal and filtered
from tenant callers. Portal: SessionAnnotationsCard on session detail.
MCP: list_session_annotations (ga) + annotate_session (strictGa, GA lane
only) + annotations key in get_session_summary. Lifecycle: cascade steps
17-19, offboarding wipe, critical-backup member. Privacy page gains the
customer-entered-content line.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-11 13:30:44 +02:00
Oliver Kieselbach 395fdd49df Transactional tenant-config writes via MCP + ConfigurationBackups safety net (#120)
* feat(backend): pre-write config backup net - every tenant/admin config save snapshots first

New ConfigurationBackups table (PK=tenantId or GlobalConfig, RK=reverse-ticks+guid,
newest-first) holds the raw stored row as EntityJson before any overwriting save of
TenantConfiguration or AdminConfiguration. Hook lives in TableConfigRepository, the
single choke point all writers funnel through - fail-soft (a backup hiccup never
breaks a save), skips row creations and noise-only changes (LastAuthClientId flips),
prunes to the newest 2 per partition. Source/reason tagging via a new
SaveTenantConfigurationAsync overload (not optional params - Moq expression trees,
CS0854); portal-put/plan/auth/app-homing/offboard call sites tagged.

Offboarding: backup partition wiped in the bulk phase (TenantPartitionTables) and by
the marker-cleanup tombstone sweep; the GlobalConfig partition never matches a tenant
GUID and survives. Table registered in TableNames.All + CriticalBackupTables.All.

Groundwork for the transactional MCP config-write flow (patch/revert endpoints follow).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* feat(backend): transactional tenant-config field patch + revert with verified writes

New GA-only surface (policy catalog: GlobalAdminOnly + RouteParam):
- PATCH config/{tenantId}/fields - field-level patch through TenantConfigPatchService:
  fresh ETag read -> field gate (deny-list incl. plan/trial, HomedAppClientId,
  auth provenance; unknown fields 400 with the name) -> patch onto a clone ->
  shared validation -> fail-CLOSED backup -> CAS replace -> fresh re-read ->
  verify that EXACTLY the intended fields changed -> automatic ETag-conditional
  rollback on drift (catches Store/Map serialization asymmetry).
- POST config/{tenantId}/revert - restore from a ConfigurationBackups snapshot
  (latest by default); snapshots current state first (revert is revertible),
  preserves plan/homing/provenance fields unless includeProtectedFields.
- GET config/{tenantId}/backups - metadata + masked diff only, never EntityJson.

Supporting changes: IConfigRepository gains GetTenantConfigurationWithEtagAsync +
TryReplaceTenantConfigurationAsync (412 -> false, no implicit backup hook);
validators moved from the PUT into shared TenantConfigValidation (PUT re-pointed,
forwarding shims keep test call sites); GET config/{tenantId} honors ?view=redacted
so a GA's MCP session never pulls clear-text secrets into model context.
Caller-tier deny-lists (TenantAdmin/DelegatedAdmin) ship now for the phase-2 flip.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* feat(mcp): tenant-config write tools - update/revert with snapshot-backed verified writes

First mutating surface on the MCP server, strictGa (real Global Admin) only:
- get_tenant_config: full config read, ALWAYS ?view=redacted (secrets never
  reach model context; pinned by config-write-tools tests)
- update_tenant_config: field-level patch via PATCH config/{tenantId}/fields;
  required reason; response = applied fields + masked diff + backupId
- list_tenant_config_backups: snapshot metadata + masked diff only
- revert_tenant_config: restore latest/named snapshot; protected fields stay
  current unless includeProtectedFields; revert is itself revertible

New MUTATING annotation + TenantGuidSchema (path-traversal guard) in shared.ts;
role-aware instructions headline (non-GA keeps the exact READ-ONLY wording);
catalog snapshot test extends GA_FULL + new CONFIG_WRITE_GA_STRICT difference
list (Global Reader/tenant/delegated see none of the four).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* test(backend): de-flake session-deletion heartbeat test - condition-gated handler instead of wall-clock window

The heartbeat test held the handler for a FIXED 350ms and expected the 50ms
heartbeat timer to tick >=3 times inside that window. On loaded CI runners the
heartbeat Task.Delay stretches disproportionately to the handler delay, the
handler completes with <3 heartbeats fired, and the awaited condition can never
become true afterwards (CI failure on PR #120). The handler now holds the
message in-flight UNTIL the test observed 3 heartbeats (with ct + own 15s
deadline as termination guards), so the assertion is reached deterministically
regardless of scheduler jitter. Verified: 12/12 local runs green, full suite green.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-08-03 14:30:05 +02:00
Oliver Kieselbach c26b25b81b feat(backend,web): offboarding farewell-email capture + dedicated feedback table + reports tabs
- Persist tenant Preview-Notification-Email on OffboardingHistory row at Phase 1 so it
  survives Phase 2 wipe; ResendEmailService.SendAsync wired but disarmed by a const-false
  arm-flag with a [DRAFT] placeholder template — explicit flip required to ship.
- Move user feedback storage out of PreviewConfig discriminator into a dedicated Feedback
  table (PK=InApp / Offboarding) that is NOT in any tenant-offboarding wipe list — feedback
  from departing tenants is exactly the signal we want to keep. New IFeedbackRepository +
  TableFeedbackRepository; FeedbackFunction migrated; (PreviewConfig, "Feedback")
  removed from TenantOffboardingHandler.DiscriminatorTables.
- POST /api/tenants/{tenantId}/offboard/feedback collects a free-form farewell comment
  during the drain-barrier countdown (marker-gated, fail-soft OpsEvent emit). Reports >
  User Feedback page gains an Offboarding-Feedback sub-tab next to In-App Feedback.
- OffboardingFeedbackReceived OpsEventType registered (info-severity) + catalog entry
  in OpsAlertRulesSection so admins can wire Telegram in two clicks.

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

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

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

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

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

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-19 01:14:21 +02:00
Oliver Kieselbach 206388da99 feat(backend,web): tenant offboarding async cascade with customs archive
Replace synchronous DeleteAllTenantDataAsync with a queued async cascade
that wipes all 24 tenant-scoped tables safely, archives custom rules,
and survives crashes at every step.

Phase 1 (TenantOffboardFunction, ~200ms sync):
  - Write History / ByTenantPointer / Marker rows (OffboardingAudit table)
  - Flip TenantConfiguration.Disabled=true (the active auth gate)
  - Enqueue with 6 min visibility-delay (cache-drain barrier =
    TenantConfigurationService.CacheDuration 5 min + 1 min safety)
  - Return 202 with EarliestProcessingAt for the UI countdown

Phase 2 (TenantOffboardingWorker + Handler, async after drain):
  - Enumerate sessions, per-session cascade-enqueue, persist
    Expectations blob (separate offboarding-state container)
  - Drain probe against per-session progress blobs; self re-enqueue
    up to 60x at 2 min visibility-delay
  - 2.D-archive: GatherRules / AnalyzeRules / ImeLogPatterns moved
    to TenantOffboardingCustomsArchive table (3-iter cap + verify)
  - 2.D-wipe: SafeWipe over all tenant tables (4 variants: exact-PK,
    composite-PK range, discriminator+TenantId, property-only) with
    fetch+verify+delete pattern + SafeWipeVerificationException
  - 2.E: blob-prefix wipe deletion-manifests/{tenantId}/
  - 2.D-pass-2: re-wipe AuditLogs (defense-in-depth)
  - 2.G: side-effects FIRST (Marker, Pointer, blob, audit, OpsEvent)
    then History=Completed LAST (DrainCompletedAt as resume anchor)
  - 2.F-final: delete TenantConfiguration row LAST so self-service
    re-onboarding (next /api/auth/me auto-creates default) works

Defense-in-depth:
  - OffboardingMarkerCleanupFunction (2h timer): sweeps Completed
    markers after 15 min grace, conditional TenantConfiguration sweep
    (only when row is still the offboarding tombstone — preserves
    user-driven self-service re-onboards)
  - Worker poison path: durable Failed state MUST land before queue
    moves; transient failure leaves message visible for retry
  - SessionDeletionHandler reorder: Audit + SignalR notify BEFORE
    CompletedAt write so the drain predicate is correct

Web UI:
  - /settings danger zone: typed "OFFBOARD" confirmation dialog,
    post-202 drain-barrier banner with minute-based countdown, hint
    "you can sign out and close this tab — deletion runs in
    background", silent auto-logout when countdown elapses
  - /admin/customs-archive (GA only): list runs + inspect entries +
    typed "DELETE" confirmation modal for run/entry deletion
  - OpsAlertRulesSection: TenantOffboardingFailed event registered
  - lib/api.ts: shared CustomsArchive* response types

Tests: 100+ new tests covering drain contract, handler ordering,
worker poison path, ETag-CAS, SafeWipe variants, OffboardingFilters
prefix-bleed safety, MarkerCleanup conditional sweep, Bootstrap
auth-disabled-tenant gate.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-18 23:06:51 +02:00
Oliver Kieselbach fce4a03d23 feat(sla): persistent SlaTenantStatus row replaces in-memory throttle, fixes 2h breach-notification spam
Background
----------
The in-memory SlaNotificationThrottleService kept its 4h cooldown in a process-
local ConcurrentDictionary. On Flex Consumption the Function host recycles
between 2h timer fires, wiping the dictionary, so the throttle was effectively
a no-op — observed in production as tenant b54dc1af receiving an SLA breach
notification every 2 hours for days on end (1 failed of 11-13 sessions, target
95%, success rate 91-92%).

Design
------
One row per tenant in a new SlaTenantStatus table (PartitionKey = tenantId,
RowKey = "status"). Flat schema with {Type}_* property prefixes for the four
breach kinds (SuccessRate, Duration, AppInstall, ConsecutiveFailures); one row,
one Upsert per evaluation cycle. Rationale documented inline: per-type rows
would be over-normalised for the access pattern (always all-types-per-tenant),
and ~50 tenants x 4 types = max 200 rows ever.

Throttle correctness
--------------------
The throttle decision (now - LastNotifiedAt >= cooldown) and the LastNotifiedAt
write happen in the same TryUpsertAsync(status, ifMatchETag) call. Two parallel
inline calls cannot both succeed in sending the same notification — the loser
gets a 412 from Table Storage, refetches, sees the updated watermark, and
either skips or schedules a new claim attempt. Up to 4 retries; on exhausted
retries we drop the notification rather than risk double-fire (at-most-once).

Cooldown is configurable: AdminConfiguration.SlaNotificationCooldownHours
(default 24, range 1-168). UI round-trip wired through the four required
files (AdminConfigContext, AdminConfigSettingsSection, types/adminConfig.ts,
SectionGlobalSettings).

Resolve path
------------
When a previously-active breach is no longer detected, the row transitions to
IsActive=false + ResolvedAt=now and a single sla_resolved notification fires.
Orphan sweep: tenants whose toggles get turned off while a breach is active
fall out of the qualifying loop, so the timer now also scans every status row
and silently clears any IsActive that no longer has a toggle backing it
(no resolved notification — the tenant disabled SLA, they don't care).

Per-type notes
--------------
- SuccessRate breach notifies with the configured warn-threshold, not the SLA
  target — a 94% rate against target=99% / threshold=95% reads "below 95%",
  not "below 99%".
- AppInstall now actually evaluates (the toggle was wired but evaluation was
  missing). Aligned to the same ISO-week window the dashboard uses
  (SlaMetricsService.GetIsoWeekKey), so notifications cannot diverge from the
  /sla page. Sample-size floor of 5 apps per week, same as the dashboard.
- AppInstall has its own notification template (own title/summary/facts plus
  TopFailingApp fact) — the builder used to silently fall through to the
  Duration template for everything not SuccessRate.
- Telemetry Period tag derived from breachType: CurrentMonth for SuccessRate
  and Duration, CurrentWeek for AppInstall — so AppInsights / ops dashboards
  don't group AppInstall under the wrong period.
- AppInstall ops events now carry the actual installer totals (10 evaluated,
  4 failed) instead of 0/0.
- ConsecutiveFailures: inline (ingest) path detects breaches, timer path
  detects resolve. Both paths write to the same row through the same CAS
  retry loop.

Clock injection
---------------
SlaBreachEvaluationService takes an optional Func<DateTime> nowProvider
(default () => DateTime.UtcNow) so the week-window regression test runs
deterministically on every test day instead of silently skipping when today
happens to fall in the first ISO week of the month.

Removed
-------
- SlaNotificationThrottleService and its tests. The in-memory model is gone;
  there is no scenario where it would be wanted alongside the persistent row.

Tests
-----
- 21 new lifecycle + mapping + telemetry tests (CAS conflict + retry, CAS
  exhausted-retries drops notification, threshold-not-target in notification,
  orphan clearing, AppInstall template / week-window / totals, ConsecutiveFailures
  inline persistence, multi-type race-probe, period-mapping theory).
- Backend xUnit: 1218 passed.
- Web vitest: 266 passed.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-11 18:47:58 +02:00
Oliver Kieselbach f3b4d7571f feat(notifications): tenant-scoped bell notifications for hardware rejections
Until now, hardware-whitelist rejections were only visible to tenant admins
who actively navigated to the settings page or had a webhook configured.
Admins missed new rejected models if neither was the case.

This adds a parallel tenant-scoped notification pipeline (separate from the
existing GlobalAdmin-only feed): on the first-ever HardwareNotAllowed
distress per (tenantId, manufacturer-lower, model-lower), a persistent
bell notification is created with a deep link to the hardware-whitelist
page. Lifetime dedup via a dedicated tracker table makes it a one-shot per
model. The webhook path is unchanged and fires in parallel.

Routes are gated TenantAdminOrGA + TenantScoping.Jwt for now; an Operator
follow-up (new TenantAdminOrOperator policy) is tracked separately.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-28 20:07:06 +02:00
Oliver Kieselbach 5eb09176b9 feat(backend): M5.d.1 index-table scaffolding for Plan §2.8 query matrix
Registers 5 secondary-index tables (SessionsByTerminal, SessionsByStage,
DeadEndsByReason, ClassifierVerdictsByIdLevel, SignalsByKind) in
Constants.TableNames.All[] so TableStorageService.InitializeTablesAsync
auto-creates them at startup.

Adds:
- 5 sealed DTO records in Shared/Models/IndexRecords.cs carrying only
  discriminator columns + back-refs (StepIndex / SessionSignalOrdinal)
  to the primary row — no PayloadJson chunking needed.
- IIndexTableRepository with 5 Store*Async methods (single-interface
  shape keeps boilerplate low; Cosmos swap remains trivial since each
  Store* is independent).
- IndexRowKeys pure static helper for all PK/RK formats per Plan §2.8:
  D6 StepIndex suffix, D10 SessionSignalOrdinal suffix, D19 Ticks
  padding for SessionsByStage (matches lex ordering to numeric time).
  Defensive Sanitize() for forbidden PK/RK chars (/\#?).
- TableIndexRepository with a generic group-by-PK + 100-op-chunk
  UpsertReplace write path mirroring TableSignalRepository.
- DI wiring in DataAccessServiceExtensions.

Scope: write-only. Query methods deferred until MCP/Web concretely need
them. No queue producer / reconcile handler yet — that lands in M5.d.2+.

Tests (+25 in Functions): 15 PK/RK format assertions in IndexRowKeysTests
+ 10 entity-projection round-trips (incl. null-tolerance) in
TableIndexRepositoryMappingTests.

Full Functions suite: 661 green (prev 636 + 25 new). Full solution build
green.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-21 11:29:35 +02:00
Oliver Kieselbach eeaa0643a2 feat(backend): M5.a primary tables — Signals + DecisionTransitions repositories
Groundwork for the V2 ingest path (/api/agent/telemetry). Adds the two
primary Decision-Engine tables projected from the agent-side SignalLog
and Journal so the Inspector + Reducer-Verifier can replay sessions.

- Constants.TableNames: Signals, DecisionTransitions (+ All[] init).
- Shared DTOs SignalRecord / DecisionTransitionRecord carry a flat
  projection plus the agent-serialised PayloadJson for full fidelity.
  DecisionTransitionRecord eagerly carries index discriminators
  (IsTerminal, ClassifierVerdictId, ClassifierHypothesisLevel) so the
  later index fan-out can read them straight off the primary row.
- ISignalRepository / IDecisionTransitionRepository: lean interfaces
  (StoreBatchAsync only — queries land with the read endpoints in M5.c).
- TableSignalRepository / TableDecisionTransitionRepository: idempotent
  UpsertReplace via entity-group-transactions, grouped per (tenant,
  session), chunked at the 100-op Azure limit. PayloadJson goes through
  TableStorageChunking so the 32K-char property ceiling can't bite on
  large Evidence + Payload blobs.
- Registered in DataAccessServiceExtensions alongside the existing 13
  Table repositories — Cosmos swap remains a single-file change.
- 11 pure mapping tests cover key builders (PK tenant_session,
  RK D19(ordinal) / D10(stepIndex), lex-ordering invariant), full
  entity projection, chunked PayloadJson handling, dead-end paths,
  and null-payload tolerance.

Index tables (SessionsByTerminal, SessionsByStage, DeadEndsByReason,
ClassifierVerdictsByIdLevel, SignalsByKind) land with the queue-driven
fan-out in M5.b; the primary schemas are stable.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-21 09:19:08 +02:00
Claude 6f0052be20 Add OpsEvents table for vital infrastructure monitoring
Introduces a new OpsEvents Azure Table Storage table that records vital
infrastructure events (consent flows, maintenance runs, security blocks,
tenant offboarding) visible to Global Admins on the Ops dashboard.

Backend:
- IOpsEventRepository interface + TableOpsEventRepository (PartitionKey=Category)
- OpsEventService with typed helpers (fire-and-forget safe, never throws)
- GetOpsEventsFunction API endpoint (GET /api/global/ops-events)
- Maintenance cleanup with configurable retention (AdminConfig.OpsEventRetentionDays, default 90d)
- Integrated into: ConsentFunction, MaintenanceService, DeviceBlock, VersionBlock, TenantOffboard

Frontend:
- OpsEventsSection component with category filters, severity badges, detail modal
- Configurable retention days directly in the Ops page
- Updated admin/ops page with new events table above existing maintenance trigger

https://claude.ai/code/session_01UCFrxsZ8QYjH7KEYmiGJ6c
2026-04-02 13:23:07 +00:00
Oliver Kieselbach d9c4ea2657 agent emergency channel established 2026-03-31 03:07:52 +02:00
Oliver Kieselbach 219f5c686a oauth for mcp server 2026-03-28 06:15:12 +01:00
Oliver Kieselbach a6f552a877 added MCP capabilities 2026-03-28 04:20:50 +01:00
Claude d9abf724a3 Migrate VulnerabilityCorrelationService to IVulnerabilityRepository — Phase 4 complete
- VulnerabilityCorrelationService: TableStorageService → IVulnerabilityRepository + ISessionRepository
- IAdminRepository: Add SetTenantMemberEnabledAsync
- TableAdminRepository, TenantAdminsService: Implement enable/disable via new method
- DataAccessServiceExtensions: Add IDeviceSecurityRepository registration

All 12 bypass services now use repository interfaces. Zero services create
their own TableServiceClient. Only repository implementations + TableStorageService
+ 2 domain-specific functions (Feedback, TenantOffboard) have direct table access.

https://claude.ai/code/session_01NHWJfPaa8TQHuPzMD7gG63
2026-03-27 07:21:37 +00:00
Claude 5405a40a5a Phase 4: Extract table storage from all bypass services into repositories
New repository implementations:
- TableConfigRepository: TenantConfig, AdminConfig, PreviewWhitelist, PreviewConfig
- TableBootstrapRepository: BootstrapSessions with dual-partition scheme
- TableNotificationRepository: GlobalNotifications + SessionReports
- TableDeviceSecurityRepository: BlockedDevices + BlockedVersions

Services migrated to use repository interfaces (keep caching + business logic):
- TenantConfigurationService → IConfigRepository
- AdminConfigurationService → IConfigRepository
- PreviewWhitelistService → IConfigRepository
- TelegramNotificationService → IConfigRepository
- GlobalAdminService → IAdminRepository
- TenantAdminsService → IAdminRepository
- BootstrapSessionService → IBootstrapRepository
- GlobalNotificationService → INotificationRepository
- SessionReportService → INotificationRepository
- BlockedDeviceService → IDeviceSecurityRepository
- BlockedVersionService → IDeviceSecurityRepository

TableAdminRepository rewritten with direct table CRUD (removed circular service deps).
IVulnerabilityRepository: Added GetCveCacheEntryAsync for VulnerabilityCorrelationService.

https://claude.ai/code/session_01NHWJfPaa8TQHuPzMD7gG63
2026-03-27 07:16:23 +00:00
Claude 36b8594ff0 Migrate all Vulnerability functions, ApiKey, bypass services to DAL (Phase 3)
Vulnerability Functions (8 files):
- GetCpeMappings, SaveCustomCpeMapping, DeleteCustomCpeMapping → IVulnerabilityRepository
- GetUnmatchedSoftware, GetSoftwareInventory → IVulnerabilityRepository
- GetVulnerabilityReport → IVulnerabilityRepository + ISessionRepository
- TriggerVulnerabilityDataSync, VulnerabilityDataSync → IVulnerabilityRepository

Admin:
- ReseedFromGitHubFunction → IVulnerabilityRepository + IRuleRepository
- ApiKeyManagementFunction → IAdminRepository
- ApiKeyMiddleware → IAdminRepository

Bypass Services (removed TableStorageService dependency):
- BlockedDeviceService → own TableClient from IConfiguration
- BlockedVersionService → own TableClient from IConfiguration
- SessionReportService → own TableClient from IConfiguration

New: TableAdminRepository implementation for IAdminRepository

https://claude.ai/code/session_01NHWJfPaa8TQHuPzMD7gG63
2026-03-27 06:54:35 +00:00
Claude 36554c5ca4 Add Data Access Layer with repository interfaces and Table Storage implementations
Introduces a clean abstraction layer to decouple domain services from Azure Table Storage,
enabling future migration to Cosmos DB and Event Streaming without touching business logic.

Architecture:
- 10 repository interfaces in Shared/DataAccess/ (ISessionRepository, IRuleRepository, etc.)
- IDataEventPublisher interface with NullDataEventPublisher for future event streaming
- IStorageInitializer for provider-agnostic initialization
- 5 Table Storage implementations that delegate to existing TableStorageService
- DI extension method AddTableStorageDataAccess() with AddEventStreaming<T>() hook
- Migration plan in tasks/todo.md for incremental adoption

https://claude.ai/code/session_01NHWJfPaa8TQHuPzMD7gG63
2026-03-27 06:03:55 +00:00