Skip to content

feat(sdk): TypeScript and React SDKs honour the dev environment switch - #483

Open
adiman9 wants to merge 5 commits into
feat/dev-edgefrom
feat/sdk-ts-dev-switch
Open

adiman9 wants to merge 5 commits into
feat/dev-edgefrom
feat/sdk-ts-dev-switch

Conversation

@adiman9

@adiman9 adiman9 commented Oct 29, 2026

Copy link
Copy Markdown
Contributor

Implements the dev environment switch contract (docs/internal/dev-environment-switch.md) in @usearete/sdk and @usearete/react. Stacked on #475 (the edge); it will be retargeted to main once #475 merges.

What changes for apps

  • With the switch unset, nothing changes. Clients make the same requests as before and read no dev input.
  • environment: { kind: 'dev' } (or ARETE_ENVIRONMENT=dev on servers) points the installed SDKs at the project's dev environment. The target is the a4 dev up edge (option url, ARETE_DEV_URL, or the running session's edgeUrl), or the environment itself with a secret key (option env / ARETE_DEV_ENV).
  • client.environment ('production' | 'dev') is available on stack, session and protocol clients.
  • React adds <AreteProvider environment> (an AreteSessionProvider below it inherits it), useAreteEnvironment(), and <AreteEnvironmentBadge />, which renders only in dev mode.

Design

  • Resolver (core/src/dev-environment.ts): resolveEnvironmentSwitch(option, host) implements §2 exactly: grammar, precedence, INVALID_CONFIG and devenv-not-configured. Everything it reads goes through an EnvironmentSwitchHost, so tests can record what was read. The .arete/dev/session.json lookup walks from the cwd upwards and only runs in Node, Bun or Deno.
  • Edge (createEdgeFetch): sends no Authorization and mints nothing. It adds a per-surface X-Arete-Dev-Identity header, refuses any URL that isn't on the edge, and fails with devenv-edge-unreachable when nothing answers or the answer lacks X-Arete-Dev-Edge: 1. GET /.well-known/arete-dev-edge runs once before any socket opens.
  • Direct (core/src/dev-targets.ts): the stack client calls resolve once in Arete.connect, with its gateway, Program Read and stack identities. The protocol client resolves lazily on its first request. Token requests carry environment, and .devstack sockets are treated as hosted. Program Read puts the served release in the path and authenticates with the installed one.
  • Per-surface wiring: gateway → createDevSolanaGatewayTransports. Program Read gets new dev-edge and refused transport kinds. The stack socket gets internal ConnectionManager options (noCredentials, sessionEnvironment, hostedSession, beforeConnect). Stack queries get the edge path plus stackManifestHash. The protocol client gets edge and direct senders; before the first publish the path names the protocol as package:<pkg>.
  • Refusals: both envelopes are parsed into DevEnvironmentError, which carries code, message, fix and refusal (the other detail fields). A refused surface fails on its own and the others keep working. Nothing falls back to production.
  • Wallets: a new optional WalletAdapter.submitsThrough field ('context' | 'adapter'). In dev mode transaction/execute refuse any adapter that doesn't declare 'context', with devenv-wallet-send-unsupported, before the wallet is asked to do anything. The kit and web3js adapters declare 'context' unless they were given their own transport.
  • Production paths are unchanged. The gateway helper was refactored into a shared builder, but production requests are byte-identical. Sessions pass the decided switch to their members as an option, so members don't read the environment again.

Contract change

Added devenv-wallet-send-unsupported (raised by the SDK) to the §6 table. The SDK work item requires this code, but the table didn't list it.

Vectors

core/src/dev-environment.vectors.test.ts runs all 100 cases across switch.json, targets.json and refusals.json. Targets and refusals run against real clients and a recording network: they check every request URL, header and identity, the resolve and mint bodies, socket URLs, and that no request reaches the other world. Nothing is skipped. Two comparisons differ from a literal reading of the files:

  • Transactions tokenRequest: the SDK mints one scope per call, as it already does in production. The test checks that the minted scopes are a subset of the vector's list and compares every other field exactly.
  • resolveRequest: the stack client and the protocol client are separate clients, so each sends its own resolve. The test merges the bodies and compares the result with the vector.

Tests

  • core: 833 pass (all 723 existing, unchanged, plus 110 new); typecheck clean. npm run lint in core fails on the base commit too: the package has no ESLint config.
  • react: 196 pass (2 new); typecheck and lint clean.
  • adapters: kit 92 and web3js 34 pass.
  • vitest.config.ts in core pins ARETE_ENVIRONMENT='', so tests stay in production even when run under a4 dev run.

