@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
@@ -33,10 +33,9 @@ export interface InstallDomainResult {
33
33
  readonly error?: string | null
34
34
  }
35
35
 
36
- export class AdminDomainNotFoundError extends Error {
37
- readonly name = 'NotFoundError'
38
-
36
+ export class AdminDomainNotFoundError extends AstraleError {
39
37
  constructor(readonly identifier: string) {
40
- super(`No visible Admin Domain matches ${JSON.stringify(identifier)}.`)
38
+ super('DOMAIN_NOT_FOUND', `No visible Admin Domain matches ${JSON.stringify(identifier)}.`)
41
39
  }
42
40
  }
41
+ import { AstraleError } from '../../errors'
@@ -0,0 +1,46 @@
1
+ import type { Input } from '@astrale-os/sdk/client'
2
+ import type { ClientSession } from '@astrale-os/sdk/client/session'
3
+ import type { ClassRef } from '@astrale-os/sdk/schema'
4
+
5
+ import { call } from '@astrale-os/sdk/client'
6
+ import { Path } from '@astrale-os/sdk/graph/path'
7
+ import { K, PropertyKey } from '@astrale-os/sdk/schema'
8
+
9
+ const origin = 'admin.astrale.ai'
10
+
11
+ function classRef(name: string): ClassRef {
12
+ return Object.freeze({ origin, kind: 'class', name }) as ClassRef
13
+ }
14
+
15
+ const Domain = classRef('Domain')
16
+
17
+ export const AdminContract = Object.freeze({
18
+ origin,
19
+ fleet: Path.parse('/:admin.astrale.ai:core.fleet'),
20
+ classes: Object.freeze({
21
+ Domain,
22
+ }),
23
+ edges: Object.freeze({
24
+ fleetInstallsDomainByDefault: classRef('fleet_installs_domain_by_default'),
25
+ }),
26
+ properties: Object.freeze({
27
+ domain: Object.freeze({
28
+ origin: PropertyKey.of(Domain, 'origin'),
29
+ name: K.classes.Named.properties.name.key,
30
+ discoveryUrl: PropertyKey.of(Domain, 'discoveryUrl'),
31
+ description: K.classes.Descriptable.properties.description.key,
32
+ createdAt: K.classes.Timestamped.properties.createdAt.key,
33
+ updatedAt: K.classes.Timestamped.properties.updatedAt.key,
34
+ }),
35
+ }),
36
+ })
37
+
38
+ /** Invoke one stable Admin instance Method without schema discovery or reflection. */
39
+ export function callAdminMethod(
40
+ session: ClientSession,
41
+ receiver: Path,
42
+ method: string,
43
+ input: Input,
44
+ ): Promise<unknown> {
45
+ return session.call(call(Path.instanceMethod(receiver, method), input))
46
+ }
@@ -1,18 +1,12 @@
1
- import type { GraphQueryOptions, NeighborsOptions, NodePage } from '@astrale-os/kernel-client/graph'
2
- import type { ClassPath } from '@astrale-os/sdk/graph/class'
1
+ import type { GraphApi } from '@astrale-os/sdk/client'
3
2
  import type { Node } from '@astrale-os/sdk/graph/node'
4
- import type { PathLike } from '@astrale-os/sdk/graph/path'
5
- import type { QueryAST, QueryResult } from '@astrale-os/sdk/query'
3
+ import type { QueryAST } from '@astrale-os/sdk/query'
6
4
 
7
5
  /** Smallest Kernel graph capability needed by bounded Admin graph reads. */
8
- export interface AdminGraphQueryApi {
9
- query(ast: QueryAST, options?: GraphQueryOptions): Promise<QueryResult>
10
- }
6
+ export type AdminGraphQueryApi = Pick<GraphApi, 'query'>
11
7
 
12
8
  /** Caller-scoped graph reads shared by the Admin catalog and Instance journeys. */
13
- export interface AdminGraphApi extends AdminGraphQueryApi {
14
- neighbors(source: PathLike, edge: ClassPath, options: NeighborsOptions): Promise<NodePage>
15
- }
9
+ export type AdminGraphApi = Pick<GraphApi, 'query' | 'neighbors'>
16
10
 
17
11
  export interface ReadAllNodesOptions {
18
12
  readonly label: string
@@ -1,4 +1,4 @@
1
- import type { GraphApi } from '@astrale-os/kernel-client/graph'
1
+ import type { GraphApi } from '@astrale-os/sdk/client'
2
2
  import type { Node } from '@astrale-os/sdk/graph/node'
3
3
  import type { QueryAST } from '@astrale-os/sdk/query'
4
4
 
@@ -1,7 +1,4 @@
1
- import type { DomainBinding } from '@astrale-os/kernel-client/domain'
2
- import type { ClientSession } from '@astrale-os/kernel-client/session'
3
-
4
- import type { AdminGraphApi } from '../../graph/.spec/api.js'
1
+ import type { ClientSession } from '@astrale-os/sdk/client/session'
5
2
 
6
3
  export type InstanceState = 'provisioning' | 'ready' | 'deleting' | 'failed' | 'deleted'
7
4
 
@@ -11,10 +8,11 @@ export interface InstanceInfo {
11
8
  readonly url: string
12
9
  readonly hostId?: string
13
10
  readonly region?: string
14
- readonly state?: InstanceState
11
+ readonly state: InstanceState
15
12
  readonly phase?: string
16
13
  readonly error?: string | null
17
14
  readonly createdAt?: string
15
+ readonly updatedAt?: string
18
16
  readonly organizationId?: string
19
17
  }
20
18
 
@@ -33,12 +31,11 @@ export interface DomainInstallReceipt {
33
31
 
34
32
  export interface AdminInstanceContext {
35
33
  readonly session: ClientSession
36
- readonly graph: AdminGraphApi
37
34
  }
38
35
 
39
36
  export interface AdminInstanceApi {
40
37
  list(): Promise<OwnedInstanceInfo[]>
41
- create(slug: string, hostId?: string): Promise<InstanceInfo>
38
+ create(slug: string): Promise<InstanceInfo>
42
39
  status(identifier: string): Promise<InstanceInfo>
43
40
  delete(identifier: string): Promise<InstanceInfo>
44
41
  installDomain(identifier: string, domain: string): Promise<DomainInstallReceipt>
@@ -51,7 +48,6 @@ export class AdminInstanceNotFoundError extends Error {
51
48
  }
52
49
 
53
50
  export interface AdminInstanceDependencies {
54
- readonly bind?: (session: ClientSession) => Promise<DomainBinding>
55
51
  readonly operationId?: (kind: 'create' | 'status' | 'delete' | 'install-domain') => string
56
52
  }
57
53
 
@@ -63,4 +59,4 @@ export function findOwnedInstance(
63
59
  instances: readonly OwnedInstanceInfo[],
64
60
  identifier: string,
65
61
  ): OwnedInstanceInfo | undefined
66
- export function formatInstanceLocation(info: InstanceInfo): string
62
+ export function formatInstanceState(info: InstanceInfo): string
@@ -1,10 +1,10 @@
1
1
  # CLI Admin Instance adapter
2
2
 
3
- The adapter discovers and binds the configured Admin Domain, proves its origin, and resolves the
4
- singleton Fleet receiver. Policy-visible `Instance` and `Host` Nodes come from bounded Graph queries;
5
- Host location is joined through `instance_runs_on_host`.
3
+ The adapter calls the stable singleton Fleet receiver directly. Listing and automatic creation are
4
+ one Admin Method call each; connection performs no schema discovery, introspection, or graph read.
5
+ Every returned summary is decoded locally before it reaches a command.
6
6
 
7
- Automatic creation invokes `Fleet.createInstance`; explicit `--host-id` resolves one visible,
8
- non-reserved Host and invokes `Host.createInstance`. Status, deletion, and Domain installation
9
- invoke the exact Instance receiver. The adapter creates internal operation IDs but preserves the
10
- existing CLI projection and selection-required experience.
7
+ Automatic creation invokes `Fleet.createInstance`. Status, deletion, and Domain installation first
8
+ resolve a caller-visible Instance through Fleet and then invoke that exact Instance receiver. The
9
+ adapter creates internal operation IDs but preserves the existing CLI projection and
10
+ selection-required experience.
@@ -1,135 +1,46 @@
1
- import type { Node } from '@astrale-os/sdk/graph/node'
2
- import type { QueryAST } from '@astrale-os/sdk/query'
3
-
4
- import { ClassPath } from '@astrale-os/sdk/graph/class'
5
- import { NodeId } from '@astrale-os/sdk/graph/node'
6
- import { Path } from '@astrale-os/sdk/graph/path'
7
- import { describe, expect, mock, test } from 'bun:test'
8
-
9
- import type { AdminBinding } from '../../binding'
10
- import type { AdminGraphApi } from '../../graph'
1
+ import { describe, expect, test } from 'bun:test'
11
2
 
3
+ import { 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
- function fixture(input: {
56
- instances?: readonly Node[]
57
- hosts?: readonly Node[]
58
- reserved?: Node | null
59
- invoke?: (method: { owner: string; name: string }, receiver: unknown, input: unknown) => unknown
60
- }) {
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 AdminBinding
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)}`)
87
- })
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)}`)
6
+ function fixture(input: { invoke?: (target: string, input: unknown) => unknown }) {
7
+ const calls: Array<{
8
+ target: string
9
+ value: unknown
10
+ }> = []
11
+ const remote = adminSession((target, value) => {
12
+ calls.push({ target, value })
13
+ return input.invoke?.(target, value)
92
14
  })
93
- const graph = { query, neighbors } as unknown as AdminGraphApi
94
15
  return {
95
- binding,
96
- graph,
97
- invoke,
16
+ call: remote.call,
17
+ reflection: remote.reflection,
18
+ calls,
98
19
  connect: () =>
99
20
  connectAdminInstances(
100
- { session: {} as never, graph },
21
+ { session: remote.session },
101
22
  {
102
- bind: async () => binding,
103
23
  operationId: (kind) => `cli.instance.${kind}:test`,
104
24
  },
105
25
  ),
106
26
  }
107
27
  }
108
28
 
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
29
  describe('V2 Admin Instance adapter', () => {
119
30
  test('lists caller-visible Instances through the Admin-owned Fleet operation', async () => {
120
31
  const contract = fixture({
121
- invoke: (method) =>
122
- method.name === 'listInstances'
32
+ invoke: (target) =>
33
+ target.endsWith('::listInstances')
123
34
  ? [
124
35
  {
125
36
  id: '@instance-node',
126
37
  slug: 'demo',
127
38
  url: 'https://demo.eu.astrale.ai',
39
+ hostId: '@host-node',
40
+ region: 'fr-par',
128
41
  state: 'ready',
129
42
  createdAt: '2026-08-12T00:00:00.000Z',
130
43
  updatedAt: '2026-08-12T00:00:00.000Z',
131
- hostId: '@host-node',
132
- region: 'fr-par',
133
44
  },
134
45
  ]
135
46
  : undefined,
@@ -140,17 +51,20 @@ describe('V2 Admin Instance adapter', () => {
140
51
  id: '@instance-node',
141
52
  slug: 'demo',
142
53
  url: 'https://demo.eu.astrale.ai',
143
- state: 'ready',
144
- createdAt: '2026-08-12T00:00:00.000Z',
145
54
  hostId: '@host-node',
146
55
  region: 'fr-par',
56
+ state: 'ready',
57
+ createdAt: '2026-08-12T00:00:00.000Z',
58
+ updatedAt: '2026-08-12T00:00:00.000Z',
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
+ target: '/:admin.astrale.ai:core.fleet::listInstances',
64
+ value: {},
65
+ },
66
+ ])
67
+ expect(contract.reflection).not.toHaveBeenCalled()
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,49 +85,18 @@ 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
+ target: '/:admin.astrale.ai:core.fleet::createInstance',
91
+ value: { operationId: 'cli.instance.create:test', slug: 'demo' },
92
+ },
93
+ ])
208
94
  })
209
95
 
210
96
  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
97
  const contract = fixture({
217
- instances: [instance],
218
- invoke: (method) =>
219
- method.name === 'listInstances'
98
+ invoke: (target) =>
99
+ target.endsWith('::listInstances')
220
100
  ? [
221
101
  {
222
102
  id: '@instance-node',
@@ -231,31 +111,34 @@ describe('V2 Admin Instance adapter', () => {
231
111
  id: '@instance-node',
232
112
  slug: 'demo',
233
113
  url: 'https://demo.eu.astrale.ai',
234
- state: method.name === 'delete' ? 'deleted' : 'ready',
114
+ state: target.endsWith('::delete') ? 'deleted' : 'ready',
115
+ createdAt: '2026-08-12T00:00:00.000Z',
116
+ updatedAt: '2026-08-12T00:00:00.000Z',
235
117
  },
236
118
  })
237
119
  const api = await contract.connect()
238
120
 
239
121
  await expect(api.status('demo')).resolves.toMatchObject({ state: 'ready' })
240
122
  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' }),
123
+ expect(contract.calls).toEqual([
124
+ { target: '/:admin.astrale.ai:core.fleet::listInstances', value: {} },
125
+ {
126
+ target: '@instance-node::status',
127
+ value: { operationId: 'cli.instance.status:test' },
128
+ },
129
+ { target: '/:admin.astrale.ai:core.fleet::listInstances', value: {} },
130
+ {
131
+ target: '@instance-node::delete',
132
+ value: { operationId: 'cli.instance.delete:test' },
133
+ },
246
134
  ])
135
+ expect(contract.reflection).not.toHaveBeenCalled()
247
136
  })
248
137
 
249
138
  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
139
  const contract = fixture({
256
- instances: [instance],
257
- invoke: (method) =>
258
- method.name === 'listInstances'
140
+ invoke: (target) =>
141
+ target.endsWith('::listInstances')
259
142
  ? [
260
143
  {
261
144
  id: '@instance-node',
@@ -282,10 +165,109 @@ describe('V2 Admin Instance adapter', () => {
282
165
  origin: 'crm.acme.dev',
283
166
  ok: true,
284
167
  })
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' },
168
+ expect(contract.calls.at(-1)).toEqual({
169
+ target: '@instance-node::installDomain',
170
+ value: { operationId: 'cli.instance.install-domain:test', domain: '@crm-domain' },
171
+ })
172
+ expect(contract.reflection).not.toHaveBeenCalled()
173
+ })
174
+
175
+ test('connects without network I/O and list performs exactly one call', async () => {
176
+ const contract = fixture({ invoke: () => [] })
177
+
178
+ const api = await contract.connect()
179
+ expect(contract.call).not.toHaveBeenCalled()
180
+ expect(contract.reflection).not.toHaveBeenCalled()
181
+
182
+ await expect(api.list()).resolves.toEqual([])
183
+ expect(contract.call).toHaveBeenCalledTimes(1)
184
+ expect(contract.reflection).not.toHaveBeenCalled()
185
+ })
186
+
187
+ test('rejects malformed inventory, lifecycle, Method, and install outputs', async () => {
188
+ await expect((await fixture({ invoke: () => ({}) }).connect()).list()).rejects.toThrow(
189
+ 'Admin Instance inventory is invalid.',
190
+ )
191
+ await expect(
192
+ (
193
+ await fixture({
194
+ invoke: () => [{ id: '@instance-node', slug: 'demo', state: 'mystery' }],
195
+ }).connect()
196
+ ).list(),
197
+ ).rejects.toThrow('Admin Instance state is invalid.')
198
+ await expect(
199
+ (
200
+ await fixture({
201
+ invoke: () => [{ id: 'not-a-path', slug: 'demo', state: 'ready' }],
202
+ }).connect()
203
+ ).list(),
204
+ ).rejects.toThrow('Admin Instance id is invalid.')
205
+ await expect(
206
+ (
207
+ await fixture({
208
+ invoke: () => [
209
+ { id: '@instance-node', slug: 'demo', hostId: 'not-a-path', state: 'ready' },
210
+ ],
211
+ }).connect()
212
+ ).list(),
213
+ ).rejects.toThrow('Admin Host id is invalid.')
214
+
215
+ const malformedStatus = fixture({
216
+ invoke: (target) =>
217
+ target.endsWith('::listInstances')
218
+ ? [
219
+ {
220
+ id: '@instance-node',
221
+ slug: 'demo',
222
+ state: 'ready',
223
+ createdAt: '2026-08-12T00:00:00.000Z',
224
+ updatedAt: '2026-08-12T00:00:00.000Z',
225
+ },
226
+ ]
227
+ : {
228
+ id: '@instance-node',
229
+ slug: 'demo',
230
+ state: 'ready',
231
+ failure: {},
232
+ createdAt: '2026-08-12T00:00:00.000Z',
233
+ updatedAt: '2026-08-12T00:00:00.000Z',
234
+ },
235
+ })
236
+ await expect((await malformedStatus.connect()).status('demo')).rejects.toThrow(
237
+ 'Admin failure message is invalid.',
289
238
  )
239
+
240
+ const malformedInstall = fixture({
241
+ invoke: (target) =>
242
+ target.endsWith('::listInstances')
243
+ ? [
244
+ {
245
+ id: '@instance-node',
246
+ slug: 'demo',
247
+ state: 'ready',
248
+ createdAt: '2026-08-12T00:00:00.000Z',
249
+ updatedAt: '2026-08-12T00:00:00.000Z',
250
+ },
251
+ ]
252
+ : {
253
+ domain: '@crm-domain',
254
+ instance: '@instance-node',
255
+ origin: 'crm.acme.dev',
256
+ ok: 'yes',
257
+ },
258
+ })
259
+ await expect(
260
+ (await malformedInstall.connect()).installDomain('demo', '@crm-domain'),
261
+ ).rejects.toThrow('Admin Domain install outcome is invalid.')
262
+
263
+ const malformedInstallPath = fixture({
264
+ invoke: (target) =>
265
+ target.endsWith('::listInstances')
266
+ ? [{ id: '@instance-node', slug: 'demo', state: 'ready' }]
267
+ : { domain: 'not-a-path', instance: '@instance-node', origin: 'crm.acme.dev', ok: true },
268
+ })
269
+ await expect(
270
+ (await malformedInstallPath.connect()).installDomain('demo', '@crm-domain'),
271
+ ).rejects.toThrow('Admin Domain reference is invalid.')
290
272
  })
291
273
  })