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

2.9 KiB

api-types — Foundation gap 5 cleanup: nullable tenant on RequestContext

Package: @suaveplan/api-types Status: PROPOSED Type: type-soundness-fix

Why

The foundation-fix-gap-5-default-tenant change widened the runtime behaviour: when the resolver chain misses and no defaultTenant is configured, RequestContext.tenant / RequestContext.tenantId are null at runtime. The static types in @suaveplan/api-types were NOT updated — they still declared tenant: TenantContext and tenantId: TenantId as non-nullable.

Consequences observed in request-pipeline-helpers.ts:

tenantId: (args.tenant?.id ?? null) as never,
tenant: (args.tenant ?? null) as never,

The as never is a documented escape hatch — but it is a type lie that defeats every handler-side guard the type system was supposed to enforce. Out-of-spec debt; resolved before moving on per the user's directive.

What changes

MODIFIED: RequestContext<Caps> field types in @suaveplan/api-types/plugin-context:

readonly tenantId: TenantId | null;
readonly tenant: TenantContext | null;

Existing tenant/tenantId consumers in the monorepo already use the null-safe ?. access pattern (verified across e2e/foundation-stack, apps/demo, apps/testbed, and every api-plugin-*). No handler needed a code change beyond stripping the now-redundant casts.

The JobContext and ConnectionContext flavours are NOT widened. Per-tenant jobs and connection-scoped contexts only exist after a tenant has been resolved — their resolver chain runs separately and never returns null to the handler. Widening those would force a spurious null-check on every job/connection handler.

The as never casts in request-pipeline-helpers.ts lines 143-150 are removed. The two remaining as casts at the same site are generic-erasure points (capabilities/instruments resolve to typed slices at the route handler call-site, not at the untyped pipeline seam); they now use the explicit as RequestContext["capabilities"] form with a multi-line code comment explaining the boundary instead of a blanket as never.

Impact

  • @suaveplan/api-types — spec MODIFIED Requirement (this delta).
  • @suaveplan/api-host — as never casts stripped from buildRequestContext; generic-erasure casts retained with the documented boundary comment.
  • E2E foundation-stack: 11 Playwright tests still green; no handler changes needed since fixture plugin already used ctx.tenant?.id.

Verification

bunx turbo lint typecheck test build --filter=@suaveplan/api-types --filter=@suaveplan/api-host
bunx turbo test:coverage --filter=@suaveplan/api-host --filter=@suaveplan/api-types
cd e2e/foundation-stack && bunx playwright test  # 11/11

All green. No as never remains for tenant/tenantId in request-pipeline-helpers.ts. No biome-ignore / @ts-ignore / @ts-expect-error added anywhere.