@run402/sdk 4.93.2 → 4.95.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (225) hide show
  1. package/README.md +40 -38
  2. package/core-dist/config.d.ts +16 -2
  3. package/core-dist/config.js +52 -18
  4. package/core-dist/control-plane-session.d.ts +29 -14
  5. package/core-dist/control-plane-session.js +31 -6
  6. package/core-dist/keystore.js +7 -7
  7. package/core-dist/profiles.d.ts +3 -3
  8. package/core-dist/profiles.js +9 -7
  9. package/core-dist/{allowance-auth.d.ts → wallet-auth.d.ts} +6 -6
  10. package/core-dist/{allowance-auth.js → wallet-auth.js} +15 -15
  11. package/core-dist/wallet.d.ts +22 -0
  12. package/core-dist/{allowance.js → wallet.js} +20 -20
  13. package/core-dist/{write-auth-session.d.ts → write-approvals.d.ts} +28 -26
  14. package/core-dist/{write-auth-session.js → write-approvals.js} +19 -19
  15. package/dist/actions.d.ts +4 -4
  16. package/dist/actions.d.ts.map +1 -1
  17. package/dist/app-up.d.ts +2 -2
  18. package/dist/app-up.d.ts.map +1 -1
  19. package/dist/config.d.ts +1 -2
  20. package/dist/config.d.ts.map +1 -1
  21. package/dist/config.js.map +1 -1
  22. package/dist/control-plane-credentials.d.ts +8 -8
  23. package/dist/control-plane-credentials.js +7 -7
  24. package/dist/credentials.d.ts +20 -20
  25. package/dist/credentials.d.ts.map +1 -1
  26. package/dist/credentials.js +1 -1
  27. package/dist/errors.d.ts +26 -28
  28. package/dist/errors.d.ts.map +1 -1
  29. package/dist/errors.js +35 -38
  30. package/dist/errors.js.map +1 -1
  31. package/dist/index.d.ts +29 -16
  32. package/dist/index.d.ts.map +1 -1
  33. package/dist/index.js +30 -17
  34. package/dist/index.js.map +1 -1
  35. package/dist/kernel.d.ts +1 -1
  36. package/dist/kernel.d.ts.map +1 -1
  37. package/dist/kernel.js +10 -9
  38. package/dist/kernel.js.map +1 -1
  39. package/dist/namespaces/admin.d.ts +52 -121
  40. package/dist/namespaces/admin.d.ts.map +1 -1
  41. package/dist/namespaces/admin.js +41 -45
  42. package/dist/namespaces/admin.js.map +1 -1
  43. package/dist/namespaces/agent.d.ts +2 -2
  44. package/dist/namespaces/agent.d.ts.map +1 -1
  45. package/dist/namespaces/agent.js +2 -2
  46. package/dist/namespaces/agent.js.map +1 -1
  47. package/dist/namespaces/ai.d.ts +2 -2
  48. package/dist/namespaces/ai.js +3 -3
  49. package/dist/namespaces/ai.js.map +1 -1
  50. package/dist/namespaces/assets.js +1 -1
  51. package/dist/namespaces/assets.js.map +1 -1
  52. package/dist/namespaces/billing.d.ts +6 -3
  53. package/dist/namespaces/billing.d.ts.map +1 -1
  54. package/dist/namespaces/billing.js.map +1 -1
  55. package/dist/namespaces/buzz-notifications.types.d.ts +1 -1
  56. package/dist/namespaces/buzz.d.ts +2 -2
  57. package/dist/namespaces/buzz.d.ts.map +1 -1
  58. package/dist/namespaces/buzz.js +2 -2
  59. package/dist/namespaces/buzz.js.map +1 -1
  60. package/dist/namespaces/buzz.types.d.ts +2 -2
  61. package/dist/namespaces/buzz.types.d.ts.map +1 -1
  62. package/dist/namespaces/ci.js +3 -3
  63. package/dist/namespaces/ci.js.map +1 -1
  64. package/dist/namespaces/credentials.types.d.ts +1 -1
  65. package/dist/namespaces/deploy.d.ts +1 -1
  66. package/dist/namespaces/deploy.d.ts.map +1 -1
  67. package/dist/namespaces/deploy.js +31 -32
  68. package/dist/namespaces/deploy.js.map +1 -1
  69. package/dist/namespaces/deploy.types.d.ts +6 -6
  70. package/dist/namespaces/deploy.types.d.ts.map +1 -1
  71. package/dist/namespaces/domains.d.ts +10 -2
  72. package/dist/namespaces/domains.d.ts.map +1 -1
  73. package/dist/namespaces/domains.js +10 -5
  74. package/dist/namespaces/domains.js.map +1 -1
  75. package/dist/namespaces/errors.js +1 -1
  76. package/dist/namespaces/escalations.d.ts +1 -1
  77. package/dist/namespaces/escalations.js +1 -1
  78. package/dist/namespaces/escalations.types.d.ts +2 -2
  79. package/dist/namespaces/escalations.types.d.ts.map +1 -1
  80. package/dist/namespaces/events.types.d.ts +1 -1
  81. package/dist/namespaces/functions.d.ts +1 -1
  82. package/dist/namespaces/functions.js +2 -2
  83. package/dist/namespaces/functions.js.map +1 -1
  84. package/dist/namespaces/gitvault.crypto.d.ts +1 -1
  85. package/dist/namespaces/gitvault.d.ts +26 -26
  86. package/dist/namespaces/gitvault.d.ts.map +1 -1
  87. package/dist/namespaces/gitvault.js +113 -113
  88. package/dist/namespaces/gitvault.js.map +1 -1
  89. package/dist/namespaces/me.d.ts +120 -0
  90. package/dist/namespaces/me.d.ts.map +1 -0
  91. package/dist/namespaces/me.js +34 -0
  92. package/dist/namespaces/me.js.map +1 -0
  93. package/dist/namespaces/org.d.ts +108 -6
  94. package/dist/namespaces/org.d.ts.map +1 -1
  95. package/dist/namespaces/org.js +63 -7
  96. package/dist/namespaces/org.js.map +1 -1
  97. package/dist/namespaces/org.types.d.ts +29 -9
  98. package/dist/namespaces/org.types.d.ts.map +1 -1
  99. package/dist/namespaces/pay.js +3 -3
  100. package/dist/namespaces/pay.js.map +1 -1
  101. package/dist/namespaces/projects.d.ts +9 -9
  102. package/dist/namespaces/projects.js +15 -15
  103. package/dist/namespaces/projects.js.map +1 -1
  104. package/dist/namespaces/projects.types.d.ts +28 -12
  105. package/dist/namespaces/projects.types.d.ts.map +1 -1
  106. package/dist/namespaces/rooms.d.ts +7 -7
  107. package/dist/namespaces/rooms.js +17 -17
  108. package/dist/namespaces/rooms.js.map +1 -1
  109. package/dist/namespaces/rooms.types.d.ts +9 -9
  110. package/dist/namespaces/rooms.types.d.ts.map +1 -1
  111. package/dist/namespaces/service.d.ts +1 -1
  112. package/dist/namespaces/service.js +1 -1
  113. package/dist/namespaces/{operator-session.d.ts → session.d.ts} +135 -51
  114. package/dist/namespaces/session.d.ts.map +1 -0
  115. package/dist/namespaces/{operator-session.js → session.js} +118 -30
  116. package/dist/namespaces/session.js.map +1 -0
  117. package/dist/namespaces/subdomains.d.ts +5 -5
  118. package/dist/namespaces/subdomains.d.ts.map +1 -1
  119. package/dist/namespaces/subdomains.js +6 -6
  120. package/dist/namespaces/subdomains.js.map +1 -1
  121. package/dist/namespaces/tier.d.ts +44 -21
  122. package/dist/namespaces/tier.d.ts.map +1 -1
  123. package/dist/namespaces/tier.js +7 -7
  124. package/dist/namespaces/tier.js.map +1 -1
  125. package/dist/namespaces/transfers.d.ts +119 -85
  126. package/dist/namespaces/transfers.d.ts.map +1 -1
  127. package/dist/namespaces/transfers.js +31 -38
  128. package/dist/namespaces/transfers.js.map +1 -1
  129. package/dist/namespaces/vouchers.d.ts +9 -10
  130. package/dist/namespaces/vouchers.d.ts.map +1 -1
  131. package/dist/namespaces/vouchers.js +4 -5
  132. package/dist/namespaces/vouchers.js.map +1 -1
  133. package/dist/namespaces/wallets.d.ts +72 -3
  134. package/dist/namespaces/wallets.d.ts.map +1 -1
  135. package/dist/namespaces/wallets.js +141 -3
  136. package/dist/namespaces/wallets.js.map +1 -1
  137. package/dist/namespaces/write-approval.d.ts +101 -0
  138. package/dist/namespaces/write-approval.d.ts.map +1 -0
  139. package/dist/namespaces/write-approval.js +62 -0
  140. package/dist/namespaces/write-approval.js.map +1 -0
  141. package/dist/node/actions-node.d.ts +3 -3
  142. package/dist/node/actions-node.d.ts.map +1 -1
  143. package/dist/node/actions-node.js +44 -44
  144. package/dist/node/actions-node.js.map +1 -1
  145. package/dist/node/assets-node.js +6 -6
  146. package/dist/node/assets-node.js.map +1 -1
  147. package/dist/node/{bearer-claim-key.d.ts → bearer-redeem-key.d.ts} +12 -12
  148. package/dist/node/bearer-redeem-key.d.ts.map +1 -0
  149. package/dist/node/{bearer-claim-key.js → bearer-redeem-key.js} +21 -21
  150. package/dist/node/bearer-redeem-key.js.map +1 -0
  151. package/dist/node/ci.d.ts +1 -1
  152. package/dist/node/ci.d.ts.map +1 -1
  153. package/dist/node/ci.js +6 -6
  154. package/dist/node/ci.js.map +1 -1
  155. package/dist/node/credentials.d.ts +18 -17
  156. package/dist/node/credentials.d.ts.map +1 -1
  157. package/dist/node/credentials.js +29 -28
  158. package/dist/node/credentials.js.map +1 -1
  159. package/dist/node/deploy-manifest.d.ts +3 -5
  160. package/dist/node/deploy-manifest.d.ts.map +1 -1
  161. package/dist/node/deploy-manifest.js +3 -12
  162. package/dist/node/deploy-manifest.js.map +1 -1
  163. package/dist/node/funding-recovery.d.ts +1 -1
  164. package/dist/node/funding-recovery.d.ts.map +1 -1
  165. package/dist/node/funding-recovery.js +1 -1
  166. package/dist/node/funding-recovery.js.map +1 -1
  167. package/dist/node/gitvault-apply.d.ts +1 -1
  168. package/dist/node/gitvault-apply.d.ts.map +1 -1
  169. package/dist/node/gitvault-apply.js +1 -1
  170. package/dist/node/gitvault-apply.js.map +1 -1
  171. package/dist/node/gitvault-handoff.d.ts +22 -22
  172. package/dist/node/gitvault-handoff.d.ts.map +1 -1
  173. package/dist/node/gitvault-handoff.js +45 -45
  174. package/dist/node/gitvault-handoff.js.map +1 -1
  175. package/dist/node/gitvault-prewarm.js +7 -7
  176. package/dist/node/gitvault-prewarm.js.map +1 -1
  177. package/dist/node/gitvault-restore.d.ts +1 -1
  178. package/dist/node/gitvault-restore.js +1 -1
  179. package/dist/node/index.d.ts +16 -16
  180. package/dist/node/index.d.ts.map +1 -1
  181. package/dist/node/index.js +15 -15
  182. package/dist/node/index.js.map +1 -1
  183. package/dist/node/manifest-export.js +2 -2
  184. package/dist/node/manifest-export.js.map +1 -1
  185. package/dist/node/org-adopt.d.ts +49 -0
  186. package/dist/node/org-adopt.d.ts.map +1 -0
  187. package/dist/node/org-adopt.js +77 -0
  188. package/dist/node/org-adopt.js.map +1 -0
  189. package/dist/node/paid-fetch.d.ts +7 -7
  190. package/dist/node/paid-fetch.d.ts.map +1 -1
  191. package/dist/node/paid-fetch.js +54 -54
  192. package/dist/node/paid-fetch.js.map +1 -1
  193. package/dist/node/preflight.d.ts +1 -1
  194. package/dist/node/preflight.d.ts.map +1 -1
  195. package/dist/node/preflight.js +1 -1
  196. package/dist/node/preflight.js.map +1 -1
  197. package/dist/node/sites-node.js +1 -1
  198. package/dist/node/sites-node.js.map +1 -1
  199. package/dist/node/source-scan.d.ts +1 -1
  200. package/dist/node/source-scan.js +2 -2
  201. package/dist/node/source-scan.js.map +1 -1
  202. package/dist/scoped.d.ts +11 -11
  203. package/dist/scoped.d.ts.map +1 -1
  204. package/dist/scoped.js +5 -5
  205. package/dist/scoped.js.map +1 -1
  206. package/package.json +1 -1
  207. package/core-dist/allowance.d.ts +0 -22
  208. package/core-dist/operator-session.d.ts +0 -80
  209. package/core-dist/operator-session.js +0 -154
  210. package/dist/namespaces/allowance.d.ts +0 -79
  211. package/dist/namespaces/allowance.d.ts.map +0 -1
  212. package/dist/namespaces/allowance.js +0 -149
  213. package/dist/namespaces/allowance.js.map +0 -1
  214. package/dist/namespaces/operator-session.d.ts.map +0 -1
  215. package/dist/namespaces/operator-session.js.map +0 -1
  216. package/dist/namespaces/operator.d.ts +0 -359
  217. package/dist/namespaces/operator.d.ts.map +0 -1
  218. package/dist/namespaces/operator.js +0 -272
  219. package/dist/namespaces/operator.js.map +0 -1
  220. package/dist/node/bearer-claim-key.d.ts.map +0 -1
  221. package/dist/node/bearer-claim-key.js.map +0 -1
  222. package/dist/node/operator-claim.d.ts +0 -49
  223. package/dist/node/operator-claim.d.ts.map +0 -1
  224. package/dist/node/operator-claim.js +0 -77
  225. package/dist/node/operator-claim.js.map +0 -1
