Skip to content

[sdk-client S4] refactor(sdk): centralize HTTP method and GET-cache policy #1351

Description

@rickylabs

Important

0.0.7 normative scope amendment — 2026-08-13. RFC 0001 Stage 3 supersedes the pinned
1.14.15 acceptance row below. This issue keeps one fetch/retry/dedupe/trace transport path,
proves unary and reconnect semantics plus header-safe dedupe, and documents/deprecates the current
no-op port/timeout options. Any dependency move is a separate whole-family, lock-only
decision targeting stable v1.15.0 with frozen-install and no-mixed-version evidence. No oRPC v2
migration and no exact manifest pins land here.

Summary

NetScript's HTTP transport policy — which procedures are GET, and which requests are deduplicated
and cached — is three literals sitting inside createHttpClientLink. oRPC v2 is in public beta and
its main branch already carries feat(rpc): restrict RPC handlers to POST, PUT, PATCH and DELETE by default plus a MethodOverrideHandlerPlugin. Once #1349 makes the link publicly composable, that
policy becomes observable to every contribution and every app, and the v2 migration stops being a
one-function change. Consolidating it behind one NetScript-owned function now is cheap; doing it
after a public extension seam exists is not.

Evidence

  • Corpus: research/external/orpc.md §0 (v2 beta dist-tag 2.0.0-beta.25; the two v2 commits
    verified absent from the 1.14.15 tarball), §6 "Upgrade implications" — "Plan the remediation so
    the transport policy lives behind one NetScript-owned function, not scattered across link
    construction — otherwise the v2 migration touches every client call path"
    ;
    research/repo-audit/services-sdk.md §0, §3.1, gap register S21/S22.
  • Source at baseline fac9e339042c (re-verified for this draft):
    • packages/sdk/src/client/http-client-link.ts:17,82inferRPCMethodFromContractRouter is
      imported from @orpc/client and applied inline as method:. Repo-wide, grep -rn 'inferRPCMethodFromContractRouter\|StrictGetMethodPlugin\|fallbackMethod' packages plugins
      returns only these two lines — there is no owned policy function anywhere.
    • packages/sdk/src/client/http-client-link.ts:109
      filter: ({ request }) => request.method === 'GET', i.e. the dedupe policy re-derives the method
      decision from the wire instead of from the contract.
    • packages/sdk/src/client/http-client-link.ts:110-125 — the force-cache group condition is a
      second, independent literal keyed off context?.cache.
    • packages/sdk/src/client/http-client-link.ts:103-107ClientRetryPlugin default frozen at
      retry: 0; research/repo-audit/services-sdk.md §3.2 lists the three upstream client plugins
      (BatchLinkPlugin, RetryAfterPlugin, SimpleCsrfProtectionLinkPlugin) that are unreachable.
    • packages/sdk/src/desktop/application/desktop-rpc-client.ts:18-20 — the second transport makes
      its own independent decisions (customJsonSerializers), so there is already policy divergence
      across two links with no shared source of truth.
    • Version state: deno.json:215-221 pins ^1.14.6 (@orpc/otel ^1.14.7); deno.lock resolves
      1.14.6, which drags @orpc/shared@1.14.6 and @orpc/shared@1.14.7 into one graph — a
      known instanceof ORPCError hazard the pinned client already ships a Symbol.hasInstance
      workaround for (research/external/orpc.md §6).

Current surface

Three coupled decisions with no owner: the contract-derived method, the GET-only dedupe filter, and
the force-cache grouping. They are consistent today only because one function wrote all three. A
consumer reading the SDK cannot discover the policy without reading the link body, and nothing
prevents the desktop link, a future in-process link (#451), or a contribution from disagreeing with
it. deno task deps:latest reports all seven oRPC packages at 1.14.6/1.14.7 → 1.14.15, and the
1.14.6 → 1.14.15 public export list for @orpc/server is byte-identical, so the bump itself is
mechanical.

Target contract

Per RFC-A §3.11:

  1. One exported-internal function — e.g. resolveTransportPolicy(contract, options) — returns the
    full policy object: method, fallbackMethod, maxUrlLength, the dedupe predicate, and the
    cache-group descriptors. Every link (HTTP, desktop, and any future one) consumes it; no link
    re-derives policy from request.method.
  2. The policy is derived from the contract and procedure metadata, not from the wire. When
    NetScriptProcedureMeta.policy.cache is present (RFC-A §3.6) it is an input to the policy
    function, not a second mechanism.
  3. Contributions never observe the HTTP method. They observe procedure path, input, context and
    metadata. This is the forward-compat rule that keeps the v2 migration inside one function.
  4. A documented policy-override point exists for the v2 transition (a single transportPolicy?
    option, resolved before contributions compose), so MethodOverrideHandlerPlugin-style adaptation
    is a config change rather than a code change.
  5. oRPC is bumped 1.14.6 → 1.14.15 and the duplicated @orpc/shared copies collapse to one,
    using the .llm/tools/deps/ wrappers rather than hand-rolled registry reads.

Acceptance

  • The complete @orpc/* v1 dependency family is upgraded together and resolves once at stable
    v1.15.0; no v2 API or behavior is introduced.
  • One NetScript-owned transport-policy function resolves method, cache, retry, dedupe, tracing,
    and fetch/link behavior. HTTP and desktop clients consume it without policy literals.
  • Contribution descriptors from [sdk-client S3] feat(sdk): expose the typed oRPC client-contribution seam #1349 cannot read or alter the resolved method, retry/dedupe,
    tracing, fetch, or link plugins.
  • port and timeout remain accepted and deprecated; their no-op/migration behavior is explicit
    and regression-tested rather than silently removed.
  • Direct tests cover GET, POST, metadata override, current wire behavior, and a POST-only policy
    simulation without adopting oRPC v2.
  • deno why @orpc/shared proves one resolved copy; root check/test/publish dry-run and the
    scaffold runtime E2E pass.

Boundaries

Docs/consumer proof

The SDK reference gains one short "transport policy" section stating where the method and cache
decisions are made and that they are contract-derived — replacing the current situation where the
only answer is "read http-client-link.ts". Consumer proof is the scaffold runtime E2E: the
generated app's showcase calls behave identically before and after, and the pinned-behaviour test
names the exact procedures whose method would change under oRPC v2, so the migration's blast radius
is a list rather than a guess.

Provenance

Seed run plan-fable5-remediation-roadmap--seed, PR #1347, 2026-08-08. Sourced from
research/external/orpc.md §0/§6 (the explicit forward-compat recommendation) and
research/repo-audit/services-sdk.md (S21, S22); all cited lines re-verified against worktree
baseline fac9e339042c, including the repo-wide grep proving there are exactly two policy call
sites. No GitHub mutation was performed.


Filed from planning seed PR #1347 · source Draft-ID T1-04 · live issue #1351.

Metadata

Metadata

Assignees

No one assigned

    Projects

    No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions