@astrale-os/cli 1.0.0-beta.2 → 1.0.0-beta.20

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 (282) hide show
  1. package/README.md +16 -1
  2. package/dist/astrale.js +51105 -58057
  3. package/dist/public/connect-core.js +2085 -30702
  4. package/dist/public/keys/index.js +884 -29476
  5. package/dist/public/paths/index.js +13101 -41452
  6. package/dist/types/admin/contract.d.ts +26 -0
  7. package/dist/types/admin/instance/client.d.ts +20 -0
  8. package/dist/types/admin/instance/index.d.ts +2 -0
  9. package/dist/types/admin/instance/model.d.ts +6 -10
  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 +25 -173
  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/output.d.ts +1 -1
  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 +109 -0
  38. package/dist/types/lib/validation.d.ts +4 -1
  39. package/package.json +15 -20
  40. package/src/__tests__/fixtures/publication.ts +20 -0
  41. package/src/admin/.spec/architecture.md +12 -5
  42. package/src/admin/__tests__/fixture.ts +25 -0
  43. package/src/admin/catalog/.spec/api.d.ts +1 -3
  44. package/src/admin/catalog/.spec/architecture.md +5 -4
  45. package/src/admin/catalog/__tests__/client.test.ts +143 -69
  46. package/src/admin/catalog/client.ts +40 -54
  47. package/src/admin/catalog/model.ts +3 -4
  48. package/src/admin/contract.ts +46 -0
  49. package/src/admin/graph/.spec/api.d.ts +4 -10
  50. package/src/admin/graph/nodes.ts +1 -1
  51. package/src/admin/instance/.spec/api.d.ts +5 -9
  52. package/src/admin/instance/.spec/architecture.md +7 -7
  53. package/src/admin/instance/__tests__/client.test.ts +157 -175
  54. package/src/admin/instance/client.ts +39 -128
  55. package/src/admin/instance/index.ts +1 -2
  56. package/src/admin/instance/model.ts +12 -18
  57. package/src/commands/__tests__/admin-instance.test.ts +11 -8
  58. package/src/commands/__tests__/call-describe.test.ts +46 -33
  59. package/src/commands/__tests__/call.test.ts +34 -0
  60. package/src/commands/__tests__/domain-install-operation.test.ts +7 -22
  61. package/src/commands/__tests__/domain-install-owned.test.ts +1 -1
  62. package/src/commands/__tests__/domain-list.test.ts +5 -25
  63. package/src/commands/__tests__/install-direct.test.ts +115 -8
  64. package/src/commands/__tests__/install-identity-override.test.ts +3 -14
  65. package/src/commands/__tests__/instance-list-rows.test.ts +1 -1
  66. package/src/commands/__tests__/instance-status.test.ts +139 -0
  67. package/src/commands/__tests__/introspect-parse.test.ts +80 -1
  68. package/src/commands/__tests__/logs.test.ts +250 -2
  69. package/src/commands/__tests__/read-commands.test.ts +62 -10
  70. package/src/commands/__tests__/token-ttl.test.ts +49 -4
  71. package/src/commands/__tests__/update.test.ts +25 -3
  72. package/src/commands/__tests__/view.test.ts +49 -0
  73. package/src/commands/call-describe.ts +60 -68
  74. package/src/commands/call.ts +71 -59
  75. package/src/commands/domain/install.ts +54 -35
  76. package/src/commands/domain/list.ts +1 -1
  77. package/src/commands/domain/publish.ts +14 -11
  78. package/src/commands/domain/uninstall.ts +2 -2
  79. package/src/commands/get.ts +14 -15
  80. package/src/commands/identity/__tests__/register.test.ts +3 -3
  81. package/src/commands/identity/register.ts +5 -4
  82. package/src/commands/instance/create.ts +8 -16
  83. package/src/commands/instance/delete.ts +12 -9
  84. package/src/commands/instance/list.ts +2 -2
  85. package/src/commands/instance/status.ts +66 -20
  86. package/src/commands/instance/use.ts +16 -12
  87. package/src/commands/introspect.ts +48 -35
  88. package/src/commands/logs.ts +123 -33
  89. package/src/commands/mutate.ts +6 -5
  90. package/src/commands/query.ts +35 -32
  91. package/src/commands/token.ts +42 -27
  92. package/src/commands/ui/__tests__/commands.test.ts +152 -0
  93. package/src/commands/ui/add.ts +51 -0
  94. package/src/commands/ui/doctor.ts +13 -0
  95. package/src/commands/ui/init.ts +38 -0
  96. package/src/commands/ui/list.ts +26 -0
  97. package/src/commands/ui/preset-apply.ts +19 -0
  98. package/src/commands/ui/preset-list.ts +12 -0
  99. package/src/commands/ui/shared.ts +25 -0
  100. package/src/commands/update.ts +31 -11
  101. package/src/commands/view.ts +82 -37
  102. package/src/connection/.spec/api.d.ts +7 -8
  103. package/src/connection/.spec/architecture.md +5 -4
  104. package/src/connection/.spec/flows/session.ts +2 -2
  105. package/src/connection/.spec/laws/connection.ts +6 -2
  106. package/src/connection/.spec/layout.ts +1 -0
  107. package/src/connection/__tests__/credential.test.ts +20 -1
  108. package/src/connection/__tests__/errors.test.ts +27 -20
  109. package/src/connection/__tests__/exchange.test.ts +30 -1
  110. package/src/connection/__tests__/failure-fixtures.ts +39 -0
  111. package/src/connection/__tests__/failure-safety.test.ts +75 -0
  112. package/src/connection/__tests__/self.test.ts +31 -11
  113. package/src/connection/__tests__/session.test.ts +3 -3
  114. package/src/connection/__tests__/target.test.ts +1 -0
  115. package/src/connection/auth.ts +13 -0
  116. package/src/connection/call.ts +1 -1
  117. package/src/connection/credential.ts +2 -2
  118. package/src/connection/errors.ts +1 -355
  119. package/src/connection/exchange.ts +47 -23
  120. package/src/connection/failure/classify.ts +110 -0
  121. package/src/connection/failure/index.ts +18 -0
  122. package/src/connection/failure/model.ts +28 -0
  123. package/src/connection/failure/render.ts +116 -0
  124. package/src/connection/reasons.ts +16 -0
  125. package/src/connection/self.ts +39 -21
  126. package/src/connection/session.ts +19 -8
  127. package/src/errors.ts +3 -2
  128. package/src/graph/.spec/api.d.ts +12 -5
  129. package/src/graph/.spec/layout.ts +9 -1
  130. package/src/graph/__tests__/mutation.test.ts +2 -2
  131. package/src/graph/__tests__/query.test.ts +25 -6
  132. package/src/graph/class.ts +30 -0
  133. package/src/graph/index.ts +1 -0
  134. package/src/graph/query.ts +21 -32
  135. package/src/identity/.spec/api.d.ts +2 -2
  136. package/src/identity/__tests__/registration.test.ts +1 -1
  137. package/src/identity/registration.ts +3 -3
  138. package/src/keys/__tests__/keys.test.ts +4 -0
  139. package/src/keys/credential.ts +1 -0
  140. package/src/lib/__tests__/admin-target.test.ts +12 -0
  141. package/src/lib/__tests__/binary.test.ts +16 -1
  142. package/src/lib/__tests__/command-dx.test.ts +1 -1
  143. package/src/lib/__tests__/domain-publication.test.ts +10 -16
  144. package/src/lib/__tests__/idp.test.ts +34 -4
  145. package/src/lib/__tests__/instance-candidates.test.ts +1 -3
  146. package/src/lib/__tests__/instance-target.test.ts +7 -39
  147. package/src/lib/__tests__/log-errors.test.ts +64 -0
  148. package/src/lib/__tests__/studio-server-deps.test.ts +1 -1
  149. package/src/lib/__tests__/update.test.ts +264 -11
  150. package/src/lib/__tests__/view-assets.test.ts +14 -0
  151. package/src/lib/__tests__/view-external-open-intent.test.ts +150 -0
  152. package/src/lib/__tests__/view-external-open-origins.test.ts +27 -0
  153. package/src/lib/__tests__/view-open-intent.test.ts +0 -2
  154. package/src/lib/__tests__/view-server.test.ts +26 -3
  155. package/src/lib/__tests__/view-session.test.ts +2 -1
  156. package/src/lib/admin-instance.ts +14 -8
  157. package/src/lib/admin-target.ts +21 -2
  158. package/src/lib/binary.ts +22 -5
  159. package/src/lib/command-dx.ts +9 -9
  160. package/src/lib/domain-publication.ts +9 -7
  161. package/src/lib/failure-debug.ts +26 -0
  162. package/src/lib/idp.ts +34 -3
  163. package/src/lib/instance-candidates.ts +2 -2
  164. package/src/lib/instance-target.ts +3 -14
  165. package/src/lib/instance.ts +16 -11
  166. package/src/lib/log.ts +16 -9
  167. package/src/lib/output.ts +1 -1
  168. package/src/lib/proc.ts +7 -2
  169. package/src/lib/provision-instance.ts +34 -57
  170. package/src/lib/update.ts +298 -45
  171. package/src/lib/validation.ts +2 -2
  172. package/src/lib/view/assets.ts +11 -2
  173. package/src/lib/view/external-open-intent.ts +41 -0
  174. package/src/lib/view/external-open-origins.ts +37 -0
  175. package/src/lib/view/host-capabilities.ts +17 -0
  176. package/src/lib/view/resolve.ts +1 -1
  177. package/src/lib/view/server.ts +18 -3
  178. package/src/lib/view/session.ts +2 -0
  179. package/src/program/.spec/api.d.ts +1 -0
  180. package/src/program/__tests__/program.test.ts +163 -6
  181. package/src/program/argv.ts +14 -0
  182. package/src/program/build.ts +26 -4
  183. package/src/program/command.ts +1 -0
  184. package/src/program/index.ts +1 -0
  185. package/src/program/registry.ts +2 -1
  186. package/src/setup/__tests__/instance-step.test.ts +27 -10
  187. package/src/setup/steps/instance.ts +22 -15
  188. package/src/telemetry/__tests__/recorder.test.ts +1 -0
  189. package/src/telemetry/recorder.ts +9 -1
  190. package/src/ui/.spec/api.d.ts +14 -0
  191. package/src/ui/.spec/architecture.md +10 -0
  192. package/src/ui/.spec/laws.ts +36 -0
  193. package/src/ui/.spec/layout.ts +16 -0
  194. package/src/ui/__tests__/ui.test.ts +981 -0
  195. package/src/ui/index.ts +13 -0
  196. package/src/ui/lock.ts +87 -0
  197. package/src/ui/model.ts +83 -0
  198. package/src/ui/operations.ts +710 -0
  199. package/src/ui/project.ts +219 -0
  200. package/src/ui/release.ts +321 -0
  201. package/src/ui/runner.ts +18 -0
  202. package/studio/client/dist/assets/elk-api-DjmKsHXc.js +1 -0
  203. package/studio/client/dist/assets/index-7YHdeCxp.js +81 -0
  204. package/studio/client/dist/assets/index-C0FAnwNw.css +2 -0
  205. package/studio/client/dist/assets/rolldown-runtime-Dd_uD5pT.js +1 -0
  206. package/studio/client/dist/assets/schema-studio-BilUNb6Z.css +1 -0
  207. package/studio/client/dist/assets/schema-studio-CABpTfvH.js +8 -0
  208. package/studio/client/dist/index.html +3 -2
  209. package/studio/package.json +7 -13
  210. package/studio/server/agent/ask.test.ts +1 -1
  211. package/studio/server/agent/bridge/grant.test.ts +1 -1
  212. package/studio/server/agent/bridge/routes.test.ts +1 -1
  213. package/studio/server/agent/harness/claude/adapter.test.ts +15 -5
  214. package/studio/server/agent/harness/gateway/token.test.ts +1 -1
  215. package/studio/server/agent/harness/gateway/token.ts +3 -3
  216. package/studio/server/agent/prompts/anchors.test.ts +32 -36
  217. package/studio/server/agent/prompts/anchors.ts +20 -49
  218. package/studio/server/agent/prompts/system.test.ts +3 -2
  219. package/studio/server/agent/prompts/system.ts +3 -3
  220. package/studio/server/agent/routes.test.ts +1 -1
  221. package/studio/server/agent/run/coordinator.test.ts +1 -1
  222. package/studio/server/api/canvas.ts +0 -1
  223. package/studio/server/api-agent-loadout.test.ts +1 -1
  224. package/studio/server/api.test.ts +1 -1
  225. package/studio/server/cache.test.ts +23 -12
  226. package/studio/server/cache.ts +12 -15
  227. package/studio/server/domain.test.ts +50 -68
  228. package/studio/server/domain.ts +82 -63
  229. package/studio/server/handoff/copy.ts +1 -1
  230. package/studio/server/index.ts +1 -1
  231. package/studio/server/instances/probe.test.ts +1 -1
  232. package/studio/server/introspect/anatomy/views/routes.ts +46 -37
  233. package/studio/server/introspect/anatomy/views.ts +9 -14
  234. package/studio/server/introspect/anatomy-extras.test.ts +26 -42
  235. package/studio/server/introspect/anatomy.test.ts +22 -14
  236. package/studio/server/introspect/anatomy.ts +6 -9
  237. package/studio/server/introspect/bundle.ts +0 -3
  238. package/studio/server/introspect/canonical-schema.test.ts +94 -389
  239. package/studio/server/introspect/canonical-schema.ts +227 -457
  240. package/studio/server/introspect/core-extractor.ts +18 -127
  241. package/studio/server/introspect/core.ts +1 -2
  242. package/studio/server/introspect/diff.test.ts +11 -7
  243. package/studio/server/introspect/diff.ts +18 -55
  244. package/studio/server/introspect/extractor.ts +22 -117
  245. package/studio/server/introspect/overlay-tsmorph.test.ts +119 -240
  246. package/studio/server/introspect/overlay.test.ts +26 -54
  247. package/studio/server/introspect/overlay.ts +10 -37
  248. package/studio/server/introspect/revision.test.ts +1 -1
  249. package/studio/server/introspect/revision.ts +1 -1
  250. package/studio/server/introspect/runtime.test.ts +65 -183
  251. package/studio/server/introspect/runtime.ts +8 -25
  252. package/studio/server/introspect/schema-ir-json.test.ts +70 -0
  253. package/studio/server/introspect/schema-ir-json.ts +38 -68
  254. package/studio/server/introspect/schema-refs.test.ts +43 -88
  255. package/studio/server/introspect/schema-refs.ts +14 -49
  256. package/studio/server/introspect/source-overlay/handlers.ts +116 -636
  257. package/studio/server/introspect/source-overlay/kernel-calls.ts +0 -3
  258. package/studio/server/introspect/source-overlay/spans.ts +15 -45
  259. package/studio/server/state/baseline.test.ts +14 -9
  260. package/studio/server/state/baseline.ts +3 -5
  261. package/studio/server/state/visibility.ts +1 -5
  262. package/studio/server/views/model.ts +3 -3
  263. package/studio/server/views/target.test.ts +17 -74
  264. package/studio/server/views/target.ts +24 -50
  265. package/studio/server/watch.test.ts +11 -11
  266. package/studio/server/watch.ts +8 -4
  267. package/studio/server/workspace-watch.ts +2 -2
  268. package/studio/shared/contracts/schema.ts +38 -107
  269. package/studio/shared/contracts/surface.test.ts +13 -15
  270. package/studio/shared/contracts/workspace.ts +2 -11
  271. package/studio/shared/schema/identity.test.ts +20 -44
  272. package/studio/shared/schema/identity.ts +9 -23
  273. package/viewer/dist/main.js +30 -51
  274. package/.check-workspace.cjs +0 -40
  275. package/src/admin/binding.ts +0 -168
  276. package/src/commands/__tests__/instance-create-hosts.test.ts +0 -29
  277. package/studio/client/dist/assets/elk-api-lwhFo7vB.js +0 -1
  278. package/studio/client/dist/assets/index-B-c-smo9.js +0 -8
  279. package/studio/client/dist/assets/index-BckHuAWk.js +0 -81
  280. package/studio/client/dist/assets/index-C4aNQ6dz.css +0 -1
  281. package/studio/client/dist/assets/index-ChOr3yP0.css +0 -1
  282. package/studio/server/introspect/anatomy/views/legacy.ts +0 -232
