@astrale-os/cli 1.0.0-beta.1 → 1.0.0-beta.11

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 (329) hide show
  1. package/README.md +23 -12
  2. package/dist/astrale.js +48048 -56798
  3. package/dist/public/connect-core.js +2011 -30765
  4. package/dist/public/keys/index.js +884 -29483
  5. package/dist/public/paths/index.js +13101 -41459
  6. package/dist/types/admin/binding.d.ts +19 -0
  7. package/dist/types/admin/instance/client.d.ts +22 -0
  8. package/dist/types/admin/instance/index.d.ts +2 -0
  9. package/dist/types/admin/instance/model.d.ts +5 -12
  10. package/dist/types/connection/auth.d.ts +3 -0
  11. package/dist/types/connection/call.d.ts +3 -0
  12. package/dist/types/connection/command.d.ts +27 -0
  13. package/dist/types/connection/credential.d.ts +14 -0
  14. package/dist/types/connection/errors.d.ts +1 -0
  15. package/dist/types/connection/exchange.d.ts +9 -0
  16. package/dist/types/connection/failure/classify.d.ts +2 -0
  17. package/dist/types/connection/failure/index.d.ts +5 -0
  18. package/dist/types/connection/failure/model.d.ts +25 -0
  19. package/dist/types/connection/failure/render.d.ts +3 -0
  20. package/dist/types/connection/index.d.ts +8 -0
  21. package/dist/types/connection/reasons.d.ts +37 -0
  22. package/dist/types/connection/self.d.ts +37 -0
  23. package/dist/types/connection/session.d.ts +31 -0
  24. package/dist/types/connection/target.d.ts +38 -0
  25. package/dist/types/errors.d.ts +1 -1
  26. package/dist/types/lib/admin-instance.d.ts +17 -0
  27. package/dist/types/lib/admin-target.d.ts +11 -36
  28. package/dist/types/lib/config.d.ts +7 -59
  29. package/dist/types/lib/failure-debug.d.ts +1 -0
  30. package/dist/types/lib/idp.d.ts +26 -168
  31. package/dist/types/lib/instance-target.d.ts +1 -1
  32. package/dist/types/lib/instance.d.ts +30 -88
  33. package/dist/types/lib/log.d.ts +5 -3
  34. package/dist/types/lib/meta.d.ts +2 -2
  35. package/dist/types/lib/proc.d.ts +43 -0
  36. package/dist/types/lib/self.d.ts +9 -0
  37. package/dist/types/lib/update.d.ts +91 -0
  38. package/dist/types/lib/validation.d.ts +4 -1
  39. package/package.json +16 -19
  40. package/src/__tests__/fixtures/publication.ts +20 -0
  41. package/src/admin/__tests__/binding.test.ts +31 -0
  42. package/src/admin/__tests__/fixture.ts +101 -0
  43. package/src/admin/binding.ts +98 -0
  44. package/src/admin/catalog/.spec/api.d.ts +2 -2
  45. package/src/admin/catalog/__tests__/client.test.ts +20 -49
  46. package/src/admin/catalog/client.ts +36 -32
  47. package/src/admin/catalog/model.ts +3 -4
  48. package/src/admin/graph/.spec/api.d.ts +4 -10
  49. package/src/admin/graph/nodes.ts +1 -1
  50. package/src/admin/instance/.spec/api.d.ts +2 -2
  51. package/src/admin/instance/__tests__/client.test.ts +39 -164
  52. package/src/admin/instance/client.ts +35 -124
  53. package/src/admin/instance/index.ts +1 -2
  54. package/src/admin/instance/model.ts +11 -20
  55. package/src/commands/__tests__/admin-instance.test.ts +11 -8
  56. package/src/commands/__tests__/auth-login.test.ts +54 -1
  57. package/src/commands/__tests__/call-describe.test.ts +46 -33
  58. package/src/commands/__tests__/call.test.ts +5 -1
  59. package/src/commands/__tests__/domain-install-operation.test.ts +12 -7
  60. package/src/commands/__tests__/domain-install-owned.test.ts +6 -2
  61. package/src/commands/__tests__/domain-list.test.ts +63 -4
  62. package/src/commands/__tests__/install-identity-override.test.ts +19 -19
  63. package/src/commands/__tests__/instance-list-rows.test.ts +1 -1
  64. package/src/commands/__tests__/instance-status.test.ts +139 -0
  65. package/src/commands/__tests__/read-commands.test.ts +35 -10
  66. package/src/commands/__tests__/studio-descriptor.test.ts +22 -0
  67. package/src/commands/__tests__/update.test.ts +48 -19
  68. package/src/commands/call-describe.ts +60 -68
  69. package/src/commands/call.ts +43 -56
  70. package/src/commands/domain/install.ts +13 -17
  71. package/src/commands/domain/list.ts +18 -13
  72. package/src/commands/domain/publish.ts +14 -11
  73. package/src/commands/domain/uninstall.ts +2 -2
  74. package/src/commands/get.ts +14 -15
  75. package/src/commands/identity/__tests__/register.test.ts +3 -3
  76. package/src/commands/identity/register.ts +5 -4
  77. package/src/commands/instance/create.ts +8 -16
  78. package/src/commands/instance/delete.ts +12 -9
  79. package/src/commands/instance/list.ts +2 -2
  80. package/src/commands/instance/status.ts +66 -20
  81. package/src/commands/instance/use.ts +16 -12
  82. package/src/commands/introspect.ts +9 -16
  83. package/src/commands/logs.ts +24 -25
  84. package/src/commands/mutate.ts +6 -5
  85. package/src/commands/query.ts +27 -30
  86. package/src/commands/studio.ts +20 -0
  87. package/src/commands/token.ts +8 -7
  88. package/src/commands/update.ts +38 -14
  89. package/src/commands/view.ts +7 -6
  90. package/src/connection/.spec/api.d.ts +7 -8
  91. package/src/connection/.spec/architecture.md +5 -4
  92. package/src/connection/.spec/flows/session.ts +2 -2
  93. package/src/connection/.spec/laws/connection.ts +6 -2
  94. package/src/connection/.spec/layout.ts +1 -0
  95. package/src/connection/__tests__/credential.test.ts +20 -1
  96. package/src/connection/__tests__/errors.test.ts +43 -32
  97. package/src/connection/__tests__/exchange.test.ts +36 -6
  98. package/src/connection/__tests__/failure-fixtures.ts +39 -0
  99. package/src/connection/__tests__/failure-safety.test.ts +75 -0
  100. package/src/connection/__tests__/self.test.ts +31 -11
  101. package/src/connection/__tests__/session.test.ts +3 -3
  102. package/src/connection/__tests__/target.test.ts +1 -0
  103. package/src/connection/auth.ts +13 -0
  104. package/src/connection/call.ts +1 -1
  105. package/src/connection/credential.ts +2 -2
  106. package/src/connection/errors.ts +1 -355
  107. package/src/connection/exchange.ts +52 -27
  108. package/src/connection/failure/classify.ts +110 -0
  109. package/src/connection/failure/index.ts +18 -0
  110. package/src/connection/failure/model.ts +28 -0
  111. package/src/connection/failure/render.ts +116 -0
  112. package/src/connection/reasons.ts +16 -0
  113. package/src/connection/self.ts +39 -21
  114. package/src/connection/session.ts +19 -8
  115. package/src/errors.ts +3 -2
  116. package/src/graph/.spec/api.d.ts +12 -5
  117. package/src/graph/.spec/layout.ts +9 -1
  118. package/src/graph/__tests__/mutation.test.ts +2 -2
  119. package/src/graph/__tests__/query.test.ts +25 -6
  120. package/src/graph/class.ts +30 -0
  121. package/src/graph/index.ts +1 -0
  122. package/src/graph/query.ts +21 -32
  123. package/src/identity/.spec/api.d.ts +2 -2
  124. package/src/identity/__tests__/registration.test.ts +1 -1
  125. package/src/identity/registration.ts +3 -3
  126. package/src/keys/__tests__/keys.test.ts +4 -0
  127. package/src/keys/credential.ts +1 -0
  128. package/src/lib/__tests__/admin-target.test.ts +12 -0
  129. package/src/lib/__tests__/command-dx.test.ts +1 -1
  130. package/src/lib/__tests__/domain-publication.test.ts +138 -0
  131. package/src/lib/__tests__/idp-session.test.ts +22 -0
  132. package/src/lib/__tests__/idp.test.ts +36 -4
  133. package/src/lib/__tests__/instance-candidates.test.ts +1 -3
  134. package/src/lib/__tests__/instance-target.test.ts +7 -39
  135. package/src/lib/__tests__/instance.test.ts +6 -0
  136. package/src/lib/__tests__/log-errors.test.ts +64 -0
  137. package/src/lib/__tests__/studio-server-deps.test.ts +53 -16
  138. package/src/lib/__tests__/update.test.ts +113 -8
  139. package/src/lib/__tests__/view-open-intent.test.ts +0 -2
  140. package/src/lib/__tests__/view-server.test.ts +18 -3
  141. package/src/lib/__tests__/view-session.test.ts +1 -1
  142. package/src/lib/admin-instance.ts +14 -8
  143. package/src/lib/admin-target.ts +21 -2
  144. package/src/lib/command-dx.ts +9 -9
  145. package/src/lib/domain-publication.ts +104 -0
  146. package/src/lib/failure-debug.ts +26 -0
  147. package/src/lib/idp.ts +39 -4
  148. package/src/lib/instance-candidates.ts +2 -2
  149. package/src/lib/instance-target.ts +3 -14
  150. package/src/lib/instance.ts +22 -12
  151. package/src/lib/log.ts +16 -9
  152. package/src/lib/login-flow.ts +13 -3
  153. package/src/lib/meta.ts +2 -2
  154. package/src/lib/provision-instance.ts +34 -57
  155. package/src/lib/update.ts +124 -30
  156. package/src/lib/validation.ts +2 -2
  157. package/src/lib/view/resolve.ts +1 -1
  158. package/src/lib/view/server.ts +17 -3
  159. package/src/program/__tests__/program.test.ts +20 -4
  160. package/src/program/build.ts +2 -2
  161. package/src/setup/__tests__/instance-step.test.ts +27 -10
  162. package/src/setup/steps/instance.ts +22 -15
  163. package/studio/client/dist/assets/elk-api-DjmKsHXc.js +1 -0
  164. package/studio/client/dist/assets/index-7YHdeCxp.js +81 -0
  165. package/studio/client/dist/assets/index-C0FAnwNw.css +2 -0
  166. package/studio/client/dist/assets/rolldown-runtime-Dd_uD5pT.js +1 -0
  167. package/studio/client/dist/assets/schema-studio-BilUNb6Z.css +1 -0
  168. package/studio/client/dist/assets/schema-studio-CABpTfvH.js +8 -0
  169. package/studio/client/dist/index.html +3 -2
  170. package/studio/package.json +10 -14
  171. package/studio/server/agent/ask.test.ts +1 -1
  172. package/studio/server/agent/bridge/grant.test.ts +1 -1
  173. package/studio/server/agent/bridge/routes.test.ts +1 -1
  174. package/studio/server/agent/bridge/stdio.ts +8 -1
  175. package/studio/server/agent/conversation.test.ts +10 -5
  176. package/studio/server/agent/conversation.ts +56 -8
  177. package/studio/server/agent/harness/claude/adapter.test.ts +15 -5
  178. package/studio/server/agent/harness/codex/loadout.ts +18 -21
  179. package/studio/server/agent/harness/gateway/config.ts +20 -18
  180. package/studio/server/agent/harness/selection.ts +9 -1
  181. package/studio/server/agent/prompts/anchors.test.ts +32 -36
  182. package/studio/server/agent/prompts/anchors.ts +20 -49
  183. package/studio/server/agent/prompts/system.test.ts +3 -2
  184. package/studio/server/agent/prompts/system.ts +6 -5
  185. package/studio/server/agent/prompts/turn.ts +13 -4
  186. package/studio/server/agent/routes.test.ts +1 -1
  187. package/studio/server/agent/run/completion.ts +2 -2
  188. package/studio/server/agent/run/coordinator.test.ts +1 -1
  189. package/studio/server/agent/run/preparation.ts +6 -5
  190. package/studio/server/agent/run/transcript.test.ts +35 -1
  191. package/studio/server/agent/run/transcript.ts +169 -3
  192. package/studio/server/agent/run/usage.test.ts +24 -0
  193. package/studio/server/agent/run/usage.ts +26 -1
  194. package/studio/server/api/canvas.ts +43 -0
  195. package/studio/server/api/comments.ts +85 -0
  196. package/studio/server/api/context.ts +61 -0
  197. package/studio/server/api/deployment.ts +39 -0
  198. package/studio/server/api/documents.ts +55 -0
  199. package/studio/server/api/domain.ts +59 -0
  200. package/studio/server/api/http.ts +45 -0
  201. package/studio/server/api/project.ts +57 -0
  202. package/studio/server/api/schema.ts +12 -0
  203. package/studio/server/api/views.ts +57 -0
  204. package/studio/server/api/workspace.ts +60 -0
  205. package/studio/server/api-agent-loadout.test.ts +1 -1
  206. package/studio/server/api.test.ts +147 -0
  207. package/studio/server/api.ts +20 -422
  208. package/studio/server/cache.test.ts +122 -0
  209. package/studio/server/cache.ts +162 -11
  210. package/studio/server/cli-consumers.test.ts +494 -0
  211. package/studio/server/cli.test.ts +88 -0
  212. package/studio/server/cli.ts +240 -0
  213. package/studio/server/client-package.ts +25 -1
  214. package/studio/server/domain.test.ts +50 -40
  215. package/studio/server/domain.ts +83 -52
  216. package/studio/server/environment/dotenv-preview.test.ts +39 -0
  217. package/studio/server/environment/dotenv-preview.ts +27 -0
  218. package/studio/server/environment/files.test.ts +53 -0
  219. package/studio/server/{state/env.ts → environment/files.ts} +7 -48
  220. package/studio/server/{state → handoff}/copy.ts +21 -6
  221. package/studio/server/handoff/service.test.ts +22 -0
  222. package/studio/server/{state/handoff.ts → handoff/service.ts} +21 -14
  223. package/studio/server/index.ts +1 -1
  224. package/studio/server/instances/active.ts +72 -0
  225. package/studio/server/instances/deploy-record.test.ts +31 -0
  226. package/studio/server/instances/deploy-record.ts +37 -0
  227. package/studio/server/instances/deploy.ts +56 -0
  228. package/studio/server/{state/instance.test.ts → instances/probe.test.ts} +25 -21
  229. package/studio/server/instances/probe.ts +67 -0
  230. package/studio/server/instances/status.test.ts +12 -0
  231. package/studio/server/instances/status.ts +49 -0
  232. package/studio/server/introspect/anatomy/client-tree.ts +57 -0
  233. package/studio/server/introspect/anatomy/env-fields.ts +66 -0
  234. package/studio/server/introspect/anatomy/schema-definition.ts +53 -0
  235. package/studio/server/introspect/anatomy/source.ts +181 -0
  236. package/studio/server/introspect/anatomy/views/routes.ts +114 -0
  237. package/studio/server/introspect/anatomy/views.ts +53 -0
  238. package/studio/server/introspect/anatomy-extras.test.ts +106 -35
  239. package/studio/server/introspect/anatomy-extras.ts +7 -726
  240. package/studio/server/introspect/anatomy.test.ts +22 -14
  241. package/studio/server/introspect/anatomy.ts +52 -28
  242. package/studio/server/introspect/bundle.ts +15 -9
  243. package/studio/server/introspect/canonical-schema.test.ts +93 -309
  244. package/studio/server/introspect/canonical-schema.ts +257 -425
  245. package/studio/server/introspect/config-preview.test.ts +32 -0
  246. package/studio/server/introspect/config-preview.ts +119 -0
  247. package/studio/server/introspect/core-extractor.ts +18 -127
  248. package/studio/server/introspect/core.ts +1 -2
  249. package/studio/server/introspect/diff.test.ts +18 -18
  250. package/studio/server/introspect/diff.ts +44 -90
  251. package/studio/server/introspect/extractor.ts +23 -116
  252. package/studio/server/introspect/hash.ts +5 -2
  253. package/studio/server/introspect/overlay-tsmorph.test.ts +119 -240
  254. package/studio/server/introspect/overlay-tsmorph.ts +7 -1305
  255. package/studio/server/introspect/overlay.test.ts +26 -54
  256. package/studio/server/introspect/overlay.ts +10 -37
  257. package/studio/server/introspect/revision.test.ts +54 -0
  258. package/studio/server/introspect/revision.ts +49 -0
  259. package/studio/server/introspect/runtime.test.ts +83 -143
  260. package/studio/server/introspect/runtime.ts +40 -11
  261. package/studio/server/introspect/schema-ir-json.test.ts +70 -0
  262. package/studio/server/introspect/schema-ir-json.ts +151 -0
  263. package/studio/server/introspect/schema-refs.test.ts +43 -88
  264. package/studio/server/introspect/schema-refs.ts +14 -49
  265. package/studio/server/introspect/source-overlay/annotations.ts +14 -0
  266. package/studio/server/introspect/source-overlay/handlers.ts +161 -0
  267. package/studio/server/introspect/source-overlay/kernel-calls.ts +46 -0
  268. package/studio/server/introspect/source-overlay/project.ts +248 -0
  269. package/studio/server/introspect/source-overlay/spans.ts +330 -0
  270. package/studio/server/json.ts +46 -0
  271. package/studio/server/lifecycle.ts +5 -1
  272. package/studio/server/sse.test.ts +26 -0
  273. package/studio/server/sse.ts +4 -1
  274. package/studio/server/state/baseline.test.ts +223 -7
  275. package/studio/server/state/baseline.ts +148 -34
  276. package/studio/server/state/comments.test.ts +66 -1
  277. package/studio/server/state/comments.ts +173 -10
  278. package/studio/server/state/context.ts +34 -2
  279. package/studio/server/state/documents.ts +42 -1
  280. package/studio/server/state/integrations.ts +27 -1
  281. package/studio/server/state/layout.test.ts +34 -0
  282. package/studio/server/state/layout.ts +44 -17
  283. package/studio/server/state/settings.ts +29 -22
  284. package/studio/server/state/store.test.ts +122 -0
  285. package/studio/server/state/store.ts +66 -17
  286. package/studio/server/state/visibility.ts +22 -11
  287. package/studio/server/views/model.test.ts +57 -0
  288. package/studio/server/views/model.ts +66 -0
  289. package/studio/server/views/runtime.ts +40 -0
  290. package/studio/server/views/selection-repository.test.ts +31 -0
  291. package/studio/server/views/selection-repository.ts +60 -0
  292. package/studio/server/views/session.test.ts +73 -0
  293. package/studio/server/views/session.ts +166 -0
  294. package/studio/server/views/target.test.ts +78 -0
  295. package/studio/server/views/target.ts +222 -0
  296. package/studio/server/watch.test.ts +11 -11
  297. package/studio/server/watch.ts +9 -5
  298. package/studio/server/workspace/catalog.test.ts +17 -0
  299. package/studio/server/workspace/catalog.ts +43 -0
  300. package/studio/server/{state → workspace}/create.test.ts +1 -1
  301. package/studio/server/{state → workspace}/create.ts +1 -1
  302. package/studio/server/{state → workspace}/git.ts +1 -1
  303. package/studio/server/workspace/updates.ts +83 -0
  304. package/studio/server/workspace-state.ts +1 -1
  305. package/studio/server/workspace-watch.ts +2 -2
  306. package/studio/shared/contracts/README.md +16 -0
  307. package/studio/shared/contracts/agent.ts +270 -0
  308. package/studio/shared/contracts/runtime.ts +50 -0
  309. package/studio/shared/contracts/schema.ts +393 -0
  310. package/studio/shared/contracts/surface.test.ts +317 -0
  311. package/studio/shared/contracts/workspace.ts +265 -0
  312. package/studio/shared/layout.test.ts +116 -0
  313. package/studio/shared/schema/identity.test.ts +34 -0
  314. package/studio/shared/schema/identity.ts +68 -0
  315. package/studio/shared/types.ts +11 -1099
  316. package/studio/tsconfig.json +1 -1
  317. package/viewer/dist/main.js +40 -61
  318. package/.check-workspace.cjs +0 -40
  319. package/src/commands/__tests__/instance-create-hosts.test.ts +0 -29
  320. package/studio/client/dist/assets/elk-api-D2xgMJvi.js +0 -1
  321. package/studio/client/dist/assets/index-BMdnsIJA.css +0 -1
  322. package/studio/client/dist/assets/index-ChOr3yP0.css +0 -1
  323. package/studio/client/dist/assets/index-D-vRV8w7.js +0 -8
  324. package/studio/client/dist/assets/index-LGSWRrk8.js +0 -81
  325. package/studio/server/state/catalog.ts +0 -117
  326. package/studio/server/state/instance.ts +0 -280
  327. package/studio/server/state/updates.ts +0 -63
  328. package/studio/server/state/views.test.ts +0 -286
  329. package/studio/server/state/views.ts +0 -535
@@ -1,103 +1,28 @@
1
- import type { DomainBinding } from '@astrale-os/kernel-client/domain'
2
- import type { Node } from '@astrale-os/sdk/graph/node'
3
- import type { QueryAST } from '@astrale-os/sdk/query'
4
-
5
- import { ClassPath } from '@astrale-os/sdk/graph/class'
6
- import { NodeId } from '@astrale-os/sdk/graph/node'
7
- import { Path } from '@astrale-os/sdk/graph/path'
8
- import { describe, expect, mock, test } from 'bun:test'
9
-
10
- import type { AdminGraphApi } from '../../graph'
1
+ import { describe, expect, test } from 'bun:test'
11
2
 
3
+ import { adminBinding, adminSession } from '../../__tests__/fixture'
12
4
  import { connectAdminInstances } from '../client'
13
5
 
14
- const methods: Array<{ owner: string; name: string }> = []
15
-
16
- function definition(name: string) {
17
- return {
18
- name,
19
- $: {
20
- method(method: string) {
21
- const value = { owner: name, name: method }
22
- methods.push(value)
23
- return value
24
- },
25
- property(property: string) {
26
- return { key: property }
27
- },
28
- },
29
- }
30
- }
31
-
32
- function node(id: string, props: Record<string, unknown>): Node {
33
- return {
34
- id: NodeId(id),
35
- class: ClassPath.from('admin.astrale.ai', 'Instance'),
36
- props: props as never,
37
- }
38
- }
39
-
40
- function graphResult(nodes: readonly Node[], cursor?: string) {
41
- return {
42
- result: {
43
- kind: 'nodes' as const,
44
- nodes: nodes.map((value) => ({ kind: 'value' as const, value })),
45
- },
46
- page: cursor === undefined ? {} : { next: cursor },
47
- }
48
- }
49
-
50
- function sourceName(ast: QueryAST): string | undefined {
51
- const term = ast.source.terms[0]
52
- return term?.kind === 'definition' ? term.definition.name : undefined
53
- }
54
-
55
6
  function fixture(input: {
56
- instances?: readonly Node[]
57
- hosts?: readonly Node[]
58
- reserved?: Node | null
59
7
  invoke?: (method: { owner: string; name: string }, receiver: unknown, input: unknown) => unknown
60
8
  }) {
61
- methods.length = 0
62
- const Instance = definition('Instance')
63
- const Host = definition('Host')
64
- const Fleet = definition('Fleet')
65
- const invoke = mock(async (method: unknown, receiver: unknown, value: unknown) =>
66
- input.invoke?.(method as { owner: string; name: string }, receiver, value),
67
- )
68
- const binding = {
69
- $: {
70
- publication: { origin: 'admin.astrale.ai' },
71
- origin: 'admin.astrale.ai',
72
- class(name: string) {
73
- if (name === 'Instance') return Instance
74
- if (name === 'Host') return Host
75
- if (name === 'Fleet') return Fleet
76
- throw new Error(`Unexpected class ${name}`)
77
- },
78
- core: { nodes: { fleet: { path: Path.id(NodeId('fleet')) } } },
79
- invoke,
80
- },
81
- } as unknown as DomainBinding
82
- const query = mock(async (ast: QueryAST) => {
83
- const name = sourceName(ast)
84
- if (name === 'Instance') return graphResult(input.instances ?? [])
85
- if (name === 'Host') return graphResult(input.hosts ?? [])
86
- throw new Error(`Unexpected graph query ${String(name)}`)
9
+ const calls: Array<{
10
+ method: { owner: string; name: string }
11
+ receiver: string
12
+ value: unknown
13
+ }> = []
14
+ const remote = adminSession((method, receiver, value) => {
15
+ calls.push({ method, receiver: String(receiver), value })
16
+ return input.invoke?.(method, receiver, value)
87
17
  })
88
- const neighbors = mock(async (source: unknown, edge: { name?: string }) => {
89
- if (String(source) === '@fleet') return page(input.reserved ?? null)
90
- if (edge.name === 'instance_runs_on_host') return page(input.hosts?.[0] ?? null)
91
- throw new Error(`Unexpected neighbor query ${String(source)} ${String(edge)}`)
92
- })
93
- const graph = { query, neighbors } as unknown as AdminGraphApi
18
+ const binding = adminBinding()
94
19
  return {
95
20
  binding,
96
- graph,
97
- invoke,
21
+ invoke: remote.invoke,
22
+ calls,
98
23
  connect: () =>
99
24
  connectAdminInstances(
100
- { session: {} as never, graph },
25
+ { session: remote.session },
101
26
  {
102
27
  bind: async () => binding,
103
28
  operationId: (kind) => `cli.instance.${kind}:test`,
@@ -106,15 +31,6 @@ function fixture(input: {
106
31
  }
107
32
  }
108
33
 
109
- function page(item: Node | null) {
110
- return {
111
- nodes: item === null ? [] : [item],
112
- first: item,
113
- graph: { nodes: item === null ? [] : [item], edges: [] },
114
- cursor: null,
115
- }
116
- }
117
-
118
34
  describe('V2 Admin Instance adapter', () => {
119
35
  test('lists caller-visible Instances through the Admin-owned Fleet operation', async () => {
120
36
  const contract = fixture({
@@ -128,8 +44,6 @@ describe('V2 Admin Instance adapter', () => {
128
44
  state: 'ready',
129
45
  createdAt: '2026-08-12T00:00:00.000Z',
130
46
  updatedAt: '2026-08-12T00:00:00.000Z',
131
- hostId: '@host-node',
132
- region: 'fr-par',
133
47
  },
134
48
  ]
135
49
  : undefined,
@@ -142,15 +56,15 @@ describe('V2 Admin Instance adapter', () => {
142
56
  url: 'https://demo.eu.astrale.ai',
143
57
  state: 'ready',
144
58
  createdAt: '2026-08-12T00:00:00.000Z',
145
- hostId: '@host-node',
146
- region: 'fr-par',
147
59
  },
148
60
  ])
149
- expect(contract.invoke).toHaveBeenCalledWith(
150
- expect.objectContaining({ owner: 'Fleet', name: 'listInstances' }),
151
- Path.id(NodeId('fleet')),
152
- {},
153
- )
61
+ expect(contract.calls).toEqual([
62
+ {
63
+ method: { owner: 'Fleet', name: 'listInstances' },
64
+ receiver: '/:admin.astrale.ai:core.fleet::listInstances',
65
+ value: {},
66
+ },
67
+ ])
154
68
  })
155
69
 
156
70
  test('delegates default placement to Fleet without reading Host inventory', async () => {
@@ -162,10 +76,7 @@ describe('V2 Admin Instance adapter', () => {
162
76
  createdAt: '2026-08-12T00:00:00.000Z',
163
77
  updatedAt: '2026-08-12T00:00:00.000Z',
164
78
  }
165
- const contract = fixture({
166
- hosts: [node('host-node', { id: 'host-paris', state: 'ready' })],
167
- invoke: () => created,
168
- })
79
+ const contract = fixture({ invoke: () => created })
169
80
 
170
81
  await expect((await contract.connect()).create('demo')).resolves.toMatchObject({
171
82
  id: created.id,
@@ -174,47 +85,17 @@ describe('V2 Admin Instance adapter', () => {
174
85
  state: created.state,
175
86
  createdAt: created.createdAt,
176
87
  })
177
- expect(contract.invoke).toHaveBeenCalledWith(
178
- expect.objectContaining({ owner: 'Fleet', name: 'createInstance' }),
179
- Path.id(NodeId('fleet')),
180
- { operationId: 'cli.instance.create:test', slug: 'demo' },
181
- )
182
- expect(contract.graph.query).not.toHaveBeenCalled()
183
- expect(contract.graph.neighbors).not.toHaveBeenCalled()
184
- })
185
-
186
- test('resolves --host-id to an exact Host receiver and rejects the reserved Admin Host', async () => {
187
- const reserved = node('admin-host', { id: 'admin', state: 'ready' })
188
- const consumer = node('consumer-host', { id: 'consumer-paris', state: 'ready' })
189
- const contract = fixture({
190
- hosts: [reserved, consumer],
191
- reserved,
192
- invoke: () => ({
193
- id: '@instance-node',
194
- slug: 'demo',
195
- url: 'https://demo.eu.astrale.ai',
196
- state: 'ready',
197
- }),
198
- })
199
- const api = await contract.connect()
200
-
201
- await expect(api.create('demo', 'admin')).rejects.toMatchObject({ name: 'NotFoundError' })
202
- await expect(api.create('demo', 'consumer-paris')).resolves.toMatchObject({ slug: 'demo' })
203
- expect(contract.invoke).toHaveBeenCalledWith(
204
- expect.objectContaining({ owner: 'Host', name: 'createInstance' }),
205
- Path.id(NodeId('consumer-host')),
206
- { operationId: 'cli.instance.create:test', slug: 'demo' },
207
- )
88
+ expect(contract.calls).toEqual([
89
+ {
90
+ method: { owner: 'Fleet', name: 'createInstance' },
91
+ receiver: '/:admin.astrale.ai:core.fleet::createInstance',
92
+ value: { operationId: 'cli.instance.create:test', slug: 'demo' },
93
+ },
94
+ ])
208
95
  })
209
96
 
210
97
  test('refreshes and deletes through the resolved Instance receiver', async () => {
211
- const instance = node('instance-node', {
212
- slug: 'demo',
213
- url: 'https://demo.eu.astrale.ai',
214
- state: 'ready',
215
- })
216
98
  const contract = fixture({
217
- instances: [instance],
218
99
  invoke: (method) =>
219
100
  method.name === 'listInstances'
220
101
  ? [
@@ -238,22 +119,16 @@ describe('V2 Admin Instance adapter', () => {
238
119
 
239
120
  await expect(api.status('demo')).resolves.toMatchObject({ state: 'ready' })
240
121
  await expect(api.delete('@instance-node')).resolves.toMatchObject({ state: 'deleted' })
241
- expect(contract.invoke.mock.calls.map(([method]) => method)).toEqual([
242
- expect.objectContaining({ owner: 'Fleet', name: 'listInstances' }),
243
- expect.objectContaining({ owner: 'Instance', name: 'status' }),
244
- expect.objectContaining({ owner: 'Fleet', name: 'listInstances' }),
245
- expect.objectContaining({ owner: 'Instance', name: 'delete' }),
122
+ expect(contract.calls.map(({ method }) => method)).toEqual([
123
+ { owner: 'Fleet', name: 'listInstances' },
124
+ { owner: 'Instance', name: 'status' },
125
+ { owner: 'Fleet', name: 'listInstances' },
126
+ { owner: 'Instance', name: 'delete' },
246
127
  ])
247
128
  })
248
129
 
249
130
  test('installs a resolved catalog Domain through Instance.installDomain', async () => {
250
- const instance = node('instance-node', {
251
- slug: 'demo',
252
- url: 'https://demo.eu.astrale.ai',
253
- state: 'ready',
254
- })
255
131
  const contract = fixture({
256
- instances: [instance],
257
132
  invoke: (method) =>
258
133
  method.name === 'listInstances'
259
134
  ? [
@@ -282,10 +157,10 @@ describe('V2 Admin Instance adapter', () => {
282
157
  origin: 'crm.acme.dev',
283
158
  ok: true,
284
159
  })
285
- expect(contract.invoke).toHaveBeenCalledWith(
286
- expect.objectContaining({ owner: 'Instance', name: 'installDomain' }),
287
- Path.id(NodeId('instance-node')),
288
- { operationId: 'cli.instance.install-domain:test', domain: '@crm-domain' },
289
- )
160
+ expect(contract.calls.at(-1)).toEqual({
161
+ method: { owner: 'Instance', name: 'installDomain' },
162
+ receiver: '@instance-node::installDomain',
163
+ value: { operationId: 'cli.instance.install-domain:test', domain: '@crm-domain' },
164
+ })
290
165
  })
291
166
  })
@@ -1,14 +1,16 @@
1
- import type { DomainBinding } from '@astrale-os/kernel-client/domain'
2
- import type { ClientSession } from '@astrale-os/kernel-client/session'
3
- import type { Node } from '@astrale-os/sdk/graph/node'
1
+ import type { ClientSession } from '@astrale-os/sdk/client/session'
4
2
 
5
- import { bind } from '@astrale-os/kernel-client/domain'
6
3
  import { Path } from '@astrale-os/sdk/graph/path'
7
- import { Query } from '@astrale-os/sdk/query'
8
4
 
9
- import { readAllNodes, type AdminGraphApi } from '../graph'
10
5
  import {
11
- AdminHostNotFoundError,
6
+ bindAdmin,
7
+ invokeAdminMethod,
8
+ requireAdminBinding,
9
+ requireAdminClass,
10
+ requireAdminCore,
11
+ type AdminBinding,
12
+ } from '../binding'
13
+ import {
12
14
  AdminInstanceNotFoundError,
13
15
  findOwnedInstance,
14
16
  type DomainInstallReceipt,
@@ -17,33 +19,20 @@ import {
17
19
  type OwnedInstanceInfo,
18
20
  } from './model'
19
21
 
20
- const ADMIN_ORIGIN = 'admin.astrale.ai'
21
- const PAGE_SIZE = 256
22
- const MAXIMUM_INSTANCES = 10_000
23
- const MAXIMUM_PAGES = Math.ceil(MAXIMUM_INSTANCES / PAGE_SIZE) + 1
24
-
25
- const HostRef = Object.freeze({ origin: ADMIN_ORIGIN, kind: 'class' as const, name: 'Host' })
26
- const fleetReservesAdminHost = Object.freeze({
27
- origin: ADMIN_ORIGIN,
28
- kind: 'class' as const,
29
- name: 'fleet_reserves_admin_host',
30
- })
31
-
32
22
  export interface AdminInstanceContext {
33
23
  readonly session: ClientSession
34
- readonly graph: AdminGraphApi
35
24
  }
36
25
 
37
26
  export interface AdminInstanceApi {
38
27
  list(): Promise<OwnedInstanceInfo[]>
39
- create(slug: string, hostId?: string): Promise<InstanceInfo>
28
+ create(slug: string): Promise<InstanceInfo>
40
29
  status(identifier: string): Promise<InstanceInfo>
41
30
  delete(identifier: string): Promise<InstanceInfo>
42
31
  installDomain(identifier: string, domain: string): Promise<DomainInstallReceipt>
43
32
  }
44
33
 
45
34
  export interface AdminInstanceDependencies {
46
- readonly bind?: (session: ClientSession) => Promise<DomainBinding>
35
+ readonly bind?: (session: ClientSession) => Promise<AdminBinding>
47
36
  readonly operationId?: (kind: 'create' | 'status' | 'delete' | 'install-domain') => string
48
37
  }
49
38
 
@@ -55,24 +44,22 @@ export async function connectAdminInstances(
55
44
  context: AdminInstanceContext,
56
45
  dependencies: AdminInstanceDependencies = {},
57
46
  ): Promise<AdminInstanceApi> {
58
- const binding = await (dependencies.bind ?? bindInstalledAdmin)(context.session)
59
- if (binding.$.publication?.origin !== ADMIN_ORIGIN || binding.$.origin !== ADMIN_ORIGIN) {
60
- throw new TypeError('Configured Admin target does not serve the Admin Domain.')
61
- }
47
+ const binding = requireAdminBinding(await (dependencies.bind ?? bindAdmin)(context.session))
62
48
 
63
- const Instance = binding.$.class('Instance')
64
- const Host = binding.$.class('Host')
65
- const Fleet = binding.$.class('Fleet')
66
- const fleet = binding.$.core.nodes.fleet?.path
67
- if (fleet === undefined) throw new TypeError('Admin Domain has no singleton Fleet receiver.')
49
+ const Instance = requireAdminClass(binding, 'Instance', 'node')
50
+ const Fleet = requireAdminClass(binding, 'Fleet', 'node')
51
+ const fleet = requireAdminCore(binding, 'fleet')
68
52
 
69
53
  const operationId = dependencies.operationId ?? defaultOperationId
70
54
 
71
55
  const list = async (): Promise<OwnedInstanceInfo[]> => {
72
- const result: unknown = await binding.$.invoke(
73
- Fleet.$.method('listInstances') as never,
56
+ const result: unknown = await invokeAdminMethod(
57
+ context.session,
58
+ binding,
59
+ Fleet,
60
+ 'listInstances',
74
61
  fleet,
75
- {} as never,
62
+ {},
76
63
  )
77
64
  if (!Array.isArray(result)) throw new TypeError('Admin Instance inventory is invalid.')
78
65
  return result.map((value) => instanceFromSummary(value) as OwnedInstanceInfo)
@@ -89,113 +76,48 @@ export async function connectAdminInstances(
89
76
  identifier: string,
90
77
  ): Promise<InstanceInfo> => {
91
78
  const instance = await requireInstance(identifier)
92
- const output = await binding.$.invoke(
93
- Instance.$.method(method) as never,
79
+ const output = await invokeAdminMethod(
80
+ context.session,
81
+ binding,
82
+ Instance,
83
+ method,
94
84
  Path.parse(instance.id),
95
- { operationId: operationId(method) } as never,
85
+ { operationId: operationId(method) },
96
86
  )
97
87
  return instanceFromSummary(output)
98
88
  }
99
89
 
100
90
  return Object.freeze({
101
91
  list,
102
- async create(slug: string, hostId?: string) {
92
+ async create(slug: string) {
103
93
  const input = Object.freeze({
104
94
  operationId: operationId('create'),
105
95
  slug,
106
96
  })
107
- if (hostId !== undefined) {
108
- const selected = await hostInventory(context.graph, binding, Host, fleet)
109
- const host = selected.hosts.find(
110
- (item) => item.id === hostId && item.state === 'ready' && item.path !== selected.reserved,
111
- )
112
- if (host === undefined) throw new AdminHostNotFoundError(hostId)
113
- return instanceFromSummary(
114
- await binding.$.invoke(
115
- Host.$.method('createInstance') as never,
116
- Path.parse(host.path),
117
- input as never,
118
- ),
119
- )
120
- }
121
-
122
- // Default placement belongs to Admin. Avoid a redundant Host graph read:
123
- // it requires broader graph authority than this Fleet operation and can
124
- // deny an otherwise authorized caller before the server selects a Host.
125
97
  return instanceFromSummary(
126
- await binding.$.invoke(Fleet.$.method('createInstance') as never, fleet, input as never),
98
+ await invokeAdminMethod(context.session, binding, Fleet, 'createInstance', fleet, input),
127
99
  )
128
100
  },
129
101
  status: (identifier: string) => invokeInstance('status', identifier),
130
102
  delete: (identifier: string) => invokeInstance('delete', identifier),
131
103
  async installDomain(identifier: string, domain: string): Promise<DomainInstallReceipt> {
132
104
  const instance = await requireInstance(identifier)
133
- const output = await binding.$.invoke(
134
- Instance.$.method('installDomain') as never,
105
+ const output = await invokeAdminMethod(
106
+ context.session,
107
+ binding,
108
+ Instance,
109
+ 'installDomain',
135
110
  Path.parse(instance.id),
136
111
  {
137
112
  operationId: operationId('install-domain'),
138
113
  domain: Path.parse(domain).raw,
139
- } as never,
114
+ },
140
115
  )
141
116
  return domainInstallReceipt(output)
142
117
  },
143
118
  })
144
119
  }
145
120
 
146
- async function bindInstalledAdmin(session: ClientSession): Promise<DomainBinding> {
147
- return bind(session, await session.installed(ADMIN_ORIGIN))
148
- }
149
-
150
- interface HostInventoryItem {
151
- readonly id: string
152
- readonly nodeId: string
153
- readonly path: string
154
- readonly state: string
155
- }
156
-
157
- async function hostInventory(
158
- graph: AdminGraphApi,
159
- binding: DomainBinding,
160
- Host: ReturnType<DomainBinding['$']['class']>,
161
- fleet: Path,
162
- ): Promise<{ readonly hosts: readonly HostInventoryItem[]; readonly reserved?: string }> {
163
- const [nodes, reservedPage] = await Promise.all([
164
- readAllNodes(
165
- graph,
166
- Query.from({ kind: 'node', definitions: [HostRef] }).select({
167
- kind: 'nodes',
168
- projection: { kind: 'value' },
169
- }),
170
- {
171
- label: 'Admin Host inventory',
172
- maximum: MAXIMUM_INSTANCES,
173
- maximumPages: MAXIMUM_PAGES,
174
- },
175
- ),
176
- graph.neighbors(fleet, fleetReservesAdminHost, {
177
- direction: 'outgoing',
178
- page: { size: 1 },
179
- }),
180
- ])
181
- const reserved = reservedPage.first === null ? undefined : Path.id(reservedPage.first.id).raw
182
- return Object.freeze({
183
- hosts: Object.freeze(
184
- nodes.map((node) =>
185
- Object.freeze({
186
- id: requiredStringProperty(Host, node, 'id'),
187
- nodeId: String(node.id),
188
- path: Path.id(node.id).raw,
189
- state: requiredStringProperty(Host, node, 'state'),
190
- }),
191
- ),
192
- ),
193
- ...(reserved === undefined ? {} : { reserved }),
194
- })
195
- }
196
-
197
- type DynamicDefinition = ReturnType<DomainBinding['$']['class']>
198
-
199
121
  function instanceFromSummary(input: unknown): InstanceInfo {
200
122
  const value = record(input, 'Admin Instance summary')
201
123
  const failure = value.failure === undefined ? undefined : record(value.failure, 'Admin failure')
@@ -204,10 +126,6 @@ function instanceFromSummary(input: unknown): InstanceInfo {
204
126
  slug: requiredString(value.slug, 'Admin Instance slug'),
205
127
  url: optionalStringValue(value.url) ?? '',
206
128
  state: instanceState(value.state),
207
- ...(value.hostId === undefined
208
- ? {}
209
- : { hostId: requiredString(value.hostId, 'Admin Host id') }),
210
- ...(value.region === undefined ? {} : { region: requiredString(value.region, 'Admin region') }),
211
129
  ...(value.phase === undefined
212
130
  ? {}
213
131
  : { phase: requiredString(value.phase, 'Admin Instance phase') }),
@@ -243,13 +161,6 @@ function domainInstallReceipt(input: unknown): DomainInstallReceipt {
243
161
  })
244
162
  }
245
163
 
246
- function requiredStringProperty(definition: DynamicDefinition, node: Node, name: string): string {
247
- return requiredString(
248
- node.props[definition.$.property(name).key],
249
- `Admin ${definition.name}.${name}`,
250
- )
251
- }
252
-
253
164
  function instanceState(input: unknown): InstanceState {
254
165
  if (
255
166
  input === 'provisioning' ||
@@ -5,10 +5,9 @@ export {
5
5
  type AdminInstanceDependencies,
6
6
  } from './client'
7
7
  export {
8
- AdminHostNotFoundError,
9
8
  AdminInstanceNotFoundError,
10
9
  findOwnedInstance,
11
- formatInstanceLocation,
10
+ formatInstanceState,
12
11
  type DomainInstallReceipt,
13
12
  type InstanceInfo,
14
13
  type InstanceState,
@@ -5,9 +5,7 @@ export interface InstanceInfo {
5
5
  readonly id: string
6
6
  readonly slug: string
7
7
  readonly url: string
8
- readonly hostId?: string
9
- readonly region?: string
10
- readonly state?: InstanceState
8
+ readonly state: InstanceState
11
9
  readonly phase?: string
12
10
  readonly error?: string | null
13
11
  readonly createdAt?: string
@@ -28,19 +26,13 @@ export interface DomainInstallReceipt {
28
26
  readonly error?: string
29
27
  }
30
28
 
31
- export class AdminInstanceNotFoundError extends Error {
32
- readonly name = 'NotFoundError'
33
-
34
- constructor(readonly identifier: string) {
35
- super(`No owned Admin Instance matches ${JSON.stringify(identifier)}.`)
36
- }
37
- }
38
-
39
- export class AdminHostNotFoundError extends Error {
40
- readonly name = 'NotFoundError'
41
-
29
+ export class AdminInstanceNotFoundError extends AstraleError {
42
30
  constructor(readonly identifier: string) {
43
- super(`No caller-usable Admin Host matches ${JSON.stringify(identifier)}.`)
31
+ super(
32
+ 'INSTANCE_NOT_FOUND',
33
+ `No owned Admin Instance matches ${JSON.stringify(identifier)}.`,
34
+ 'Run `astrale instance list` to see your instances.',
35
+ )
44
36
  }
45
37
  }
46
38
 
@@ -57,9 +49,8 @@ export function findOwnedInstance(
57
49
  )
58
50
  }
59
51
 
60
- /** Human-readable location of an Instance (state, region, and Host Path). */
61
- export function formatInstanceLocation(info: InstanceInfo): string {
62
- return [info.state && info.state !== 'ready' ? info.state : undefined, info.region, info.hostId]
63
- .filter(Boolean)
64
- .join(' · ')
52
+ /** Human-readable non-ready lifecycle state for one Instance. */
53
+ export function formatInstanceState(info: InstanceInfo): string {
54
+ return info.state === 'ready' ? '' : `${info.state}${info.phase ? ` (${info.phase})` : ''}`
65
55
  }
56
+ import { AstraleError } from '../../errors'
@@ -4,7 +4,10 @@ import type { IdentityStore } from '../../identity/index'
4
4
 
5
5
  import { AuthError } from '../../errors'
6
6
  import { findOwnedInstance, type OwnedInstanceInfo } from '../../lib/admin-instance'
7
- import { assertAlphaCreateIdentity } from '../instance/create'
7
+ import {
8
+ assertInstanceCreateIdentity,
9
+ selectInstanceCreateIdentity,
10
+ } from '../../lib/provision-instance'
8
11
 
9
12
  describe('admin-backed instance commands', () => {
10
13
  test('findOwnedInstance matches owner inventory by slug or stable node id', () => {
@@ -14,8 +17,6 @@ describe('admin-backed instance commands', () => {
14
17
  url: 'https://demo.eu.astrale.ai',
15
18
  state: 'failed',
16
19
  organizationId: 'org_123',
17
- hostId: 'host-1',
18
- region: 'eu',
19
20
  phase: 'installing:default-domains',
20
21
  error: 'postInstall failed',
21
22
  createdAt: '2026-07-16T00:00:00.000Z',
@@ -39,8 +40,8 @@ describe('admin-backed instance commands', () => {
39
40
  },
40
41
  }
41
42
 
42
- expect(() => assertAlphaCreateIdentity(store)).toThrow(AuthError)
43
- expect(() => assertAlphaCreateIdentity(store)).toThrow('WorkOS login required')
43
+ expect(() => assertInstanceCreateIdentity(store)).toThrow(AuthError)
44
+ expect(() => assertInstanceCreateIdentity(store)).toThrow('WorkOS login required')
44
45
  })
45
46
 
46
47
  test('instance create preflight accepts IdP-backed identities', () => {
@@ -58,7 +59,8 @@ describe('admin-backed instance commands', () => {
58
59
  },
59
60
  }
60
61
 
61
- expect(() => assertAlphaCreateIdentity(store)).not.toThrow()
62
+ expect(() => assertInstanceCreateIdentity(store)).not.toThrow()
63
+ expect(selectInstanceCreateIdentity(store)).toBe('bryan')
62
64
  })
63
65
 
64
66
  test('--as selection must also be IdP-backed', () => {
@@ -82,7 +84,8 @@ describe('admin-backed instance commands', () => {
82
84
  },
83
85
  }
84
86
 
85
- expect(() => assertAlphaCreateIdentity(store, { as: 'local' })).toThrow(AuthError)
86
- expect(() => assertAlphaCreateIdentity(store, { as: 'workos' })).not.toThrow()
87
+ expect(() => assertInstanceCreateIdentity(store, { as: 'local' })).toThrow(AuthError)
88
+ expect(() => assertInstanceCreateIdentity(store, { as: 'workos' })).not.toThrow()
89
+ expect(selectInstanceCreateIdentity(store, { as: 'workos' })).toBe('workos')
87
90
  })
88
91
  })