@astrale-os/cli 1.0.0-beta.3 → 1.0.0-beta.30

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 (343) hide show
  1. package/README.md +80 -6
  2. package/dist/astrale.js +55471 -58674
  3. package/dist/public/connect-core.js +2158 -30706
  4. package/dist/public/keys/index.js +1016 -29541
  5. package/dist/public/paths/index.js +370 -28654
  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 +13 -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/dist/types/state/exchange-credentials.d.ts +6 -2
  40. package/dist/types/state/files.d.ts +2 -0
  41. package/dist/types/state/index.d.ts +3 -2
  42. package/dist/types/state/paths.d.ts +2 -0
  43. package/dist/types/state/session-routes.d.ts +10 -0
  44. package/package.json +16 -19
  45. package/src/__tests__/fixtures/publication.ts +20 -0
  46. package/src/admin/.spec/architecture.md +12 -5
  47. package/src/admin/__tests__/fixture.ts +25 -0
  48. package/src/admin/catalog/.spec/api.d.ts +1 -3
  49. package/src/admin/catalog/.spec/architecture.md +5 -4
  50. package/src/admin/catalog/__tests__/client.test.ts +143 -69
  51. package/src/admin/catalog/client.ts +40 -54
  52. package/src/admin/catalog/model.ts +3 -4
  53. package/src/admin/contract.ts +47 -0
  54. package/src/admin/graph/.spec/api.d.ts +4 -10
  55. package/src/admin/graph/nodes.ts +1 -1
  56. package/src/admin/instance/.spec/api.d.ts +5 -9
  57. package/src/admin/instance/.spec/architecture.md +7 -7
  58. package/src/admin/instance/__tests__/client.test.ts +157 -175
  59. package/src/admin/instance/client.ts +39 -128
  60. package/src/admin/instance/index.ts +1 -2
  61. package/src/admin/instance/model.ts +12 -18
  62. package/src/commands/__tests__/admin-instance.test.ts +11 -8
  63. package/src/commands/__tests__/call-describe.test.ts +46 -33
  64. package/src/commands/__tests__/call.test.ts +34 -0
  65. package/src/commands/__tests__/domain-install-operation.test.ts +30 -22
  66. package/src/commands/__tests__/domain-install-owned.test.ts +1 -1
  67. package/src/commands/__tests__/domain-list.test.ts +5 -25
  68. package/src/commands/__tests__/install-direct.test.ts +115 -8
  69. package/src/commands/__tests__/install-identity-override.test.ts +6 -17
  70. package/src/commands/__tests__/instance-list-rows.test.ts +1 -1
  71. package/src/commands/__tests__/instance-status.test.ts +139 -0
  72. package/src/commands/__tests__/introspect-parse.test.ts +80 -1
  73. package/src/commands/__tests__/logs.test.ts +250 -2
  74. package/src/commands/__tests__/read-commands.test.ts +118 -10
  75. package/src/commands/__tests__/token-ttl.test.ts +49 -4
  76. package/src/commands/__tests__/update.test.ts +25 -3
  77. package/src/commands/__tests__/view.test.ts +50 -2
  78. package/src/commands/auth/logout.ts +2 -1
  79. package/src/commands/browser.ts +29 -0
  80. package/src/commands/call-describe.ts +60 -68
  81. package/src/commands/call.ts +71 -59
  82. package/src/commands/domain/install.ts +73 -46
  83. package/src/commands/domain/list.ts +1 -1
  84. package/src/commands/domain/publish.ts +14 -11
  85. package/src/commands/domain/uninstall.ts +2 -2
  86. package/src/commands/get.ts +15 -16
  87. package/src/commands/identity/__tests__/register.test.ts +3 -3
  88. package/src/commands/identity/register.ts +8 -8
  89. package/src/commands/instance/create.ts +8 -16
  90. package/src/commands/instance/delete.ts +12 -9
  91. package/src/commands/instance/list.ts +2 -2
  92. package/src/commands/instance/status.ts +66 -20
  93. package/src/commands/instance/use.ts +16 -12
  94. package/src/commands/introspect.ts +53 -41
  95. package/src/commands/logs.ts +123 -36
  96. package/src/commands/mutate.ts +6 -5
  97. package/src/commands/query.ts +35 -32
  98. package/src/commands/session/analyze.ts +26 -4
  99. package/src/commands/token.ts +42 -27
  100. package/src/commands/ui/__tests__/commands.test.ts +152 -0
  101. package/src/commands/ui/add.ts +51 -0
  102. package/src/commands/ui/doctor.ts +21 -0
  103. package/src/commands/ui/init.ts +38 -0
  104. package/src/commands/ui/list.ts +26 -0
  105. package/src/commands/ui/preset-apply.ts +19 -0
  106. package/src/commands/ui/preset-list.ts +12 -0
  107. package/src/commands/ui/shared.ts +25 -0
  108. package/src/commands/update.ts +89 -48
  109. package/src/commands/view.ts +82 -37
  110. package/src/connection/.spec/api.d.ts +7 -8
  111. package/src/connection/.spec/architecture.md +14 -5
  112. package/src/connection/.spec/flows/session.ts +2 -2
  113. package/src/connection/.spec/laws/connection.ts +6 -2
  114. package/src/connection/.spec/layout.ts +1 -0
  115. package/src/connection/__tests__/auth.test.ts +1 -0
  116. package/src/connection/__tests__/credential.test.ts +21 -1
  117. package/src/connection/__tests__/errors.test.ts +27 -20
  118. package/src/connection/__tests__/exchange.test.ts +64 -11
  119. package/src/connection/__tests__/failure-fixtures.ts +39 -0
  120. package/src/connection/__tests__/failure-safety.test.ts +75 -0
  121. package/src/connection/__tests__/self.test.ts +31 -11
  122. package/src/connection/__tests__/session.test.ts +8 -4
  123. package/src/connection/__tests__/target.test.ts +2 -0
  124. package/src/connection/auth.ts +13 -0
  125. package/src/connection/call.ts +1 -1
  126. package/src/connection/credential.ts +2 -2
  127. package/src/connection/errors.ts +1 -355
  128. package/src/connection/exchange.ts +110 -61
  129. package/src/connection/failure/classify.ts +110 -0
  130. package/src/connection/failure/index.ts +18 -0
  131. package/src/connection/failure/model.ts +28 -0
  132. package/src/connection/failure/render.ts +116 -0
  133. package/src/connection/reasons.ts +16 -0
  134. package/src/connection/self.ts +39 -21
  135. package/src/connection/session.ts +27 -9
  136. package/src/errors.ts +3 -2
  137. package/src/graph/.spec/api.d.ts +12 -5
  138. package/src/graph/.spec/layout.ts +9 -1
  139. package/src/graph/__tests__/mutation.test.ts +2 -2
  140. package/src/graph/__tests__/query.test.ts +25 -6
  141. package/src/graph/class.ts +30 -0
  142. package/src/graph/index.ts +1 -0
  143. package/src/graph/query.ts +21 -32
  144. package/src/identity/.spec/api.d.ts +2 -2
  145. package/src/identity/__tests__/fixtures/registry-journey.ts +13 -1
  146. package/src/identity/__tests__/registration.test.ts +1 -1
  147. package/src/identity/__tests__/registry.test.ts +4 -0
  148. package/src/identity/registration.ts +3 -3
  149. package/src/identity/registry.ts +2 -0
  150. package/src/keys/__tests__/keys.test.ts +4 -0
  151. package/src/keys/credential.ts +1 -0
  152. package/src/lib/__tests__/admin-target.test.ts +12 -0
  153. package/src/lib/__tests__/binary.test.ts +16 -1
  154. package/src/lib/__tests__/browser-retention.test.ts +212 -0
  155. package/src/lib/__tests__/command-dx.test.ts +1 -1
  156. package/src/lib/__tests__/config.test.ts +27 -0
  157. package/src/lib/__tests__/domain-publication.test.ts +10 -16
  158. package/src/lib/__tests__/idp.test.ts +34 -4
  159. package/src/lib/__tests__/instance-candidates.test.ts +1 -3
  160. package/src/lib/__tests__/instance-target.test.ts +7 -39
  161. package/src/lib/__tests__/log-errors.test.ts +64 -0
  162. package/src/lib/__tests__/skills.test.ts +809 -0
  163. package/src/lib/__tests__/studio-server-deps.test.ts +1 -1
  164. package/src/lib/__tests__/update.test.ts +264 -11
  165. package/src/lib/__tests__/view-assets.test.ts +25 -0
  166. package/src/lib/__tests__/view-external-open-intent.test.ts +150 -0
  167. package/src/lib/__tests__/view-external-open-origins.test.ts +27 -0
  168. package/src/lib/__tests__/view-open-intent.test.ts +0 -2
  169. package/src/lib/__tests__/view-server.test.ts +26 -3
  170. package/src/lib/__tests__/view-session.test.ts +2 -1
  171. package/src/lib/admin-instance.ts +14 -8
  172. package/src/lib/admin-target.ts +21 -2
  173. package/src/lib/binary.ts +22 -5
  174. package/src/lib/browser-retention.ts +210 -0
  175. package/src/lib/command-dx.ts +9 -9
  176. package/src/lib/config.ts +12 -1
  177. package/src/lib/domain-publication.ts +9 -7
  178. package/src/lib/failure-debug.ts +26 -0
  179. package/src/lib/idp.ts +34 -3
  180. package/src/lib/instance-candidates.ts +2 -2
  181. package/src/lib/instance-target.ts +3 -14
  182. package/src/lib/instance.ts +16 -11
  183. package/src/lib/log.ts +16 -9
  184. package/src/lib/output.ts +1 -1
  185. package/src/lib/proc.ts +7 -2
  186. package/src/lib/provision-instance.ts +34 -57
  187. package/src/lib/skills/lock.ts +117 -0
  188. package/src/lib/skills/sync.ts +814 -0
  189. package/src/lib/skills.ts +1 -52
  190. package/src/lib/update.ts +298 -45
  191. package/src/lib/validation.ts +2 -2
  192. package/src/lib/view/assets.ts +11 -2
  193. package/src/lib/view/external-open-intent.ts +41 -0
  194. package/src/lib/view/external-open-origins.ts +37 -0
  195. package/src/lib/view/host-capabilities.ts +17 -0
  196. package/src/lib/view/resolve.ts +1 -1
  197. package/src/lib/view/server.ts +18 -3
  198. package/src/lib/view/session.ts +2 -0
  199. package/src/program/.spec/api.d.ts +1 -0
  200. package/src/program/__tests__/program.test.ts +163 -6
  201. package/src/program/argv.ts +14 -0
  202. package/src/program/build.ts +26 -4
  203. package/src/program/command.ts +1 -0
  204. package/src/program/index.ts +1 -0
  205. package/src/program/registry.ts +2 -1
  206. package/src/setup/__tests__/instance-step.test.ts +27 -10
  207. package/src/setup/steps/instance.ts +22 -15
  208. package/src/setup/steps/skills.ts +40 -38
  209. package/src/state/.spec/api.d.ts +21 -2
  210. package/src/state/.spec/architecture.md +18 -4
  211. package/src/state/.spec/layout.ts +1 -0
  212. package/src/state/__tests__/exchange-credentials.test.ts +88 -42
  213. package/src/state/__tests__/files.test.ts +24 -1
  214. package/src/state/__tests__/fixtures/session-route-process.ts +93 -0
  215. package/src/state/__tests__/paths.test.ts +1 -0
  216. package/src/state/__tests__/session-routes.test.ts +138 -0
  217. package/src/state/exchange-credentials.ts +22 -9
  218. package/src/state/files.ts +41 -0
  219. package/src/state/index.ts +3 -1
  220. package/src/state/paths.ts +3 -0
  221. package/src/state/session-routes.ts +34 -0
  222. package/src/telemetry/__tests__/analyze-log.test.ts +38 -0
  223. package/src/telemetry/__tests__/recorder.test.ts +1 -0
  224. package/src/telemetry/__tests__/retention.test.ts +267 -0
  225. package/src/telemetry/__tests__/settings.test.ts +102 -0
  226. package/src/telemetry/__tests__/store-scan.test.ts +128 -0
  227. package/src/telemetry/__tests__/trigger.test.ts +33 -4
  228. package/src/telemetry/analyze.ts +24 -2
  229. package/src/telemetry/recorder.ts +15 -4
  230. package/src/telemetry/retention.ts +176 -0
  231. package/src/telemetry/session.ts +18 -12
  232. package/src/telemetry/settings.ts +64 -11
  233. package/src/telemetry/store.ts +92 -9
  234. package/src/telemetry/trigger.ts +16 -28
  235. package/src/ui/.spec/api.d.ts +14 -0
  236. package/src/ui/.spec/architecture.md +14 -0
  237. package/src/ui/.spec/laws.ts +50 -0
  238. package/src/ui/.spec/layout.ts +16 -0
  239. package/src/ui/__tests__/ui.test.ts +1742 -0
  240. package/src/ui/index.ts +13 -0
  241. package/src/ui/lock.ts +89 -0
  242. package/src/ui/model.ts +83 -0
  243. package/src/ui/operations.ts +1044 -0
  244. package/src/ui/project.ts +333 -0
  245. package/src/ui/release.ts +398 -0
  246. package/src/ui/runner.ts +18 -0
  247. package/studio/client/dist/assets/elk-api-DjmKsHXc.js +1 -0
  248. package/studio/client/dist/assets/index-BFVFs_8x.js +81 -0
  249. package/studio/client/dist/assets/index-DuB9iUdu.css +2 -0
  250. package/studio/client/dist/assets/rolldown-runtime-Dd_uD5pT.js +1 -0
  251. package/studio/client/dist/assets/schema-studio--a0kX3QU.css +1 -0
  252. package/studio/client/dist/assets/schema-studio-DH6oEWME.js +8 -0
  253. package/studio/client/dist/index.html +4 -3
  254. package/studio/package.json +8 -14
  255. package/studio/server/agent/ask.test.ts +1 -1
  256. package/studio/server/agent/bridge/grant.test.ts +1 -1
  257. package/studio/server/agent/bridge/routes.test.ts +1 -1
  258. package/studio/server/agent/harness/claude/adapter.test.ts +15 -5
  259. package/studio/server/agent/harness/gateway/token.test.ts +1 -1
  260. package/studio/server/agent/harness/gateway/token.ts +3 -3
  261. package/studio/server/agent/prompts/anchors.test.ts +41 -32
  262. package/studio/server/agent/prompts/anchors.ts +23 -51
  263. package/studio/server/agent/prompts/system.test.ts +4 -4
  264. package/studio/server/agent/prompts/system.ts +5 -5
  265. package/studio/server/agent/routes.test.ts +1 -1
  266. package/studio/server/agent/run/coordinator.test.ts +1 -1
  267. package/studio/server/agent/run/preparation.ts +1 -1
  268. package/studio/server/api/canvas.ts +0 -1
  269. package/studio/server/api/context.ts +1 -1
  270. package/studio/server/api/deployment.ts +1 -1
  271. package/studio/server/api/views.ts +2 -2
  272. package/studio/server/api/workspace.ts +1 -1
  273. package/studio/server/api-agent-loadout.test.ts +1 -1
  274. package/studio/server/api.test.ts +1 -1
  275. package/studio/server/cache.test.ts +24 -18
  276. package/studio/server/cache.ts +10 -54
  277. package/studio/server/cli-consumers.test.ts +31 -3
  278. package/studio/server/domain.test.ts +50 -68
  279. package/studio/server/domain.ts +82 -63
  280. package/studio/server/handoff/copy.ts +2 -2
  281. package/studio/server/index.ts +1 -1
  282. package/studio/server/instances/probe.test.ts +1 -1
  283. package/studio/server/introspect/anatomy/views/routes.ts +46 -37
  284. package/studio/server/introspect/anatomy/views.ts +10 -16
  285. package/studio/server/introspect/anatomy-extras.test.ts +29 -56
  286. package/studio/server/introspect/anatomy.test.ts +22 -14
  287. package/studio/server/introspect/anatomy.ts +6 -9
  288. package/studio/server/introspect/bundle.ts +0 -3
  289. package/studio/server/introspect/canonical-schema.test.ts +155 -424
  290. package/studio/server/introspect/canonical-schema.ts +248 -601
  291. package/studio/server/introspect/core.ts +14 -22
  292. package/studio/server/introspect/diff.test.ts +21 -9
  293. package/studio/server/introspect/diff.ts +19 -64
  294. package/studio/server/introspect/extractor.ts +23 -122
  295. package/studio/server/introspect/overlay-tsmorph.test.ts +116 -241
  296. package/studio/server/introspect/overlay-tsmorph.ts +0 -1
  297. package/studio/server/introspect/overlay.ts +3 -51
  298. package/studio/server/introspect/revision.test.ts +24 -28
  299. package/studio/server/introspect/revision.ts +11 -22
  300. package/studio/server/introspect/runtime.test.ts +77 -195
  301. package/studio/server/introspect/runtime.ts +5 -67
  302. package/studio/server/introspect/schema-ir-json.test.ts +81 -0
  303. package/studio/server/introspect/schema-ir-json.ts +39 -69
  304. package/studio/server/introspect/schema-refs.test.ts +43 -88
  305. package/studio/server/introspect/schema-refs.ts +14 -49
  306. package/studio/server/introspect/source-overlay/handlers.ts +116 -636
  307. package/studio/server/introspect/source-overlay/kernel-calls.ts +0 -3
  308. package/studio/server/introspect/source-overlay/spans.ts +15 -45
  309. package/studio/server/lifecycle.ts +4 -1
  310. package/studio/server/state/baseline.test.ts +14 -9
  311. package/studio/server/state/baseline.ts +3 -5
  312. package/studio/server/state/documents.test.ts +69 -0
  313. package/studio/server/state/documents.ts +57 -10
  314. package/studio/server/state/visibility.ts +1 -5
  315. package/studio/server/views/model.ts +3 -3
  316. package/studio/server/views/target.test.ts +18 -76
  317. package/studio/server/views/target.ts +24 -50
  318. package/studio/server/watch.test.ts +11 -11
  319. package/studio/server/watch.ts +8 -4
  320. package/studio/server/workspace/updates.ts +23 -1
  321. package/studio/server/workspace-watch.ts +2 -2
  322. package/studio/shared/contracts/runtime.ts +4 -0
  323. package/studio/shared/contracts/schema.ts +44 -131
  324. package/studio/shared/contracts/surface.test.ts +15 -17
  325. package/studio/shared/contracts/workspace.ts +5 -11
  326. package/studio/shared/schema/identity.test.ts +20 -44
  327. package/studio/shared/schema/identity.ts +9 -24
  328. package/studio/tsconfig.json +2 -1
  329. package/tsconfig.json +1 -1
  330. package/viewer/dist/index.html +21 -0
  331. package/viewer/dist/main.js +41 -62
  332. package/.check-workspace.cjs +0 -40
  333. package/src/admin/binding.ts +0 -168
  334. package/src/commands/__tests__/instance-create-hosts.test.ts +0 -29
  335. package/studio/client/dist/assets/elk-api-lwhFo7vB.js +0 -1
  336. package/studio/client/dist/assets/index-B-c-smo9.js +0 -8
  337. package/studio/client/dist/assets/index-BckHuAWk.js +0 -81
  338. package/studio/client/dist/assets/index-C4aNQ6dz.css +0 -1
  339. package/studio/client/dist/assets/index-ChOr3yP0.css +0 -1
  340. package/studio/server/introspect/anatomy/views/legacy.ts +0 -232
  341. package/studio/server/introspect/core-extractor.ts +0 -139
  342. package/studio/server/introspect/overlay.test.ts +0 -72
  343. package/studio/server/introspect/source-overlay/annotations.ts +0 -14
@@ -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
  })
@@ -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')