Skip to content

[finding] at a RestServer provider seam a SYNCHRONOUS throw loses the whole execution context while an async rejection is absorbed — same fault, two different wire answers #13280

Description

@os-trump

Found while measuring #13255. ⛔ No severity is asserted here; this records what was driven and what came back, for triage.

The shape

RestServer.computeExecCtx reaches its host-wired providers as, e.g.

 const settings = this.settingsServiceProvider
? await this.settingsServiceProvider(environmentId).catch(() => undefined)
: undefined;

.catch is attached to the promise the call RETURNS. A provider that returns a rejecting promise is therefore absorbed at the seam and the resolution continues. A provider that throws SYNCHRONOUSLY throws before there is a promise to attach to, so the .catch is never reached and the throw lands in computeExecCtx's outer catch { return undefined; } — which discards the entire execution context, identity included.

The same asymmetry is structural at authServiceProvider, objectQLProvider and settingsServiceProvider; kernelManager.getOrCreate and authService.getApi() carry no seam .catch at all and lose the context in both directions.

What was measured

Real RestServer, real registerPackageRoutes, wired as rest-api-plugin.ts wires it. Both rows are the SAME seam and the SAME fault, differing only in how the provider fails. Harness: packages/rest/src/package-door-execctx-fault-reachability.test.ts, section 7.

settingsServiceProviderGET /api/v1/packages
async () = throws (a rejecting promise)200 — the caller keeps manage_metadata + studio.access
() = throws (synchronous)401 UNAUTHENTICATED

Both callers hold a valid session and identical grants. The wire answer is decided by whether the host happened to declare its provider async.

The 401 half is also the sharpest instance of the degrade #13255 is measuring: the fault is in a LOCALIZATION/settings input that has nothing to do with authorization, it happens AFTER identity has already resolved, and the caller is nonetheless told "Authentication is required to access this endpoint."

Not ruled here

Two candidate directions, neither taken: normalize the seams (Promise.resolve().then(() = provider(...) ) so a sync throw and a rejection agree), or narrow computeExecCtx's outer catch so a post-identity fault does not discard identity. The second is a behaviour change on a public door and is the same class of decision #13255 defers.

Refs

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions