6.2 KiB
6.2 KiB
Tasks: enterprise-ab-testing
Change ID: enterprise-ab-testing
Package: @suaveplan/ab-testing
Target path: universal/workflow/ab-testing
Prerequisite packages on Verdaccio: @suaveplan/error, @suaveplan/types, @suaveplan/events, @suaveplan/logger, @suaveplan/feature-flags
1. Scaffold
- Create
universal/workflow/ab-testing/withsrc/,tests/,src/__benchmarks__/, and feature subdirectories per the layout inproposal.md. - Write
package.jsonat version0.1.0:"type": "module","sideEffects": falseexportsmap:"."barrel + one subpath per feature moduledependencies:@suaveplan/error workspace:^,@suaveplan/types workspace:^,@suaveplan/events workspace:^,@suaveplan/logger workspace:^,@suaveplan/feature-flags workspace:^,zod catalog:forms,@opentelemetry/api catalog:observability,tslib catalog:builddevDependencies:vitest catalog:testing,@vitest/coverage-istanbul catalog:testing,fast-check catalog:testing,mitata catalog:testing,@suaveplan/testing workspace:^
- Write
tsconfig.jsonextending@suaveplan/typescript-config/library.json. - Write
vitest.config.tsusingcreateTestConfig(). - Add
lint,typecheck,test,test:coverage,build,docs:check,benchscripts.
2. Types and error codes
- Define all public types in
src/types/common.ts:ExperimentDefinition,Variant,TargetingRule,Assignment,MetricEvent,ExperimentResults,VariantResults,ExperimentManagerOptions,ExperimentProvider,UserAttributes. - Write
src/types/common.md(≥200 words, simple + advanced examples). - Define error codes in
src/error-codes.ts:AbTestingErrorCodeunion, metadata,registerAbTestingErrorCodes(). - Write Zod schemas in
src/types/schemas.ts+src/types/schemas.md.
3. Bucketing (src/bucketing/)
- Implement
bucketUser(userId, experimentId, seed?):- FNV-1a hash of
${experimentId}:${userId}(with optional seed XOR'd in). - Normalises to
[0, 1)float.
- FNV-1a hash of
- Implement
normaliseWeights(variants)— normalisesweightvalues to sum to 1. - Implement
selectVariant(variants, bucket)— binary search over cumulative normalised weights. - Write
bucketing.test.ts(≤500 lines): determinism for same userId+experimentId, distribution spread across 10k users, weight edge cases (all equal, single variant, zero-weight skipped). - Write
bucketing.md(≥200 words).
4. Targeting (src/targeting/)
- Implement
evaluateTargeting(rules: TargetingRule[], attributes: UserAttributes): boolean:- All rules must pass (implicit AND).
- Operators:
eq,neq,in,notIn,gt,lt,gte,lte. - Returns
truewhen user matches all rules (or no rules defined).
- Write
targeting.test.ts: each operator, multi-rule AND, no rules = true, missing attribute = false. - Write
targeting.md(≥200 words).
5. Results (src/results/)
- Implement
computeResults(experimentId, assignments, conversions): ExperimentResults:- Group assignments and conversions by variant.
- Compute
conversionRate= conversions / assignments. - Aggregate metrics (count, sum, avg) per metric name per variant.
- Write
results.test.ts: known data produces expected conversion rate, empty data, single variant. - Write
results.md(≥200 words).
6. Experiment manager (src/core/experiment-manager/)
- Implement
createExperimentManager(options?):- Stores experiments in
Map<string, ExperimentDefinition>. - Stores assignments and metric events in
Mapkeyed by${experimentId}:${userId}.
- Stores experiments in
- Implement
manager.defineExperiment(def)— validates with Zod; throwsAbTestingError(AB_INVALID_DEFINITION)on schema error. - Implement
manager.assign(experimentId, userId, attributes?):- Check experiment exists and is
running; throwAB_EXPERIMENT_INACTIVEotherwise. - Evaluate targeting rules; set
inExperiment: falseif excluded. - Apply
sampleRatecheck usingrng.next(). - Use
bucketUserto select variant. - Store assignment, emit OTel span
ab.assign, counterab.assignments_total. - Emit event-bus event
ab:assigned. - Call any registered provider
trackAssignment.
- Check experiment exists and is
- Implement
manager.track(experimentId, userId, metric)— appends metric event; counterab.conversions_total. - Implement
manager.getResults(experimentId)— delegates tocomputeResults. - Implement
manager.use(provider)— registers provider. - Write
experiment-manager.test.ts(≤500 lines): full assignment flow, targeting exclusion, sample rate, metric tracking, results, provider called. - Write
experiment-manager.md(≥200 words).
7. Provider interface (src/providers/)
- Define
ExperimentProviderinterface. - Write
provider.test.ts: provider registered,trackAssignmentcalled on assign. - Write
provider.md.
8. Property-based tests (tests/property.test.ts)
- Bucketing determinism: same
userId+experimentIdalways produces same bucket value. - Weight distribution: across 10k users, each variant's assignment count is within 10% of its weight proportion.
- Targeting AND semantics: if any rule fails, result is
false; all rules passing →true.
9. Benchmarks (src/__benchmarks__/ab-testing.bench.ts)
assign_hot: 10kassigncalls for the same experiment (cache warm).bucket_hot: 10kbucketUsercalls.
10. Docs
- All per-file
.mdfiles per layout inproposal.md. universal/workflow/ab-testing/README.md.
11. Verify and publish
bunx turbo lint --filter=@suaveplan/ab-testing— zero errors.bunx turbo typecheck --filter=@suaveplan/ab-testing— zero errors.bunx turbo test --filter=@suaveplan/ab-testing— all tests pass.bunx turbo test:coverage --filter=@suaveplan/ab-testing— 100% on all touched modules.bun run docs:check --filter=@suaveplan/ab-testing— zero errors.bun scripts/publish.ts --package @suaveplan/ab-testing.- Update
CLAUDE.mdpackage listing. - Archive: move
openspec/changes/enterprise-ab-testing/toopenspec/archive/2026-04-24-enterprise-ab-testing/.