Skip to content

feat(automations): follow the dev environment switch; a4 dev keeps the client's production deployment - #486

Merged
adiman9 merged 9 commits into
mainfrom
feat/automations-dev-switch
Oct 30, 2026
Merged

adiman9 merged 9 commits into
mainfrom
feat/automations-dev-switch

Conversation

@adiman9

@adiman9 adiman9 commented Oct 29, 2026

Copy link
Copy Markdown
Contributor

@usearete/automations follows the dev environment switch. a4 dev up stops writing the dev deployment into the installed protocol client. These two have to ship in the same release: once the client no longer names the dev deployment, automations must read the switch, or dev runs count as mainnet.

Stacked on #483 (the TS/React switch), which is stacked on #475 (the edge). This branch also merges the latest feat/dev-edge commits (the protocol instance, intent and receipt routes), which #483 doesn't have yet. That's why the edge's last three commits show in this diff until #483 picks them up.

What changes

Automations

  • clientCluster(client) returns dev when client.environment === 'dev'. The old deployment.environment === 'dev' check stays as a fallback for clients older a4 versions generated (exported as legacyDevClient, marked for removal in the next major).
  • In dev mode the installed client names production's deployment (or none). The runtime calls client.resolveDeployment() before it binds the store, matches webhook deliveries, labels stream events or checks consents, so all of these use the environment's deployment. Clients from SDKs without the method behave as before.
  • checkCluster compares ARETE_CLUSTER with the switch-derived cluster. The messages no longer say to restore the published client, because the file is the same. A mismatch now says the variable and the switch disagree. A legacy dev-generated client still gets the old a4 install --locked message.
  • createAutomations without simulator: production throws ("pass a simulator"), so the requirement stays the same, now checked at construction. Dev mode simulates against the environment.
  • rpcSimulator() with no URL: in dev mode it uses the edge. In production it throws. In direct mode it throws devenv-surface-unavailable with a fix.
  • a4 dev runner: it refuses a client whose environment isn't dev. a4 dev up now starts it with ARETE_ENVIRONMENT=dev and ARETE_DEV_URL=<edge>, and removes any ARETE_DEV_ENV. It waits up to 15s for the edge to name the deployment a4 dev up just made, which covers the edge's resolve cache after a redeploy. View sources connect with Arete.connect(stack, { environment: { kind: 'dev' } }) through the edge. A stack the environment lacks is refused (devenv-stack-not-loaded) instead of falling back to the published stack's data.

Protocol client (a4 dev up / a4 dev sdk)

  • sync_installed_client still regenerates types from the local build, and the local_build marker and the a4 install --check refusal stay. The descriptor now keeps the production deployment arete.lock pins (from a publish or an a4 protocol deploy), or none before the first deploy. A dev deployment id an older a4 wrote into the lock is dropped, not kept as production's.
  • For the published release's own spec (an unpublished local change is absent, no marker), nothing is generated, so the client stays byte-for-byte the published one.
  • TypeScript emits deployment: null, and ProtocolDefinition.deployment is now ProtocolDeploymentDescriptor | null. Rust and Python emit an empty deployment id, which is how feat(rust-sdk): honour the dev environment switch; ARETE_ENVIRONMENT in a4 #482/feat(python-sdk): honour the dev environment switch #484 already read "not published yet". The environment: 'dev' marker is no longer generated. READMEs say "not deployed to production yet" instead of naming a deployment.
  • createProtocolClient with no deployment in production fails with PROTOCOL_NOT_DEPLOYED: " is not deployed to production yet …". feat(sdk): TypeScript and React SDKs honour the dev environment switch #483 accepted null only in dev mode, but with a generic message.
  • a4 dev up already binds each deployment to the environment (environment on deployment create), and resolve returns the newest active one. No change was needed.

