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

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 (322) hide show
  1. package/README.md +23 -12
  2. package/dist/astrale.js +51471 -56290
  3. package/dist/public/connect-core.js +2028 -26846
  4. package/dist/public/keys/index.js +1755 -40694
  5. package/dist/public/paths/index.js +859 -39518
  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 +6 -2
  28. package/dist/types/lib/failure-debug.d.ts +1 -0
  29. package/dist/types/lib/idp.d.ts +7 -0
  30. package/dist/types/lib/instance-target.d.ts +1 -1
  31. package/dist/types/lib/instance.d.ts +10 -2
  32. package/dist/types/lib/log.d.ts +5 -3
  33. package/dist/types/lib/meta.d.ts +2 -2
  34. package/dist/types/lib/proc.d.ts +43 -0
  35. package/dist/types/lib/self.d.ts +9 -0
  36. package/dist/types/lib/update.d.ts +131 -0
  37. package/package.json +8 -10
  38. package/src/__tests__/fixtures/publication.ts +20 -0
  39. package/src/admin/__tests__/binding.test.ts +31 -0
  40. package/src/admin/__tests__/fixture.ts +101 -0
  41. package/src/admin/binding.ts +98 -0
  42. package/src/admin/catalog/.spec/api.d.ts +2 -2
  43. package/src/admin/catalog/__tests__/client.test.ts +20 -49
  44. package/src/admin/catalog/client.ts +36 -32
  45. package/src/admin/catalog/model.ts +3 -4
  46. package/src/admin/graph/.spec/api.d.ts +4 -10
  47. package/src/admin/graph/nodes.ts +1 -1
  48. package/src/admin/instance/.spec/api.d.ts +2 -2
  49. package/src/admin/instance/__tests__/client.test.ts +39 -164
  50. package/src/admin/instance/client.ts +35 -124
  51. package/src/admin/instance/index.ts +1 -2
  52. package/src/admin/instance/model.ts +11 -20
  53. package/src/commands/__tests__/admin-instance.test.ts +11 -8
  54. package/src/commands/__tests__/auth-login.test.ts +54 -1
  55. package/src/commands/__tests__/call-describe.test.ts +46 -33
  56. package/src/commands/__tests__/call.test.ts +5 -1
  57. package/src/commands/__tests__/domain-install-operation.test.ts +12 -7
  58. package/src/commands/__tests__/domain-install-owned.test.ts +6 -2
  59. package/src/commands/__tests__/domain-list.test.ts +63 -4
  60. package/src/commands/__tests__/install-identity-override.test.ts +19 -19
  61. package/src/commands/__tests__/instance-list-rows.test.ts +1 -1
  62. package/src/commands/__tests__/instance-status.test.ts +139 -0
  63. package/src/commands/__tests__/read-commands.test.ts +35 -10
  64. package/src/commands/__tests__/studio-descriptor.test.ts +22 -0
  65. package/src/commands/__tests__/update.test.ts +48 -19
  66. package/src/commands/call-describe.ts +60 -68
  67. package/src/commands/call.ts +43 -56
  68. package/src/commands/domain/install.ts +13 -17
  69. package/src/commands/domain/list.ts +18 -13
  70. package/src/commands/domain/publish.ts +14 -11
  71. package/src/commands/domain/uninstall.ts +2 -2
  72. package/src/commands/get.ts +14 -15
  73. package/src/commands/identity/__tests__/register.test.ts +3 -3
  74. package/src/commands/identity/register.ts +5 -4
  75. package/src/commands/instance/create.ts +8 -16
  76. package/src/commands/instance/delete.ts +12 -9
  77. package/src/commands/instance/list.ts +2 -2
  78. package/src/commands/instance/status.ts +66 -20
  79. package/src/commands/instance/use.ts +16 -12
  80. package/src/commands/introspect.ts +9 -16
  81. package/src/commands/logs.ts +24 -25
  82. package/src/commands/mutate.ts +6 -5
  83. package/src/commands/query.ts +27 -30
  84. package/src/commands/studio.ts +20 -0
  85. package/src/commands/token.ts +8 -7
  86. package/src/commands/update.ts +38 -14
  87. package/src/commands/view.ts +7 -6
  88. package/src/connection/.spec/api.d.ts +7 -8
  89. package/src/connection/.spec/architecture.md +5 -4
  90. package/src/connection/.spec/flows/session.ts +2 -2
  91. package/src/connection/.spec/laws/connection.ts +6 -2
  92. package/src/connection/.spec/layout.ts +1 -0
  93. package/src/connection/__tests__/credential.test.ts +20 -1
  94. package/src/connection/__tests__/errors.test.ts +43 -32
  95. package/src/connection/__tests__/exchange.test.ts +36 -6
  96. package/src/connection/__tests__/failure-fixtures.ts +39 -0
  97. package/src/connection/__tests__/failure-safety.test.ts +75 -0
  98. package/src/connection/__tests__/self.test.ts +31 -11
  99. package/src/connection/__tests__/session.test.ts +3 -3
  100. package/src/connection/__tests__/target.test.ts +1 -0
  101. package/src/connection/auth.ts +13 -0
  102. package/src/connection/call.ts +1 -1
  103. package/src/connection/credential.ts +2 -2
  104. package/src/connection/errors.ts +1 -355
  105. package/src/connection/exchange.ts +52 -27
  106. package/src/connection/failure/classify.ts +110 -0
  107. package/src/connection/failure/index.ts +18 -0
  108. package/src/connection/failure/model.ts +28 -0
  109. package/src/connection/failure/render.ts +116 -0
  110. package/src/connection/reasons.ts +16 -0
  111. package/src/connection/self.ts +39 -21
  112. package/src/connection/session.ts +19 -8
  113. package/src/errors.ts +3 -2
  114. package/src/graph/.spec/api.d.ts +12 -5
  115. package/src/graph/.spec/layout.ts +9 -1
  116. package/src/graph/__tests__/mutation.test.ts +2 -2
  117. package/src/graph/__tests__/query.test.ts +25 -6
  118. package/src/graph/class.ts +30 -0
  119. package/src/graph/index.ts +1 -0
  120. package/src/graph/query.ts +21 -32
  121. package/src/identity/.spec/api.d.ts +2 -2
  122. package/src/identity/__tests__/registration.test.ts +1 -1
  123. package/src/identity/registration.ts +3 -3
  124. package/src/keys/__tests__/keys.test.ts +4 -0
  125. package/src/keys/credential.ts +1 -0
  126. package/src/lib/__tests__/admin-target.test.ts +12 -0
  127. package/src/lib/__tests__/command-dx.test.ts +1 -1
  128. package/src/lib/__tests__/domain-publication.test.ts +138 -0
  129. package/src/lib/__tests__/idp-session.test.ts +22 -0
  130. package/src/lib/__tests__/idp.test.ts +36 -4
  131. package/src/lib/__tests__/instance-candidates.test.ts +1 -3
  132. package/src/lib/__tests__/instance-target.test.ts +7 -39
  133. package/src/lib/__tests__/instance.test.ts +6 -0
  134. package/src/lib/__tests__/log-errors.test.ts +64 -0
  135. package/src/lib/__tests__/studio-server-deps.test.ts +53 -16
  136. package/src/lib/__tests__/update.test.ts +113 -8
  137. package/src/lib/__tests__/view-open-intent.test.ts +0 -2
  138. package/src/lib/__tests__/view-server.test.ts +1 -1
  139. package/src/lib/__tests__/view-session.test.ts +1 -1
  140. package/src/lib/admin-instance.ts +14 -8
  141. package/src/lib/admin-target.ts +21 -2
  142. package/src/lib/command-dx.ts +9 -9
  143. package/src/lib/domain-publication.ts +104 -0
  144. package/src/lib/failure-debug.ts +26 -0
  145. package/src/lib/idp.ts +39 -4
  146. package/src/lib/instance-candidates.ts +2 -2
  147. package/src/lib/instance-target.ts +3 -14
  148. package/src/lib/instance.ts +22 -12
  149. package/src/lib/log.ts +16 -9
  150. package/src/lib/login-flow.ts +13 -3
  151. package/src/lib/meta.ts +2 -2
  152. package/src/lib/provision-instance.ts +34 -57
  153. package/src/lib/update.ts +124 -30
  154. package/src/lib/validation.ts +2 -2
  155. package/src/lib/view/resolve.ts +1 -1
  156. package/src/program/__tests__/program.test.ts +20 -4
  157. package/src/program/build.ts +2 -2
  158. package/src/setup/__tests__/instance-step.test.ts +27 -10
  159. package/src/setup/steps/instance.ts +22 -15
  160. package/studio/client/dist/assets/{elk-api-D2xgMJvi.js → elk-api-ByzoDZWH.js} +1 -1
  161. package/studio/client/dist/assets/index-B0RCgUOT.js +8 -0
  162. package/studio/client/dist/assets/index-BiGR8Oo9.css +1 -0
  163. package/studio/client/dist/assets/index-N8J8EKlB.js +81 -0
  164. package/studio/client/dist/index.html +2 -2
  165. package/studio/package.json +5 -10
  166. package/studio/server/agent/ask.test.ts +1 -1
  167. package/studio/server/agent/bridge/grant.test.ts +1 -1
  168. package/studio/server/agent/bridge/routes.test.ts +1 -1
  169. package/studio/server/agent/bridge/stdio.ts +8 -1
  170. package/studio/server/agent/conversation.test.ts +10 -5
  171. package/studio/server/agent/conversation.ts +56 -8
  172. package/studio/server/agent/harness/claude/adapter.test.ts +15 -5
  173. package/studio/server/agent/harness/codex/loadout.ts +18 -21
  174. package/studio/server/agent/harness/gateway/config.ts +20 -18
  175. package/studio/server/agent/harness/selection.ts +9 -1
  176. package/studio/server/agent/prompts/anchors.test.ts +32 -36
  177. package/studio/server/agent/prompts/anchors.ts +20 -49
  178. package/studio/server/agent/prompts/system.test.ts +3 -2
  179. package/studio/server/agent/prompts/system.ts +6 -5
  180. package/studio/server/agent/prompts/turn.ts +13 -4
  181. package/studio/server/agent/routes.test.ts +1 -1
  182. package/studio/server/agent/run/completion.ts +2 -2
  183. package/studio/server/agent/run/coordinator.test.ts +1 -1
  184. package/studio/server/agent/run/preparation.ts +6 -5
  185. package/studio/server/agent/run/transcript.test.ts +35 -1
  186. package/studio/server/agent/run/transcript.ts +169 -3
  187. package/studio/server/agent/run/usage.test.ts +24 -0
  188. package/studio/server/agent/run/usage.ts +26 -1
  189. package/studio/server/api/canvas.ts +43 -0
  190. package/studio/server/api/comments.ts +85 -0
  191. package/studio/server/api/context.ts +61 -0
  192. package/studio/server/api/deployment.ts +39 -0
  193. package/studio/server/api/documents.ts +55 -0
  194. package/studio/server/api/domain.ts +59 -0
  195. package/studio/server/api/http.ts +45 -0
  196. package/studio/server/api/project.ts +57 -0
  197. package/studio/server/api/schema.ts +12 -0
  198. package/studio/server/api/views.ts +57 -0
  199. package/studio/server/api/workspace.ts +60 -0
  200. package/studio/server/api-agent-loadout.test.ts +1 -1
  201. package/studio/server/api.test.ts +147 -0
  202. package/studio/server/api.ts +20 -422
  203. package/studio/server/cache.test.ts +122 -0
  204. package/studio/server/cache.ts +162 -11
  205. package/studio/server/cli-consumers.test.ts +494 -0
  206. package/studio/server/cli.test.ts +88 -0
  207. package/studio/server/cli.ts +240 -0
  208. package/studio/server/client-package.ts +25 -1
  209. package/studio/server/domain.test.ts +50 -40
  210. package/studio/server/domain.ts +83 -52
  211. package/studio/server/environment/dotenv-preview.test.ts +39 -0
  212. package/studio/server/environment/dotenv-preview.ts +27 -0
  213. package/studio/server/environment/files.test.ts +53 -0
  214. package/studio/server/{state/env.ts → environment/files.ts} +7 -48
  215. package/studio/server/{state → handoff}/copy.ts +21 -6
  216. package/studio/server/handoff/service.test.ts +22 -0
  217. package/studio/server/{state/handoff.ts → handoff/service.ts} +21 -14
  218. package/studio/server/index.ts +1 -1
  219. package/studio/server/instances/active.ts +72 -0
  220. package/studio/server/instances/deploy-record.test.ts +31 -0
  221. package/studio/server/instances/deploy-record.ts +37 -0
  222. package/studio/server/instances/deploy.ts +56 -0
  223. package/studio/server/{state/instance.test.ts → instances/probe.test.ts} +25 -21
  224. package/studio/server/instances/probe.ts +67 -0
  225. package/studio/server/instances/status.test.ts +12 -0
  226. package/studio/server/instances/status.ts +49 -0
  227. package/studio/server/introspect/anatomy/client-tree.ts +57 -0
  228. package/studio/server/introspect/anatomy/env-fields.ts +66 -0
  229. package/studio/server/introspect/anatomy/schema-definition.ts +53 -0
  230. package/studio/server/introspect/anatomy/source.ts +181 -0
  231. package/studio/server/introspect/anatomy/views/routes.ts +114 -0
  232. package/studio/server/introspect/anatomy/views.ts +53 -0
  233. package/studio/server/introspect/anatomy-extras.test.ts +106 -35
  234. package/studio/server/introspect/anatomy-extras.ts +7 -726
  235. package/studio/server/introspect/anatomy.test.ts +22 -14
  236. package/studio/server/introspect/anatomy.ts +52 -28
  237. package/studio/server/introspect/bundle.ts +15 -9
  238. package/studio/server/introspect/canonical-schema.test.ts +93 -309
  239. package/studio/server/introspect/canonical-schema.ts +257 -425
  240. package/studio/server/introspect/config-preview.test.ts +32 -0
  241. package/studio/server/introspect/config-preview.ts +119 -0
  242. package/studio/server/introspect/core-extractor.ts +18 -127
  243. package/studio/server/introspect/core.ts +1 -2
  244. package/studio/server/introspect/diff.test.ts +18 -18
  245. package/studio/server/introspect/diff.ts +44 -90
  246. package/studio/server/introspect/extractor.ts +23 -116
  247. package/studio/server/introspect/hash.ts +5 -2
  248. package/studio/server/introspect/overlay-tsmorph.test.ts +119 -240
  249. package/studio/server/introspect/overlay-tsmorph.ts +7 -1305
  250. package/studio/server/introspect/overlay.test.ts +26 -54
  251. package/studio/server/introspect/overlay.ts +10 -37
  252. package/studio/server/introspect/revision.test.ts +54 -0
  253. package/studio/server/introspect/revision.ts +49 -0
  254. package/studio/server/introspect/runtime.test.ts +83 -143
  255. package/studio/server/introspect/runtime.ts +40 -11
  256. package/studio/server/introspect/schema-ir-json.test.ts +70 -0
  257. package/studio/server/introspect/schema-ir-json.ts +151 -0
  258. package/studio/server/introspect/schema-refs.test.ts +43 -88
  259. package/studio/server/introspect/schema-refs.ts +14 -49
  260. package/studio/server/introspect/source-overlay/annotations.ts +14 -0
  261. package/studio/server/introspect/source-overlay/handlers.ts +161 -0
  262. package/studio/server/introspect/source-overlay/kernel-calls.ts +46 -0
  263. package/studio/server/introspect/source-overlay/project.ts +248 -0
  264. package/studio/server/introspect/source-overlay/spans.ts +330 -0
  265. package/studio/server/json.ts +46 -0
  266. package/studio/server/lifecycle.ts +5 -1
  267. package/studio/server/sse.test.ts +26 -0
  268. package/studio/server/sse.ts +4 -1
  269. package/studio/server/state/baseline.test.ts +223 -7
  270. package/studio/server/state/baseline.ts +148 -34
  271. package/studio/server/state/comments.test.ts +66 -1
  272. package/studio/server/state/comments.ts +173 -10
  273. package/studio/server/state/context.ts +34 -2
  274. package/studio/server/state/documents.ts +42 -1
  275. package/studio/server/state/integrations.ts +27 -1
  276. package/studio/server/state/layout.test.ts +34 -0
  277. package/studio/server/state/layout.ts +44 -17
  278. package/studio/server/state/settings.ts +29 -22
  279. package/studio/server/state/store.test.ts +122 -0
  280. package/studio/server/state/store.ts +66 -17
  281. package/studio/server/state/visibility.ts +22 -11
  282. package/studio/server/views/model.test.ts +57 -0
  283. package/studio/server/views/model.ts +66 -0
  284. package/studio/server/views/runtime.ts +40 -0
  285. package/studio/server/views/selection-repository.test.ts +31 -0
  286. package/studio/server/views/selection-repository.ts +60 -0
  287. package/studio/server/views/session.test.ts +73 -0
  288. package/studio/server/views/session.ts +166 -0
  289. package/studio/server/views/target.test.ts +78 -0
  290. package/studio/server/views/target.ts +222 -0
  291. package/studio/server/watch.test.ts +11 -11
  292. package/studio/server/watch.ts +9 -5
  293. package/studio/server/workspace/catalog.test.ts +17 -0
  294. package/studio/server/workspace/catalog.ts +43 -0
  295. package/studio/server/{state → workspace}/create.test.ts +1 -1
  296. package/studio/server/{state → workspace}/create.ts +1 -1
  297. package/studio/server/{state → workspace}/git.ts +1 -1
  298. package/studio/server/workspace/updates.ts +83 -0
  299. package/studio/server/workspace-state.ts +1 -1
  300. package/studio/server/workspace-watch.ts +2 -2
  301. package/studio/shared/contracts/README.md +16 -0
  302. package/studio/shared/contracts/agent.ts +270 -0
  303. package/studio/shared/contracts/runtime.ts +50 -0
  304. package/studio/shared/contracts/schema.ts +393 -0
  305. package/studio/shared/contracts/surface.test.ts +317 -0
  306. package/studio/shared/contracts/workspace.ts +265 -0
  307. package/studio/shared/layout.test.ts +116 -0
  308. package/studio/shared/schema/identity.test.ts +34 -0
  309. package/studio/shared/schema/identity.ts +68 -0
  310. package/studio/shared/types.ts +11 -1099
  311. package/studio/tsconfig.json +1 -1
  312. package/viewer/dist/main.js +40 -61
  313. package/.check-workspace.cjs +0 -40
  314. package/src/commands/__tests__/instance-create-hosts.test.ts +0 -29
  315. package/studio/client/dist/assets/index-BMdnsIJA.css +0 -1
  316. package/studio/client/dist/assets/index-D-vRV8w7.js +0 -8
  317. package/studio/client/dist/assets/index-LGSWRrk8.js +0 -81
  318. package/studio/server/state/catalog.ts +0 -117
  319. package/studio/server/state/instance.ts +0 -280
  320. package/studio/server/state/updates.ts +0 -63
  321. package/studio/server/state/views.test.ts +0 -286
  322. package/studio/server/state/views.ts +0 -535