package/README.md CHANGED
@@ -14,7 +14,7 @@ Deployment summaries share the SDK workflow view. CLI writes redacted detail und
14
14
 
15
15
  | Import | Use when |
16
16
  |---|---|
17
- | `@run402/sdk/node` | Running in Node 22 with the local profile state, project-key credential cache, and allowance. Auto-loads the configured API base, profile `credentials/project-keys.v1.json`, and signs x402 payments from the selected allowance or opaque signer. Includes `r.actions.run(...)`, `r.up(...)`, `r.sites.deployDir(dir)`, `fileSetFromDir(dir)`, `loadDeployManifest(path)`, `normalizeDeployManifest(input)`, and `resolveRun402TargetProfile()`. |
17
+ | `@run402/sdk/node` | Running in Node 22 with the local profile state, project-key credential cache, and wallet. Auto-loads the configured API base, profile `credentials/project-keys.v1.json`, and signs x402 payments from the selected wallet or opaque signer. Includes `r.actions.run(...)`, `r.up(...)`, `r.sites.deployDir(dir)`, `fileSetFromDir(dir)`, `loadDeployManifest(path)`, `normalizeDeployManifest(input)`, and `resolveRun402TargetProfile()`. |
18
18
  | `@run402/sdk` | Isomorphic — works in Node, Deno, Bun, V8 isolates. No filesystem access. Bring your own `CredentialsProvider` (a session-token shim, a remote vault, anything that resolves project keys + auth headers). |
19
19
 
20
20
  The Node entry sends bounded client-version metadata on gateway requests using the unprefixed `Run402-Client` header, for example `surface="sdk", version="3.7.14", sdk="3.7.14"`. The CLI passes `surface: "cli"`, so gateway compatibility hints can distinguish CLI-created SDK traffic from direct SDK callers. Metadata never includes local paths, package manager details, wallet/org/project ids, secrets, or install confidence. The isomorphic entry does not send this header by default; pass `clientMetadata` explicitly only in runtimes where custom headers are expected.
@@ -78,14 +78,14 @@ provider controls API authentication; the x402 payer is resolved exactly once
78
78
  in this order:
79
79
 
80
80
  1. `paymentSigner` — an explicit async EVM signer provider (KMS/HSM friendly).
81
- 2. `allowancePath` — an explicit local allowance file.
82
- 3. `credentials.readAllowance()` — when a supplied provider implements it.
83
- 4. The Node default provider's active-profile allowance — only when the caller
81
+ 2. `walletPath` — an explicit local wallet file.
82
+ 3. `credentials.readWallet()` — when a supplied provider implements it.
83
+ 4. The Node default provider's active-profile wallet — only when the caller
84
84
  did not supply a custom credentials provider.
85
85
 
86
86
  Once a source is selected, the SDK never falls back to the ambient/global