@@ -1,13 +1,9 @@
1
- import type { ClientSession } from '@astrale-os/kernel-client/session'
2
- import type { Node } from '@astrale-os/sdk/graph/node'
1
+ import type { ClientSession } from '@astrale-os/sdk/client/session'
3
2
 
4
3
  import { Path } from '@astrale-os/sdk/graph/path'
5
- import { Query } from '@astrale-os/sdk/query'
6
4
 
7
- import { bindAdmin, type AdminBinding } from '../binding'
8
- import { readAllNodes, type AdminGraphApi } from '../graph'
5
+ import { AdminContract, callAdminMethod } from '../contract'
9
6
  import {
10
- AdminHostNotFoundError,
11
7
  AdminInstanceNotFoundError,
12
8
  findOwnedInstance,
13
9
  type DomainInstallReceipt,
@@ -16,62 +12,38 @@ import {
16
12
  type OwnedInstanceInfo,
17
13
  } from './model'
18
14
 
19
- const ADMIN_ORIGIN = 'admin.astrale.ai'
20
- const PAGE_SIZE = 256
21
- const MAXIMUM_INSTANCES = 10_000
22
- const MAXIMUM_PAGES = Math.ceil(MAXIMUM_INSTANCES / PAGE_SIZE) + 1
23
-
24
- const HostRef = Object.freeze({ origin: ADMIN_ORIGIN, kind: 'class' as const, name: 'Host' })
25
- const fleetReservesAdminHost = Object.freeze({
26
- origin: ADMIN_ORIGIN,
27
- kind: 'class' as const,
28
- name: 'fleet_reserves_admin_host',
29
- })
30
-
31
15
  export interface AdminInstanceContext {
32
16
  readonly session: ClientSession
33
- readonly graph: AdminGraphApi
34
17
  }
35
18
 
36
19
  export interface AdminInstanceApi {
37
20
  list(): Promise<OwnedInstanceInfo[]>
38
- create(slug: string, hostId?: string): Promise<InstanceInfo>
21
+ create(slug: string): Promise<InstanceInfo>
39
22
  status(identifier: string): Promise<InstanceInfo>
40
23
  delete(identifier: string): Promise<InstanceInfo>
41
24
  installDomain(identifier: string, domain: string): Promise<DomainInstallReceipt>
42
25
  }
43
26
 
44
27
  export interface AdminInstanceDependencies {
45
- readonly bind?: (session: ClientSession) => Promise<AdminBinding>
46
28
  readonly operationId?: (kind: 'create' | 'status' | 'delete' | 'install-domain') => string
47
29
  }
48
30
 
49
31
  /**
50
- * Bind one discovered Admin root revision and expose only the public Instance
51
- * product journey. No Host lifecycle method is present on this capability.
32
+ * Connect the public Instance journey through stable Admin call paths. Routine
33
+ * operations perform no schema discovery. No Host lifecycle method is present.
52
34
  */
53
35
  export async function connectAdminInstances(
54
36
  context: AdminInstanceContext,
55
37
  dependencies: AdminInstanceDependencies = {},
56
38
  ): Promise<AdminInstanceApi> {
57
- const binding = await (dependencies.bind ?? bindAdmin)(context.session)
58
- if (binding.$.publication?.origin !== ADMIN_ORIGIN || binding.$.origin !== ADMIN_ORIGIN) {
59
- throw new TypeError('Configured Admin target does not serve the Admin Domain.')
60
- }
61
-
62
- const Instance = binding.$.class('Instance')
63
- const Host = binding.$.class('Host')
64
- const Fleet = binding.$.class('Fleet')
65
- const fleet = binding.$.core.nodes.fleet?.path
66
- if (fleet === undefined) throw new TypeError('Admin Domain has no singleton Fleet receiver.')
67
-
68
39
  const operationId = dependencies.operationId ?? defaultOperationId
69
40
 
70
41
  const list = async (): Promise<OwnedInstanceInfo[]> => {
71
- const result: unknown = await binding.$.invoke(
72
- Fleet.$.method('listInstances') as never,
73
- fleet,
74
- {} as never,
42
+ const result: unknown = await callAdminMethod(
43
+ context.session,
44
+ AdminContract.fleet,
45
+ 'listInstances',
46
+ {},
75
47
  )
76
48
  if (!Array.isArray(result)) throw new TypeError('Admin Instance inventory is invalid.')
77
49
  return result.map((value) => instanceFromSummary(value) as OwnedInstanceInfo)
@@ -88,121 +60,55 @@ export async function connectAdminInstances(
88
60
  identifier: string,
89
61
  ): Promise<InstanceInfo> => {
90
62
  const instance = await requireInstance(identifier)
91
- const output = await binding.$.invoke(
92
- Instance.$.method(method) as never,
93
- Path.parse(instance.id),
94
- { operationId: operationId(method) } as never,
95
- )
63
+ const output = await callAdminMethod(context.session, Path.parse(instance.id), method, {
64
+ operationId: operationId(method),
65
+ })
96
66
  return instanceFromSummary(output)
97
67
  }
98
68
 
99
69
  return Object.freeze({
100
70
  list,
101
- async create(slug: string, hostId?: string) {
71
+ async create(slug: string) {
102
72
  const input = Object.freeze({
103
73
  operationId: operationId('create'),
104
74
  slug,
105
75
  })
106
- if (hostId !== undefined) {
107
- const selected = await hostInventory(context.graph, binding, Host, fleet)
108
- const host = selected.hosts.find(
109
- (item) => item.id === hostId && item.state === 'ready' && item.path !== selected.reserved,
110
- )
111
- if (host === undefined) throw new AdminHostNotFoundError(hostId)
112
- return instanceFromSummary(
113
- await binding.$.invoke(
114
- Host.$.method('createInstance') as never,
115
- Path.parse(host.path),
116
- input as never,
117
- ),
118
- )
119
- }
120
-
121
- // Default placement belongs to Admin. Avoid a redundant Host graph read:
122
- // it requires broader graph authority than this Fleet operation and can
123
- // deny an otherwise authorized caller before the server selects a Host.
124
76
  return instanceFromSummary(
125
- await binding.$.invoke(Fleet.$.method('createInstance') as never, fleet, input as never),
77
+ await callAdminMethod(context.session, AdminContract.fleet, 'createInstance', input),
126
78
  )
127
79
  },
128
80
  status: (identifier: string) => invokeInstance('status', identifier),
129
81
  delete: (identifier: string) => invokeInstance('delete', identifier),
130
82
  async installDomain(identifier: string, domain: string): Promise<DomainInstallReceipt> {
131
83
  const instance = await requireInstance(identifier)
132
- const output = await binding.$.invoke(
133
- Instance.$.method('installDomain') as never,
84
+ const output = await callAdminMethod(
85
+ context.session,
134
86
  Path.parse(instance.id),
87
+ 'installDomain',
135
88
  {
136
89
  operationId: operationId('install-domain'),
137
90
  domain: Path.parse(domain).raw,
138
- } as never,
91
+ },
139
92
  )
140
93
  return domainInstallReceipt(output)
141
94
  },
142
95
  })
143
96
  }
144
97
 
145
- interface HostInventoryItem {
146
- readonly id: string
147
- readonly nodeId: string
148
- readonly path: string
149
- readonly state: string
150
- }
151
-
152
- async function hostInventory(
153
- graph: AdminGraphApi,
154
- binding: AdminBinding,
155
- Host: ReturnType<AdminBinding['$']['class']>,
156
- fleet: Path,
157
- ): Promise<{ readonly hosts: readonly HostInventoryItem[]; readonly reserved?: string }> {
158
- const [nodes, reservedPage] = await Promise.all([
159
- readAllNodes(
160
- graph,
161
- Query.from({ kind: 'node', definitions: [HostRef] }).select({
162
- kind: 'nodes',
163
- projection: { kind: 'value' },
164
- }),
165
- {
166
- label: 'Admin Host inventory',
167
- maximum: MAXIMUM_INSTANCES,
168
- maximumPages: MAXIMUM_PAGES,
169
- },
170
- ),
171
- graph.neighbors(fleet, fleetReservesAdminHost, {
172
- direction: 'outgoing',
173
- page: { size: 1 },
174
- }),
175
- ])
176
- const reserved = reservedPage.first === null ? undefined : Path.id(reservedPage.first.id).raw
177
- return Object.freeze({
178
- hosts: Object.freeze(
179
- nodes.map((node) =>
180
- Object.freeze({
181
- id: requiredStringProperty(Host, node, 'id'),
182
- nodeId: String(node.id),
183
- path: Path.id(node.id).raw,
184
- state: requiredStringProperty(Host, node, 'state'),
185
- }),
186
- ),
187
- ),
188
- ...(reserved === undefined ? {} : { reserved }),
189
- })
190
- }
191
-
192
- type DynamicDefinition = ReturnType<AdminBinding['$']['class']>
193
-
194
98
  function instanceFromSummary(input: unknown): InstanceInfo {
195
99
  const value = record(input, 'Admin Instance summary')
196
100
  const failure = value.failure === undefined ? undefined : record(value.failure, 'Admin failure')
197
101
  return Object.freeze({
198
- id: requiredString(value.id, 'Admin Instance id'),
102
+ id: requiredNodePath(value.id, 'Admin Instance id'),
199
103
  slug: requiredString(value.slug, 'Admin Instance slug'),
200
104
  url: optionalStringValue(value.url) ?? '',
201
- state: instanceState(value.state),
202
105
  ...(value.hostId === undefined
203
106
  ? {}
204
- : { hostId: requiredString(value.hostId, 'Admin Host id') }),
205
- ...(value.region === undefined ? {} : { region: requiredString(value.region, 'Admin region') }),
107
+ : { hostId: requiredNodePath(value.hostId, 'Admin Host id') }),
108
+ ...(value.region === undefined
109
+ ? {}
110
+ : { region: requiredString(value.region, 'Admin Host region') }),
111
+ state: instanceState(value.state),
206
112
  ...(value.phase === undefined
207
113
  ? {}
208
114
  : { phase: requiredString(value.phase, 'Admin Instance phase') }),
@@ -212,6 +118,9 @@ function instanceFromSummary(input: unknown): InstanceInfo {
212
118
  ...(value.createdAt === undefined
213
119
  ? {}
214
120
  : { createdAt: requiredString(value.createdAt, 'Admin Instance creation time') }),
121
+ ...(value.updatedAt === undefined
122
+ ? {}
123
+ : { updatedAt: requiredString(value.updatedAt, 'Admin Instance update time') }),
215
124
  ...(value.organizationId === undefined
216
125
  ? {}
217
126
  : { organizationId: requiredString(value.organizationId, 'Admin organization id') }),
@@ -223,8 +132,8 @@ function domainInstallReceipt(input: unknown): DomainInstallReceipt {
223
132
  const failure = value.failure === undefined ? undefined : record(value.failure, 'Admin failure')
224
133
  if (typeof value.ok !== 'boolean') throw new TypeError('Admin Domain install outcome is invalid.')
225
134
  return Object.freeze({
226
- domain: requiredString(value.domain, 'Admin Domain reference'),
227
- instance: requiredString(value.instance, 'Admin Instance reference'),
135
+ domain: requiredNodePath(value.domain, 'Admin Domain reference'),
136
+ instance: requiredNodePath(value.instance, 'Admin Instance reference'),
228
137
  origin: requiredString(value.origin, 'Installed Domain origin'),
229
138
  ok: value.ok,
230
139
  ...(value.installedRevision === undefined
@@ -238,13 +147,6 @@ function domainInstallReceipt(input: unknown): DomainInstallReceipt {
238
147
  })
239
148
  }
240
149
 
241
- function requiredStringProperty(definition: DynamicDefinition, node: Node, name: string): string {
242
- return requiredString(
243
- node.props[definition.$.property(name).key],
244
- `Admin ${definition.name}.${name}`,
245
- )
246
- }
247
-
248
150
  function instanceState(input: unknown): InstanceState {
249
151
  if (
250
152
  input === 'provisioning' ||
@@ -263,6 +165,15 @@ function requiredString(input: unknown, label: string): string {
263
165
  return input
264
166
  }
265
167
 
168
+ function requiredNodePath(input: unknown, label: string): string {
169
+ const value = requiredString(input, label)
170
+ try {
171
+ return Path.parse(value).raw
172
+ } catch {
173
+ throw new TypeError(`${label} is invalid.`)
174
+ }
175
+ }
176
+
266
177
  function optionalStringValue(input: unknown): string | undefined {
267
178
  if (input === undefined) return undefined
268
179
  return requiredString(input, 'Admin string value')
@@ -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,
@@ -7,10 +7,11 @@ export interface InstanceInfo {
7
7
  readonly url: string
8
8
  readonly hostId?: string
9
9
  readonly region?: string
10
- readonly state?: InstanceState
10
+ readonly state: InstanceState
11
11
  readonly phase?: string
12
12
  readonly error?: string | null
13
13
  readonly createdAt?: string
14
+ readonly updatedAt?: string
14
15
  readonly organizationId?: string
15
16
  }
16
17
 
@@ -28,19 +29,13 @@ export interface DomainInstallReceipt {
28
29
  readonly error?: string
29
30
  }
30
31
 
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
-
32
+ export class AdminInstanceNotFoundError extends AstraleError {
42
33
  constructor(readonly identifier: string) {
43
- super(`No caller-usable Admin Host matches ${JSON.stringify(identifier)}.`)
34
+ super(
35
+ 'INSTANCE_NOT_FOUND',
36
+ `No owned Admin Instance matches ${JSON.stringify(identifier)}.`,
37
+ 'Run `astrale instance list` to see your instances.',
38
+ )
44
39
  }
45
40
  }
46
41
 
@@ -57,9 +52,8 @@ export function findOwnedInstance(
57
52
  )
58
53
  }
59
54
 
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(' · ')
55
+ /** Human-readable non-ready lifecycle state for one Instance. */
56
+ export function formatInstanceState(info: InstanceInfo): string {
57
+ return info.state === 'ready' ? '' : `${info.state}${info.phase ? ` (${info.phase})` : ''}`
65
58
  }
59
+ 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
  })
@@ -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
  })
@@ -26,4 +26,38 @@ describe('call command result lifetime', () => {
26
26
  if (materialized.kind !== 'stream') throw new Error('expected a materialized stream')
27
27
  expect(Object.isFrozen(materialized.values)).toBe(true)
28
28
  })
29
+
30
+ test('drains streaming binary bytes before the command session closes', async () => {
31
+ let sessionOpen = true
32
+ async function* body() {
33
+ for (const chunk of [new Uint8Array([1, 2]), new Uint8Array([3])]) {
34
+ if (!sessionOpen) throw new Error('session closed before binary consumption')
35
+ yield chunk
36
+ }
37
+ }
38
+
39
+ const materialized = await materializeCallResult({
40
+ kind: 'binary',
41
+ invocation: { source: 'https://kernel.test' as never, id: 'test-binary' },
42
+ value: {
43
+ body: body(),
44
+ mediaType: 'application/octet-stream',
45
+ status: 206,
46
+ headers: { 'content-range': 'bytes 0-2/3' },
47
+ },
48
+ })
49
+ sessionOpen = false
50
+
51
+ expect(materialized).toMatchObject({
52
+ kind: 'binary',
53
+ value: {
54
+ mediaType: 'application/octet-stream',
55
+ status: 206,
56
+ headers: { 'content-range': 'bytes 0-2/3' },
57
+ },
58
+ })
59
+ if (materialized.kind !== 'binary') throw new Error('expected materialized binary')
60
+ expect([...materialized.value.body]).toEqual([1, 2, 3])
61
+ expect(Object.isFrozen(materialized.value)).toBe(true)
62
+ })
29
63
  })
@@ -1,9 +1,7 @@
1
- import { defineDomain } from '@astrale-os/sdk'
2
- import { issuer } from '@astrale-os/sdk/auth'
3
- import { createDeployment } from '@astrale-os/sdk/deployment'
4
- import { defineSchema } from '@astrale-os/sdk/schema/v1'
1
+ import { defineSchema } from '@astrale-os/sdk/schema'
5
2
  import { afterEach, describe, expect, mock, test } from 'bun:test'
6
3
 
4
+ import { releaseFor } from '../../__tests__/fixtures/publication'
7
5
  import { installDirect } from '../domain/install'
8
6
 
9
7
  const GENERATED = '4a4c9a18-50f6-4d84-a7b7-2d83e3e45dc8'
@@ -12,16 +10,7 @@ const RETRY = '139137b5-af47-47ce-92b2-b64a2b0c63d7'
12
10
  const originalFetch = globalThis.fetch
13
11
 
14
12
  const schema = defineSchema('crm.test', {})
15
- const definition = defineDomain({
16
- schema,
17
- handlers: { functions: {}, classes: {}, interfaces: {} },
18
- })
19
- const deployed = createDeployment({
20
- definition,
21
- issuer: issuer.accept('https://crm.test'),
22
- bundleHref: 'https://crm.test/domain.bundle.json',
23
- bindings: { callables: [] },
24
- }).publication
13
+ const deployed = releaseFor(schema, 'https://crm.test').publication
25
14
 
26
15
  function publicationResponse(): Response {
27
16
  return Response.json(deployed)
@@ -49,9 +38,7 @@ describe('direct domain install operation identity', () => {
49
38
  recovery = input.recovery
50
39
  requests.push(
51
40
  await input.fn({
52
- session: {
53
- call: async (call: { input: unknown }) => call.input,
54
- },
41
+ session: { schema: { install: async (input: unknown) => input } },
55
42
  } as never),
56
43
  )
57
44
  },
@@ -62,7 +49,7 @@ describe('direct domain install operation identity', () => {
62
49
  expect(requests).toEqual([
63
50
  {
64
51
  operation: GENERATED,
65
- domains: [{ source: { kind: 'remote', url: 'https://crm.test' } }],
52
+ domains: [{ publication: { url: 'https://crm.test' } }],
66
53
  },
67
54
  ])
68
55
  expect(recovery).toEqual({
@@ -89,9 +76,7 @@ describe('direct domain install operation identity', () => {
89
76
  runKernelCommand: async (input) => {
90
77
  requests.push(
91
78
  await input.fn({
92
- session: {
93
- call: async (call: { input: unknown }) => call.input,
94
- },
79
+ session: { schema: { install: async (input: unknown) => input } },
95
80
  } as never),
96
81
  )
97
82
  },
@@ -103,7 +88,7 @@ describe('direct domain install operation identity', () => {
103
88
  expect(requests).toEqual([
104
89
  {
105
90
  operation: RETRY,
106
- domains: [{ source: { kind: 'remote', url: 'https://crm.test' } }],
91
+ domains: [{ publication: { url: 'https://crm.test' } }],
107
92
  },
108
93
  ])
109
94
  })
@@ -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'
@@ -1,11 +1,9 @@
1
- import { defineDomain } from '@astrale-os/sdk'
2
- import { issuer } from '@astrale-os/sdk/auth'
3
- import { createDeployment } from '@astrale-os/sdk/deployment'
4
- import { defineSchema } from '@astrale-os/sdk/schema/v1'
1
+ import { defineSchema, schema as schemaApi } from '@astrale-os/sdk/schema'
5
2
  import { afterAll, describe, expect, test } from 'bun:test'
6
3
 
7
4
  import type { DomainInfo } from '../../lib/admin-domain'
8
5
 
6
+ import { releaseFor } from '../../__tests__/fixtures/publication'
9
7
  import { byDefaultThenName, domainProjection, probe, type DomainRow } from '../domain/list'
10
8
 
11
9
  const strip = (s: string): string => s.replace(/\[[0-9;]*m/g, '')
@@ -85,16 +83,7 @@ describe('domain list — canonical Publication check', () => {
85
83
 
86
84
  test('reports the admitted schema revision in machine data', async () => {
87
85
  const schema = defineSchema('catalog-check.example.dev', {})
88
- const definition = defineDomain({
89
- schema,
90
- handlers: { functions: {}, classes: {}, interfaces: {} },
91
- })
92
- const deployed = createDeployment({
93
- definition,
94
- issuer: issuer.accept('https://catalog-check.example.dev'),
95
- bundleHref: 'https://catalog-check.example.dev/domain.bundle.json',
96
- bindings: { callables: [] },
97
- }).publication
86
+ const deployed = releaseFor(schema, 'https://catalog-check.example.dev').publication
98
87
  const server = Bun.serve({
99
88
  port: 0,
100
89
  fetch(request) {
@@ -114,23 +103,14 @@ describe('domain list — canonical Publication check', () => {
114
103
  ),
115
104
  ).resolves.toMatchObject({
116
105
  reachable: true,
117
- schemaRevision: definition.domain.$.revision,
106
+ schemaRevision: schemaApi.revision(schema),
118
107
  checkError: null,
119
108
  })
120
109
  })
121
110
 
122
111
  test('rejects a Publication whose origin differs from the catalog entry', async () => {
123
112
  const schema = defineSchema('deployed.example.dev', {})
124
- const definition = defineDomain({
125
- schema,
126
- handlers: { functions: {}, classes: {}, interfaces: {} },
127
- })
128
- const deployed = createDeployment({
129
- definition,
130
- issuer: issuer.accept('https://deployed.example.dev'),
131
- bundleHref: 'https://deployed.example.dev/domain.bundle.json',
132
- bindings: { callables: [] },
133
- }).publication
113
+ const deployed = releaseFor(schema, 'https://deployed.example.dev').publication
134
114
  const server = Bun.serve({
135
115
  port: 0,
136
116
  fetch: () => Response.json(deployed),