Files
suaveplan/openspec/changes/api-conformance-suites/proposal.md
2026-05-27 23:29:53 +01:00

5.6 KiB

api-conformance-suites — ./testing subpath conformance functions for every abstract capability

Package: multi-package — adds ./testing subpaths to every Stage-1 abstract package Status: PROPOSED Type: feature (cross-package) Stage: 5 (Testing kit + conformance suites) — see umbrella openspec/changes/api-plugin-foundation/

Why

Per design doc §13.4, every abstract capability ships a conformance suite under its ./testing subpath. The pattern mirrors @suaveplan/i18n-shared/testing's runBackendConformance: a single function that exhaustively pins the contract; every adapter (Stage 4 production AND Stage 5 in-memory) runs the suite; new adapters that pass the suite are automatically known good; new invariants added to the suite are automatically enforced across every existing adapter.

This change adds those ./testing subpaths. It's a multi-package change because each function lives in its respective abstract package — co-location matters (the contract owner owns the conformance gate). The companion in-memory adapters (Stage 5) provide the canonical "in-memory passes the suite" self-test, proving the suite isn't trivially empty.

What changes

Adds a ./testing export to each of the following abstract packages (created in Stage 1 — api-router, api-auth, api-tenant-resolver (currently inside api-auth umbrella), api-tenant-provider, api-secrets, api-events, api-jobs, api-graphql, api-ws, api-sse, api-saga, api-2pc, api-db). Each ./testing subpath exports one conformance function:

  • ADDED: @suaveplan/api-router/testing → runRouterConformance(label, factory) — pins route registration, dispatch, blue-green slot semantics, streaming bypass.
  • ADDED: @suaveplan/api-auth/testing → runAuthSessionConformance(label, factory) — pins resolveSession contract, null-principal handling, securityScheme contribution.
  • ADDED: @suaveplan/api-tenant-resolver/testing (or @suaveplan/api-auth/testing namespace) → runTenantResolverConformance(label, factory) — pins chain-advance sentinel, principal-derived resolution.
  • ADDED: @suaveplan/api-tenant-provider/testing → runTenantProviderConformance(label, factory) — pins list, listAsync, mutation semantics.
  • ADDED: @suaveplan/api-secrets/testing → runSecretsConformance(label, factory) — pins get, manifest validation, redaction.
  • ADDED: @suaveplan/api-events/testing → runEventsBusConformance(label, factory) — pins publish/subscribe, ordering, idempotency, bounded-queue backpressure.
  • ADDED: @suaveplan/api-jobs/testing → runJobsConformance(label, factory) — pins schedule, runScheduled, perTenant fan-out.
  • ADDED: @suaveplan/api-graphql/testing → runGraphqlEngineConformance(label, factory) — pins schema merge, resolver execution, subscription delivery.
  • ADDED: @suaveplan/api-ws/testing → runWsEngineConformance(label, factory) AND runWsBrokerConformance(label, factory) — pins connection lifecycle (engine) and subscription fan-out (broker).
  • ADDED: @suaveplan/api-sse/testing → runSseEngineConformance(label, factory) — pins SSE stream lifecycle, reconnect-id handling.
  • ADDED: @suaveplan/api-saga/testing → runSagaParticipantConformance(label, factory) — pins step idempotency, compensation, replay safety.
  • ADDED: @suaveplan/api-2pc/testing → run2pcParticipantConformance(label, factory) — pins prepare/commit/rollback, in-doubt recovery.
  • ADDED: @suaveplan/api-db/testing → runDbAdapterConformance(label, factory) — pins admin vs scoped surfaces, tenant injection, migration runner, unsafe-queries gating.

Self-tests:

  • ADDED: For each runXConformance, a self-test file (src/testing/<name>.self-test.test.ts) that runs the function against the Stage-5 in-memory adapter (or, for api-router-hono's case, the direct surface) AND asserts that the run reports at least one passing invariant per category (sanity check: a conformance function that runs zero invariants is broken).

Out of scope

  • The abstract package interfaces themselves — those are Stage-1 changes (already proposed).
  • Adapter packages that consume these suites — those are separate changes (Stage 4 + Stage 5).
  • Runtime-only test helpers unrelated to capability conformance — those live in @suaveplan/testing.

Source mapping

Genesis-original. Source of design intent: docs/superpowers/specs/2026-05-13-api-plugin-foundation-design.md.

Requirement Design doc section
./testing subpath pattern (mirrors i18n-shared/testing) §13.4 (intro)
One conformance fn per capability §13.4 (final paragraph)
Same suite runs against every adapter §13.4 (middle paragraph)
New invariants automatically enforce across adapters §13.4 (middle paragraph)

Verification approach

  • Each conformance function lives at packages/api/<abstract>/src/testing/<run-name>.ts with a sibling .md documenting which invariants it pins (one bullet per it(...) title).
  • Each function's self-test (<run-name>.self-test.test.ts) calls it with the Stage-5 in-memory adapter as the factory and asserts non-empty pass count.
  • Each Stage-4 production adapter (in its own change) and each Stage-5 in-memory adapter (in its own change) runs the function against its own factory; those tests live in the adapter package, not here.
  • A dep-graph guard (tests/dep-graph/conformance-subpath-shape.test.ts) iterates every abstract package and asserts the ./testing subpath export exists AND exports the expected runXConformance symbol.

Migration impact

None — additive only. Existing abstract packages get a new subpath export; no existing import surface is touched.