Files
suaveplan/openspec/archive/2026-05-24-api-plugin-campaigns/tasks.md
2026-05-27 23:29:53 +01:00

5.7 KiB

Tasks — @suaveplan/api-plugin-campaigns

§0 Pre-flight (every dispatched agent, every chunk)

  • pwd && git worktree list && git status — confirm we are inside .claude/worktrees/agent-* and NOT in /home/paul/web/genesis/ directly.
  • §0a Sync — fast-forward worktree to the orchestrator-supplied EXPECTED_BASE SHA; STOP if git merge-base --is-ancestor returns non-zero.

§A Chunk A — foundation (this dispatch)

  • A.1 Package shell — package.json (@suaveplan/api-plugin-campaigns@0.1.0, "sideEffects": false, subpath exports per feature folder), vite.config.ts, vitest.config.ts, tsconfig.json + tsconfig.lib.json, tests/setup/polyfills.ts, README.md.
  • A.2 src/error-codes/ — CAMPAIGNS_API_CODES (Chunk-A subset of the registered codes; B/C extend) + CampaignsApiError with explicit public constructor.
  • A.3 src/otel/ — meter binding + counter (campaigns_api.requests_total) + histogram (campaigns_api.request_duration_ms); cached + resettable.
  • A.4 src/types/ — CampaignsPluginOptions, CampaignsPluginConfig.
  • A.5 src/schemas/ — CampaignsPluginOptionsSchema Zod validator.
  • A.6 src/context/ + src/services/ — Hono ContextVariableMap and ServiceRegistry declaration-merge augmentations (Chunk-A baseline; B will add repo slots).
  • A.7 src/plugin/ — createCampaignsPlugin via definePlugin from @suaveplan/api-plugin. onInit is a no-op Chunk-A-correct placeholder; documented in plugin.md.
  • A.8 src/index.ts — explicit named re-exports for Chunk-A surface.
  • A.9 Per-file .md siblings with the six required sections.
  • A.10 Per-file .test.ts siblings; runner imports from @suaveplan/testing/runner only.
  • A.11 Verification gate green: bunx turbo lint typecheck test --filter=@suaveplan/api-plugin-campaigns, bunx turbo test:coverage --filter=... 100/100/100/100, bunx turbo build --filter=..., feature-layout check empty.
  • A.12 bun scripts/folder-reorg/generate-manifest.ts re-runs cleanly.

§B Chunk B — repositories (follow-up)

  • B.1 src/repositories/campaign/ — SurrealCampaignRepository implementing CampaignStore from @suaveplan/campaigns; uses positional $p0 placeholders against the genesis SurrealAdapter. The package also re-exports InMemoryCampaignStore from @suaveplan/campaigns as the in-memory reference implementation.
  • B.2 src/repositories/suppression/ — local CampaignSuppressionStore interface + SurrealCampaignSuppressionRepository + reference InMemoryCampaignSuppressionStore (tenant + channel shape; NOT a wrapper over @suaveplan/suppression, which is email-only).
  • B.3 src/repositories/delivery-events/ — wrapper that consumes the already-shipped SurrealDeliveryEventStore from @suaveplan/delivery-tracking. (Out of scope for this dispatch per orchestrator's Chunk-B scope; rolls into a follow-up.)
  • B.4 Extended CAMPAIGNS_API_CODES registry — added six codes: CAMPAIGN_NOT_FOUND, SUPPRESSION_NOT_FOUND, INVALID_INPUT, DB_UNAVAILABLE, DUPLICATE_SUPPRESSION, TENANT_REQUIRED.
  • B.5 Bundled SurrealQL DDL strings. (Deferred to Chunk C; plugin's applySchemaOnInit is wired there.)
  • B.6 Plugin onInit registers the repositories on the ServiceRegistry and Hono ContextVariableMap. (Deferred to Chunk C which owns the lifecycle; service-registry slots are reserved here as optional fields.)
  • B.7 100/100/100/100 coverage on the full package (Chunk A + Chunk B modules); verified via bunx turbo test:coverage.

§C Chunk C — routes (this dispatch)

  • C.1 src/routes/campaigns/ — 12 routes total: list/create/get/update/delete plus send/pause/cancel/duplicate/test action routes and stats/events reporting routes. State guard uses CampaignStateMachine from @suaveplan/campaigns; invalid transitions raise CAMPAIGNS_API.INVALID_STATE_TRANSITION (HTTP 409).
  • C.2 src/routes/suppression/ — 4 routes: list/check/add/remove with tenant scoping and channel-keyed dedup.
  • C.3 src/routes/webhooks/ — 2 routes: provider delivery-event ingest (auto-suppress on unsubscribed/bounced/complained) and base64-JSON one-click unsubscribe with INVALID_UNSUBSCRIBE_TOKEN mapping.
  • C.4 Plugin onInit resolves app + db + openAPIRegistry from the ServiceRegistry, constructs SurrealCampaignRepository, SurrealCampaignSuppressionRepository, and SurrealDeliveryEventStore, injects them on the Hono ContextVariableMap, and mounts every route via definePluginRoutes. Collision guard raises CAMPAIGNS_API.ROUTE_PREFIX_COLLISION. Missing-service guard raises CAMPAIGNS_API.SERVICE_NOT_AVAILABLE.
  • C.5 Integration tests in tests/integration/plugin-integration.test.ts against a real OpenAPIHono host with in-memory repositories; 8 happy-path round-trips covering every route group.
  • C.6 100/100/100/100 coverage on the full package (Chunks A + B + C).
  • C.7 Extended CAMPAIGNS_API_CODES with five Chunk-C codes: INVALID_STATE_TRANSITION, INVALID_UNSUBSCRIBE_TOKEN, ROUTE_PREFIX_COLLISION, WEBHOOK_REJECTED, SERVICE_NOT_AVAILABLE.

Post-C orchestrator steps (NOT a chunk)

  • Fold the spec delta into openspec/specs/api-plugin-capaigns/spec.md.
  • Move openspec/changes/api-plugin-campaigns/ to openspec/archive/<yyyy-mm-dd>-api-plugin-campaigns/.
  • Update MANIFEST.md and CLAUDE.md package roster.