4.6 KiB
4.6 KiB
Tasks: enterprise-campaigns
Change ID: enterprise-campaigns
Package: @suaveplan/campaigns
Target path: universal/email/campaigns
Prerequisite packages on Verdaccio: @suaveplan/error, @suaveplan/types, @suaveplan/events, @suaveplan/email-templates, @suaveplan/audience-rules, @suaveplan/idempotency, @suaveplan/retry, @suaveplan/suppression
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. Scaffold
- Create
universal/email/campaigns/withsrc/,tests/,src/__benchmarks__/and all feature subdirectories per the layout inproposal.md. - Write
package.jsonat version0.1.0with"type": "module","sideEffects": false, correctexportsmap,dependencies, anddevDependencies. - Write
tsconfig.jsonextending@suaveplan/typescript-config/library.json. - Write
vitest.config.tsusingcreateTestConfig()from@suaveplan/testing/config. - Add
lint,typecheck,test,test:coverage,build,docs:checkscripts.
2. Types + errors
src/types/common.ts—Campaign,CampaignStatus,CampaignSchedule,AbVariant,CampaignSendOptions,CampaignAnalytics,CampaignFilter,CampaignSender,CampaignManagerOptions.src/types/common.md(≥200 words, both examples, API reference).src/error-codes.ts—CampaignErrorCode(6 codes), metadata, register, eager call.src/types/schemas.ts+schemas.md— Zod schemas.
3. Core modules
src/core/campaign-fsm/—CampaignFsm: defines valid transitions betweenCampaignStatusvalues; illegal transitions throwCAMPAIGN_INVALID_TRANSITION. Tests: all valid paths, two invalid transition attempts.src/core/ab-bucketing/—bucketRecipient(email, variants): AbVariant | undefined: FNV-1a hash of email, compare against cumulative fraction bounds. Tests: determinism (same input → same bucket), distribution across 10 000 random emails is within 5% of configured fraction.src/scheduling/schedule-parser/—parseSchedule(schedule, clock): returns next fireDatefor immediate/at/recurring. Validates cron expressions (5-field). Tests: immediate →now, at → future date, recurring → next slot, invalid cron throws.src/core/campaign-manager/—CampaignManager:create,schedule,send,cancel,get,list.senditerates audience from@suaveplan/audience-rules, checks suppression per recipient, renders template per recipient (optionally with A/B variant), callsCampaignSender.send. Wraps each recipient delivery in idempotency guard.AbortSignalcancel mid-send. Tests: send happy path, abort mid-send, suppressed recipient is counted not sent, A/B variant selection, analytics returned correctly.src/analytics/campaign-analytics/—aggregateResults(results): CampaignAnalytics. Tests: all-sent, mixed-failure, all-suppressed.
4. OTel instrumentation
send: spancampaign.sendwith attributescampaign.id,campaign.recipient_count; countercampaign.send_total; histogramcampaign.send_duration_ms.
5. Property-based tests (tests/property.test.ts)
- A/B bucketing: for arbitrary variant fractions summing to ≤1,
bucketRecipientalways returns a variant within the configured set orundefined. - FSM: any reachable path from
draftreaches a terminal state (sent,cancelled,failed) in ≤4 transitions.
6. Benchmarks (src/__benchmarks__/campaigns.bench.ts)
bucketRecipientfor 100 000 recipients.
7. Docs + verify
- One
.mdper implementation file (≥200 words, both examples, API reference). RootREADME.md. bunx turbo lint typecheck test --filter=@suaveplan/campaigns— green.bunx turbo test:coverage --filter=@suaveplan/campaigns— 100%.bun scripts/publish.ts --package @suaveplan/campaigns.- Archive:
openspec/archive/2026-04-24-enterprise-campaigns/.