Files
2026-05-27 23:29:53 +01:00

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 status reports nothing) and no in-flight worktrees with uncommitted work (git worktree list then git status in 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.md matches find packages -maxdepth 3 -name package.json count. Drift here means a prior change archived without regenerating the manifest.

1. Scaffold

  • Create universal/email/campaigns/ with src/, tests/, src/__benchmarks__/ and all feature subdirectories per the layout in proposal.md.
  • Write package.json at version 0.1.0 with "type": "module", "sideEffects": false, correct exports map, dependencies, and devDependencies.
  • Write tsconfig.json extending @suaveplan/typescript-config/library.json.
  • Write vitest.config.ts using createTestConfig() from @suaveplan/testing/config.
  • Add lint, typecheck, test, test:coverage, build, docs:check scripts.

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 between CampaignStatus values; illegal transitions throw CAMPAIGN_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 fire Date for 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. send iterates audience from @suaveplan/audience-rules, checks suppression per recipient, renders template per recipient (optionally with A/B variant), calls CampaignSender.send. Wraps each recipient delivery in idempotency guard. AbortSignal cancel 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: span campaign.send with attributes campaign.id, campaign.recipient_count; counter campaign.send_total; histogram campaign.send_duration_ms.

5. Property-based tests (tests/property.test.ts)

  • A/B bucketing: for arbitrary variant fractions summing to ≤1, bucketRecipient always returns a variant within the configured set or undefined.
  • FSM: any reachable path from draft reaches a terminal state (sent, cancelled, failed) in ≤4 transitions.

6. Benchmarks (src/__benchmarks__/campaigns.bench.ts)

  • bucketRecipient for 100 000 recipients.

7. Docs + verify

  • One .md per implementation file (≥200 words, both examples, API reference). Root README.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/.