fix(rpc): close the four final-review blockers on inter-app RPC
- Resolve RPC call origins via a new WRNEXUS_INTERNAL_ORIGINS map (loopback origins the gateway hands each child before spawning it), falling back to the public appOrigin only when it is absent. Calls previously always went to the public gateway origin, which the gateway unconditionally 404s on the RPC prefix by design — every real cross-app call failed. - Stop loadServices() from running ahead of routing and stop memoizing a rejected load: one bad file under app/services/ no longer permanently breaks every route in the app. A failed load logs loudly, is retried on the next RPC request, and the RPC path gets a structured RPC_UNKNOWN instead of an unhandled throw. - Reject a service whose contract.name does not match the filename it is mounted under, naming both, instead of silently mounting under the filename while the typed client calls by contract name. - Let ServiceError accept an explicit retryable and have the client pass the wire value through, instead of recomputing (and silently flipping) it from the error code alone. - Document the gateway/X-Forwarded-* deployment requirement in the RPC README. Each of the three code blockers has a new/extended test that was verified to fail when its fix was reverted (rpc/test/integration.test.ts, dev-server/test/rpc-services-loading.test.ts). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -59,11 +59,19 @@ export class ServiceError extends Error {
|
||||
readonly code: string;
|
||||
readonly retryable: boolean;
|
||||
|
||||
constructor(code: string, message: string) {
|
||||
/**
|
||||
* `retryable` defaults to the code-derived value for callers that
|
||||
* construct a `ServiceError` directly. Pass it explicitly when relaying a
|
||||
* wire result: the transport already computed the authoritative value
|
||||
* (e.g. a bounded HTTP-status check), and recomputing it here from the
|
||||
* code alone would silently flip it — `RPC_TRANSPORT` derives to `true`,
|
||||
* even for a non-retryable 403.
|
||||
*/
|
||||
constructor(code: string, message: string, retryable?: boolean) {
|
||||
super(message);
|
||||
this.name = "ServiceError";
|
||||
this.code = code;
|
||||
this.retryable = retryableFor(code);
|
||||
this.retryable = retryable ?? retryableFor(code);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user