@shivaedev/platform 0.16.0 → 0.16.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,11 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.16.1 - 2026-10-18
4
+
5
+ ### Fixed
6
+
7
+ - Report an expired session during Better Auth's provider account lookup as Unauthorized instead of AuthUnavailable.
8
+
3
9
  ## 0.16.0 - 2026-10-18
4
10
 
5
11
  ### Added
package/README.md CHANGED
@@ -65,7 +65,7 @@ Omit `services` when the app has no service Layer of its own. The host uses `Lay
65
65
 
66
66
  The listener uses `HOST` or `127.0.0.1`, uses `PORT` or 3000, and reads `DATABASE_URL`. Set overrides in the one `config` object: `host`, `port` (0 selects an ephemeral port), `trustedProxy`, `maxBodySize`, `databaseUrl` (an Effect `Redacted<string>`), `applicationName`, and optional `changes`. Migration options accept `table` and `lockTimeout` (default `effect_sql_migrations` and five seconds). Native Layer failures remain in the build error channel; missing application dependencies remain in the requirement type.
67
67
 
68
- RPC request bodies are limited to 1 MiB by default. Set `MAX_BODY_SIZE` to a byte count or override it with `config.maxBodySize`, a nonnegative safe integer. An oversized NDJSON body returns a 4xx before any RPC handler runs; real HTTP tests cover fixed-length and chunked requests, byte counting, and configuration precedence. This limit applies to the host's RPC route.
68
+ RPC request bodies are limited to 1 MiB by default. Set `MAX_BODY_SIZE` to a byte count or override it with `config.maxBodySize`, a nonnegative safe integer. An oversized NDJSON body, fixed-length or chunked, returns a 4xx before any RPC handler runs. This limit applies to the host's RPC route.
69
69
 
70
70
  For a native Node HTTP route, use `readBoundedBody` from `@shivaedev/platform/server/boundedBody.ts` with Effect's `HttpIncomingMessage.MaxBodySize`. It returns an `ArrayBuffer` without decoding the bytes. Oversized `content-length` headers are rejected before reading; chunked overflow drains the remaining body and preserves the connection for another request. `RequestBodyTooLarge` is a typed failure that implements Effect's `HttpServerRespondable` protocol with status 413; other read errors remain native `HttpServerError` failures. Consume the body once through this reader and decode the result in the application.
71
71
 
@@ -84,9 +84,9 @@ const routes = HttpRouter.add("POST", "/webhook", (request) =>
84
84
 
85
85
  RPC uses the existing `localRpcGuard`: it requires a loopback peer and Host, same-origin or absent Origin, and the NDJSON content type. Session lookup uses transport headers. The host applies `RequestTracing` last to every served RPC, including loopback RPC, so it wraps authentication and handlers without a per-contract tracing declaration. Existing tracing declarations run once at that outermost boundary. With a session policy, contracts declare `Authenticated` and `MaybeAuthenticated` as needed; the host supplies their implementations. For `AuthenticatedUser`, merge `authenticatedUserLayer(sessions)` beside the handlers as shown below. Declared errors retain their Schema types. Applications still own resource authorization and use `rpcBoundary` when they need to map unexpected failures.
86
86
 
87
- For local tools, use `policy: "loopback"` with contracts that do not use session middleware to serve RPC without a session provider. The guard requires a loopback peer and Host, absent or same-origin Origin, and NDJSON content type. This policy does not use `TRUSTED_PROXY` or `config.trustedProxy`. Set `rpcPath` to choose another native RPC route; `/rpc` remains the default.
87
+ For local tools, use `policy: "loopback"` with contracts that do not use session middleware to serve RPC without a session provider. The same guard applies. This policy does not use `TRUSTED_PROXY` or `config.trustedProxy`. Set `rpcPath` to choose another native RPC route; `/rpc` remains the default.
88
88
 
89
- Omit `migrations` to host an app without a database. This form creates no SQL pool and reads no `DATABASE_URL`; `AppHttpHostOptions` describes it. `AppHostOptions` requires migrations for database-backed hosts. A service that needs SQL still requires a SQL Layer. Provide `appDatabase` to the application’s service Layer when it owns database composition, as Fleet's daemon does with its migration loader and namespace ledger. Real HTTP tests cover the custom route, session-free loopback guard and database-free startup; Fleet's PostgreSQL daemon tests cover the adopted composition.
89
+ Omit `migrations` to host an app without a database. This form creates no SQL pool and reads no `DATABASE_URL`; `AppHttpHostOptions` describes it. `AppHostOptions` requires migrations for database-backed hosts. A service that needs SQL still requires a SQL Layer. Provide `appDatabase` to the application’s service Layer when it owns database composition.
90
90
 
91
91
  `appHost` serves unauthenticated `GET /health`. It queries its SQL client on each request and returns `200` only when the query succeeds within two seconds; a missing SQL client, query failure or timeout returns `503`. Both responses have an empty body and `Cache-Control: no-store`. This route also works when the SQL client comes from the application’s `services` Layer. Use it for database readiness checks.
92
92
 
@@ -107,7 +107,7 @@ const host = appHost({
107
107
 
108
108
  Construct Better Auth inside `application`, since its constructor starts schema validation. Keep validation enabled so a current migration ledger with missing auth columns still reports drift. The starter uses this form after its pre-deploy migrations.
109
109
 
110
- Set `OTEL_EXPORTER_OTLP_ENDPOINT` (the collector base URL; `/v1/traces` is appended) or `OTEL_EXPORTER_OTLP_TRACES_ENDPOINT` (the complete traces URL, which takes precedence) to export `appHost`'s Effect spans over OTLP/HTTP. `OTEL_EXPORTER_OTLP_PROTOCOL` selects `http/protobuf` or `http/json`; `OTEL_EXPORTER_OTLP_TRACES_PROTOCOL` takes precedence for traces. When neither protocol variable is set, the default is `http/protobuf`, following the [OpenTelemetry recommendation](https://opentelemetry.io/docs/specs/otel/protocol/exporter/#specify-protocol). Unsupported protocols, including `grpc`, fail startup with a `ConfigError` naming the value. `OTEL_EXPORTER_OTLP_HEADERS` supplies comma-separated `key=value` exporter headers with URL-encoded values. `OTEL_SERVICE_NAME` sets the service name and `OTEL_SERVICE_VERSION` sets `service.version`; `OTEL_RESOURCE_ATTRIBUTES` adds comma-separated, URL-encoded resource attributes and can supply `service.name` when no service name is set. The fallback name is `unknown_service`. Configuration is read through Effect Config. Without an endpoint, no exporter or export loop is built, even when service metadata is set.
110
+ Set `OTEL_EXPORTER_OTLP_ENDPOINT` or `OTEL_EXPORTER_OTLP_TRACES_ENDPOINT` to export `appHost`'s Effect spans over OTLP/HTTP. Without an endpoint, no exporter or export loop is built, even when service metadata is set. The `http/protobuf` default follows the [OpenTelemetry recommendation](https://opentelemetry.io/docs/specs/otel/protocol/exporter/#specify-protocol); unsupported protocols, including `grpc`, fail startup with a `ConfigError` naming the value. Header and resource-attribute values are `key=value` pairs with URL-encoded values. Configuration is read through Effect Config.
111
111
 
112
112
  The supported tracing variables and defaults are:
113
113
 
@@ -129,20 +129,18 @@ The supported tracing variables and defaults are:
129
129
 
130
130
  Timeouts fail through the HTTP client's typed transport-error channel and use the same warning path as other transport failures. Effect retains its native retry policy; the request timeout applies to each attempt. The batch defaults follow Effect's `OtlpTracer.layer`. Its options cannot express a separate batch-operation deadline or a queue capacity, so `OTEL_BSP_EXPORT_TIMEOUT` controls shutdown only and `OTEL_BSP_MAX_QUEUE_SIZE` is unsupported. Other `OTEL_BSP_*` variables are unsupported.
131
131
 
132
- On Railway, enable the per-service tracing setting separately for the web service and worker, and leave both exporter endpoint variables unset in your app's configuration. Railway injects the `OTEL_*` variables, including `OTEL_EXPORTER_OTLP_PROTOCOL=http/protobuf`, only when the app sets no endpoint of its own. The framework exports through Effect's own `effect/observability` OTLP tracer; eBPF auto-instrumentation is outside this path. For a worker, provide `otlpTracing` from `@shivaedev/platform/server/otlpTracing.ts` to its application Layer. It batches ended Effect spans and flushes during scoped shutdown with a three-second bound by default. The first failed export logs a warning with the collector status and redacted response body, or the transport failure kind when no response arrives. Repeated failures stay quiet until a successful export. Collector connection failures and HTTP errors leave requests, jobs and shutdown successful. HTTP receiver tests cover the host and the starter's PostgreSQL word-count worker; they do not establish delivery in a deployed collector.
132
+ On Railway, enable the per-service tracing setting separately for the web service and worker, and leave both exporter endpoint variables unset in your app's configuration. Railway documents that it injects the `OTEL_*` variables, including `OTEL_EXPORTER_OTLP_PROTOCOL=http/protobuf`, only when the app sets no endpoint of its own. The framework exports through Effect's own `effect/observability` OTLP tracer; eBPF auto-instrumentation is outside this path. For a worker, provide `otlpTracing` from `@shivaedev/platform/server/otlpTracing.ts` to its application Layer. It batches ended Effect spans and flushes during scoped shutdown with a three-second bound by default. The first failed export logs a warning with the collector status and redacted response body, or the transport failure kind when no response arrives. Repeated failures stay quiet until a successful export. Collector connection failures and HTTP errors leave requests, jobs and shutdown successful.
133
133
 
134
134
  Exported HTTP server spans use `{method} {route}` with `http.route` for matched RPC and router paths, including parameter templates; unmatched requests use the method alone. The production host continues incoming W3C `traceparent` context, including one sent by an edge proxy.
135
135
 
136
136
  Both JSON and protobuf export serialization apply sensitive-key and credential-text redaction to span attributes, events, links, error details and resource attributes. Credential text includes JWTs, Bearer/Basic credentials, sensitive assignments including bare `sig=...` anywhere in text (with or without `?`), and complete PEM private-key blocks through their matching END marker, including RSA, EC, OPENSSH and encrypted private keys. Matches become `<redacted>`. `sig` uses the shared sensitive-key policy for attributes and text. URL query values, fragments and user information are also redacted. Exporter headers stay in the collector request, outside the spans. Applications must still avoid placing secrets in span names, free text or custom attributes.
137
137
 
138
- Set `config.maxConnections` on `appDatabase` or a migrated `appHost` to bound the pool. A daemon with long-lived RPC streams can use `config.disablePreemptiveShutdown: true` to let scope closure interrupt those streams before closing the listener; omitting it keeps Node's native graceful-shutdown default. Fleet's daemon tests cover one bounded pool and scoped provider and listener cleanup, including follower reconnection after shutdown.
138
+ Set `config.maxConnections` on `appDatabase` or a migrated `appHost` to bound the pool. A daemon with long-lived RPC streams can use `config.disablePreemptiveShutdown: true` to let scope closure interrupt those streams before closing the listener; omitting it keeps Node's native graceful-shutdown default. No platform test covers these two options.
139
139
 
140
- For a deployment behind a proxy, set `HOST=0.0.0.0` and `TRUSTED_PROXY` to a comma-separated list of proxy peer IPs or CIDR ranges (IPv4 or IPv6), or use `config.host` and `config.trustedProxy`. Only matching peers may supply `X-Forwarded-Host` and `X-Forwarded-Proto` (`http` or `https`) for the guard's same-origin check; the proxy must overwrite both headers and preserve Origin. A single authority is required, and comma-separated forwarding chains are refused. IPv4-mapped IPv6 peers match their IPv4 address. Malformed addresses or ranges fail `appHost` startup with a typed `ConfigError`. NDJSON and the application's session Origin policy still apply. Real forwarding-proxy tests cover trusted and untrusted peers, configuration overrides and typed refusals; they do not establish any hosting provider's proxy configuration. See the [deployment quickstart](https://github.com/ShivaeDev/platform/blob/main/docs/framework/quickstart.md#deploy).
140
+ For a deployment behind a proxy, set `HOST=0.0.0.0` and `TRUSTED_PROXY` to a comma-separated list of proxy peer IPs or CIDR ranges (IPv4 or IPv6), or use `config.host` and `config.trustedProxy`. Only matching peers may supply `X-Forwarded-Host` and `X-Forwarded-Proto` (`http` or `https`) for the guard's same-origin check; the proxy must overwrite both headers and preserve Origin. A single authority is required, and comma-separated forwarding chains are refused. IPv4-mapped IPv6 peers match their IPv4 address. Malformed addresses or ranges fail `appHost` startup with a typed `ConfigError`. NDJSON and the application's session Origin policy still apply. See the [deployment quickstart](https://github.com/ShivaeDev/platform/blob/main/docs/framework/quickstart.md#deploy).
141
141
 
142
142
  A subscription handler reads `AppChanges` and returns `changes.stream(keys)` after its authorization check. Writes use `transact` and `invalidateOnCommit` from effect-sql: local streams receive committed invalidations and rollback emits none. For delivery between processes, set `config.changes: { channel: "app_changes" }`; the host binds PostgreSQL LISTEN/NOTIFY to the same SQL pool and resyncs streams when its listener recovers. The native heartbeat and retry options may be supplied in `changes`. A worker can provide `appDatabase({ migrations, config })` to its service Layer to share this database and delivery composition without starting HTTP.
143
143
 
144
- The [composed PostgreSQL fixture](https://github.com/ShivaeDev/platform/blob/main/docs/framework/composed-example.md) uses this host for its HTTP servers and `appDatabase` for its worker. It proves commit visibility, rollback silence, typed refusals and delivery between server processes. Platform's host tests also cover static files, environment configuration, guard rejection before session lookup, startup ordering, migration failure and scoped shutdown.
145
-
146
144
  ### 1. Once per application: contracts and session policy
147
145
 
148
146
  Declare the RPC group in a shared module:
@@ -219,8 +217,6 @@ export const AccountServer = Layer.merge(AccountHandlers, authenticatedUserLayer
219
217
 
220
218
  Here `sessions` is the application's policy from the previous example, with a Better Auth lookup that returns all three user fields. Merge `authenticatedUserLayer(sessions)` alongside the handler Layer when using `appHost`; the host continues to supply its id-only and optional middleware. A missing session fails with typed `Unauthorized`; rejected origins fail with `Forbidden`, and provider failures remain `AuthUnavailable`. The profile contains id, name and email, plus an optional role when the provider supplies it, without exposing the rest of the provider's user record. Handlers do not query the provider's user table.
221
219
 
222
- The starter's `notes.whoami` query returns id, name and email, and its note handlers use `SessionUser.id` for ownership. Its PostgreSQL/HTTP test checks concurrent users, anonymous requests and sign-out revocation.
223
-
224
220
  #### Reading provider account IDs
225
221
 
226
222
  Opt in to provider account IDs by passing Better Auth's account lookup as the second argument to `betterAuthSessions`. It runs after a session is found, using the same transport headers:
@@ -241,7 +237,7 @@ const githubAccount = Effect.map(SessionUser, (user) =>
241
237
 
242
238
  Use `authenticatedUserLayer(sessions)` as above. `SessionUserValue.providerAccounts` is an optional readonly array of `ProviderAccountValue`, each containing only `{ providerId: string, accountId: string }`. GitHub's numeric user ID is represented as a string, for example `{ providerId: "github", accountId: "123" }`; it is not Better Auth's account-row id. A user without a GitHub account has no matching entry. Without the second lookup, the profile omits `providerAccounts`. No application SQL against Better Auth's `account` table is needed.
243
239
 
244
- An account lookup failure follows the same `AuthUnavailable` path as a session lookup failure. The starter's social-session story uses real PostgreSQL, GitHub OAuth responses from a local provider fixture, and Platform's HTTP host: GitHub sign-in exposes its account ID through native RPC, email sign-up has no GitHub entry, sign-out rejects access, and an account lookup failure stays distinct from a missing session.
240
+ An account lookup failure follows the same `AuthUnavailable` path as a session lookup failure.
245
241
 
246
242
  ### Requiring an admin
247
243
 
@@ -275,7 +271,7 @@ const routes = HttpRouter.use((router) =>
275
271
  );
276
272
  ```