87
- wallet. `paymentSigner` and `allowancePath` together throw
88
- `PAYMENT_SOURCE_CONFLICT`. Passing both `credentials` and `allowancePath` is
87
+ wallet. `paymentSigner` and `walletPath` together throw
88
+ `PAYMENT_SOURCE_CONFLICT`. Passing both `credentials` and `walletPath` is
89
89
  valid: auth uses `credentials`, while payment intentionally uses that file.
90
90
  `fetch` still takes precedence over built-in paid fetch, and
91
91
  `disablePaidFetch: true` disables automatic payment entirely.
@@ -123,7 +123,7 @@ const payer = await r.paymentPayer();
123
123
 
124
124
  The provider may return `null` for an unsupported Base network. Paid-fetch
125
125
  initialization is lazy and retries after missing/recoverable local state, so a
126
- long-lived client can start paying after its selected allowance/provider
126
+ long-lived client can start paying after its selected wallet/provider
127
127
  becomes available without being reconstructed. `r.paymentPayer()` initializes
128
128
  the selected source if necessary and returns only its source, rail, public
129
129
  address(es), and network(s); it never returns a key, signed authorization, or
@@ -211,7 +211,7 @@ await r.actions.run({
211
211
  });
212
212
  ```
213
213
 
214
- Action identifiers are exported constants plus a string-literal union, so inputs narrow by `type`. `up` validates `run402.deploy.json` / `app.json` before any mutation, resolves the project as explicit `projectId` → `.run402/project.json` → manifest `project_id` → approved creation from `name`; global active state never selects a deployment target, then delegates to `r.project(id).apply(...)`. `name` is only project creation/link metadata; it is not a manifest field and never renames an existing project. If allowance/tier/project/link are already configured, `r.up()` can run the requested deploy with the default approval policy; pass `{ approval: "yes" }` only when you want recursive prerequisites/local writes to proceed unattended.
214
+ Action identifiers are exported constants plus a string-literal union, so inputs narrow by `type`. `up` validates `run402.deploy.json` / `app.json` before any mutation, resolves the project as explicit `projectId` → `.run402/project.json` → manifest `project_id` → approved creation from `name`; global active state never selects a deployment target, then delegates to `r.project(id).apply(...)`. `name` is only project creation/link metadata; it is not a manifest field and never renames an existing project. If wallet/tier/project/link are already configured, `r.up()` can run the requested deploy with the default approval policy; pass `{ approval: "yes" }` only when you want recursive prerequisites/local writes to proceed unattended.
215
215
 
216
216
  Before any gateway call, in every mode, `up` verifies every local file the manifest references (migration `sql_path`/`sql_file`, function `source`/`files`, site `{ path }` entries and `dir()` targets, `assets.put[].source`) and throws `MANIFEST_FILE_MISSING` (`details.missing[]` of `{ field_path, path, kind }`, one `create_file` next action per file); `manifest` pointing at a missing path is `MANIFEST_NOT_FOUND`, and with no manifest in the directory `UP_MANIFEST_REQUIRED` lists `details.nearby_manifests[]` one level down with a read-only `run_in_directory` action for a single candidate, or one unranked `select_application` action for multiple apps. The same check is exported standalone from `@run402/sdk/node`: `collectLocalFileReferences(spec)`, `findMissingLocalFileReferences(spec)`, `assertLocalFileReferencesExist(spec, { manifestPath? })`, plus the `manifestFileMissingError` / `manifestNotFoundError` builders.
217
217
 
@@ -303,14 +303,14 @@ const r = new Run402({
303
303
  });
304
304
  ```
305
305
 
306
- The `CredentialsProvider` interface has two required methods (`getAuth`, `getProject`) plus optional ones (`saveProject`, `removeProject`, `setActiveProject`, `readAllowance`, `saveAllowance`, …) for hosts that want full sticky-default behavior.
306
+ The `CredentialsProvider` interface has two required methods (`getAuth`, `getProject`) plus optional ones (`saveProject`, `removeProject`, `setActiveProject`, `readWallet`, `saveWallet`, …) for hosts that want full sticky-default behavior.
307
307
 
308
308
  ## Namespaces
309
309
 
310
310
  | Namespace | Highlights |
311
311
  |---|---|
312
312
  | `actions` | Node entry only (`@run402/sdk/node`). Generic recursive action runner: `actions.run({ type: Run402Action.Up | ProjectsProvision | TierSet, ... })`; `r.up(input, opts)` is the convenience for repo-level manifest deploys. Recursive mutations are approval-gated; `mode: "check" | "printSpec" | "printManifest" | "plan" | { kind: "applyReviewed" }` distinguishes local validation, gateway review, and exact reviewed apply. Child gateway mutations derive idempotency keys from the root action. |
313
- | `pay` | `fetch(url, init?, { maxUsdMicros?, idempotencyKey?, requireReceipt? })` — bounded arbitrary-URL x402 buyer; Node uses the selected allowance/signer and returns the response plus settlement and independently verified merchant evidence. |
313
+ | `pay` | `fetch(url, init?, { maxUsdMicros?, idempotencyKey?, requireReceipt? })` — bounded arbitrary-URL x402 buyer; Node uses the selected wallet/signer and returns the response plus settlement and independently verified merchant evidence. |
314
314
  | `projects` | `provision`, `delete`, `list`, `get`, `use`, `active`, `sql`, `rest`, `validateExpose`, `applyExpose`, `getExpose`, `getUsage`, `getSchema`, `info`, `keys`, `pin`, `getQuote`. `list`/`get`/`use` are server-authoritative; local key reads are moving to `credentials.projectKeys`. |
315
315
  | `snapshots` | Internal project restore points: `create`, `list`, `get`, `restorePlan`, `restore`, `delete`. Restore is a two-step plan/confirm handshake. |
316
316
  | `branches` | Contained project data branches: `create`, `list`, `renew`, `delete`. Branches default to expiring, noindex, sandboxed-email copies. |