@@ -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
  })
@@ -81,11 +81,56 @@ describe('auth login command', () => {
81
81
  expect(result.exitCode).toBe(0)
82
82
  const session = JSON.parse(await readFile(join(tmp, 'idp-sessions', 'alice.json'), 'utf-8'))
83
83
  expect(session.audience).toBe('https://kernel.example.com')
84
+ expect(session.clientId).toBe('client_123')
84
85
  expect(session.subject).toBe('user_123')
85
86
  } finally {
86
87
  await server.stop()
87
88
  }
88
89
  })
90
+
91
+ test('persists an explicit client id and reuses it for a named login', async () => {
92
+ const server = oauthServer({
93
+ accessToken: unsignedJwt({
94
+ iss: 'http://127.0.0.1',
95
+ sub: 'user_123',
96
+ exp: 1893456000,
97
+ }),
98
+ })
99
+ try {
100
+ await writeIdpConfig(server.url)
101
+
102
+ const explicit = await runAuthLogin(
103
+ '--idp',
104
+ 'test',
105
+ '--name',
106
+ 'alice',
107
+ '--client-id',
108
+ 'client_override',
109
+ '--client-credentials',
110
+ '--client-secret-env',
111
+ 'TEST_CLIENT_SECRET',
112
+ '--raw',
113
+ )
114
+ const reused = await runAuthLogin(
115
+ '--idp',
116
+ 'test',
117
+ '--name',
118
+ 'alice',
119
+ '--client-credentials',
120
+ '--client-secret-env',
121
+ 'TEST_CLIENT_SECRET',
122
+ '--raw',
123
+ )
124
+
125
+ expect(explicit.exitCode).toBe(0)
126
+ expect(reused.exitCode).toBe(0)
127
+ expect(server.clientIds()).toEqual(['client_override', 'client_override'])
128
+ const session = JSON.parse(await readFile(join(tmp, 'idp-sessions', 'alice.json'), 'utf-8'))
129
+ expect(session.clientId).toBe('client_override')
130
+ } finally {
131
+ await server.stop()
132
+ }
133
+ })
89
134
  })
