From b3fab7898a6ced10d3ffb75fcd88443013fe0ee8 Mon Sep 17 00:00:00 2001 From: Alex Date: Mon, 13 Jul 2026 23:40:05 -0400 Subject: [PATCH 1/4] docs(dirt): add team-level CLAUDE.md for DIRT server code Adds src/Core/Dirt/CLAUDE.md as the DIRT context anchor for the server. Lean and reference-based: teaches the SQL/Dapper/EF triple-write pattern, report command/query handlers, feature-flag location, and the zero-knowledge server boundary, and maps where each DIRT feature area lives. Points at the existing EventIntegrations README and the repo-wide .claude config instead of duplicating them. [PM-37613] --- src/Core/Dirt/CLAUDE.md | 98 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 98 insertions(+) create mode 100644 src/Core/Dirt/CLAUDE.md diff --git a/src/Core/Dirt/CLAUDE.md b/src/Core/Dirt/CLAUDE.md new file mode 100644 index 000000000000..6f3fab6d024f --- /dev/null +++ b/src/Core/Dirt/CLAUDE.md @@ -0,0 +1,98 @@ +# CLAUDE.md - DIRT Team (Server) + +> Scope: `src/Core/Dirt/` and its children. Claude Code reads CLAUDE.md files hierarchically +> (repo root + every parent dir), so this supplements the repo-wide `/.claude/CLAUDE.md` with +> DIRT-specific server context. It does NOT auto-load in sibling subtrees such as +> `src/Api/Dirt/`, `src/Sql/dbo/Dirt/`, `src/Infrastructure.*/Dirt/`, `src/Events/`, or +> `src/EventsProcessor/` - see "Where DIRT code lives" for those. + +## Project Overview + +The DIRT team (Data, Insights, Reporting & Tooling) owns, on the server: Access Intelligence / +Reports, Event Integrations, the Events audit pipeline, and Phishing Detection support. This is +the .NET (C#) server; it stores and serves data. Unencrypted vault data never reaches the server +(zero-knowledge) - the server persists encrypted blobs and non-secret metadata. + +## Where DIRT code lives + +- `src/Core/Dirt/` - domain logic: `Entities/`, `Repositories/` (interfaces), `Reports/` (report + command/query handlers + file storage), `EventIntegrations/` + `Services/` (Slack/Teams/webhook/ + HEC/Datadog), `Enums/`, `Models/`, `Utilities/`. +- `src/Api/Dirt/` - controllers (`OrganizationReportsController`, `ReportsController`, the + `OrganizationIntegration*`/`SlackIntegration`/`TeamsIntegration` controllers, `EventsController`, + `HibpController`) + request/response models + `Public/` API. +- `src/Sql/dbo/Dirt/` - raw SQL: `Tables/`, `Stored Procedures/`, `Views/`. +- `src/Infrastructure.Dapper/Dirt/` - Dapper repositories (production data path). +- `src/Infrastructure.EntityFramework/Dirt/` - EF Core repositories + `Configurations/` + `Models/`. +- `src/Events/` - the Events collector app (ingests audit events). `src/EventsProcessor/` - Azure + queue processor. Both are DIRT-owned but live outside `src/Core/Dirt/`. + +## Architecture & Patterns + +- **Data access is a triple-write.** Adding or changing a stored entity touches three places that + MUST stay in sync: + 1. `src/Sql/dbo/Dirt/` - table, stored procedures, and any view (plus a migration script under + `util/Migrator/DbScripts/`). + 2. `src/Infrastructure.Dapper/Dirt/` - the Dapper repository (production path). + 3. `src/Infrastructure.EntityFramework/Dirt/` - the EF Core repository, entity `Configurations/`, + and `Models/` (EF providers + integration tests). + Reference existing entities that already span all three: `OrganizationReport`, + `OrganizationApplication`, `Event`. +- **Commands / Queries.** Report operations under `Reports/ReportFeatures/` are one-class-per-operation + handlers (`*Command` / `*Query`) wired in `ReportingServiceCollectionExtensions`. Follow that shape; + keep controllers thin and put logic in Core. +- **DI:** use the `TryAdd*` pattern (ADR 0026). No code regions (root rule). +- **Naming:** "Access Intelligence" is the current name; "Risk Insights" is the deprecated name for + the same feature (legacy paths/classes like `RiskInsightsReportQuery` remain during migration). + Use "Access Intelligence" in new code, comments, and API surface. V2 report work is gated behind + `FeatureFlagKeys.AccessIntelligenceNewArchitecture`. + +## Feature Flags + +DIRT server flags are string constants in `src/Core/Constants.cs` (`FeatureFlagKeys`): +`PhishingDetection`, `AccessIntelligenceNewArchitecture` +(`pm-31936-access-intelligence-new-architecture`), `AccessIntelligenceVersion2`, +`AccessIntelligenceAdoptionUxImprovements`. Gate new endpoints/behavior on the appropriate flag; +default new flags off. + +## Common Commands + +- `dotnet build` / `dotnet test` +- `dotnet format` - format C# before committing +- `dotnet test test/Core.Test/ --filter "FullyQualifiedName~"` - run one test +- `pwsh dev/migrate.ps1` - apply local DB migrations after editing `src/Sql/` + adding a migrator script + +## Testing Standards + +- xUnit. Add/maintain tests for new logic (root rule). DIRT test projects: `test/Core.Test/Dirt`, + `test/Api.Test/Dirt`, `test/Infrastructure.EFIntegration.Test/Dirt`, and the `Events`/ + `EventsProcessor` test projects. +- Use `NSubstitute` + `AutoFixture` (`[SutAutoData]`/`[BitAutoData]`) following existing DIRT specs. +- Deterministic data only; no PII or real secrets in fixtures. + +## Security & Compliance + +- **Zero-knowledge:** the server never sees plaintext vault data. Never log or return PII, keys, or + decrypted data; audit events and report metadata may carry org/user IDs - keep them out of logs. +- New encryption logic does not belong here; follow the security definitions + (https://contributing.bitwarden.com/architecture/security/definitions). +- Respect CODEOWNERS; AI-config and security-sensitive changes are review-required, never auto-merged. + +## Gotchas & Tips + +- Forgetting one leg of the triple-write (SQL / Dapper / EF) is the most common DIRT server mistake - + a Dapper-only change passes some tests but breaks EF-provider/integration paths. +- Report file storage has Azure / Local / Noop implementations (`Reports/Services/`); pick by + environment, do not hardcode Azure. +- When extending an abstraction, confirm it still serves more than one consumer before adding to it. + +## References + +- Event Integrations design (deep dive): `/src/Core/Dirt/EventIntegrations/README.md` + (and the scoped `/src/Core/Dirt/EventIntegrations/CLAUDE.md`) +- Events pipeline apps context: `/src/Events/CLAUDE.md` +- Repo-wide rules and commands: `/.claude/CLAUDE.md` +- Contributing Claude context to this repo: `/.claude/CONTRIBUTING.md` +- Server architecture: https://contributing.bitwarden.com/architecture/server/ +- ADRs: https://contributing.bitwarden.com/architecture/adr/ +- Caching (used by Event Integrations): `/src/Core/Utilities/CACHING.md` From 2a7252c1b69c3e3fa81e232b04a259d40f1de02b Mon Sep 17 00:00:00 2001 From: Alex Date: Mon, 13 Jul 2026 23:40:15 -0400 Subject: [PATCH 2/4] docs(dirt): add Event Integrations subsystem CLAUDE.md Adds src/Core/Dirt/EventIntegrations/CLAUDE.md as a thin nav hub pointing at the existing README, surfacing the easily-missed rules: tag-based cache invalidation, ASB-subscriptions-must-pre-exist, listener-owns-retries, and platform-agnostic handlers. [PM-37613] --- src/Core/Dirt/EventIntegrations/CLAUDE.md | 46 +++++++++++++++++++++++ 1 file changed, 46 insertions(+) create mode 100644 src/Core/Dirt/EventIntegrations/CLAUDE.md diff --git a/src/Core/Dirt/EventIntegrations/CLAUDE.md b/src/Core/Dirt/EventIntegrations/CLAUDE.md new file mode 100644 index 000000000000..0f474bc8144e --- /dev/null +++ b/src/Core/Dirt/EventIntegrations/CLAUDE.md @@ -0,0 +1,46 @@ +# Event Integrations - Subsystem Context + +> Scope: `src/Core/Dirt/EventIntegrations/`. Supplements the DIRT team file +> `/src/Core/Dirt/CLAUDE.md` and the repo-wide `/.claude/CLAUDE.md`. +> +> **Read first:** `/src/Core/Dirt/EventIntegrations/README.md` - the full design doc +> (two-tier exchange, listener/handler pattern, retries, caching, and a step-by-step +> "Building a new integration" guide). This file only surfaces the rules most easily missed. + +## What this subsystem does + +Fans out organization audit events to external destinations (Slack, Teams, webhook, HTTP Event +Collector, Datadog) over a two-tier AMQP pipeline that runs on RabbitMQ (self-host) or Azure +Service Bus (cloud). Organizations configure which events go where via `OrganizationIntegration` ++ `OrganizationIntegrationConfiguration`, with optional filters. + +## Critical rules & gotchas + +- **Two tiers, decoupled listeners/handlers.** Event tier (fan-out of `EventMessage`) -> + `EventIntegrationHandler` -> integration tier (`IntegrationMessage`) -> per-integration handler. + Handlers know nothing about the messaging platform; listeners own platform specifics. Keep new + handlers platform-agnostic and unit-testable in isolation (they return `IntegrationHandlerResult`). +- **Cache invalidation is tag-based and easy to get wrong.** `OrganizationIntegrationConfigurationDetails` + is served from a named extended cache with a long (1-day) TTL. Admin create/update/delete commands + MUST invalidate by tag using `EventIntegrationsCacheConstants.BuildCacheTagForOrganizationIntegration`, + and `EventIntegrationHandler` MUST fetch with the same tag. Change one side without the other and reads + go stale. See the README "Caching" section. +- **Azure Service Bus subscriptions must exist before deploy.** ASB does NOT create resources on the + fly (RabbitMQ does). New integrations need their event- and integration-level subscriptions created + in ASB first, and added to `servicebusemulator_config.json` locally. See README "Deploying a new + integration." +- **Retries live in the listener, not the handler.** Backoff, jitter, `MaxRetries`, and DLQ routing are + the listener's job via `IntegrationMessage.ApplyRetry(...)`. Handlers only report success/retryable. + +## Adding a new integration + +Follow the README's "Building a new integration" checklist exactly (IntegrationType, configuration +models, request/response model switch cases + tests, handler, GlobalSettings queue/subscription names, +`ListenerConfiguration` subclass, `ServiceCollectionExtensions` wiring) and add a row to the README's +integrations table. + +## References + +- Full design doc: `/src/Core/Dirt/EventIntegrations/README.md` +- Caching internals: `/src/Core/Utilities/CACHING.md` +- DIRT team context: `/src/Core/Dirt/CLAUDE.md` From 92c8facfd5cb87053071a0089865897fd96bcadc Mon Sep 17 00:00:00 2001 From: Alex Date: Mon, 13 Jul 2026 23:40:16 -0400 Subject: [PATCH 3/4] docs(dirt): add CLAUDE.md for the Events pipeline apps Adds src/Events/CLAUDE.md orienting the Events collector and its EventsProcessor sibling: the ingestion/write path, the hot-path and zero-knowledge constraints, membership auth on /collect, and links back to the DIRT team context and the event-integration pipeline. [PM-37613] --- src/Events/CLAUDE.md | 37 +++++++++++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) create mode 100644 src/Events/CLAUDE.md diff --git a/src/Events/CLAUDE.md b/src/Events/CLAUDE.md new file mode 100644 index 000000000000..23bbb2c5bef6 --- /dev/null +++ b/src/Events/CLAUDE.md @@ -0,0 +1,37 @@ +# Events Pipeline - App Context + +> Scope: `src/Events/`. Supplements the repo-wide `/.claude/CLAUDE.md`. The DIRT team file at +> `/src/Core/Dirt/CLAUDE.md` does NOT auto-load here (different subtree), so the key team context +> is linked below. + +## What this app does + +`src/Events/` is the Events collector: a lightweight ASP.NET app that ingests organization/user +audit events (the `/collect` path via `EventsController`) and hands them to the event write pipeline. +Its sibling `src/EventsProcessor/` (`AzureQueueHostedService`) drains the Azure queue and persists +events (Azure Table Storage in cloud; database self-hosted). When Event Integrations are enabled, the +`EventIntegrationEventWriteService` also broadcasts events onto the AMQP exchange (see +`/src/Core/Dirt/EventIntegrations/README.md`). + +## Critical rules + +- **Events are metadata, not vault data**, but they carry org/user/entity IDs. Never log PII, tokens, + or anything that could deanonymize a user; keep the zero-knowledge invariant. +- **This is a hot path.** The collector runs at high volume - avoid per-request DB calls where a cached + lookup exists, and keep handlers cheap. Prefer the existing write-service abstractions over ad-hoc + persistence. +- **Auth/membership:** logging org events requires validated org membership (see recent `/collect` + membership guard). Do not weaken those checks. +- Add/maintain xUnit tests in `test/Events.Test`, `test/Events.IntegrationTest`, and + `test/EventsProcessor.Test`. + +## Common commands + +- Run locally: `dotnet run --project src/Events` +- Test: `dotnet test test/Events.Test` (and the integration/processor test projects) + +## References + +- Event write / integration pipeline: `/src/Core/Dirt/EventIntegrations/README.md` +- DIRT team context: `/src/Core/Dirt/CLAUDE.md` +- Repo-wide rules: `/.claude/CLAUDE.md` From 11e86e1f26e90f5d8d47a2be9446cb131e2cf820 Mon Sep 17 00:00:00 2001 From: Alex Date: Tue, 14 Jul 2026 00:07:02 -0400 Subject: [PATCH 4/4] docs(dirt): address review feedback on DIRT CLAUDE.md files - Fix self-hosted event-write path in src/Events/CLAUDE.md: self-host writes directly to the DB via RepositoryEventWriteService; the Azure queue + EventsProcessor are the cloud-only path (EventsProcessor has no SelfHosted appsettings). Previously implied the processor persisted self-hosted events too. - Reframe src/Core/Dirt/CLAUDE.md away from team-persona framing per .claude/CONTRIBUTING.md: domain-first title and overview, with the owning team kept as a CODEOWNERS note. - Use bare repo-relative reference paths (drop leading slash) to match the util/Seeder/CLAUDE.md precedent. - Clarify that ReportingServiceCollectionExtensions predates TryAdd and uses AddScoped; new registrations should prefer TryAdd (ADR 0026). [PM-37613] --- src/Core/Dirt/CLAUDE.md | 35 ++++++++++++----------- src/Core/Dirt/EventIntegrations/CLAUDE.md | 12 ++++---- src/Events/CLAUDE.md | 23 +++++++++------ 3 files changed, 39 insertions(+), 31 deletions(-) diff --git a/src/Core/Dirt/CLAUDE.md b/src/Core/Dirt/CLAUDE.md index 6f3fab6d024f..5a5729410484 100644 --- a/src/Core/Dirt/CLAUDE.md +++ b/src/Core/Dirt/CLAUDE.md @@ -1,19 +1,21 @@ -# CLAUDE.md - DIRT Team (Server) +# Access Intelligence, Reports & Event Integrations (Server) > Scope: `src/Core/Dirt/` and its children. Claude Code reads CLAUDE.md files hierarchically -> (repo root + every parent dir), so this supplements the repo-wide `/.claude/CLAUDE.md` with -> DIRT-specific server context. It does NOT auto-load in sibling subtrees such as +> (repo root + every parent dir), so this supplements the repo-wide `.claude/CLAUDE.md` with +> domain-specific context. It does NOT auto-load in sibling subtrees such as > `src/Api/Dirt/`, `src/Sql/dbo/Dirt/`, `src/Infrastructure.*/Dirt/`, `src/Events/`, or -> `src/EventsProcessor/` - see "Where DIRT code lives" for those. +> `src/EventsProcessor/` - see "Where this code lives" for those. ## Project Overview -The DIRT team (Data, Insights, Reporting & Tooling) owns, on the server: Access Intelligence / -Reports, Event Integrations, the Events audit pipeline, and Phishing Detection support. This is -the .NET (C#) server; it stores and serves data. Unencrypted vault data never reaches the server +This subtree holds the server-side domain logic for **Access Intelligence / Reports**, **Event +Integrations**, the **Events audit pipeline**, and **Phishing Detection** support. This is the +.NET (C#) server; it stores and serves data. Unencrypted vault data never reaches the server (zero-knowledge) - the server persists encrypted blobs and non-secret metadata. -## Where DIRT code lives +> Owned by `@bitwarden/team-data-insights-and-reporting-dev` (DIRT) via CODEOWNERS. + +## Where this code lives - `src/Core/Dirt/` - domain logic: `Entities/`, `Repositories/` (interfaces), `Reports/` (report command/query handlers + file storage), `EventIntegrations/` + `Services/` (Slack/Teams/webhook/ @@ -39,8 +41,9 @@ the .NET (C#) server; it stores and serves data. Unencrypted vault data never re Reference existing entities that already span all three: `OrganizationReport`, `OrganizationApplication`, `Event`. - **Commands / Queries.** Report operations under `Reports/ReportFeatures/` are one-class-per-operation - handlers (`*Command` / `*Query`) wired in `ReportingServiceCollectionExtensions`. Follow that shape; - keep controllers thin and put logic in Core. + handlers (`*Command` / `*Query`) registered in `ReportingServiceCollectionExtensions`. Follow that + one-class-per-operation shape; keep controllers thin and put logic in Core. (That extension predates + the `TryAdd*` convention and still uses `AddScoped` - new registrations should prefer `TryAdd*`.) - **DI:** use the `TryAdd*` pattern (ADR 0026). No code regions (root rule). - **Naming:** "Access Intelligence" is the current name; "Risk Insights" is the deprecated name for the same feature (legacy paths/classes like `RiskInsightsReportQuery` remain during migration). @@ -88,11 +91,11 @@ default new flags off. ## References -- Event Integrations design (deep dive): `/src/Core/Dirt/EventIntegrations/README.md` - (and the scoped `/src/Core/Dirt/EventIntegrations/CLAUDE.md`) -- Events pipeline apps context: `/src/Events/CLAUDE.md` -- Repo-wide rules and commands: `/.claude/CLAUDE.md` -- Contributing Claude context to this repo: `/.claude/CONTRIBUTING.md` +- Event Integrations design (deep dive): `src/Core/Dirt/EventIntegrations/README.md` + (and the scoped `src/Core/Dirt/EventIntegrations/CLAUDE.md`) +- Events pipeline apps context: `src/Events/CLAUDE.md` +- Repo-wide rules and commands: `.claude/CLAUDE.md` +- Contributing Claude context to this repo: `.claude/CONTRIBUTING.md` - Server architecture: https://contributing.bitwarden.com/architecture/server/ - ADRs: https://contributing.bitwarden.com/architecture/adr/ -- Caching (used by Event Integrations): `/src/Core/Utilities/CACHING.md` +- Caching (used by Event Integrations): `src/Core/Utilities/CACHING.md` diff --git a/src/Core/Dirt/EventIntegrations/CLAUDE.md b/src/Core/Dirt/EventIntegrations/CLAUDE.md index 0f474bc8144e..71b52c2ee8d5 100644 --- a/src/Core/Dirt/EventIntegrations/CLAUDE.md +++ b/src/Core/Dirt/EventIntegrations/CLAUDE.md @@ -1,9 +1,9 @@ # Event Integrations - Subsystem Context -> Scope: `src/Core/Dirt/EventIntegrations/`. Supplements the DIRT team file -> `/src/Core/Dirt/CLAUDE.md` and the repo-wide `/.claude/CLAUDE.md`. +> Scope: `src/Core/Dirt/EventIntegrations/`. Supplements the team-level file +> `src/Core/Dirt/CLAUDE.md` and the repo-wide `.claude/CLAUDE.md`. > -> **Read first:** `/src/Core/Dirt/EventIntegrations/README.md` - the full design doc +> **Read first:** `src/Core/Dirt/EventIntegrations/README.md` - the full design doc > (two-tier exchange, listener/handler pattern, retries, caching, and a step-by-step > "Building a new integration" guide). This file only surfaces the rules most easily missed. @@ -41,6 +41,6 @@ integrations table. ## References -- Full design doc: `/src/Core/Dirt/EventIntegrations/README.md` -- Caching internals: `/src/Core/Utilities/CACHING.md` -- DIRT team context: `/src/Core/Dirt/CLAUDE.md` +- Full design doc: `src/Core/Dirt/EventIntegrations/README.md` +- Caching internals: `src/Core/Utilities/CACHING.md` +- Team-level context: `src/Core/Dirt/CLAUDE.md` diff --git a/src/Events/CLAUDE.md b/src/Events/CLAUDE.md index 23bbb2c5bef6..f058511836a8 100644 --- a/src/Events/CLAUDE.md +++ b/src/Events/CLAUDE.md @@ -1,17 +1,22 @@ # Events Pipeline - App Context -> Scope: `src/Events/`. Supplements the repo-wide `/.claude/CLAUDE.md`. The DIRT team file at -> `/src/Core/Dirt/CLAUDE.md` does NOT auto-load here (different subtree), so the key team context +> Scope: `src/Events/`. Supplements the repo-wide `.claude/CLAUDE.md`. The team-level DIRT file at +> `src/Core/Dirt/CLAUDE.md` does NOT auto-load here (different subtree), so the key team context > is linked below. ## What this app does `src/Events/` is the Events collector: a lightweight ASP.NET app that ingests organization/user audit events (the `/collect` path via `EventsController`) and hands them to the event write pipeline. -Its sibling `src/EventsProcessor/` (`AzureQueueHostedService`) drains the Azure queue and persists -events (Azure Table Storage in cloud; database self-hosted). When Event Integrations are enabled, the -`EventIntegrationEventWriteService` also broadcasts events onto the AMQP exchange (see -`/src/Core/Dirt/EventIntegrations/README.md`). +The write path is selected in `AddEventWriteServices` by deployment: +- **Cloud:** the collector enqueues to Azure Queue Storage, and its sibling `src/EventsProcessor/` + (`AzureQueueHostedService`) drains the queue and persists events to Azure Table Storage. +- **Self-hosted:** there is no queue and no processor - the collector writes events straight to the + database via `RepositoryEventWriteService` (selected when `GlobalSettings.SelfHosted` is true). + `src/EventsProcessor/` is a cloud-only component (it has no `appsettings.SelfHosted.json`). + +When Event Integrations are enabled, `EventIntegrationEventWriteService` also broadcasts events onto +the AMQP exchange (see `src/Core/Dirt/EventIntegrations/README.md`). ## Critical rules @@ -32,6 +37,6 @@ events (Azure Table Storage in cloud; database self-hosted). When Event Integrat ## References -- Event write / integration pipeline: `/src/Core/Dirt/EventIntegrations/README.md` -- DIRT team context: `/src/Core/Dirt/CLAUDE.md` -- Repo-wide rules: `/.claude/CLAUDE.md` +- Event write / integration pipeline: `src/Core/Dirt/EventIntegrations/README.md` +- Team-level context: `src/Core/Dirt/CLAUDE.md` +- Repo-wide rules: `.claude/CLAUDE.md`