277
273
 
278
- `withSessionUser(policy, headers, operation)` resolves the session through the existing transport-header and Origin policy path and provides `Identity` and `SessionUser` only around the supplied Effect. It is also the boundary used by `authenticatedUserLayer`. Both the guard and identity boundary use public provider data, without reading Better Auth's tables or internal services. Keep Better Auth's cookie cache disabled for database-backed role removal to take effect on the next request, as in the starter. The auth story kit proves admin access, HTTP 401/403 and typed RPC refusals, and role removal with a retained session against PostgreSQL and a loopback host.
274
+ `withSessionUser(policy, headers, operation)` resolves the session through the existing transport-header and Origin policy path and provides `Identity` and `SessionUser` only around the supplied Effect. It is also the boundary used by `authenticatedUserLayer`. Both the guard and identity boundary use public provider data, without reading Better Auth's tables or internal services. Keep Better Auth's cookie cache disabled for database-backed role removal to take effect on the next request, as in the starter.
279
275
 
280
276
  ### Transport headers and request ids
281
277
 
@@ -314,7 +310,7 @@ A missing session produces `Option.none()` here. The same policy still rejects f
314
310
 
315
311
  For an HTTP RPC server that serves only the local machine, wrap its HTTP effect with `localRpcGuard` from `@shivaedev/platform/server/rpc-server/local.ts`. Provide the native `HttpServerRequest` and `RpcSerialization` services around the returned effect, using the same serialization as the RPC server. No session provider is required.