Known gaps / not verified

  • Not run against a live a4 dev up edge or a real environment. Everything is checked against vectors and mocks only.
  • The edge only proxies /api/protocol-deployments/*. Protocol calls on other paths are refused by the edge with devenv-surface-unavailable: instance reads, non-create prepares, metadata, submit, intents and receipts. This needs an edge follow-up.
  • Metadata proofs in dev mode sign with the client's current deploymentId. Through the edge that is the installed one, so participant-writer proofs may not verify against the environment's deployment.
  • ProtocolDefinition.deployment is still typed non-null. A null deployment (before the first publish) is accepted at runtime in dev mode only. Generated clients get the type change with the protocol-client generator work.
  • url/httpUrl with dev mode (and apiUrl/sessionEndpoint/deploymentId on protocol clients) throw INVALID_CONFIG instead of being honoured.
  • Direct mode's secret key comes from auth.secretKey, ARETE_API_KEY or the a4 login. A tokenKind: 'api' credential is not used for resolve.

One resolver decides, once per client, between production (the default,
unchanged) and the project's dev environment: the `environment` option,
then ARETE_ENVIRONMENT outside browsers; in dev mode the option's url/env,
ARETE_DEV_URL, ARETE_DEV_ENV, then the nearest .arete/dev/session.json.

Through the edge every surface (gateway chain and transactions, Program
Read, stack socket and queries, protocol API) goes to the loopback edge with
no Authorization, the installed identity in X-Arete-Dev-Identity, the
well-known check before any socket, and devenv-edge-unreachable when an
answer is not an edge's. Direct mode resolves the client's identities once
with a secret key and mints environment tokens. Refusals from either
envelope are DevEnvironmentError with code, message and fix; nothing falls
back.

In dev mode sends require a sign-only wallet adapter (submitsThrough:
'context'); the kit and web3js adapters declare it. client.environment on
stack, session and protocol clients; <AreteProvider environment>,
useAreteEnvironment and AreteEnvironmentBadge in React. Every
dev-environment-switch/v1 vector runs in core tests.
@vercel

vercel Bot commented Oct 29, 2026 •

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated
arete-docs Ready Ready Preview Oct 29, 2026 3:15pm UTC

Request Review

@greptile-apps

greptile-apps Bot commented Oct 29, 2026 •

Copy link
Copy Markdown
Contributor

RetriggerView in GreptileConfidence Score: 4/5

[Medium impact] The change adds an opt-in dev route across the TypeScript and React client workflows.

Fix the endpoint checks before merging so dev clients can use endpoints supplied by the dev environment.

Fix All in Claude CodeFindings

  1. P1 Dev clients require production endpoints ▶
Fix with agent prompt
### Issue 1
typescript/core/src/client.ts:1374-1378
`Arete.connect` checks the production endpoints before it calls `planDevClient`. A stack without a generated HTTP endpoint or independent program reads therefore fails with `INVALID_CONFIG`, even when the edge or resolve answer supplies its HTTP endpoint. A definition without either endpoint also fails the WebSocket check, and dev mode forbids supplying these endpoints through options.

Choose the dev endpoints before these checks, and keep the production endpoint requirements on the production path.

---

For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.

Summary

Adds a shared dev switch for TypeScript clients, sessions, protocol calls, and React providers.

  • Installed clients can choose the project’s dev environment or production.
  • Stack clients send each supported request to the selected environment.
  • Protocol clients send supported calls to the selected environment.
  • Dev-mode sends require wallets that submit through the client.
  • React providers can use dev mode and show which environment is active.

Diagram

%%{init: {'theme': 'neutral'}}%%
flowchart TD
  App[App creates client] --> Switch{Environment}
  Switch -->|Production| Installed[Installed endpoints]
  Switch -->|Dev| Target{Dev target}
  Target -->|Edge| Edge[Local edge]
  Target -->|Direct| Resolve[Resolve environment targets]
  Edge --> Services[Dev services]
  Resolve --> Services
Loading

Reviews (3) · Last reviewed commit: "Merge remote-tracking branch 'origin/fea..." · Reviewed by Greptile

Comment thread typescript/core/src/dev-targets.ts
Comment thread typescript/core/src/client.ts
Comment thread typescript/core/src/dev-environment.ts Outdated
…figured apiUrl in direct dev mode; name unreadable session files
Comment on lines +1374 to +1378
const dev = devMode
? await planDevClient(devMode, effectiveStack, programReadOverrides, options)
: undefined;
const socketUrl = dev && url !== null
? (dev.stack instanceof Error ? REFUSED_STACK_SOCKET : dev.stack.websocketUrl)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Dev clients require production endpoints

Arete.connect checks the production endpoints before it calls planDevClient. A stack without a generated HTTP endpoint or independent program reads therefore fails with INVALID_CONFIG, even when the edge or resolve answer supplies its HTTP endpoint. A definition without either endpoint also fails the WebSocket check, and dev mode forbids supplying these endpoints through options.

Choose the dev endpoints before these checks, and keep the production endpoint requirements on the production path.

Prompt To Fix With AI
This is a comment left during a code review.
Path: typescript/core/src/client.ts
Line: 1374-1378

Comment:
**Dev clients require production endpoints**

`Arete.connect` checks the production endpoints before it calls `planDevClient`. A stack without a generated HTTP endpoint or independent program reads therefore fails with `INVALID_CONFIG`, even when the edge or resolve answer supplies its HTTP endpoint. A definition without either endpoint also fails the WebSocket check, and dev mode forbids supplying these endpoints through options.

Choose the dev endpoints before these checks, and keep the production endpoint requirements on the production path.

---

For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.

Fix in Claude Code

This branch was successfully deployed

1 active deployment
Preview — 087eedd8 Deployed Oct 29, 2026 by vercel[bot]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant