9.6 KiB
Bitwarden Seeder Library - Claude Code Configuration
Quick Reference
For detailed pattern descriptions (Factories, Recipes, Models, Scenes, Queries, Data), read README.md.
For detailed usages of the Seeder library, read util/SeederUtility/README.md and util/SeederApi/README.md
Commands
# Build
dotnet build util/Seeder/Seeder.csproj
# Run tests
dotnet test test/SeederApi.IntegrationTest/
# Run single test
dotnet test test/SeederApi.IntegrationTest/ --filter "FullyQualifiedName~TestMethodName"
Pattern Decision Tree
Need to create test data?
├─ ONE entity with encryption? → Factory
├─ ONE cipher from a SeedVaultItem? → CipherSeed.FromSeedItem() + {Type}CipherSeeder.Create()
├─ MANY entities as cohesive operation? → Recipe or Pipeline
├─ Flexible preset-based seeding? → Pipeline (RecipeBuilder + Steps)
├─ Complete test scenario with ID mangling? → Scene
├─ READ existing seeded data? → Query
└─ Data transformation plaintext ↔ encrypted? → Model
Pipeline Architecture
Modern pattern for composable fixture-based and generated seeding.
Flow: Preset JSON or Options → RecipeOrchestrator → RecipeBuilder → IStep[] → RecipeExecutor → SeederContext → BulkCommitter
Key actors:
- RecipeBuilder: Fluent API with dependency validation
- IStep: Isolated units of work (CreateOrganizationStep, CreateUsersStep, etc.)
- SeederContext: Shared mutable state bag (NOT thread-safe)
- RecipeExecutor: Executes steps sequentially, captures statistics, commits via BulkCommitter
- RecipeOrchestrator: Orchestrates recipe building and execution (from presets or options)
- SeederDependencies (
Options/): Bundles infrastructure services (DatabaseContext,IMapper,IPasswordHasher<User>,IManglerService) into a single record. Recipes and the Orchestrator accept this instead of loose parameters. The CLI utility builds it viaSeederServiceFactory.Create().ToDependencies().
Fixture/preset separation: Fixtures (organizations, rosters, ciphers) are independent and never reference each other. The preset is the only layer that composes fixtures and defines cross-cutting relationships (folder assignments, favorites). See Seeds/docs/architecture.md.
Phase order (org presets): Org → OrgApiKey → Roster → Owner (conditional) → Generator (conditional) → Users → Groups → Collections → Folders → Ciphers → CipherCollections → CipherFolders → CipherFavorites → PersonalCiphers Phase order (individual presets): IndividualUser → NamedFolders → Generator → Folders → Ciphers → FolderAssignments → FavoriteAssignments
Individual user presets use the Pipeline with CreateIndividualUserStep (no org, no groups, no collections). These presets live in Seeds/fixtures/presets/individual/ and are identified by having a "user" key instead of "organization". They support folderNames, folderAssignments, and favoriteAssignments for fixture-driven personal vault organization. See Seeds/docs/presets.md for the catalog.
See Pipeline/ folder for implementation.
Parallelism
Steps execute sequentially (phase order preserved by RecipeExecutor). Within a step, CreateUsersStep and GeneratePersonalCiphersStep use Parallel.For internally for CPU-bound Rust FFI work (key generation, encryption).
Thread-safety requirements:
GeneratorContextlazy properties (??=) must be force-initialized before anyParallel.Forloop to prevent a data race- Generators use
ThreadLocal<Faker>for thread-safe deterministic data generation ManglerServiceandSeederContextare NOT thread-safe -- pre-compute their outputs before entering parallel loops
Performance A/B Testing
When measuring step-level performance changes, use paired worktrees:
- Create
server-PM-XXXXX/perf-baselineandserver-PM-XXXXX/perf-optimizedworktrees - Both worktrees get
Stopwatchtiming inRecipeExecutor.Execute()(the baseline measurement) - Only the optimized worktree gets actual code changes
- Run presets with
--mangleflag to avoid DB collisions between runs - Compare per-step timings across 3+ runs each, discard the first run (JIT warmup)
.worktrees/is already in.gitignore
Density Profiles
Steps accept an optional DensityProfile that controls relationship patterns between users, groups, collections, and ciphers. When null, steps use the original round-robin behavior. When present, steps branch into density-aware algorithms.
Key files:
Options/DensityProfile.cs— strongly-typed options (public class)Models/SeedPresetDensity.cs— JSON preset deserialization targets (internal records)Data/Enums/MembershipDistributionShape.cs— Uniform, PowerLaw, MegaGroupData/Enums/CollectionFanOutShape.cs— Uniform, PowerLaw, FrontLoadedData/Enums/CipherCollectionSkew.cs— Uniform, HeavyRightData/Distributions/PermissionDistributions.cs— 11 named distributions by org tier
Backward compatibility contract: DensityProfile? == null MUST produce identical output to the original code. Every step guards this with if (_density == null) { /* original path */ }.
Preset JSON: Add an optional "density": { ... } block. See Seeds/schemas/preset.schema.json for the full schema.
Presets: Organized into features/, qa/, scale/, validation/ folders under Seeds/fixtures/presets/. See Seeds/docs/presets.md for the full catalog.
Verification: SQL queries for validating density algorithms are in Seeds/docs/verification.md.
The Recipe Contract
Recipes follow strict rules:
- A Recipe SHALL accept
SeederDependenciesas its single constructor parameter - A Recipe SHALL have exactly one public method named
Seed() - A Recipe MUST produce one cohesive result
- A Recipe MAY have overloaded
Seed()methods with different parameters - A Recipe SHALL use private helper methods for internal steps
- A Recipe SHALL use BulkCopy for performance when creating multiple entities
- A Recipe SHALL compose Factories for individual entity creation
- A Recipe SHALL NOT expose implementation details as public methods
Zero-Knowledge Architecture
Critical: Unencrypted vault data never leaves the client. The server never sees plaintext.
The Seeder uses the Rust SDK via FFI because it must behave like a real Bitwarden client:
- Generate encryption keys (like client account setup)
- Encrypt vault data client-side (same SDK as real clients)
- Store only encrypted result
Data Flow
Pipeline path (fixture → entity)
SeedVaultItem → CipherSeed.FromSeedItem() → CipherSeed → {Type}CipherSeeder.Create(options) → CipherViewDto → encrypt_fields (Rust FFI) → EncryptedCipherDto → EncryptedCipherDtoExtensions → Server Cipher Entity
Core encryption (shared by all paths)
CipherViewDto → JSON + [EncryptProperty] field paths → encrypt_fields (Rust FFI, bitwarden_crypto) → EncryptedCipherDto → EncryptedCipherDtoExtensions → Server Cipher Entity
Shared logic: Factories/CipherEncryption.cs, Models/EncryptedCipherDtoExtensions.cs
Rust Crypto Dependency
The Rust shim (util/RustSdk/rust/) depends only on bitwarden_crypto. It does not depend on bitwarden_vault — the seeder drives field selection via [EncryptProperty] attributes, not SDK cipher types.
Before modifying encryption integration, run RustSdkCipherTests to validate roundtrip encryption.
Deterministic Data Generation
Same domain = same seed = reproducible data:
var seed = options.Seed ?? DeriveStableSeed(options.Domain);
Scenarios
Developer-facing documentation in Seeds/docs/scenarios/. Each file maps an engineering problem to a Seeder command.
Maintenance rules:
- When adding a new preset, check if an existing scenario should reference it as a variation
- When adding a new command or flag, check if it enables a new scenario or changes an existing one
- When CLI flags, commands, or preset names change, scan all
*.mdfiles underSeeds/andSeederUtility/for stale references - Scenario files follow the template in
Seeds/docs/scenarios/README.md - Never duplicate CLI flag documentation — link to
SeederUtility/README.md - Never duplicate preset catalog details — link to
Seeds/docs/presets.md - Scenarios describe why (the problem). READMEs describe how (the tool). Keep the split clean.
File relationships:
SeederUtility/README.md→ CLI reference (commands, flags, examples) → links to scenariosSeeds/docs/presets.md→ what exists (the catalog) → scenarios link back to itSeeds/docs/scenarios/→ why you'd use it (problem → command)
Collection Management Settings
Collection management settings are not plan-gated. AllowAdminAccessToAllCollectionItems, LimitCollectionCreation, LimitCollectionDeletion, and LimitItemDeletion apply identically across all plan types. They are org-level admin settings, not billing-plan features.
These settings alter access control behavior. When seeding scenarios that test member vs. admin permissions, collection creation/deletion policies, or item-level access, set them explicitly in the preset rather than relying on defaults.
Configurable in presets and CLI. Use the JSON preset organization block (e.g. "limitCollectionCreation": true) or the CLI flags: --limit-collection-creation, --limit-collection-deletion, --limit-item-deletion, --allow-admin-collection-access.
Security Reminders
- Default test password:
asdfasdfasdf(overridable via--passwordCLI flag orSeederSettings) - Never commit database dumps with seeded data
- Seeded keys are for testing only