@@ -323,23 +323,25 @@ The `CredentialsProvider` interface has two required methods (`getAuth`, `getPro
323
323
  | `functions` | `deploy`, `invoke`, `logs` (`{ tail?, since?, requestId?, origin? }`; every entry carries `origin: "app" \| "platform"`, computed by the exported `classifyFunctionLogLine`, and `origin` filters client-side with `hidden: { platform, app }` counts), `logsByRequestId(projectId, requestId, { tail?, since?, origin?, functionName? })` (project-wide search for a `req_`/`fnrun_`/`fnatt_` id, the `x-run402-request-id` response header: reads every function, merges oldest-first, tags each entry with its `function`; also `r.project(id).functions.logsByRequestId`), `update`, `list`, `delete`, `rebuild`, `rebuildAll`, `runs.*` durable function requests |
324
324
  | `jobs` | `submit`, `get`, `logs`, `cancel`, `purge` for platform-managed jobs |
325
325
  | `secrets` | `set`, `list`, `delete` |
326
- | `subdomains` | `claim`, `list`, `delete` (most agents declare subdomains in `r.project(id).apply({ subdomains: { set: [...] } })` instead) |
326
+ | `subdomains` | `add`, `list`, `delete` (most agents declare subdomains in `r.project(id).apply({ subdomains: { set: [...] } })` instead) |
327
327
  | `domains` | The ProjectDomain lifecycle — the one surface for custom domains (web + email): `ensure` (connect / update desired state), `get`, `list`, `check` (refresh observations), `apply` (records Run402 has authority over), `repair`, `wait`, `testReceive`, `activate`, `disconnect`. `desired` carries `web`, `email`, and an optional `authority` — `"hosted_dns_zone"` is the root-domain path (one nameserver change at the registrar; Run402 applies every in-zone record and issues TLS). Every response carries `next_actions[]`. |
328
328
  | `email` | `createMailbox`, `listMailboxes`, `setMailboxDefaults`, `updateMailbox`, `getMailbox`, `deleteMailbox`, `send`, `list`, `get`, `getRaw`, `webhooks.*` |
329
329
  | `auth` | `requestMagicLink` (link/code/both), `verifyMagicLink`, `verifyEmailCode`, `createUser`, `inviteUser`, `setUserPassword`, `settings`, passkey registration/login/list/delete helpers, typed `providers`, `promote`, `demote` |
330
330
  | `apps` | `browse`, `getApp`, `fork`, `publish`, `listVersions`, `updateVersion`, `deleteVersion` |
331
331
  | `tier` | `set`, `status` (tier pricing lives on `r.projects.getQuote()`) |
332
332
  | `billing` | `createEmailOrganization`, `linkWallet`, `createCheckout`, `setAutoRecharge`, `checkBalance`, `getOrganization`, `lookupOrganization`, `getHistory`, `balance`, `history` |
333
- | `vouchers` | `redeem` (promo code → prepaid credit; safe to retry) |
333
+ | `vouchers` | `redeem` (promo code → the organization's allowance; safe to retry) |
334
334
  | `contracts` | `provisionSigner`, `getSigner`, `listSigners`, `setRecovery`, `setLowBalanceAlert`, `call`, `read`, `callStatus`, `drain`, `deleteSigner` |
335
335
  | `ai` | `translate`, `moderate`, `usage`, `generateImage` |
336
- | `allowance` | `status`, `create`, `export`, `faucet` |
337
336
  | `service` | `status`, `health` (no auth, no setup — works on a fresh install) |
338
- | `admin` | Operator/admin endpoints: messages/contact, per-project finance (`getProjectFinance`) |
339
- | `operator` | **The human / email principal** — distinct from the agent's per-wallet SIWX identity (and from platform-`admin`). Read session: `deviceStart`, `devicePoll`, `overview({ token })`, `revoke({ token })` — browser-delegated device-authorization (RFC 8628, the `aws sso login` model); `overview` returns the email-union across every wallet that verified the email. Write session (v1.78): `buildCliAuthorizeUrl`/`exchangeCliToken` (loopback-PKCE CLI login) + the hosted `operator.session.*` surface (email magic-link / passkey / OAuth login, `whoami`/`refresh`/`revoke`, step-up, authenticators, recovery) — carry a minted session SDK-wide with `controlPlaneSessionCredentials({ token })`. Drives `run402 operator login[/--loopback]/overview/whoami/logout`. No MCP tool by design — MCP authenticates as the agent, not the human. |
337
+ | `admin` | Admin endpoints: messages/contact, notifications, staff-only per-project finance (`getProjectFinance`) |
338
+ | `session` | **A person's sign-in session** — distinct from the agent's per-wallet SIWX identity. One session class, graded by provenance (`grade`: `browser`, `loopback`, `device` — read-only). CLI seams: `buildCliAuthorizeUrl`/`exchangeCliToken` (loopback PKCE, `run402 login`) and `deviceStart`/`devicePoll` (RFC 8628, `run402 login --device`). Browser surface: email magic link / passkey / OAuth sign-in, `whoami`/`refresh`/`revoke`, step-up, authenticators, recovery. Carry a minted session SDK-wide with `controlPlaneSessionCredentials({ token })`. No MCP tool by design. |
339
+ | `writeApproval` | `requestChallenge`, `exchangeClaimCode` — the passkey-signed, `(action, target)`-scoped write approval a person without a wallet needs to provision, deploy, or write secrets (`run402 approve`); a gated write without one throws `WriteApprovalRequiredError`. |
340
+ | `me` | `overview()` (`GET /agent/v1/me/overview`, joined into `run402 orgs list`), `status()` (`GET /agent/v1/me/status`, read by `run402 doctor` and MCP `whoami`) |
340
341
  | `identityLinks` | Public human/agent external identity attribution with a discriminated proof protocol. Agent `nostr.begin`/`complete` uses EOA + kind 1; human creation/revocation is browser-canonical. `list`, `getProof`, and `revoke` preserve multiple active and revoked records. Never accepts a Nostr secret and never grants authority. |
341
342
  | `buzz` | Capability-detecting Buzz control plane. `offerAdoption` plus `humanAdoptionOffers` creates/reads/cancels durable HTTPS handoffs and creates human-bound attempts; direct `humanAdoptions` is advanced compatibility. `install/enroll` and typed community/enrollment methods preserve separate principals, bounded named-project grants, drift, and scoped revocation. `notifications` routes project events into a Buzz channel (`createRoute`/`list`/`get`/`update`/`pause`/`resume`/`rotate`/`revoke`/`test`/`deliveries`/`testAndWait` — configure → authorize → test → live; the signing secret never leaves the gateway). Buzz signing stays outside the SDK. |
342
- | `wallet(address)` | `getLabel()`, `setLabel(label)` — the signed server-side wallet label (gateway `/wallets/v1/:address/label`) surfaced in the operator console; pushed on `wallets use` unless `RUN402_WALLET_LABEL_SYNC=0`. Use the `r.wallet(address)` handle; `r.wallets.getLabel(address)` remains a bare read |
343
+ | `wallets` | The local wallet: `status`, `create`, `export`, `faucet`; plus `getLabel(address)` |
344
+ | `wallet(address)` | `getLabel()`, `setLabel(label)` — the signed server-side wallet label (gateway `/wallets/v1/:address/label`) surfaced in the console; pushed on `wallets use` unless `RUN402_WALLET_LABEL_SYNC=0`. Use the `r.wallet(address)` handle; `r.wallets.getLabel(address)` remains a bare read |
343
345
  | `orgs` | **Org-owned control plane** (first-class orgs). `create`, `list`, `whoami` (the gateway-resolved control-plane identity) on the collection; the scoped `r.org(id)` sub-client (org analog of `r.project(id)`) adds `get`, `rename`, `setPayoutWallet`, `members.*` (`list`/`add`/`setRole`/`revoke`), `invites.*` (`list`/`create`/`revoke`), `audit`. Org create/read/rename summaries include `tier`, `lease_started_at`, and `lease_expires_at`. |
344
346
  | `grants` | `create`, `revoke` — per-project capability grants (e.g. `"deploy"`, `"functions:write"`) for agent/CI principals; owner-gated, also reachable project-scoped as `r.project(id).grants` |
345
347
  | `events` | `list`, `listForOrg` — the cursored project events feed ("what happened since I last looked"): deploy activations, suspensions, transfers, lifecycle cliffs, each with platform-suggested `next_actions`, plus app-emitted business facts (`source: "app"`) alongside the platform's own (`source: "platform"`) — filter with `{ source?, eventType? }`. Opaque store-and-echo cursor; `reset: true` + `earliest_cursor` instead of errors on expiry. Also reachable project-scoped as `r.project(id).events` |
@@ -535,7 +537,7 @@ if (result.hasErrors) console.log(result.errors);
535
537
 
536
538
  `migrationSql` is reference context only; it is not executed as a PostgreSQL dry run. This method validates authorization manifests, not deploy manifests.
537
539
 
538
- ### Unified apply — `r.project(spec.project).apply`
540
+ ### Unified apply — `r.project(spec.project_id).apply`
539
541
 
540
542
  The canonical primitive for any deploy (database + migrations + manifest + value-free secret declarations + functions + site + subdomain). Three layers:
541
543
 
@@ -544,7 +546,7 @@ import { run402, summarizeDeployResult, type ReleaseSpec } from "@run402/sdk/nod
544
546
 
545
547
  const r = run402();
546
548
  const spec: ReleaseSpec = {
547
- project: "prj_...",
549
+ project_id: "prj_...",
548
550
  site: {
549
551
  patch: {
550
552
  put: {
@@ -560,12 +562,12 @@ const spec: ReleaseSpec = {
560
562
  };
561
563
 
562
564
  // One-shot — most agents use this.
563
- const result = await (await r.project(spec.project)).apply(spec);
565
+ const result = await (await r.project(spec.project_id)).apply(spec);
564
566
  const summary = summarizeDeployResult(result);
565
567
  console.log(summary.headline);
566
568
 
567
569
  // Long-running with progress events. Events are a discriminated union on `type`.
568
- const op = await (await r.project(spec.project)).apply.start(spec);
570
+ const op = await (await r.project(spec.project_id)).apply.start(spec);
569
571
  for await (const ev of op.events()) console.log(ev.type);
570
572
  const final = await op.result();
571
573
 
@@ -581,11 +583,11 @@ const resumed = await (await r.project(projectId)).apply.resume("op_...");
581
583
  - **Warnings are structured.** `DeployResult.warnings` contains `WarningEntry[]` (`code`, `severity`, `requires_confirmation`, `message`, optional `affected`/`details`/`confidence`); the type preserves legacy low/medium/high plan warnings and modern deploy-observability info/warn/high warnings. `apply()` emits `plan.warnings` and stops before upload/commit on confirmation-required warnings unless broad `allowWarnings` is set or every blocking code is listed in `allowWarningCodes`. For `MISSING_REQUIRED_SECRET`, set the affected keys with `r.secrets.set`, then retry.
582
584
  - **Deploy summaries are SDK-owned convenience.** `summarizeDeployResult(result)` returns `DeploySummary` (`schema_version: "deploy-summary.v1"`) with a headline plus reliable current buckets for site path counts, CAS new/reused bytes, functions, migrations, routes, secrets, subdomains, and warning counts. It is derived from `DeployResult.diff` / `DeployResult.warnings`; it makes no extra gateway calls, omits sections the gateway did not return, and intentionally excludes timings, client-side duration estimates, and function old/new code hashes.
583
585
  - **Safe release-race retries are SDK-owned.** `apply()` automatically re-plans and retries omitted/current-base specs when the gateway returns `BASE_RELEASE_CONFLICT` with `safe_to_retry: true`. Static activation/config failures reported from `activation_pending` throw immediately with gateway metadata preserved. The default retry budget is two retries after the initial attempt; pass `{ maxRetries: 0 }` to opt out.
584
- - **Planning has two explicit non-deploying modes.** `(await r.project(spec.project)).apply.plan(spec, { mode: "reviewedPlan" })` calls the gateway reviewed-plan route and returns `plan_id`, `plan_fingerprint`, `plan_expires_at`, diff, warnings, and `next_actions[]` without uploading bytes or committing. Exact apply passes `{ requiredPlan: { planId, planFingerprint? } }` to `apply()` / `start()` / `commit()`; the SDK verifies before upload and commit. Legacy `{ dryRun: true }` still calls the no-row debug route and returns `plan_id: null`, but it is not require-able.
586
+ - **Planning has two explicit non-deploying modes.** `(await r.project(spec.project_id)).apply.plan(spec, { mode: "reviewedPlan" })` calls the gateway reviewed-plan route and returns `plan_id`, `plan_fingerprint`, `plan_expires_at`, diff, warnings, and `next_actions[]` without uploading bytes or committing. Exact apply passes `{ requiredPlan: { planId, planFingerprint? } }` to `apply()` / `start()` / `commit()`; the SDK verifies before upload and commit. Legacy `{ dryRun: true }` still calls the no-row debug route and returns `plan_id: null`, but it is not require-able.
585
587
  - **Rehearsals run candidate plans on contained branches.** Use the lower-level sequence when you want an explicit gate before commit:
586
588
 
587
589
  ```ts
588
- const p = await r.project(spec.project);
590
+ const p = await r.project(spec.project_id);
589
591
  const { plan, byteReaders } = await p.apply.plan(spec);
590
592
  await p.apply.upload(plan, { byteReaders });
591
593
  if (!plan.plan_id) throw new Error("Preview plans cannot be rehearsed");
@@ -627,7 +629,7 @@ const resumed = await (await r.project(projectId)).apply.resume("op_...");
627
629
  { pattern: "/login", methods: ["POST"], target: { type: "function", name: "auth" } },
628
630
  ];