Metadata proofs (the #483 gap)
The API verifies a participant-writer proof against the instance's deployment, i.e. the environment's. The wallet's signature covers the deployment id, so the edge can't rewrite it. The fix is in the SDK, which signs with the environment's deployment:

  • On every /api/protocol-deployments/… answer it forwards, the edge adds X-Arete-Dev-Deployment: <environment deployment>, exposed to loopback pages through CORS.
  • The protocol client picks it up and has a new resolveDeployment(). In production it returns the generated id with no request. In edge mode it does a GET of the deployment route if no call has named it yet. In direct mode it uses resolve. After that, client.deploymentId is the environment's.
  • proofMessage in dev mode throws "await client.resolveDeployment() before building one" until the deployment is known, instead of signing a proof that can't verify. The contract (§3.1) documents the header and this rule.

session.json
a4 dev up no longer mints a stack session or writes stackSession. Nothing read it: the old runner minted its own, and DevClient/a4 dev status/a4 stream --dev mint with the key. Older files still parse. The doc comment on DevSession, docs/cli/local-development, the streams skill and the dev_status MCP description are updated.

Not done / deviations

  • a4 stream --dev still streams with stack sessions it mints, not through the edge. The edge's /stacks/<alias> route resolves installed identities (manifest hash + live alias). a4 stream --dev streams the environment's own stacks (the protocol's stack, custom stacks). Those have no installed identity, and the environment's stack listing doesn't expose one. Routing it through the edge needs an identity on the listing, or an edge route by environment stack. That's follow-up work. Its behaviour is unchanged here; it never read the session file's token.
  • The default simulator uses the edge's JSON-RPC, not /transactions/v1/simulate. The policy's Simulator is JSON-RPC (getMultipleAccounts, simulateTransaction, getTransaction, isBlockhashValid, getSignatureStatuses; schedules also read the clock). Several of these have no REST counterpart on the transactions transport. The edge's JSON-RPC is the same environment (its fork), so "simulates against the environment" holds. Direct mode has no JSON-RPC target in the resolve answer, so rpcSimulator() refuses there with a fix rather than deriving a URL.
  • With an explicit environment: { kind: 'dev', url } option (rather than variables or the session file), the default simulator can't see that URL. Pass rpcSimulator(undefined, { environment }). a4 dev up and a4 dev run set the variables, so the runner isn't affected.
  • @usearete/automations now imports resolveEnvironmentSwitch/DevEnvironmentError from @usearete/sdk. Its @usearete/sdk peer range needs the release that ships feat(sdk): TypeScript and React SDKs honour the dev environment switch #483.
  • feat(automations): export the dev runner's helpers as @usearete/automations/dev #472 (@usearete/automations/dev) is still open. When both land, its helpers' gate (clientCluster(client) === 'dev') should move to the switch (client.environment === 'dev'). clientCluster already reads the switch, so the existing gate works as is.

Conditions

  • automations-dev-cluster: a client in dev mode whose generated deployment is production's (or none) reports dev. It accepts the environment's webhook deliveries, binds the store to the environment's deployment and completes steps (runtime.test.ts). The runner builds a real createProtocolClient in dev mode from ARETE_ENVIRONMENT=dev and asks the edge for the deployment (dev.test.ts). a4 dev up starts the runner with the switch set (Rust test runs_the_runner_in_dev_mode_through_the_edge).
  • automations-prod-cluster: the same client with the switch unset reports mainnet, and the mainnet policy rules are unchanged (runtime.test.ts, existing cluster refusal test). checkCluster messages are covered in host.test.ts.
  • automations-simulator: with no simulator in dev mode, simulateTransaction goes to the edge's JSON-RPC. Production still requires one (runtime.test.ts, policy.test.ts).
  • protocol-client-same-file: for the published release's spec nothing is regenerated, and the file and lock are untouched (an_unchanged_spec_leaves_the_published_client_as_it_is). A spec change keeps the production deployment (a_published_protocol_keeps_its_pins_and_its_production_deployment), and an unpublished protocol gets deployment: null (goldens updated). The same definition calls the production deployment with the switch unset and the edge in dev mode (core dev-environment.test.ts).

What ran where

  • Locally: @usearete/sdk (core) typecheck + 836 tests; @usearete/automations typecheck, lint and 418 tests (Postgres suites skipped locally); @usearete/protocol typecheck + 319 tests; @usearete/react 188 tests (its typecheck needs the adapter packages built, which CI does).
  • Rust: rustfmt on the changed files only. Nothing was compiled or tested locally; CI compiles and runs the CLI, MCP and SDK tests. The dev-sdk goldens were edited by hand (deployment line only) and are checked by CI.
  • Not run against a live a4 dev up or a real environment.

Merge order

#475 → #483 → this PR. Release automations and the CLI together, along with #482/#484 if Rust/Python dev mode should match. #472 can land before or after; see above.

…e client's production deployment

Automations take their cluster from the client's environment switch
(`client.environment`), with the descriptor's old `environment: 'dev'` kept
as a fallback for clients generated by older versions. In dev mode they
learn the environment's deployment from the client before binding the store
or matching deliveries, and simulate against the environment by default.
`checkCluster` compares `ARETE_CLUSTER` with the switch and says when the two
disagree. The `a4 dev` runner refuses a production client, waits for the
edge to name the deployment `a4 dev up` made, and connects view sources
through the installed stack SDK in dev mode.

`a4 dev up` and `a4 dev sdk` stop writing the dev deployment into the
installed protocol client. The descriptor keeps the production deployment
arete.lock pins, or none before the first deploy (`deployment: null` in
TypeScript); for the published release's own spec nothing is generated. The
`local_build` marker and the `a4 install --check` refusal stay. A TypeScript
client with no deployment fails in production with "not deployed to
production yet".

The edge names the environment's deployment in `X-Arete-Dev-Deployment` on
protocol routes; the protocol client reads it (`resolveDeployment()`) and
signs metadata wallet proofs with it, since the API checks a proof against
the instance's own deployment.

`.arete/dev/session.json` no longer carries a stack session token.
@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 30, 2026 2:11am UTC

Request Review

@greptile-apps

greptile-apps Bot commented Oct 29, 2026 •

Copy link
Copy Markdown
Contributor

RetriggerView in GreptileConfidence Score: 5/5

[Medium impact] The changes since the previous review appear safe to merge, with both numbered findings addressed.

Summary

Automations now follow the client's environment switch while generated clients keep their production deployment. The latest changes address both numbered findings:

  • Dev automations follow the environment switch for protocol and stack calls.
  • Protocol clients learn the environment deployment from the edge.
  • Dev-generated clients keep production deployment pins instead of dev deployment IDs.
  • a4 dev up no longer saves stack-session tokens in the session file.

Earlier thread context:

  • adiman9 deferred the by-ID environment check and event resume headers to feat(dev): a4 dev up serves the edge; a4 dev run #475 because that PR owns those routes; Greptile accepted that attribution.
  • The earlier production-target and store-boundary findings were addressed by production provenance checks and the deployment check in bound().

Diagram

%%{init: {'theme': 'neutral'}}%%
flowchart TD
  Client[Installed protocol client] --> Switch{Environment switch}
  Switch -->|Production| Production[Generated production deployment]
  Switch -->|Dev| Edge[Dev edge]
  Edge --> Resolve[Resolve environment deployment]
  Resolve --> Policy[Build policy with resolved deployment]
  Policy --> Store[Bind store to that deployment]
  Store --> Jobs[Accept and run automation jobs]
Loading

Reviews (7) · Last reviewed commit: "fix(automations): resolve the dev deploy..." · Reviewed by Greptile

Comment thread cli/src/commands/dev/edge.rs Resolved
Comment thread cli/src/project/installer.rs Resolved
Comment thread cli/src/commands/dev/edge.rs Resolved
…a redeploy; repack skills

a4 dev keeps the deployment arete.lock pins only for a published release:
an older a4 wrote dev deployments into never-published entries, and one
from before the current deployment would otherwise pass as production's.

Automations refuse new work once the client calls another deployment than
the one their store was bound to (a redeploy into the dev environment).

a4 dev up no longer mints a stack session, so the integration tests check
that it doesn't. The skill archives are repacked.
Comment thread cli/src/project/installer.rs Resolved
…fore the publish

A client generated from a local build after `a4 protocol deploy` marks its
deployment as production in sdk-provenance.json. `a4 dev` keeps a
never-published entry's deployment only with that mark, so an unmarked id an
older a4 wrote for the dev environment is still dropped.
Comment thread cli/src/project/installer.rs Resolved
# Conflicts:
#	docs/public/.well-known/agent-skills/arete-protocols.tar.gz
#	docs/public/.well-known/agent-skills/index.json
Base automatically changed from feat/sdk-ts-dev-switch to main October 30, 2026 01:59
@adiman9
adiman9 merged commit 10b8470 into main Oct 30, 2026
35 checks passed
@adiman9
adiman9 deleted the feat/automations-dev-switch branch October 30, 2026 02:25

This branch was successfully deployed

1 active deployment
Preview — 12177291 Deployed Oct 30, 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