15 KiB
15 KiB
Tasks — A/B testing completion
Every checkbox MUST reflect work actually done in this change. Do not retroactively tick boxes for work that was already in main when this change opened. Do not tick a box for a test that doesn't exist or doesn't pin the requirement it claims to verify. Retroactive ticking is the failure mode this template guards against.
0. Pre-flight (non-negotiable)
- 0.1 Confirm clean working tree (
git statusreports nothing) and no in-flight worktrees with uncommitted work (git worktree listthengit statusin each) - 0.2 If any worktrees exist under
.claude/worktrees/, account for each one: still-active agent, work-pending-merge, or stale-and-prune. Stale worktrees MUST be removed (git worktree remove) before opening a new one — code that has been lost twice on main is the failure mode this guards against. - 0.3 Confirm the package set in
MANIFEST.mdmatchesfind packages -maxdepth 3 -name package.jsoncount. Drift here means a prior change archived without regenerating the manifest.
1. Spec authoring
- 1.1 Read
../suaveplan/packages/universal/ab-testing/src/end-to-end (bandit/, multivariate/, segmentation/, sequential/, stats/, store/, types/, engine/) - 1.2 Read existing
openspec/specs/ab-testing/spec.md(none exists yet — will be created at archive) - 1.3 Read
openspec/AGENTS.mdSections 13, 14, and §0a (migration parity) - 1.4 Draft
proposal.mdfromopenspec/templates/proposal.md - 1.5 Fill in
## Source mappingtable — every requirement cites a line range OR appears in the "no source line" table with justification - 1.6 Draft
specs/ab-testing/spec.mdfromopenspec/templates/spec-delta.md - 1.7 Every requirement in the spec delta carries a
**Verified by:**line naming concreteit(...)titles - 1.8
## Parity Checklisttable fully populated; every NO has architectural justification ≤1 sentence
2. Implementation
2a. Promote AssignmentStoreLike to @suaveplan/types
- 2.1 Add
AssignmentStoreLikeinterface topackages/types/types/src/storage/assignment-store.tswith five async methods (get,set,getByExperiment,getByUser,deleteByExperiment) - 2.2 Re-export
AssignmentStoreLikefrom@suaveplan/typesroot barrel - 2.3 Add
Assignmentinterface alongside (or import-only re-export from@suaveplan/ab-testingonce defined there) — pin the cross-package contract
2b. Bandit subsystem
- 2.4 Add
src/bandit/bandit.types.tswithBandit<TArmId>interface andBanditConfig<TArmId> - 2.5 Add
src/bandit/epsilon-greedy.ts—EpsilonGreedyBandit<TArmId>class; constructor accepts{ arms, epsilon, rng }; defaultepsilon = 0.1; require explicitRng(noMath.randomfallback at construction) - 2.6 Add
src/bandit/ucb1.ts—UCB1Bandit<TArmId>class - 2.7 Add
src/bandit/thompson.ts—ThompsonSamplingBandit<TArmId>class; depends onbetaSamplefrom./stats/bayesian - 2.8 Add
src/bandit/index.tsbarrel - 2.9 Throw
AbTestingError(AB_CODES.INVALID_BANDIT_CONFIG, ...)whenarms.length < 2orepsilon ∉ [0, 1]
2c. Multivariate engine
- 2.10 Add
src/multivariate/multivariate.types.tswithFactor,TreatmentCombination,TreatmentResult,FactorEffect,LevelEffect,InteractionEffect,MultivariateResult - 2.11 Add
src/multivariate/factorial.ts—fullFactorial,fractionalFactorial - 2.12 Add
src/multivariate/multivariate-engine.ts—analyzeMultivariate+ factor-effect / interaction-effect helpers; split helpers intosrc/multivariate/factor-effects.tsandsrc/multivariate/interactions.tsif file > 250 L - 2.13 Add
src/multivariate/index.tsbarrel - 2.14 Throw
AbTestingError(AB_CODES.INVALID_FACTOR_DESIGN, ...)whenfactors.length === 0or any factor haslevels.length === 0
2d. Segment analysis
- 2.15 Add
src/segments/segments.types.tswithSegmentResult,SegmentConfidenceInterval,SegmentInteraction - 2.16 Add
src/segments/segment-analyzer.ts—analyzeBySegments,segmentConfidenceIntervals,detectSegmentInteraction - 2.17 Move
normalQuantilehelper tosrc/stats/normal.ts(shared by segments and sequential) - 2.18 Add
src/segments/index.tsbarrel - 2.19 Throw
AbTestingError(AB_CODES.INVALID_SEGMENT_ATTRIBUTE, ...)whenassignments.every(a => a.attributes?.[segmentAttribute] === undefined)
2e. Sequential testing
- 2.20 Add
src/sequential/sequential.types.tswithSequentialTestConfig(extended withboundaryType),InterimResult - 2.21 Add
src/sequential/boundaries.ts—obrienFlemingBoundary,pocockBoundary, dispatch onboundaryType - 2.22 Add
src/sequential/sequential-test.ts—alphaSpent,interimAnalysis,runSequentialTest,checkFutility - 2.23 Add
src/sequential/index.tsbarrel - 2.24 Throw
AbTestingError(AB_CODES.INVALID_SEQUENTIAL_CONFIG, ...)whenalpha ∉ (0, 1),maxLooks < 1,mde ≤ 0, orpower ∉ (0, 1)
2f. Bayesian stats
- 2.25 Add
src/stats/beta.ts—BetaParams,betaPdf,betaCdf,betaQuantile,betaSample,posteriorBeta, internallogBeta/logGamma/continuedFraction/gammaSample/normalSample - 2.26 Add
src/stats/bayesian.ts—BayesianResult,thompsonSample,probabilityBGreaterA,credibleInterval,expectedLoss,bayesianAnalysis - 2.27 Add
src/stats/normal.ts—normalCdf,normalQuantile(Beasley-Springer-Moro) shared with sequential and segments - 2.28 Add
src/stats/index.tsbarrel - 2.29 Replace every
rng: () => number = Math.randomdefault with requiredrng: Rngparam; exposecreateDefaultRng()from@suaveplan/ab-testing/statsfor callers wanting acrypto.getRandomValues-backed default
2g. Assignment store
- 2.30 Add
src/store/memory.ts—MemoryAssignmentStoreclass implementingAssignmentStoreLike(imported from@suaveplan/types) - 2.31 Add
createMemoryAssignmentStore()factory - 2.32 Add
src/store/index.tsbarrel - 2.33 Wire
store?: AssignmentStoreLikefield throughExperimentManagerOptionsinsrc/types/common.ts; updatecreateExperimentManagerto consult and persist via the store when provided
2h. Errors and exports
- 2.34 Add
INVALID_BANDIT_CONFIG,INVALID_FACTOR_DESIGN,INVALID_SEGMENT_ATTRIBUTE,INSUFFICIENT_SAMPLE_SIZE,INVALID_SEQUENTIAL_CONFIGtoAB_CODESinsrc/error-codes.ts - 2.35 Update
src/index.tsroot barrel to re-export from./bandit,./multivariate,./segments,./sequential,./stats,./store - 2.36 Add subpath entries to
package.jsonexportsmap for the six new subpaths (matches existing./bucketing/./targeting/./results/./providers/./errorsshape) - 2.37 Update
package.jsondescriptionfield if needed (currently mentions only deterministic bucketing + targeting + variants + conversions; extend to mention bandits / multivariate / sequential / Bayesian)
3. Tests
3a. Bandit subsystem
- 3.1
src/bandit/bandit.test.ts—EpsilonGreedyBanditexploration (when rng < epsilon) vs. exploitation (else);UCB1Banditplays each arm once before applying score;ThompsonSamplingBanditposterior update on success vs. failure - 3.2
src/bandit/bandit.spec-conformance.test.ts— injectedRngis the only randomness source;INVALID_BANDIT_CONFIGthrown for invalid configs
3b. Multivariate engine
- 3.3
src/multivariate/multivariate-engine.test.ts—fullFactorialcount =Π levels;fractionalFactorial(fraction = 0.5)returns half; factor importance sum-of-squares ranking - 3.4
src/multivariate/multivariate-engine.spec-conformance.test.ts— interaction strength is non-negative; pairwise interactions cover every(i, j)withi < j;INVALID_FACTOR_DESIGNthrown for empty factors
3c. Segment analysis
- 3.5
src/segments/segment-analyzer.test.ts— segment grouping by attribute; significance with min sample size 10; CI is[0, 1]-clamped - 3.6
src/segments/segment-analyzer.spec-conformance.test.ts— interaction-strength threshold;INVALID_SEGMENT_ATTRIBUTEthrown when attribute is absent
3d. Sequential testing
- 3.7
src/sequential/sequential-test.test.ts— O'Brien-Fleming boundary monotone-decreasing ininformationFraction; Pocock boundary constant across looks;runSequentialTeststops at firstshouldStop - 3.8
src/sequential/sequential-test.spec-conformance.test.ts— alpha-spent monotone-increasing; futility only fires wheninformationFraction ≥ 0.75;INVALID_SEQUENTIAL_CONFIGthrown for invalid configs
3e. Bayesian stats
- 3.9
src/stats/bayesian.test.ts—betaPdf/betaCdf/betaQuantileround-trip;posteriorBetaconjugate update (priors + data);probabilityBGreaterAconverges to closed-form0.5for symmetric posteriors - 3.10
src/stats/bayesian.spec-conformance.test.ts— sameRngseed produces identicalbetaSample/thompsonSample/expectedLossoutputs across runs (reproducibility pin)
3f. Assignment store
- 3.11
src/store/memory-assignment-store.test.ts—set/getround-trip;getByExperiment/getByUserfiltering;deleteByExperimentcascade;sizeandclear - 3.12
src/store/memory-assignment-store.spec-conformance.test.ts—AssignmentStoreLikecontract: every method returns aPromise;getresolvesundefinedfor missing keys;setis idempotent on identical assignments
3g. ExperimentManager integration with store
- 3.13
src/core/experiment-manager/experiment-manager.store.test.ts— whenoptions.storeis provided,assignVariantchecks store first and writes through on miss; second call for same(experimentId, userId)returns the persisted variant without re-bucketing
3h. Cross-package
- 3.14
tests/ab-testing-public-surface.test.ts— assert root barrel re-exports includeEpsilonGreedyBandit,UCB1Bandit,ThompsonSamplingBandit,fullFactorial,fractionalFactorial,analyzeMultivariate,analyzeBySegments,segmentConfidenceIntervals,detectSegmentInteraction,obrienFlemingBoundary,pocockBoundary,interimAnalysis,runSequentialTest,posteriorBeta,bayesianAnalysis,MemoryAssignmentStore,createMemoryAssignmentStore - 3.15 Every
it(...)carries{ timeout: 5000 }(Bayesian Monte Carlo tests use 30000)
4. Documentation
- 4.1 Sibling
.mdfor every new implementation.ts(≥200 words, basic + advanced examples, full API reference): one per file underbandit/,multivariate/,segments/,sequential/,stats/,store/ - 4.2 README links every new co-located
.mdunder## Modules - 4.3 Update root
README.mdquick-start with three new sections: bandit selection, multivariate analysis, sequential testing - 4.4 Update
CLAUDE.md@suaveplan/ab-testingpackage listing to include bandits, multivariate, segment analysis, sequential testing, Bayesian stats, assignment store - 4.5 Update
@suaveplan/typeslisting inCLAUDE.mdto mentionAssignmentStoreLikeif the listing is detailed enough
5. Pre-archive verification (the 5-gate triple)
- 5.1
bun run check— Biome 0 errors / 0 warnings / 0 info across the whole monorepo - 5.2
bunx turbo lint typecheck test --filter=@suaveplan/ab-testing— green - 5.3
bunx turbo test:coverage --filter=@suaveplan/ab-testing— 100/100/100/100 - 5.4
bun scripts/openspec-conformance.ts @suaveplan/ab-testing— exit 0, noerrorfindings - 5.5
bunx turbo build --filter=@suaveplan/ab-testing— green - 5.6
bunx turbo lint typecheck test --filter=@suaveplan/types— green (foundation-package side-effect fromAssignmentStoreLikepromotion)
6. Pre-archive 7-row checklist (AGENTS.md Section 13)
- 6.1 Generics threaded —
Bandit<TArmId>,EpsilonGreedyBandit<TArmId>,UCB1Bandit<TArmId>,ThompsonSamplingBandit<TArmId>infer arm-id type from constructorarmsarray;expectTypeOfassertion thatbandit.selectArm()returnsTArmId, notnumber - 6.2 Zod schemas wired at every declared boundary —
BanditConfigSchema,FactorSchema,SequentialTestConfigSchemavalidate at construction - 6.3 Every error code in
error-codes.tsthrown by at least one production path (the five new codes each tested in spec-conformance suites) - 6.4 Strict-mode behaviors tested both on and off —
Banditwithstrict: truethrows on unknown arm IDs;Banditwithstrict: falseignores - 6.5 OTel instruments emitted from production paths and asserted with a real in-memory
MeterProvider—ab.bandit.arm.selected,ab.multivariate.combinations.evaluated,ab.sequential.alpha.spent,ab.bayesian.simulations.duration_ms,ab.assignment.store.lookup - 6.6 Lifecycle dedup tested where claimed —
MemoryAssignmentStore.setis idempotent;ExperimentManagerwithstoreoption does not re-bucket after a hit - 6.7 No retroactive tick — every box above reflects work done in this change
7. Archive
- 7.1 Fold spec delta into
openspec/specs/ab-testing/spec.md(file does not exist yet — this change creates it) - 7.2 Move
openspec/changes/ab-testing-completion/→openspec/archive/<yyyy-mm-dd>-ab-testing-completion/ - 7.3 Refresh
MANIFEST.mdif package set or deps changed (no new deps;@suaveplan/typesgainsAssignmentStoreLikeinterface)
Audit Note (2026-05-02)
Audit verdict: DEFERRED — 0/81 tasks evidenced in code.
Evidence reviewed:
packages/testing/ab-testing/src/containsbucketing/,targeting/,providers/,results/,core/experiment-manager/,error-codes.ts,types/,constants/. Nobandit/, nomultivariate/, nosegments/, nosequential/, nostats/, nostore/.error-codes.tsdoes not declare the five new codes (INVALID_BANDIT_CONFIG,INVALID_FACTOR_DESIGN,INVALID_SEGMENT_ATTRIBUTE,INSUFFICIENT_SAMPLE_SIZE,INVALID_SEQUENTIAL_CONFIG).@suaveplan/typeshas noAssignmentStoreLikeinterface yet (the §2a promotion is not done).
Missing pieces:
- Bandit subsystem (
epsilon-greedy,ucb1,thompsonwith explicitRnginjection) and Bayesian stats module (stats/{beta,bayesian,normal}.ts). - Multivariate engine (
fullFactorial/fractionalFactorial/analyzeMultivariate+ factor-effect / interaction helpers). - Segment analyzer + sequential testing (O'Brien-Fleming + Pocock boundaries) +
MemoryAssignmentStoreplusAssignmentStoreLikepromotion to@suaveplan/typesand wiring throughExperimentManagerOptions.
Decision: leave change open. Six new feature blocks across statistics + storage + cross-package type-promotion is a multi-day implementation, not an audit pass.