316
312
 
317
- By default, the guard returns HTTP 403 before running the effect unless the transport reports a loopback peer address, the Host is a loopback host, any Origin matches the HTTP Host, and the content type matches the serialization. An absent peer address is rejected. Real Node HTTP tests cover accepted local requests and rejected Origin, Host and content-type requests, including a DNS-rebinding Host on a loopback connection. The optional second argument, `localRpcGuard(effect, trustedProxy)`, enables the same address and range forwarding policy as `appHost`.
313
+ By default, the guard returns HTTP 403 before running the effect unless the transport reports a loopback peer address, the Host is a loopback host, any Origin matches the HTTP Host, and the content type matches the serialization. An absent peer address is rejected, and a DNS-rebinding Host on a loopback connection is refused. The optional second argument, `localRpcGuard(effect, trustedProxy)`, enables the same address and range forwarding policy as `appHost`.
318
314
 
319
315
  The same module exports `loopbackOnly(handler)` for local page routes and `sameOrigin(request)` for an HTTP Origin check. `loopbackOnly` checks Host and any known peer address; it permits an absent peer address so embedded Fetch page handlers remain usable. It does not check Origin or content type.
320
316
 
@@ -370,8 +366,6 @@ export const save = storageSave.pipe(expose);
370
366
 
371
367
  `storageSave` is the application's Effect. `unexpected` constructs a member of the declared schema from the public message. The default message is `Internal server error`; `message` supplies application text, with credential-shaped strings redacted. The original Effect cause is kept on the server rejection's non-enumerable `cause`, so an RPC server span can trace it. Keep `cause` out of the wire schema, as the taxonomy classes do. `ServiceUnavailable` describes a declared availability failure without implying a session-provider outage.
372
368
 
373
- The native HTTP RPC fixture in `src/server/rpc-server/unauthenticatedRejections.spec.ts` proves decoded taxonomy types, redacted fallback messages and the original server trace cause without session middleware. Work Board's `src/server/rpc/rejectionTypes.spec.ts` proves its adoption through real loopback HTTP.
374
-
375
369
  ### Request diagnostics
376
370
 