90
135
 
91
136
  async function writeIdpConfig(baseUrl: string): Promise<void> {
@@ -131,12 +176,19 @@ async function writeIdpConfig(baseUrl: string): Promise<void> {
131
176
  )
132
177
  }
133
178
 
134
- function oauthServer(args: { accessToken: string }): { url: string; stop: () => Promise<void> } {
179
+ function oauthServer(args: { accessToken: string }): {
180
+ url: string
181
+ clientIds: () => Array<string | null>
182
+ stop: () => Promise<void>
183
+ } {
184
+ const clientIds: Array<string | null> = []
135
185
  const server = Bun.serve({
136
186
  port: 0,
137
187
  fetch: async (request) => {
138
188
  const url = new URL(request.url)
139
189
  if (url.pathname !== '/token') return new Response('not found', { status: 404 })
190
+ const form = new URLSearchParams(await request.text())
191
+ clientIds.push(form.get('client_id'))
140
192
  return Response.json({
141
193
  access_token: args.accessToken,
142
194
  token_type: 'Bearer',
@@ -146,6 +198,7 @@ function oauthServer(args: { accessToken: string }): { url: string; stop: () =>
146
198
  })
147
199
  return {
148
200
  url: `http://${server.hostname}:${server.port}`,
201
+ clientIds: () => clientIds,
149
202
  stop: () => server.stop(true),
150
203
  }
151
204
  }
@@ -1,34 +1,47 @@
1
1
  import { Path } from '@astrale-os/sdk/graph/path'
2
+ import { bundle, defineSchema, func, method, nodeClass } from '@astrale-os/sdk/schema'
2
3
  import { describe, expect, test } from 'bun:test'
3
4
 
4
- import { describeCallableFromSchema } from '../call-describe'
5
+ import { describeCallableFromBundle } from '../call-describe'
5
6
 
6
- const schema = {
7
- classes: {
8
- Manager: {
9
- methods: {
10
- createInstance: {
11
- auth: 'authorized',
12
- description: 'Create one child Instance.',
13
- input: { type: 'object', required: ['operationId', 'slug'] },
14
- output: { mode: 'value' },
15
- },
16
- },
17
- },
7
+ const createInstance = method({
8
+ auth: 'authenticated',
9
+ description: 'Create one child Instance.',
10
+ input: {
11
+ type: 'object',
12
+ properties: { operationId: { type: 'string' }, slug: { type: 'string' } },
13
+ required: ['operationId', 'slug'],
14
+ additionalProperties: false,
18
15
  },
19
- functions: {
20
- journal: {
21
- description: 'Read the authorized Kernel journal.',
22
- input: { type: 'object' },
23
- },
16
+ output: { type: 'object', additionalProperties: true },
17
+ static: true,
18
+ })
19
+ const inspectInstance = method({
20
+ auth: 'authenticated',
21
+ description: 'Inspect one child Instance.',
22
+ input: { type: 'object', properties: {}, additionalProperties: false },
23
+ output: { type: 'object', additionalProperties: true },
24
+ })
25
+ const journal = func({
26
+ auth: 'authenticated',
27
+ description: 'Read the authorized Kernel journal.',
28
+ input: { type: 'object', properties: {}, additionalProperties: false },
29
+ output: { type: 'object', additionalProperties: true },
30
+ })
31
+ const source = defineSchema('host.astrale.ai', {
32
+ classes: {
33
+ Manager: nodeClass({ methods: { createInstance } }),
34
+ Instance: nodeClass({ methods: { inspectInstance } }),
24
35
  },
25
- }
36
+ functions: { journal },
37
+ })
38
+ const installed = bundle.create(source)
26
39
 
27
- describe('describeCallableFromSchema', () => {
28
- test('reads a static Class method from the Domain schema', () => {
29
- const described = describeCallableFromSchema(
40
+ describe('describeCallableFromBundle', () => {
41
+ test('reads a static Class method from the resolved Domain', () => {
42
+ const described = describeCallableFromBundle(
30
43
  Path.parse('/:host.astrale.ai:class.Manager:createInstance'),
31
- schema,
44
+ installed,
32
45
  )
33
46
  expect(described).toMatchObject({
34
47
  origin: 'host.astrale.ai',
@@ -36,30 +49,30 @@ describe('describeCallableFromSchema', () => {
36
49
  method: 'createInstance',
37
50
  dispatch: 'static',
38
51
  description: 'Create one child Instance.',
39
- auth: 'authorized',
52
+ auth: 'authenticated',
40
53
  })
41
54
  expect(described?.input).toMatchObject({ required: ['operationId', 'slug'] })
42
55
  })
43
56
 
44
- test('finds an instance method when the receiver Path is not a Class projection', () => {
57
+ test('finds one instance method when the receiver Path is not a Class projection', () => {
45
58
  expect(
46
- describeCallableFromSchema(
47
- Path.parse('/:host.astrale.ai:core.manager::createInstance'),
48
- schema,
59
+ describeCallableFromBundle(
60
+ Path.parse('/:host.astrale.ai:core.manager::inspectInstance'),
61
+ installed,
49
62
  ),
50
63
  ).toMatchObject({
51
64
  origin: 'host.astrale.ai',
52
- class: 'Manager',
53
- method: 'createInstance',
65
+ class: 'Instance',
66
+ method: 'inspectInstance',
54
67
  dispatch: 'instance',
55
68
  })
56
69
  })
57
70
 
58
- test('reads a standalone Function from the Domain schema', () => {
71
+ test('reads a standalone Function from the resolved Domain', () => {
59
72
  expect(
60
- describeCallableFromSchema(Path.parse('/:kernel.astrale.ai:function.journal'), schema),
73
+ describeCallableFromBundle(Path.parse('/:host.astrale.ai:function.journal'), installed),
61
74
  ).toMatchObject({
62
- origin: 'kernel.astrale.ai',
75
+ origin: 'host.astrale.ai',
63
76
  function: 'journal',
64
77
  description: 'Read the authorized Kernel journal.',
65
78
  })
@@ -14,7 +14,11 @@ describe('call command result lifetime', () => {
14
14
  }
15
15
 
16
16
  const sessionStream = Object.assign(stream(), { async cancel() {} })
17
- const materialized = await materializeCallResult({ kind: 'stream', stream: sessionStream })
17
+ const materialized = await materializeCallResult({
18
+ kind: 'stream',
19
+ invocation: { source: 'https://kernel.test' as never, id: 'test-stream' },
20
+ stream: sessionStream,
21
+ })
18
22
  sessionOpen = false
19
23
 
20
24
  expect(materialized).toEqual({ kind: 'stream', values: ['first', 'second'] })
@@ -1,5 +1,7 @@
1
+ import { defineSchema } from '@astrale-os/sdk/schema'
1
2
  import { afterEach, describe, expect, mock, test } from 'bun:test'
2
3
 
4
+ import { releaseFor } from '../../__tests__/fixtures/publication'
3
5
  import { installDirect } from '../domain/install'
4
6
 
5
7
  const GENERATED = '4a4c9a18-50f6-4d84-a7b7-2d83e3e45dc8'
@@ -7,6 +9,13 @@ const RETRY = '139137b5-af47-47ce-92b2-b64a2b0c63d7'
7
9
 
8
10
  const originalFetch = globalThis.fetch
9
11
 
12
+ const schema = defineSchema('crm.test', {})
13
+ const deployed = releaseFor(schema, 'https://crm.test').publication
14
+
15
+ function publicationResponse(): Response {
16
+ return Response.json(deployed)
17
+ }
18
+
10
19
  class ExitError extends Error {}
11
20
 
12
21
  afterEach(() => {
@@ -15,9 +24,7 @@ afterEach(() => {
15
24
 
16
25
  describe('direct domain install operation identity', () => {
17
26
  test('generates one operation before transport and exposes it for recovery', async () => {
18
- globalThis.fetch = mock(async () =>
19
- Response.json({ domainName: 'crm.test' }),
20
- ) as unknown as typeof fetch
27
+ globalThis.fetch = mock(async () => publicationResponse()) as unknown as typeof fetch
21
28
  const create = mock(() => GENERATED)
22
29
  const requests: unknown[] = []
23
30
  let recovery: unknown
@@ -54,9 +61,7 @@ describe('direct domain install operation identity', () => {
54
61
  })
55
62
 
56
63
  test('accepts an explicit operation only as the exact retry identity', async () => {
57
- globalThis.fetch = mock(async () =>
58
- Response.json({ domainName: 'crm.test' }),
59
- ) as unknown as typeof fetch
64
+ globalThis.fetch = mock(async () => publicationResponse()) as unknown as typeof fetch
60
65
  const create = mock(() => GENERATED)
61
66
  const accepted: string[] = []
62
67
  const requests: unknown[] = []
@@ -95,7 +100,7 @@ describe('direct domain install operation identity', () => {
95
100
  test('rejects a non-UUID operation before metadata or Kernel transport', async () => {
96
101
  const originalExit = process.exit
97
102
  const originalWrite = process.stderr.write
98
- const fetch = mock(async () => Response.json({ domainName: 'crm.test' }))
103
+ const fetch = mock(async () => publicationResponse())
99
104
  const run = mock(async () => undefined)
100
105
  globalThis.fetch = fetch as unknown as typeof globalThis.fetch
101
106
  process.exit = (() => {
@@ -1,4 +1,4 @@
1
- import { ResponseError } from '@astrale-os/kernel-client'
1
+ import { ResponseError } from '@astrale-os/sdk/client'
2
2
  import { afterEach, beforeEach, describe, expect, mock, test } from 'bun:test'
3
3
 
4
4
  import type { OwnedInstanceInfo } from '../../lib/admin-instance'
@@ -11,6 +11,10 @@ class ExitError extends Error {
11
11
 
12
12
  const calls: Array<{ path: string; params: unknown }> = []
13
13
  let inventory: OwnedInstanceInfo[] = []
14
+ const TEST_INVOCATION = {
15
+ source: 'https://kernel.test',
16
+ id: 'domain-install-owned',
17
+ } as ConstructorParameters<typeof ResponseError>[2]
14
18
 
15
19
  const hostCall = mock(async (call: { target: string; input: unknown }): Promise<unknown> => {
16
20
  const path = call.target
@@ -137,7 +141,7 @@ describe('admin domain install owner boundary', () => {
137
141
  createdAt: '2026-08-20T00:00:00.000Z',
138
142
  updatedAt: '2026-08-20T00:00:00.000Z',
139
143
  }
140
- const failure = new ResponseError(5001, 'Schema operation is not supported.', {
144
+ const failure = new ResponseError(5001, 'Schema operation is not supported.', TEST_INVOCATION, {
141
145
  code: 'SCHEMA_UPGRADE_INCOMPATIBLE',
142
146
  details: {
143
147
  phase: 'upgrade',