629
631
  const spec: ReleaseSpec = {
630
- project: projectId,
632
+ project_id: projectId,
631
633
  functions: {
632
634
  replace: {
633
635
  api: { source: "export default async function handler(req) { const url = new URL(req.url); return Response.json({ ok: true, path: url.pathname }); }" },
@@ -643,7 +645,7 @@ const resumed = await (await r.project(projectId)).apply.resume("op_...");
643
645
  routes: { replace: routes },
644
646
  };
645
647
 
646
- await (await r.project(spec.project)).apply(spec);
648
+ await (await r.project(spec.project_id)).apply(spec);
647
649
  ```
648
650
 
649
651
  Matching is exact or final `/*` prefix only. `/admin/*` does not match `/admin`; deploy both `/admin` and `/admin/*` when the section root is dynamic. Release static asset paths and public browser paths are distinct. In the example, `events.html` is a release asset and `/events` is the public static URL declared by `site.public_paths`; `/events.html` is not public in explicit mode unless separately declared. A route-only static alias looks like `{ pattern: "/events", methods: ["GET", "HEAD"], target: { type: "static", file: "events.html" } }`; prefer `site.public_paths` for ordinary clean URLs and reserve static route targets for exact method-aware route-table behavior. Avoid routing every static file, wildcard static targets, leading-slash files, directory shorthand, broad method lists by default, and one-static-route-target-per-page route-table exhaustion. Query strings are ignored for matching and preserved in the handler's full public `req.url`. Exact beats prefix, longest prefix wins, and method-compatible dynamic routes beat static files. A method-specific `POST /login` route lets static `GET /login` serve HTML. Unsafe method mismatch returns `405`; matched dynamic route failures do not fall back to static assets.
@@ -705,11 +707,11 @@ const resumed = await (await r.project(projectId)).apply.resume("op_...");
705
707
 
706
708
  const r = run402();
707
709
  const { spec, idempotencyKey } = await loadDeployManifest("./run402.deploy.json");
708
- await (await r.project(spec.project)).apply(spec, { idempotencyKey });
710
+ await (await r.project(spec.project_id)).apply(spec, { idempotencyKey });
709
711
  ```
710
712
 
711
- `loadDeployManifest(path)` parses JSON relative to the manifest file, maps
712
- agent-friendly `project_id` into `ReleaseSpec.project`, decodes base64 file
713
+ `loadDeployManifest(path)` parses JSON relative to the manifest file, carries
714
+ `project_id` into `ReleaseSpec.project_id`, decodes base64 file
713
715
  entries, turns `{ path }` entries into lazy `FsFileSource` values, and reads
714
716
  migration `sql_path` / `sql_file`. It also loads explicit executable
715
717
  `.ts/.mts/.cts/.js/.mjs/.cjs` configs and rejects executable auto-discovery
@@ -727,7 +729,7 @@ const resumed = await (await r.project(projectId)).apply.resume("op_...");
727
729
  import { defineConfig, dir, nodeFunction, sqlFile } from "@run402/sdk/config";
728
730
 
729
731
  export default defineConfig(({ env }) => ({
730
- project: env.required("RUN402_PROJECT_ID"),
732
+ project_id: env.required("RUN402_PROJECT_ID"),
731
733
  database: { migrations: [sqlFile("db/001_init.sql")] },
732
734
  site: { replace: dir("dist"), public_paths: { mode: "implicit" } },
733
735
  functions: { replace: { api: nodeFunction("dist/functions/api.js") } },
@@ -823,12 +825,12 @@ const minted = await r.gitvault.handoff({
823
825
  });
824
826
  console.log(minted.handoff_key); // kgh1_… — print it ALONE, this is the only copy
825
827
 
826
- // on another machine, with no shared keystore or allowance:
828
+ // on another machine, with no shared keystore or wallet:
827
829
  const resumed = await r.gitvault.resume({ key: "kgh1_…" });
828
830
  console.log(resumed.restored.dir); // the fresh checkout, dirty state reapplied
829
831
  ```
830
832
 
831
- **Invite / join — a second agent into the SAME work, dirty tree included (kygit-invite).** The second claim kind beside handoff/resume, sharing the same crypto/keystore/restore machinery, kind-parameterized: a Handoff passes the work on and the sender stops; an Invite grows the team while the sender keeps working. `r.gitvault.invite({ project_id, note, roomKey?, role?, ttlSeconds?, includeSensitive?, program?, model?, sessionKey? })` captures the checkpoint exactly like `handoff` — the inviter's own worktree, index, branch, refs, and access are all untouched — registers the inviter's OWN presence in the invite's room (the project's default room, or `roomKey` for a named org room) BEFORE minting, mints a single-use bearer `kgi1_<base64url(invite_id[16] ‖ master_secret[32])>` key (returned in `invite_key`, the ONLY copy), and posts ONE room message naming the checkpoint and invite id (never the key) — a presence or fact-post failure is reported in `inviter_presence`/`room_fact` and never voids the mint. `r.gitvault.join({ key, to?, program?, model?, sessionKey? })` claims it (a same-principal replay dedups safely), clones a fresh checkout, reapplies the checkpoint with `git stash apply --index`, pins the invite's OWN room locally (`r402.room`), registers this session's presence, posts ONE arrival message, and returns the inviter (name, labels, liveness), `live_presences`, the catch-up `cursor`, and the last few `recent_messages` — the joiner's first message needs no lookup. `r.gitvault.listInvites`/`revokeInvite` manage outstanding keys. A `kgh1_…` key handed to `join()`, or a `kgi1_…` key handed to `resume()`, is refused BY NAME (`INVITE_KEY_WRONG_KIND`/`HANDOFF_KEY_WRONG_KIND`) before any network call.
833
+ **Invite / join — a second agent into the SAME work, dirty tree included (kygit-invite).** The second claim kind beside handoff/resume, sharing the same crypto/keystore/restore machinery, kind-parameterized: a Handoff passes the work on and the sender stops; an Invite grows the team while the sender keeps working. `r.gitvault.invite({ project_id, note, roomKey?, role?, ttlSeconds?, includeSensitive?, program?, model?, sessionKey? })` captures the checkpoint exactly like `handoff` — the inviter's own worktree, index, branch, refs, and access are all untouched — registers the inviter's OWN presence in the invite's room (the project's default room, or `roomKey` for a named org room) BEFORE minting, mints a single-use bearer `kgi1_<base64url(invite_id[16] ‖ master_secret[32])>` key (returned in `invite_key`, the ONLY copy), and posts ONE room message naming the checkpoint and invite id (never the key) — a presence or fact-post failure is reported in `inviter_presence`/`room_message` and never voids the mint. `r.gitvault.join({ key, to?, program?, model?, sessionKey? })` claims it (a same-principal replay dedups safely), clones a fresh checkout, reapplies the checkpoint with `git stash apply --index`, pins the invite's OWN room locally (`r402.room`), registers this session's presence, posts ONE arrival message, and returns the inviter (name, labels, liveness), `live_presences`, the catch-up `cursor`, and the last few `recent_messages` — the joiner's first message needs no lookup. `r.gitvault.listInvites`/`revokeInvite` manage outstanding keys. A `kgh1_…` key handed to `join()`, or a `kgi1_…` key handed to `resume()`, is refused BY NAME (`INVITE_KEY_WRONG_KIND`/`HANDOFF_KEY_WRONG_KIND`) before any network call.
832
834
 
833
835
  ```ts
834
836
  const minted = await r.gitvault.invite({
@@ -837,7 +839,7 @@ const minted = await r.gitvault.invite({
837
839
  });
838
840
  console.log(minted.invite_key); // kgi1_… — print it ALONE, this is the only copy
839
841
 
840
- // on another machine, with no shared keystore or allowance:
842
+ // on another machine, with no shared keystore or wallet:
841
843
  const joined = await r.gitvault.join({ key: "kgi1_…" });
842
844
  console.log(joined.restored.dir); // the fresh checkout, dirty state reapplied
843
845
  console.log(joined.inviter?.name); // who invited you, and whether they're still live
@@ -857,7 +859,7 @@ console.log(heard.settled, heard.messages, heard.live_presences);
857
859
 
858
860
  ### GitHub Actions OIDC — CI credentials drive deploy
859
861
 
860
- The v1 CI path keeps the deploy primitive simple: link a GitHub repository once, then call the existing `r.project(spec.project).apply` with CI-marked credentials. There is no separate `r.ci.deployApply` method and no public `ci: true` deploy option.
862
+ The v1 CI path keeps the deploy primitive simple: link a GitHub repository once, then call the existing `r.project(spec.project_id).apply` with CI-marked credentials. There is no separate `r.ci.deployApply` method and no public `ci: true` deploy option.
861
863
 
862
864
  The CLI is the easiest setup path (`run402 ci link github`), but the SDK exposes the building blocks:
863
865
 
@@ -903,15 +905,15 @@ const r = run402({
903
905
  });
904
906
 
905
907
  const ciSpec: ReleaseSpec = {
906
- project: projectId,
908
+ project_id: projectId,
907
909
  base: { release: "current" },
908
910
  site: { patch: { put: { "index.html": "<h1>ship</h1>" } } },
909
911
  };
910
912
 
911
- await (await r.project(ciSpec.project)).apply(ciSpec);
913
+ await (await r.project(ciSpec.project_id)).apply(ciSpec);
912
914
  ```
913
915
 
914
- CI deploys intentionally allow only `project`, `database`, `functions`, `site`, absent/current `base`, and `routes` authorized by the binding's `route_scopes`. Omitted or empty `route_scopes` preserves the original no-routes CI posture. The SDK normalizes scopes, sends `route_scopes` only when non-empty, and still rejects `secrets`, `subdomains`, `checks`, unknown future top-level fields, non-current `base`, and specs large enough to require `manifest_ref` before upload/plan. Gateway planning enforces route diffs and can return `CI_ROUTE_SCOPE_DENIED`; re-link with covering exact scopes like `/admin` or final-wildcard scopes like `/api/*`, or deploy locally. Use the canonical builders (`buildCiDelegationStatement`, `buildCiDelegationResourceUri`) instead of hand-rolling SIWX text; gateway tests pin those strings as golden vectors.
916
+ CI deploys intentionally allow only `project_id`, `database`, `functions`, `site`, absent/current `base`, and `routes` authorized by the binding's `route_scopes`. Omitted or empty `route_scopes` preserves the original no-routes CI posture. The SDK normalizes scopes, sends `route_scopes` only when non-empty, and still rejects `secrets`, `subdomains`, `checks`, unknown future top-level fields, non-current `base`, and specs large enough to require `manifest_ref` before upload/plan. Gateway planning enforces route diffs and can return `CI_ROUTE_SCOPE_DENIED`; re-link with covering exact scopes like `/admin` or final-wildcard scopes like `/api/*`, or deploy locally. Use the canonical builders (`buildCiDelegationStatement`, `buildCiDelegationResourceUri`) instead of hand-rolling SIWX text; gateway tests pin those strings as golden vectors.
915
917
 
916
918
  ### Timestamp Convention
917
919
 
@@ -962,7 +964,7 @@ declare const spec: ReleaseSpec;
962
964
  const r = run402();
963
965
 
964
966
  try {
965
- await (await r.project(spec.project)).apply(spec);
967
+ await (await r.project(spec.project_id)).apply(spec);
966
968
  } catch (e) {
967
969
  if (isPaymentRequired(e)) {
968
970
  // e is narrowed to PaymentRequired
@@ -990,7 +992,7 @@ should not duplicate or corrupt state, not that lifecycle/payment/auth gates
990
992
  will become allowed without an action. Pair retries with the SDK method's own
991
993
  `idempotencyKey` so retried mutations dedup server-side:
992
994
 
993
- For `r.project(spec.project).apply()`, safe `BASE_RELEASE_CONFLICT` release races are already
995
+ For `r.project(spec.project_id).apply()`, safe `BASE_RELEASE_CONFLICT` release races are already
994
996
  handled by the apply hero with a fresh plan and visible `deploy.retry`
995
997
  events. Use `withRetry` for caller-owned retry policies around other operations,
996
998
  or pass `maxRetries: 0` to `apply` when you want to handle deploy races
@@ -1010,7 +1012,7 @@ const r = run402();
1010
1012
 
1011
1013
  try {
1012
1014
  const release = await withRetry(
1013
- async () => (await r.project(spec.project)).apply(spec, { idempotencyKey: "deploy-2026-05-01" }),
1015
+ async () => (await r.project(spec.project_id)).apply(spec, { idempotencyKey: "deploy-2026-05-01" }),
1014
1016
  {
1015
1017
  attempts: 3,
1016
1018
  onRetry: (e, attempt, delayMs) =>
@@ -39,7 +39,7 @@ export declare function getProfilesDir(): string;
39
39
  * The effective config directory for the *active* wallet. The `default` wallet
40
40
  * resolves to the base dir (zero migration for existing single-wallet
41
41
  * installs); any named wallet resolves to `{base}/profiles/<name>`. Because
42
- * keystore, allowance, and meta paths all derive from this, switching the
42
+ * keystore, wallet, and meta paths all derive from this, switching the
43
43
  * profile env var moves the whole wallet bundle atomically — and the SDK/MCP
44
44
  * inherit profile selection for free.
45
45
  */
@@ -57,5 +57,19 @@ export declare function configureApiBase(apiBase: string, options?: Omit<ApiTarg
57
57
  export declare function getConfiguredApiBase(): string | null;
58
58
  export declare function getApiTargetKind(): ApiTargetKind;
59
59
  export declare function isCoreApiTarget(): boolean;
60
- export declare function getAllowancePath(): string;
60
+ /**
61
+ * The file that holds a wallet's key, in the base config dir (the `default`
62
+ * wallet) and in each `profiles/<name>/` directory.
63
+ */
64
+ export declare const WALLET_FILE_NAME = "wallet.json";
65
+ /**
66
+ * One-time local move of the wallet key file to its current name: in the base
67
+ * config dir and every `profiles/<name>/` directory, `allowance.json` is
68
+ * renamed to `wallet.json` (an atomic same-directory rename) when
69
+ * `wallet.json` is absent, then tightened to 0600. A directory that already
70
+ * holds `wallet.json` is left untouched. Runs once per base dir per process;
71
+ * nothing reads the previous name afterwards.
72
+ */
73
+ export declare function moveWalletFiles(): void;
74
+ export declare function getWalletPath(): string;
61
75
  //# sourceMappingURL=config.d.ts.map
@@ -1,6 +1,6 @@
1
1
  import { homedir } from "node:os";
2
2
  import { dirname, join } from "node:path";
3
- import { existsSync, renameSync, mkdirSync, chmodSync, readFileSync, writeFileSync } from "node:fs";
3
+ import { existsSync, renameSync, mkdirSync, chmodSync, readFileSync, readdirSync, writeFileSync } from "node:fs";
4
4
  import { randomBytes } from "node:crypto";
5
5
  import { describeRejectedValue } from "./redact.js";
6
6
  export const DEFAULT_API_BASE = "https://api.run402.com";
@@ -118,7 +118,7 @@ export function getProfilesDir() {
118
118
  * The effective config directory for the *active* wallet. The `default` wallet
119
119
  * resolves to the base dir (zero migration for existing single-wallet
120
120
  * installs); any named wallet resolves to `{base}/profiles/<name>`. Because
121
- * keystore, allowance, and meta paths all derive from this, switching the
121
+ * keystore, wallet, and meta paths all derive from this, switching the
122
122
  * profile env var moves the whole wallet bundle atomically — and the SDK/MCP
123
123
  * inherit profile selection for free.
124
124
  */
@@ -232,27 +232,61 @@ export function isCoreApiTarget() {
232
232
  return true;
233
233
  return false;
234
234
  }
235
- export function getAllowancePath() {
236
- if (process.env.RUN402_ALLOWANCE_PATH)
237
- return process.env.RUN402_ALLOWANCE_PATH;
238
- const dir = getConfigDir();
239
- const newPath = join(dir, "allowance.json");
240
- const oldPath = join(dir, "wallet.json");
241
- // Auto-migrate from wallet.json → allowance.json. renameSync preserves the
242
- // source file's mode, so a legacy world-readable wallet.json (mode 0644)
243
- // would otherwise carry that mode forward and leave the private key
244
- // world-readable on a shared machine. Tighten to 0600 after the rename.
245
- if (!existsSync(newPath) && existsSync(oldPath)) {
246
- mkdirSync(dir, { recursive: true });
247
- renameSync(oldPath, newPath);
235
+ /**
236
+ * The file that holds a wallet's key, in the base config dir (the `default`
237
+ * wallet) and in each `profiles/<name>/` directory.
238
+ */
239
+ export const WALLET_FILE_NAME = "wallet.json";
240
+ /** The name the wallet key file had before it was `wallet.json`. */
241
+ const PREVIOUS_WALLET_FILE_NAME = "allowance.json";
242
+ const walletFilesMovedFor = new Set();
243
+ /**
244
+ * One-time local move of the wallet key file to its current name: in the base
245
+ * config dir and every `profiles/<name>/` directory, `allowance.json` is
246
+ * renamed to `wallet.json` (an atomic same-directory rename) when
247
+ * `wallet.json` is absent, then tightened to 0600. A directory that already
248
+ * holds `wallet.json` is left untouched. Runs once per base dir per process;
249
+ * nothing reads the previous name afterwards.
250
+ */
251
+ export function moveWalletFiles() {
252
+ const base = getConfigBaseDir();
253
+ if (walletFilesMovedFor.has(base))
254
+ return;
255
+ walletFilesMovedFor.add(base);
256
+ const dirs = [base];
257
+ try {
258
+ for (const entry of readdirSync(join(base, "profiles"), { withFileTypes: true })) {
259
+ if (entry.isDirectory())
260
+ dirs.push(join(base, "profiles", entry.name));
261
+ }
262
+ }
263
+ catch {
264
+ // No profiles directory yet.
265
+ }
266
+ for (const dir of dirs) {
267
+ const from = join(dir, PREVIOUS_WALLET_FILE_NAME);
268
+ const to = join(dir, WALLET_FILE_NAME);
269
+ if (existsSync(to) || !existsSync(from))
270
+ continue;
271
+ try {
272
+ renameSync(from, to);
273
+ }
274
+ catch {
275
+ continue;
276
+ }
248
277
  try {
249
- chmodSync(newPath, 0o600);
278
+ chmodSync(to, 0o600);
250
279
  }
251
280
  catch {
252
281
  // Best-effort (e.g. Windows / exotic filesystems). Read-time self-heal
253
- // in readAllowance() is the backstop.
282
+ // in readWallet() is the backstop.
254
283
  }
255
284
  }
256
- return newPath;
285
+ }
286
+ export function getWalletPath() {
287
+ if (process.env.RUN402_WALLET_PATH)
288
+ return process.env.RUN402_WALLET_PATH;
289
+ moveWalletFiles();
290
+ return join(getConfigDir(), WALLET_FILE_NAME);
257
291
  }
258
292
  //# sourceMappingURL=config.js.map
@@ -1,33 +1,40 @@
1
1
  /**
2
- * A cached **control-plane session** — the human principal's bearer, minted by
3
- * the loopback-PKCE flow (`run402 operator login --loopback`). Distinct from the
4
- * device-flow {@link OperatorSession} (read-only): this one carries `provenance`
5
- * (`loopback_pkce`) and `amr`. It authorizes most control-plane operations, but
6
- * since gateway v1.85/v1.87 it is NOT sufficient on its own for the high-stakes
7
- * writes `provision` / `deploy` / secrets — those additionally require a
8
- * passkey-fresh **operator approval** (`X-Run402-Write-Auth`, minted by
9
- * `run402 operator approve`; see {@link WriteAuthApproval}). Cached at the BASE
10
- * config dir (email/principal-scoped, shared across local named wallets), mode
11
- * 0600 — the token is as sensitive as the allowance key.
2
+ * The cached **sign-in session** — a person's one Run402 session, bound to
3
+ * their principal and graded by how it was minted: `loopback` (`run402 login`,
4
+ * the loopback-PKCE browser ceremony; full, step-up-able) or `device`
5
+ * (`run402 login --device`, RFC 8628; read-only — the gateway refuses every
6
+ * mutation with `SESSION_READ_ONLY`). The wire token class is
7
+ * `control_plane_session`. The session authorizes most control-plane writes,
8
+ * but provisioning, deploying, and writing secrets additionally need a
9
+ * passkey-signed **write approval** (`X-Run402-Write-Approval`, minted by
10
+ * `run402 approve`; see `write-approvals.ts`). Cached at the BASE config dir
11
+ * (principal-scoped, shared across local named wallets), mode 0600 — the token
12
+ * is as sensitive as the wallet key.
12
13
  *
13
14
  * Stored shape vs the gateway payload: the gateway returns a relative
14
15
  * `expires_in` (seconds); we persist the absolute `expires_at` (epoch ms) so a
15
16
  * cached session can be expiry-checked without knowing when it was written.
16
17
  */
18
+ /** How a sign-in session was minted, as the gateway reports it. */
19
+ export type SessionGrade = "browser" | "loopback" | "device";
17
20
  export interface ControlPlaneSessionCache {
18
21
  control_plane_session_token: string;
19
22
  token_type: string;
20
- provenance: string;
23
+ grade: SessionGrade;
21
24
  principal_id: string;
22
25
  amr: string[];
23
26
  /** Epoch ms when the session expires (issued_at + expires_in). */
24
27
  expires_at: number;
25
28
  }
26
- /** The token payload returned by `POST /agent/v1/control-plane/cli/token`. */
29
+ /**
30
+ * The token payload returned by `POST /agent/v1/control-plane/cli/token`
31
+ * (grade `loopback`) and `POST /agent/v1/control-plane/cli/device/token`
32
+ * (grade `device`).
33
+ */
27
34
  export interface ControlPlaneSessionTokenResponse {
28
35
  control_plane_session_token: string;
29
36
  token_type?: string;
30
- provenance?: string;
37
+ grade?: string;
31
38
  principal_id?: string;
32
39
  amr?: string[];
33
40
  expires_in?: number;
@@ -45,7 +52,7 @@ export declare function getControlPlaneSessionPath(): string;
45
52
  export declare function readControlPlaneSession(path?: string): ControlPlaneSessionCache | null;
46
53
  /** Persist a control-plane session atomically (temp-file + rename), mode 0600. */
47
54
  export declare function saveControlPlaneSession(data: ControlPlaneSessionCache, path?: string): void;
48
- /** Delete the cached control-plane session — local half of `operator logout`. Idempotent. */
55
+ /** Delete the cached sign-in session — local half of `run402 logout`. Idempotent. */
49
56
  export declare function clearControlPlaneSession(path?: string): void;
50
57
  /** Whether a cached session is past its usable life (with a small skew buffer). */
51
58
  export declare function isControlPlaneSessionExpired(session: ControlPlaneSessionCache, nowMs?: number, skewMs?: number): boolean;
@@ -53,4 +60,12 @@ export declare function isControlPlaneSessionExpired(session: ControlPlaneSessio
53
60
  export declare function loadLiveControlPlaneSession(path?: string, nowMs?: number): ControlPlaneSessionCache | null;
54
61
  /** Map a gateway token payload (relative `expires_in`) into the cached shape (absolute `expires_at`). */
55
62
  export declare function controlPlaneSessionFromTokenResponse(resp: ControlPlaneSessionTokenResponse, nowMs?: number): ControlPlaneSessionCache;
63
+ /**
64
+ * Delete the session caches of the retired email-scoped session
65
+ * (`{base}/operator-session.json`) and the retired approval cache file
66
+ * (`{base}/write-auth-session.json`). Neither is read any more; `run402 login`
67
+ * and `run402 logout` call this so a stale token does not linger on disk.
68
+ * Best-effort and idempotent.
69
+ */
70
+ export declare function clearRetiredSessionCaches(): void;
56
71
  //# sourceMappingURL=control-plane-session.d.ts.map
@@ -10,6 +10,14 @@ export function getControlPlaneSessionPath() {
10
10
  return (process.env.RUN402_CONTROL_PLANE_SESSION_PATH ||
11
11
  join(getConfigBaseDir(), "control-plane-session.json"));
12
12
  }
13
+ /**
14
+ * Read a wire grade. An unknown or absent value is treated as `device` (the
15
+ * read-only grade) so a cache the client cannot classify never claims more
16
+ * authority than the gateway will grant.
17
+ */
18
+ function toGrade(raw) {
19
+ return raw === "browser" || raw === "loopback" || raw === "device" ? raw : "device";
20
+ }
13
21
  /** Tighten 0600 if group/other-readable, warning once. Best-effort, POSIX-only. */
14
22
  function selfHealPermissions(p) {
15
23
  if (process.platform === "win32")
@@ -50,20 +58,20 @@ export function readControlPlaneSession(path) {
50
58
  return null;
51
59
  }
52
60
  if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) {
53
- throw new Error("control-plane-session.json must contain a JSON object. Delete it and run 'run402 operator login --loopback' to recreate it.");
61
+ throw new Error("control-plane-session.json must contain a JSON object. Delete it and run 'run402 login' to recreate it.");
54
62
  }
55
63
  const data = parsed;
56
64
  if (typeof data.control_plane_session_token !== "string" ||
57
65
  data.control_plane_session_token.length === 0) {
58
- throw new Error("control-plane-session.json missing valid 'control_plane_session_token'. Run 'run402 operator login --loopback' to refresh it.");
66
+ throw new Error("control-plane-session.json missing valid 'control_plane_session_token'. Run 'run402 login' to refresh it.");
59
67
  }
60
68
  if (typeof data.expires_at !== "number" || !Number.isFinite(data.expires_at)) {
61
- throw new Error("control-plane-session.json missing valid 'expires_at'. Run 'run402 operator login --loopback' to refresh it.");
69
+ throw new Error("control-plane-session.json missing valid 'expires_at'. Run 'run402 login' to refresh it.");
62
70
  }
63
71
  return {
64
72
  control_plane_session_token: data.control_plane_session_token,
65
73
  token_type: typeof data.token_type === "string" ? data.token_type : "Bearer",
66
- provenance: typeof data.provenance === "string" ? data.provenance : "loopback_pkce",
74
+ grade: toGrade(data.grade),
67
75
  principal_id: typeof data.principal_id === "string" ? data.principal_id : "",
68
76
  amr: Array.isArray(data.amr) ? data.amr.filter((a) => typeof a === "string") : [],
69
77
  expires_at: data.expires_at,
@@ -79,7 +87,7 @@ export function saveControlPlaneSession(data, path) {
79
87
  renameSync(tmp, p);
80
88
  chmodSync(p, 0o600);
81
89
  }
82
- /** Delete the cached control-plane session — local half of `operator logout`. Idempotent. */
90
+ /** Delete the cached sign-in session — local half of `run402 logout`. Idempotent. */
83
91
  export function clearControlPlaneSession(path) {
84
92
  const p = path ?? getControlPlaneSessionPath();
85
93
  try {
@@ -105,10 +113,27 @@ export function controlPlaneSessionFromTokenResponse(resp, nowMs = Date.now()) {
105
113
  return {
106
114
  control_plane_session_token: resp.control_plane_session_token,
107
115
  token_type: resp.token_type ?? "Bearer",
108
- provenance: resp.provenance ?? "loopback_pkce",
116
+ grade: toGrade(resp.grade),
109
117
  principal_id: resp.principal_id ?? "",
110
118
  amr: Array.isArray(resp.amr) ? resp.amr.filter((a) => typeof a === "string") : [],
111
119
  expires_at: nowMs + (typeof resp.expires_in === "number" ? resp.expires_in : 0) * 1000,
112
120
  };
113
121
  }
122
+ /**
123
+ * Delete the session caches of the retired email-scoped session
124
+ * (`{base}/operator-session.json`) and the retired approval cache file
125
+ * (`{base}/write-auth-session.json`). Neither is read any more; `run402 login`
126
+ * and `run402 logout` call this so a stale token does not linger on disk.
127
+ * Best-effort and idempotent.
128
+ */
129
+ export function clearRetiredSessionCaches() {
130
+ for (const name of ["operator-session.json", "write-auth-session.json"]) {
131
+ try {
132
+ rmSync(join(getConfigBaseDir(), name), { force: true });
133
+ }
134
+ catch {
135
+ // Best-effort: a failed unlink never blocks login or logout.
136
+ }
137
+ }
138
+ }
114
139
  //# sourceMappingURL=control-plane-session.js.map
@@ -3,7 +3,7 @@ import { dirname, join } from "node:path";
3
3
  import { randomBytes } from "node:crypto";
4
4
  import { getLegacyProjectsPath, getProjectCredentialsPath } from "./config.js";
5
5
  import { clearActiveProjectId as clearProfileActiveProjectId, getActiveProjectId as getProfileActiveProjectId, recordMigration, setActiveProjectId as setProfileActiveProjectId, } from "./profile-state.js";
6
- import { readAllowance } from "./allowance.js";
6
+ import { readWallet } from "./wallet.js";
7
7
  function withFileLock(path, fn, { retries = 200, delayMs = 20 } = {}) {
8
8
  const lockDir = path + ".lock";
9
9
  mkdirSync(dirname(path), { recursive: true });
@@ -99,7 +99,7 @@ function migrateLegacyProjectsJson(targetPath) {
99
99
  // Scoped the same way every other write in this module now is (see
100
100
  // `currentPrincipal`'s doc comment) — an unscoped write here is exactly
101
101
  // what poisons the "unknown"-principal bucket for good on a machine that
102
- // migrates before its wallet allowance exists yet.
102
+ // migrates before its wallet exists yet.
103
103
  setProfileActiveProjectId(legacy.active_project_id, undefined, { principal: currentPrincipal() });
104
104
  }
105
105
  recordMigration("projects_json_import", {
@@ -174,7 +174,7 @@ export function removeProject(projectId, path) {
174
174
  }
175
175
  /**
176
176
  * The active-project scope's `principal`, derived from the CURRENT wallet's
177
- * allowance — matching exactly what `NodeCredentialsProvider.setActiveProject`
177
+ * wallet — matching exactly what `NodeCredentialsProvider.setActiveProject`
178
178
  * (sdk/src/node/credentials.ts) writes after `projects.provision` /
179
179
  * `projects.use`. Without this, every reader here used the profile-state
180
180
  * module's own default (empty) scope, which resolves to a FIXED
@@ -185,7 +185,7 @@ export function removeProject(projectId, path) {
185
185
  * `profile-state.ts#getActiveProjectId` happens to agree), but once the
186
186
  * "unknown" bucket is EVER populated by any principal-less write — the
187
187
  * one-time legacy `projects.json` migration below, or any provision/`use`
188
- * call made before this machine had a wallet allowance — it never gets
188
+ * call made before this machine had a wallet — it never gets
189
189
  * updated again (real wallet operations write to the principal-keyed
190
190
  * bucket, not "unknown") and PERMANENTLY shadows every later wallet-scoped
191
191
  * activation for every caller that reads through this module: `resolveProjectId`
@@ -197,13 +197,13 @@ export function removeProject(projectId, path) {
197
197
  * stale "unknown"-bucket entry first and never falls through to the
198
198
  * freshly-updated flat value.
199
199
  *
200
- * Best-effort: an unreadable/malformed allowance degrades to `null` (the
200
+ * Best-effort: an unreadable/malformed wallet degrades to `null` (the
201
201
  * "unknown" bucket callers tolerate), never a
202
- * throw — this is a read-scoping concern, not an allowance-validity one.
202
+ * throw — this is a read-scoping concern, not a wallet-validity one.
203
203
  */
204
204
  function currentPrincipal() {
205
205
  try {
206
- return readAllowance()?.address ?? null;
206
+ return readWallet()?.address ?? null;
207
207
  }
208
208
  catch {
209
209
  return null;