377
371
  Tracing logs declared failures with `RPC failure` and their tag, and defects with `RPC defect`. The diagnostic payload masks nested credential-shaped keys and Effect `Redacted` values. Credential-shaped strings, such as bearer tokens, URL passwords and sensitive `key=value` text, are masked too. Strings retain at most 2,048 characters before a truncation marker; collections retain at most 50 entries with a marker for the remainder, and binary values become size summaries.
@@ -507,7 +501,7 @@ it.effectApp("stores the user's email", function* ({ db, trpc, userExists }) {
507
501
  });
508
502
  ```
509
503
 
510
- The direct database, tRPC caller and extension share the test's PostgreSQL transaction. Successful and expected-failing tests roll their writes back. `trpc({ actor: "admin" })` selects another caller context within the test. `promise(() => auth.api.getSession({ headers }))` runs a Promise boundary with the test's services; the Better Auth adapter tests use that helper to join the rollback transaction.
504
+ The direct database, tRPC caller and extension share the test's PostgreSQL transaction. Successful and expected-failing tests roll their writes back. `trpc({ actor: "admin" })` selects another caller context within the test. `promise(() => auth.api.getSession({ headers }))` runs a Promise boundary with the test's services, so Better Auth adapter calls inside it join the rollback transaction.
511
505
 
512
506
  ### API
513
507
 
@@ -515,33 +509,26 @@ Import the module that defines the name; there is no package-root entry. Paths b
515
509
 
516
510
  | Module | Names and purpose |
517
511
  | --- | --- |
518
- | `server/otlpTracing.ts` | `otlpTracing`: scoped, opt-in Effect OTLP trace export configured through standard exporter variables; provide it to worker Layers. |
519
- | `server/boundedBody.ts` | `readBoundedBody`, `RequestBodyTooLarge`: bounded body bytes and a typed HTTP 413 failure. |
520
- | `server/appHost.ts` | `appHost({ group, handlers, services, migrations?, sessions?, policy?, rpcPath?, assets?, routes?, config? })` or `appHost({ application, migrations, config? })`, `appDatabase({ migrations, config? })`, `AppChanges`, `AppHostOptions`, `AppHttpHostOptions`, `AppLocalHostOptions`, `AppDatabaseOptions`: scoped HTTP and PostgreSQL composition with loopback defaults and optional proxy trust. |
521
512
  | `errors/taxonomy.ts` | `NotFound`, `Unauthorized`, `Forbidden`, `BadRequest`, `Conflict`, `PreconditionFailed`, `TooManyRequests`, `AuthUnavailable`, `InternalServerError`, `ServiceUnavailable`: Schema tagged error classes with a `message`; `BadRequest` and `Conflict` also accept an optional `field`. |
522
- | `errors/rejectedField.ts` | `rejectedField(unknown): Option<RejectedField>`; `RejectedField` is `{ field: string, message: string }`. |
523
- | `rpc/identity.ts` | `RequestId`: string service. `Identity`: `IdentityValue`, `{ id: string }`. `OptionalIdentity`: `Option<IdentityValue>`. `SessionUser`: `SessionUserValue`, `{ id, name, email, providerAccounts? }`; `ProviderAccountValue`: `{ providerId, accountId }`. |
513
+ | `errors/rejectedField.ts` | `rejectedField(unknown)`: the field and message of a field rejection. |
514
+ | `rpc/identity.ts` | `RequestId`, `Identity`, `OptionalIdentity`, `SessionUser`: request-local services. |
524
515
  | `rpc/middleware.ts` | `RequestTracing`, `Authenticated`, `AuthenticatedUser`, `MaybeAuthenticated`: native RPC middleware declarations. |
525
- | `server/rpc-server/boundary.ts` | `rpcBoundary(schema, { unexpected, message? })`; `RpcBoundaryOptions<Error>`: declared failures pass through, unexpected failures become one declared rejection. |
526
- | `server/rpc-server/session.ts` | `authenticatedLayer(policy)`, `maybeAuthenticatedLayer(policy)`, `resolveSession(policy, headers, rpc)`; `SessionShape`, `SessionProvider<Session>`, `SessionPolicy<Session>`. `authenticatedUserLayer(policy)` requires `UserSessionShape` and implements `AuthenticatedUser`. |
527
- | `server/rpc-server/adapters/betterAuthSessions.ts` | `betterAuthSessions(getSession, getProviderAccounts?)`; `GetSession<Session>` returns `Promise<Session \| null>`, `GetProviderAccounts` returns provider/id pairs, and `SessionWithProviderAccounts<Session>` describes an enriched session. |
528
- | `server/rpc-server/origin.ts` | `trustedOrigins({ allow, missing })`, `originRequest(headers, rpc)`; `OriginPolicy`, `OriginRequest`, `TrustedOriginsOptions`. |
529
- | `server/rpc-server/local.ts` | `localRpcGuard(effect)`: HTTP RPC guard using native request and serialization services; `loopbackOnly(handler)`, `sameOrigin(request)`, `trustedProxyPeer(trustedProxy?)`, `trustedProxyRanges(trustedProxy?)`. |
530
- | `server/rpc-server/transportHeaders.ts` | `transportHeaders(rpcHeaders)`: Effect returning the headers used for a transport-level decision. |
531
- | `server/rpc-server/tracing.ts` | `requestTracingLayer({ header?, sensitive? })`; `RequestTracingOptions`. |
532
- | `server/rpc-server/redact.ts` | `redact(value, sensitive?)`: unknown diagnostic value. |
533
- | `server/rpc-server/redactCause.ts` | `redactCause(cause, sensitive?)`, `redactDefect(defect, sensitive?)`, `redactingErrorReporter(reporter, sensitive?)`. |
534
- | `server/rpc-server/sensitive.ts` | `isSensitiveKey`, `redactText`, `REDACTED`; `SensitiveKey` is `(key: string) => boolean`. |
516
+ | `rpc/requireAdmin.ts` | `requireAdmin`: the signed-in user with an admin role. |
517
+ | `server/appHost.ts` | `appHost`, `appDatabase`, `AppChanges`: scoped HTTP and PostgreSQL composition with loopback defaults and optional proxy trust. |
518
+ | `server/boundedBody.ts` | `readBoundedBody`, `RequestBodyTooLarge`: bounded body bytes and a typed HTTP 413 failure. |
519
+ | `server/otlpTracing.ts` | `otlpTracing`: opt-in Effect OTLP trace export for worker Layers. |
520
+ | `server/rpc-server/session.ts` | `authenticatedLayer`, `maybeAuthenticatedLayer`, `authenticatedUserLayer`, `withSessionUser`, `resolveSession`: session middleware implementations and the identity boundary. |
521
+ | `server/rpc-server/adapters/betterAuthSessions.ts` | `betterAuthSessions(getSession, getProviderAccounts?)`: a session provider from Better Auth lookups. |
522
+ | `server/rpc-server/origin.ts` | `trustedOrigins({ allow, missing })`: an exact Origin allowlist. |
523
+ | `server/rpc-server/local.ts` | `localRpcGuard`, `loopbackOnly`, `sameOrigin`, `trustedProxyPeer`, `trustedProxyRanges`: loopback and proxy guards. |
524
+ | `server/rpc-server/transportHeaders.ts` | `transportHeaders(rpcHeaders)`: the headers used for a transport-level decision. |
525
+ | `server/rpc-server/boundary.ts` | `rpcBoundary(schema, { unexpected, message? })`: declared failures pass through, unexpected failures become one declared rejection. |
526
+ | `server/rpc-server/tracing.ts` | `requestTracingLayer({ header?, sensitive? })`. |
527
+ | `server/rpc-server/redact.ts`, `server/rpc-server/redactCause.ts`, `server/rpc-server/sensitive.ts` | `redact`, `redactCause`, `redactDefect`, `redactingErrorReporter`, `isSensitiveKey`, `redactText`: diagnostic redaction. |
535
528
  | `server/runtime/makePlatform.ts` | `makePlatformRuntime(layer, { developmentCacheKey? })`. |
536
- | `server/runtime/types.ts` | `PlatformRuntime<Services, BuildError>`, `PlatformRuntimeOptions`, `PlatformRuntimeServices<Runtime>`, `PlatformRuntimeBuildError<Runtime>`. |
537
- | `server/node-http/subscriptionSignal.ts` | `nodeSubscriptionSignal({ nodeRequest?, request?, signals? })` returns `{ signal, dispose }`; `NodeSubscriptionSignalOptions`, `NodeSubscriptionSignal`. |
538
- | `server/better-auth/adapter.ts` | `effectPrismaAdapter(Database, runtime, { debugLogs?, modelName?, usePlural? })`; `EffectPrismaAdapterOptions`. |
529
+ | `server/node-http/subscriptionSignal.ts` | `nodeSubscriptionSignal({ nodeRequest?, request?, signals? })`, returning `{ signal, dispose }`. |
530
+ | `server/better-auth/adapter.ts` | `effectPrismaAdapter(Database, runtime, options?)`: a Better Auth adapter over Effect Prisma; `modelName` maps model names. |
539
531
  | `server/testing/makePlatformIt.ts` | `makePlatformIt(Database)({ adapter, createCaller, layer, extend? })`. |
540
- | `server/testing/types.ts` | `MakePlatformItOptions`, `PlatformHarness`, `PlatformIt`, `PlatformTest`, `PlatformTester`. |
541
-
542
- `PlatformRuntime` exposes `runPromise(effect, { signal? })`, `runPromiseExit(effect, { signal? })`, `runWithServices(context, evaluate)`, `contextEffect`, `currentServices()` and `dispose()`. The harness exposes native Vitest methods plus `effectApp(name, body, options?)`; its tester also has `skip`, `skipIf`, `runIf`, `only`, `each` and `fails`.
543
-
544
- The exports also include the adapter's translation modules: `makeRowAdapter` and `RelationQuery` in `server/better-auth/rowAdapter.ts`, `refineRelation` and `DynamicRelation` in `server/better-auth/relation.ts`, and `whereExpression`, `CleanedWhere`, `DynamicField` and `Expression` in `server/better-auth/where.ts`. Application storage setup uses `effectPrismaAdapter`.
545
532
 
546
533
  ### Install, setup and limits
547
534
 
@@ -549,7 +536,7 @@ The exports also include the adapter's translation modules: `makeRowAdapter` and
549
536
  pnpm add @shivaedev/platform effect@4.0.4
550
537
  ```
551
538
 
552
- Effect is the only required peer. The peer-free implementation lives in the shared `errors/` and `rpc/` folders and the `server/node-http/`, `server/rpc-server/` and `server/runtime/` folders; a module's import path is its path under `src/`. The package-local `quality.fences.config.ts` prevents core from reaching optional peers listed in the manifest. The core errors, RPC declarations, server middleware, runtime and Node HTTP modules can be imported without the optional peers, including the Promise-based `betterAuthSessions` function. The runtime uses Node's `AsyncLocalStorage`; browser consumers import the shared `errors/` and `rpc/` modules.
539
+ Effect is the only required peer. The peer-free implementation lives in the shared `errors/` and `rpc/` folders and the `server/node-http/`, `server/rpc-server/` and `server/runtime/` folders; a module's import path is its path under `src/`. The core errors, RPC declarations, server middleware, runtime and Node HTTP modules can be imported without the optional peers, including the Promise-based `betterAuthSessions` function. The runtime uses Node's `AsyncLocalStorage`; browser consumers import the shared `errors/` and `rpc/` modules.
553
540
 
554
541
  Install the optional peers for the selected integration:
555
542
 
@@ -560,14 +547,12 @@ Install the optional peers for the selected integration:
560
547
  | `server/better-auth/adapter.ts` | `better-auth`, `@shivaedev/effect-prisma`, `@prisma-next/sql-orm-client` |
561
548
  | `server/testing/makePlatformIt.ts` | `@effect/vitest`, `@shivaedev/effect-prisma`, `@shivaedev/effect-trpc`, `@trpc/server` |
562
549
 
563
- - The package declares Node 24 or newer and uses the Effect version in its peer manifest.
550
+ - The package needs Node 24 or newer.
564
551
  - Keep the auth provider's configuration, cookie settings and resource authorization in the application. The session adapter takes a session lookup and an optional provider-account lookup and returns a session result; it does not write provider refresh cookies into RPC responses.
565
552
  - Without an `HttpServerRequest`, `transportHeaders` falls back to the supplied RPC headers. An application using another transport must decide what headers it can trust.
566
553
  - `appHost` adds tracing last on its server-side copy of the group. When composing a native RPC server directly, add `RequestTracing` last on the group, as in the example, so it wraps authentication too. Middleware outside that tracing boundary is responsible for its own diagnostics.
567
554
  - Redaction recognizes common shapes. Keep secrets out of messages and extend the key predicate for application-specific sensitive fields.
568
555
  - A development cache key is ignored when `NODE_ENV` is `production`. The application owns runtime disposal.
569
556
  - The Better Auth storage adapter's default model mapping capitalizes the model name. Supply `modelName` for a generated schema with another naming convention. Native Better Auth joins (`advanced.database.joins`) are rejected; leave that option off so Better Auth reads related rows with separate queries.
570
- - Package session tests use real Better Auth with ephemeral SQLite and injected HTTP; the WebSocket test uses an in-memory socket. Node signal tests simulate close events. These checks do not establish deployed browser, mobile or Bun behavior.
571
- - The storage and harness tests require real PostgreSQL and skip without `PLATFORM_EFFECT_PRISMA_TEST_DATABASE_URL`. A skipped test supplies no transaction evidence.
572
557
 
573
- For the purpose and trade-offs, read [the north star](https://github.com/ShivaeDev/platform/blob/main/packages/platform/docs/north-star.md). Implementation status and maintainer decisions live in [the package roadmap](https://github.com/ShivaeDev/platform/blob/main/packages/platform/docs/roadmap.md). The [framework request-context guide](https://github.com/ShivaeDev/platform/blob/main/docs/framework/request-context.md) explains the browser and native-client Origin choices.
558
+ The [framework request-context guide](https://github.com/ShivaeDev/platform/blob/main/docs/framework/request-context.md) explains the browser and native-client Origin choices.
@@ -1,4 +1,4 @@
1
- import { Effect, Option } from "effect";
1
+ import { Effect, Option, Predicate } from "effect";
2
2
  import { AuthUnavailable } from "#errors/taxonomy.ts";
3
3
  import { redact } from "#server/rpc-server/redact.ts";
4
4
  function unavailable(cause) {
@@ -12,10 +12,18 @@ export function betterAuthSessions(getSession, getProviderAccounts) {
12
12
  if (resolved === null || getProviderAccounts === undefined) {
13
13
  return resolved;
14
14
  }
15
- const accounts = await getProviderAccounts(transport);
15
+ const accounts = await getProviderAccounts(transport).catch((cause) => {
16
+ if (Predicate.hasProperty(cause, "statusCode") && cause.statusCode === 401) {
17
+ return null;
18
+ }
19
+ throw cause;
20
+ });
21
+ if (accounts === null) {
22
+ return null;
23
+ }
16
24
  return {
17
25
  ...resolved,
18
- user: { ...resolved.user, providerAccounts: accounts.map(({ accountId, providerId }) => ({ accountId, providerId })) },
26
+ user: { ...resolved.user, providerAccounts: accounts },
19
27
  };
20
28
  }).pipe(Effect.catch((error) => unavailable(error.cause)));
21
29
  return Option.fromNullishOr(session);
@@ -1 +1 @@
1
- {"version":3,"file":"betterAuthSessions.js","sourceRoot":"","sources":["../../../../src/server/rpc-server/adapters/betterAuthSessions.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAExC,OAAO,EAAE,eAAe,EAAE,MAAM,qBAAqB,CAAC;AAEtD,OAAO,EAAE,MAAM,EAAE,MAAM,8BAA8B,CAAC;AAOtD,SAAS,WAAW,CAAC,KAAc;IAClC,OAAO,MAAM,CAAC,OAAO,CACpB,MAAM,CAAC,QAAQ,CAAC,gCAAgC,EAAE,MAAM,CAAC,KAAK,CAAC,CAAC,EAChE,MAAM,CAAC,IAAI,CAAC,IAAI,eAAe,CAAC,EAAE,OAAO,EAAE,2CAA2C,EAAE,CAAC,CAAC,CAC1F,CAAC;AACH,CAAC;AAMD,MAAM,UAAU,kBAAkB,CAAgC,UAAgC,EAAE,mBAAyC;IAC5I,MAAM,iBAAiB,GAAG,MAAM,CAAC,EAAE,CAAC,+BAA+B,CAAC,CAAC,QAAQ,CAAC,EAAE,OAAwB;QACvG,MAAM,OAAO,GAAG,KAAK,CAAC,CAAC,MAAM,CAAC,UAAU,CAAC,KAAK,IAAI,EAAE;YACnD,MAAM,SAAS,GAAG,IAAI,UAAU,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC;YAClD,MAAM,QAAQ,GAAG,MAAM,UAAU,CAAC,SAAS,CAAC,CAAC;YAC7C,IAAI,QAAQ,KAAK,IAAI,IAAI,mBAAmB,KAAK,SAAS,EAAE,CAAC;gBAC5D,OAAO,QAAQ,CAAC;YACjB,CAAC;YACD,MAAM,QAAQ,GAAG,MAAM,mBAAmB,CAAC,SAAS,CAAC,CAAC;YACtD,OAAO;gBACN,GAAG,QAAQ;gBACX,IAAI,EAAE,EAAE,GAAG,QAAQ,CAAC,IAAI,EAAE,gBAAgB,EAAE,QAAQ,CAAC,GAAG,CAAC,CAAC,EAAE,SAAS,EAAE,UAAU,EAAE,EAAE,EAAE,CAAC,CAAC,EAAE,SAAS,EAAE,UAAU,EAAE,CAAC,CAAC,EAAE;aACtH,CAAC;QACH,CAAC,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,WAAW,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;QAC3D,OAAO,MAAM,CAAC,aAAa,CAAC,OAAO,CAAC,CAAC;IACtC,CAAC,CAAC,CAAC;IACH,OAAO,EAAE,GAAG,EAAE,iBAAiB,EAAE,CAAC;AACnC,CAAC"}
1
+ {"version":3,"file":"betterAuthSessions.js","sourceRoot":"","sources":["../../../../src/server/rpc-server/adapters/betterAuthSessions.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,QAAQ,CAAC;AAEnD,OAAO,EAAE,eAAe,EAAE,MAAM,qBAAqB,CAAC;AAEtD,OAAO,EAAE,MAAM,EAAE,MAAM,8BAA8B,CAAC;AAOtD,SAAS,WAAW,CAAC,KAAc;IAClC,OAAO,MAAM,CAAC,OAAO,CACpB,MAAM,CAAC,QAAQ,CAAC,gCAAgC,EAAE,MAAM,CAAC,KAAK,CAAC,CAAC,EAChE,MAAM,CAAC,IAAI,CAAC,IAAI,eAAe,CAAC,EAAE,OAAO,EAAE,2CAA2C,EAAE,CAAC,CAAC,CAC1F,CAAC;AACH,CAAC;AAMD,MAAM,UAAU,kBAAkB,CAAgC,UAAgC,EAAE,mBAAyC;IAC5I,MAAM,iBAAiB,GAAG,MAAM,CAAC,EAAE,CAAC,+BAA+B,CAAC,CAAC,QAAQ,CAAC,EAAE,OAAwB;QACvG,MAAM,OAAO,GAAG,KAAK,CAAC,CAAC,MAAM,CAAC,UAAU,CAAC,KAAK,IAAI,EAAE;YACnD,MAAM,SAAS,GAAG,IAAI,UAAU,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC;YAClD,MAAM,QAAQ,GAAG,MAAM,UAAU,CAAC,SAAS,CAAC,CAAC;YAC7C,IAAI,QAAQ,KAAK,IAAI,IAAI,mBAAmB,KAAK,SAAS,EAAE,CAAC;gBAC5D,OAAO,QAAQ,CAAC;YACjB,CAAC;YACD,MAAM,QAAQ,GAAG,MAAM,mBAAmB,CAAC,SAAS,CAAC,CAAC,KAAK,CAAC,CAAC,KAAc,EAAE,EAAE;gBAC9E,IAAI,SAAS,CAAC,WAAW,CAAC,KAAK,EAAE,YAAY,CAAC,IAAI,KAAK,CAAC,UAAU,KAAK,GAAG,EAAE,CAAC;oBAC5E,OAAO,IAAI,CAAC;gBACb,CAAC;gBACD,MAAM,KAAK,CAAC;YACb,CAAC,CAAC,CAAC;YACH,IAAI,QAAQ,KAAK,IAAI,EAAE,CAAC;gBACvB,OAAO,IAAI,CAAC;YACb,CAAC;YACD,OAAO;gBACN,GAAG,QAAQ;gBACX,IAAI,EAAE,EAAE,GAAG,QAAQ,CAAC,IAAI,EAAE,gBAAgB,EAAE,QAAQ,EAAE;aACtD,CAAC;QACH,CAAC,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,WAAW,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;QAC3D,OAAO,MAAM,CAAC,aAAa,CAAC,OAAO,CAAC,CAAC;IACtC,CAAC,CAAC,CAAC;IACH,OAAO,EAAE,GAAG,EAAE,iBAAiB,EAAE,CAAC;AACnC,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@shivaedev/platform",
3
- "version": "0.16.0",
3
+ "version": "0.16.1",
4
4
  "description": "Opinionated Effect application infrastructure for ShivaeDev services",
5
5
  "homepage": "https://github.com/ShivaeDev/platform/tree/main/packages/platform#readme",
6
6
  "bugs": {
@@ -36,13 +36,12 @@
36
36
  "!src/**/*.test.ts",
37
37
  "!src/**/*.spec.ts",
38
38
  "!src/**/test-support",
39
- "docs",
40
39
  "CHANGELOG.md",
41
40
  "README.md"
42
41
  ],
43
42
  "dependencies": {
44
43
  "@shivaedev/effect-contract": "0.5.1",
45
- "@shivaedev/effect-sql": "0.5.1",
44
+ "@shivaedev/effect-sql": "0.5.2",
46
45
  "@shivaedev/types": "0.2.1"
47
46
  },
48
47
  "devDependencies": {
@@ -56,20 +55,19 @@
56
55
  "@prisma-next/sql-orm-client": "0.16.0",
57
56
  "@prisma-next/target-postgres": "0.16.0",
58
57
  "@shivaedev/effect-prisma": "0.10.0",
59
- "@shivaedev/effect-test-sql": "0.5.4",
58
+ "@shivaedev/effect-test-sql": "0.5.5",
60
59
  "@shivaedev/effect-trpc": "0.8.0",
61
- "@shivaedev/quality": "0.25.0",
62
- "@shivaedev/test-story": "0.7.1",
60
+ "@shivaedev/quality": "0.26.0",
61
+ "@shivaedev/test-story": "0.8.0",
63
62
  "@trpc/client": "11.19.0",
64
63
  "@trpc/server": "11.19.0",
65
64
  "@types/node": "24.19.2",
66
65
  "@types/pg": "8.20.0",
67
- "@typescript/native": "npm:typescript@7.0.2",
68
66
  "better-auth": "1.7.7",
69
67
  "effect": "4.0.4",
70
68
  "pg": "8.23.0",
71
69
  "superjson": "2.2.6",
72
- "typescript": "npm:@typescript/typescript6@6.0.2",
70
+ "typescript": "7.0.2",
73
71
  "vitest": "5.0.3"
74
72
  },
75
73
  "peerDependencies": {
@@ -1,4 +1,4 @@
1
- import { Effect, Option } from "effect";
1
+ import { Effect, Option, Predicate } from "effect";
2
2
  import type { Headers } from "effect/http";
3
3
  import { AuthUnavailable } from "#errors/taxonomy.ts";
4
4
  import type { ProviderAccountValue } from "#rpc/identity.ts";
@@ -28,10 +28,18 @@ export function betterAuthSessions<TSession extends SessionShape>(getSession: Ge
28
28
  if (resolved === null || getProviderAccounts === undefined) {
29
29
  return resolved;
30
30
  }
31
- const accounts = await getProviderAccounts(transport);
31
+ const accounts = await getProviderAccounts(transport).catch((cause: unknown) => {
32
+ if (Predicate.hasProperty(cause, "statusCode") && cause.statusCode === 401) {
33
+ return null;
34
+ }
35
+ throw cause;
36
+ });
37
+ if (accounts === null) {
38
+ return null;
39
+ }
32
40
  return {
33
41
  ...resolved,
34
- user: { ...resolved.user, providerAccounts: accounts.map(({ accountId, providerId }) => ({ accountId, providerId })) },
42
+ user: { ...resolved.user, providerAccounts: accounts },
35
43
  };
36
44
  }).pipe(Effect.catch((error) => unavailable(error.cause)));
37
45
  return Option.fromNullishOr(session);
@@ -1,36 +0,0 @@
1
- # North star
2
-
3
- ## The problem
4
-
5
- A service can express its work clearly in Effect and still lose that clarity at its boundary. An HTTP request arrives with cookies and headers; native RPC supplies middleware arguments; an authentication provider returns a Promise; a database transaction replaces a service for one invocation; and a test must reach all of them together. Rebuilding that glue in each application gives the same concepts different names and leaves room for differences that matter: credentials taken from an RPC message, a shared Layer holding one user's identity, an outage mistaken for a missing session, or a Promise call escaping the test's transaction.
6
-
7
- Callers also need a common language for expected failures. A field rejection should arrive with its field and message, while a defect should remain a defect and diagnostics should not expose its credentials. Neither concern belongs in each feature's business logic.
8
-
9
- ## The ideal
10
-
11
- An application declares errors and native RPC middleware in modules its browser can import. The server supplies a session provider and an explicit Origin policy, and a handler reads a small request-local identity service. It then makes its own resource-authorization decision. The same request id identifies the invocation in its handler, logs and span.
12
-
13
- A Promise boundary uses the application's runtime and its invocation's services. A long-lived Node response has one cancellation signal and an explicit cleanup function. An application using Better Auth with Effect Prisma can store its auth rows through that runtime. An application using Prisma and tRPC can set up its test composition once and write feature tests against the caller and database in one rollback transaction.
14
-
15
- These are separate entry modules. The core request/error/runtime work should require Effect alone. Optional storage and test composition should load their own peers only when selected.
16
-
17
- ## Which trade-offs win
18
-
19
- 1. **Native Effect semantics.** Typed failures, invocation-local services, interruption and transaction scope matter more than a short integration example. A convenience must not hide a boundary that its caller needs to reason about.
20
- 2. **An explicit source of trust.** HTTP authentication and Origin decisions use transport headers, not values an RPC message can override. The request id is correlation data a caller may choose; it must not become an authorization credential. A provider outage must remain distinct from an anonymous caller.
21
- 3. **One owner for each job.** Platform supplies shared request services and the glue across boundaries. Native repositories belong to effect-sql, declarations to effect-contract, services to effect-service and generic test execution to effect-test. The optional adapters compose effect-prisma and effect-trpc rather than implementing them again.
22
- 4. **Useful evidence and diagnostics.** Describe the composition a test actually runs. A bounded redactor can reduce common credential leaks; it cannot decide every application's sensitive fields or justify putting secrets in messages. Keep application-specific data policy explicit.
23
- 5. **Small conveniences.** A provider function, a Layer and a defining-module import are preferable to a second configuration language or application container. Add a helper when it removes repeated work while leaving the native pieces visible.
24
-
25
- ## What the package deliberately leaves out
26
-
27
- - Authentication-provider selection, OAuth configuration, cookies, session lifetimes and client refresh policy.
28
- - Resource ownership and role checks. An authenticated user id is input to authorization, not permission to access every resource.
29
- - Application transports, CORS configuration, subscription limits, event buses and deployment policy.
30
- - A native SQL auth storage choice. Session resolution can use a provider without making its storage part of the application's repository model.
31
- - A second service container, RPC framework, persistence model or generic test runner.
32
- - Guarantees about SSR isolation, mobile cookie persistence or a particular Node/Bun host inferred from local fixtures.
33
-
34
- ## Where planning belongs
35
-
36
- [The package roadmap](roadmap.md) owns changes to these modules, their evidence and the maintainer's scope decisions. [The framework roadmap](https://github.com/ShivaeDev/platform/blob/main/docs/framework/roadmap.md) owns composed fixtures, application adoption and deployment validation across packages.
package/docs/roadmap.md DELETED
@@ -1,31 +0,0 @@
1
- # Roadmap
2
-
3
- This file owns Platform's module implementation, package tests and scope questions. The [framework roadmap](https://github.com/ShivaeDev/platform/blob/main/docs/framework/roadmap.md) owns cross-package fixtures, application adoption and browser, mobile and deployment validation.
4
-
5
- ## Built
6
-
7
- - [x] `localRpcGuard` rejects unknown or non-loopback transport addresses before calling the handler and accepts known IPv4, IPv6 and mapped loopback addresses. [Injected-request evidence](https://github.com/ShivaeDev/platform/blob/main/packages/platform/src/server/rpc-server/local.test.ts); this is separate from deployed host or proxy validation.
8
- - [x] `rpcBoundary` passes declared errors and successes through, normalizes unexpected failures and defects to a declared rejection with a redacted public message, retains the original cause off the wire, and preserves interruption, including mixed failure/cancellation causes. [Boundary evidence](https://github.com/ShivaeDev/platform/blob/main/packages/platform/src/server/rpc-server/boundary.test.ts).
9
- - [x] Shared Schema tagged errors and field-rejection extraction, with native RPC JSON and tRPC rejection tests.
10
- - [x] Browser-safe native RPC middleware declarations and request-id, identity and optional-identity services, with compile-time checks on guarded handlers.
11
- - [x] Request-local session middleware, a Better Auth session-provider boundary and an explicit Origin policy. Tests cover concurrent identities, anonymous callers, provider failures and rejection before provider lookup.
12
- - [x] HTTP transport-header authentication, direct tests of the RPC-header fallback and HTTP-header precedence, and a WebSocket protocol fixture that uses upgrade-request headers instead of RPC message credentials. [Header selection evidence](https://github.com/ShivaeDev/platform/blob/main/packages/platform/src/server/rpc-server/transportHeaders.test.ts).
13
- - [x] Request-id log/span annotations and redaction of failure payloads, defects, reported causes and credential-shaped strings, with bounded collection and string output. [Tracing tests](https://github.com/ShivaeDev/platform/blob/main/packages/platform/src/server/rpc-server/tracing.test.ts) cover successful requests and interruption without failure logs.
14
- - [x] An application runtime that acquires its Layer once, releases it exactly once, shares an explicit development cache key and recreates disposed cache entries, bypasses caching in production, and carries service overrides across Promise calls. [Runtime tests](https://github.com/ShivaeDev/platform/blob/main/packages/platform/src/server/runtime/makePlatform.test.ts) cover typed Layer build failures, Promise rejection, signal interruption with finalization, and nested override replacement and restoration.
15
- - [x] Node subscription cancellation from Web/procedure signals and simulated request/socket close events, with listener cleanup.
16
- - [x] A Better Auth Effect Prisma database adapter and Prisma/tRPC rollback harness. Real PostgreSQL tests cover shared transactions, failed transaction rollback, selected row operations and harness types; they require `PLATFORM_EFFECT_PRISMA_TEST_DATABASE_URL`.
17
- - [x] Better Auth adapter filters preserve PostgreSQL null, membership, comparison and literal-pattern semantics, including nonunique updates, OR-filter updates and deletes, and rejection of nonstring patterns. Configured model names map by default. Reads retain their filters through selection, ascending/descending ordering and limit/offset pagination; experimental native joins are rejected for both single and collection reads. [Adapter boundary tests](../src/server/better-auth/filterBoundaries.spec.ts) assert these paths against PostgreSQL.
18
- - [x] Packed-consumer checks for defining-module imports, types and core entries installed without optional peers.
19
- - [x] A local app host Layer and reusable database Layer, extracted from the PostgreSQL composition: migrations before services, guarded NDJSON RPC, session policy, static assets, scoped shutdown and commit-bound live delivery. The composed fixture covers two HTTP server processes and a worker. Fleet’s daemon adopts `appHost` and `appDatabase` with a custom RPC route, session-free loopback policy and its existing migration ledger. Platform’s real HTTP option tests cover hosting without a database, while Fleet’s PostgreSQL daemon tests cover pool limits, migration upgrades and existing clients.
20
-
21
- ## Next
22
-
23
- - [ ] Add focused adapter tests for the remaining public options, `debugLogs` and `usePlural`.
24
-
25
- ## Open questions
26
-
27
- - What comprehensive application redaction policy should cover span names, free text and custom attributes, and how should request/action correlation work across boundaries?
28
-
29
- - Should the package keep the optional Better Auth storage adapter and Prisma/tRPC harness beside its native request-context modules, or should those compositions live with the integration packages? Both serve real boundary work, but they give this package a wider surface than its request-identity job alone.
30
- - If an application needs Better Auth storage on native Effect SQL, should Platform own that adapter or should the provider retain separate storage? The request middleware needs a session provider, not a particular database.
31
- - The exports make Better Auth translation helpers importable beside the adapter. Are these helpers intended as supported consumer APIs, or should a future public API change narrow the surface to the adapter?