@astrale-os/cli 0.8.0-alpha.0 → 0.8.1-alpha.2

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 (317) hide show
  1. package/README.md +4 -1
  2. package/dist/astrale.js +62968 -41240
  3. package/dist/public/connect-core.js +55030 -0
  4. package/dist/public/keys/index.js +45631 -0
  5. package/dist/public/paths/index.js +43990 -0
  6. package/dist/types/admin/instance/model.d.ts +40 -0
  7. package/dist/types/connect-core.d.ts +22 -0
  8. package/dist/types/connection/auth.d.ts +33 -0
  9. package/dist/types/errors.d.ts +23 -0
  10. package/dist/types/identity/registry.d.ts +25 -0
  11. package/dist/types/keys/algorithm.d.ts +6 -0
  12. package/dist/types/keys/credential.d.ts +21 -0
  13. package/dist/types/keys/index.d.ts +4 -0
  14. package/dist/types/keys/pair.d.ts +34 -0
  15. package/dist/types/lib/admin-target.d.ts +74 -0
  16. package/dist/types/lib/ca-fetch.d.ts +2 -0
  17. package/dist/types/lib/config.d.ts +71 -0
  18. package/dist/types/lib/format.d.ts +1 -0
  19. package/dist/types/lib/idp-session.d.ts +33 -0
  20. package/dist/types/lib/idp.d.ts +397 -0
  21. package/dist/types/lib/instance-target.d.ts +36 -0
  22. package/dist/types/lib/instance.d.ts +186 -0
  23. package/dist/types/lib/log.d.ts +30 -0
  24. package/dist/types/lib/login-flow.d.ts +49 -0
  25. package/dist/types/lib/meta.d.ts +36 -0
  26. package/dist/types/lib/output.d.ts +83 -0
  27. package/dist/types/lib/table.d.ts +18 -0
  28. package/dist/types/lib/validation.d.ts +11 -0
  29. package/dist/types/paths/index.d.ts +2 -0
  30. package/dist/types/state/exchange-credentials.d.ts +25 -0
  31. package/dist/types/state/files.d.ts +9 -0
  32. package/dist/types/state/identities.d.ts +42 -0
  33. package/dist/types/state/index.d.ts +8 -0
  34. package/dist/types/state/paths.d.ts +32 -0
  35. package/package.json +30 -8
  36. package/src/admin/.spec/api.d.ts +3 -0
  37. package/src/admin/.spec/architecture.md +15 -0
  38. package/src/admin/.spec/icon.svg +3 -0
  39. package/src/admin/.spec/layout.ts +6 -0
  40. package/src/admin/catalog/.spec/api.d.ts +65 -0
  41. package/src/admin/catalog/.spec/architecture.md +6 -0
  42. package/src/admin/catalog/.spec/icon.svg +3 -0
  43. package/src/admin/catalog/.spec/layout.ts +6 -0
  44. package/src/admin/catalog/__tests__/client.test.ts +171 -0
  45. package/src/admin/catalog/client.ts +214 -0
  46. package/src/admin/catalog/index.ts +13 -0
  47. package/src/admin/catalog/model.ts +42 -0
  48. package/src/admin/catalog/tsconfig.json +8 -0
  49. package/src/admin/graph/.spec/api.d.ts +28 -0
  50. package/src/admin/graph/.spec/architecture.md +5 -0
  51. package/src/admin/graph/.spec/icon.svg +3 -0
  52. package/src/admin/graph/.spec/layout.ts +3 -0
  53. package/src/admin/graph/index.ts +6 -0
  54. package/src/admin/graph/nodes.ts +56 -0
  55. package/src/admin/index.ts +2 -0
  56. package/src/admin/instance/.spec/api.d.ts +66 -0
  57. package/src/admin/instance/.spec/architecture.md +10 -0
  58. package/src/admin/instance/.spec/icon.svg +3 -0
  59. package/src/admin/instance/.spec/layout.ts +6 -0
  60. package/src/admin/instance/__tests__/client.test.ts +289 -0
  61. package/src/admin/instance/client.ts +298 -0
  62. package/src/admin/instance/index.ts +16 -0
  63. package/src/admin/instance/model.ts +65 -0
  64. package/src/admin/instance/tsconfig.json +8 -0
  65. package/src/commands/__tests__/admin-instance.test.ts +3 -36
  66. package/src/commands/__tests__/call.test.ts +21 -68
  67. package/src/commands/__tests__/domain-install-owned.test.ts +26 -27
  68. package/src/commands/__tests__/domain-list.test.ts +2 -6
  69. package/src/commands/__tests__/logs.test.ts +50 -98
  70. package/src/commands/__tests__/ls.test.ts +21 -17
  71. package/src/commands/__tests__/read-commands.test.ts +171 -101
  72. package/src/commands/__tests__/view.test.ts +100 -51
  73. package/src/commands/admin/status.ts +3 -3
  74. package/src/commands/admin/use.ts +29 -6
  75. package/src/commands/auth/login.ts +1 -1
  76. package/src/commands/auth/logout.ts +4 -2
  77. package/src/commands/auth/status.ts +2 -2
  78. package/src/commands/auth/token.ts +2 -2
  79. package/src/commands/browser.ts +1 -1
  80. package/src/commands/call.ts +55 -91
  81. package/src/commands/describe.ts +52 -147
  82. package/src/commands/domain/install.ts +81 -50
  83. package/src/commands/domain/list.ts +4 -8
  84. package/src/commands/domain/publish.ts +9 -31
  85. package/src/commands/get.ts +32 -65
  86. package/src/commands/identity/__tests__/register.test.ts +93 -0
  87. package/src/commands/identity/create.ts +2 -2
  88. package/src/commands/identity/delete.ts +2 -2
  89. package/src/commands/identity/export.ts +7 -33
  90. package/src/commands/identity/import.ts +21 -70
  91. package/src/commands/identity/list.ts +2 -2
  92. package/src/commands/identity/register.ts +148 -117
  93. package/src/commands/identity/sync.ts +2 -2
  94. package/src/commands/identity/unsync.ts +2 -2
  95. package/src/commands/identity/use.ts +2 -2
  96. package/src/commands/identity/whoami.ts +2 -2
  97. package/src/commands/idp/add.ts +1 -1
  98. package/src/commands/idp/list.ts +1 -1
  99. package/src/commands/idp/refresh.ts +1 -1
  100. package/src/commands/idp/remove.ts +2 -2
  101. package/src/commands/idp/show.ts +1 -1
  102. package/src/commands/instance/active.ts +2 -2
  103. package/src/commands/instance/bookmark.ts +8 -2
  104. package/src/commands/instance/create.ts +1 -1
  105. package/src/commands/instance/delete.ts +4 -10
  106. package/src/commands/instance/forget.ts +1 -1
  107. package/src/commands/instance/list.ts +3 -3
  108. package/src/commands/instance/status.ts +5 -7
  109. package/src/commands/instance/use.ts +3 -3
  110. package/src/commands/logs.ts +152 -279
  111. package/src/commands/ls.ts +78 -204
  112. package/src/commands/mutate.ts +70 -139
  113. package/src/commands/query.ts +100 -278
  114. package/src/commands/session/analyze.ts +1 -1
  115. package/src/commands/session/list.ts +1 -1
  116. package/src/commands/setup.ts +1 -1
  117. package/src/commands/status.ts +1 -1
  118. package/src/commands/studio.ts +1 -1
  119. package/src/commands/token.ts +26 -21
  120. package/src/commands/update.ts +1 -1
  121. package/src/commands/use.ts +2 -3
  122. package/src/commands/view-serve.ts +1 -1
  123. package/src/commands/view.ts +67 -109
  124. package/src/connect-core.test.ts +38 -0
  125. package/src/connect-core.ts +42 -0
  126. package/src/connection/.spec/api.d.ts +111 -0
  127. package/src/connection/.spec/architecture.md +41 -0
  128. package/src/connection/.spec/capabilities/connection.ts +13 -0
  129. package/src/connection/.spec/examples/call.ts +7 -0
  130. package/src/connection/.spec/flows/command.ts +20 -0
  131. package/src/connection/.spec/flows/session.ts +61 -0
  132. package/src/connection/.spec/laws/connection.ts +181 -0
  133. package/src/connection/.spec/layout.ts +19 -0
  134. package/src/{kernel → connection}/__tests__/auth.test.ts +26 -2
  135. package/src/connection/__tests__/ca-fetch.test.ts +53 -0
  136. package/src/connection/__tests__/call.test.ts +17 -0
  137. package/src/connection/__tests__/credential.test.ts +109 -0
  138. package/src/connection/__tests__/errors.test.ts +96 -0
  139. package/src/connection/__tests__/exchange.test.ts +224 -0
  140. package/src/connection/__tests__/fixtures/localhost-cert.pem +19 -0
  141. package/src/connection/__tests__/fixtures/localhost-key.base64 +1 -0
  142. package/src/connection/__tests__/managed.test.ts +39 -0
  143. package/src/connection/__tests__/self.test.ts +51 -0
  144. package/src/connection/__tests__/session.test.ts +140 -0
  145. package/src/connection/__tests__/target.test.ts +93 -0
  146. package/src/{kernel → connection}/auth.ts +8 -8
  147. package/src/connection/call.ts +7 -0
  148. package/src/{kernel/run.ts → connection/command.ts} +22 -13
  149. package/src/connection/credential.ts +114 -0
  150. package/src/{kernel → connection}/errors.ts +32 -9
  151. package/src/connection/exchange.ts +274 -0
  152. package/src/connection/index.ts +8 -0
  153. package/src/connection/self.ts +120 -0
  154. package/src/connection/session.ts +176 -0
  155. package/src/connection/target.ts +111 -0
  156. package/src/connection/tsconfig.json +6 -0
  157. package/src/graph/.spec/api.d.ts +34 -0
  158. package/src/graph/.spec/laws/documents.ts +24 -0
  159. package/src/graph/.spec/laws/projection.ts +13 -0
  160. package/src/graph/.spec/layout.ts +6 -0
  161. package/src/graph/__tests__/mutation.test.ts +43 -0
  162. package/src/graph/__tests__/projection.test.ts +22 -0
  163. package/src/graph/__tests__/query.test.ts +154 -0
  164. package/src/graph/index.ts +3 -0
  165. package/src/graph/mutation.ts +18 -0
  166. package/src/graph/projection.ts +21 -0
  167. package/src/graph/query.ts +108 -0
  168. package/src/graph/tsconfig.json +6 -0
  169. package/src/identity/.history/ard/bootstrap-root-transfer.md +57 -0
  170. package/src/identity/.history/ard/domain-mediated-registration.md +36 -0
  171. package/src/identity/.spec/api.d.ts +124 -0
  172. package/src/identity/.spec/architecture.md +33 -0
  173. package/src/identity/.spec/capabilities/identity.ts +7 -0
  174. package/src/identity/.spec/flows/bootstrap-root.ts +79 -0
  175. package/src/identity/.spec/flows/domain-mediated-registration.ts +51 -0
  176. package/src/identity/.spec/flows/import.ts +29 -0
  177. package/src/identity/.spec/laws/identity.ts +56 -0
  178. package/src/identity/.spec/layout.ts +13 -0
  179. package/src/identity/.spec/schemas/identity-export-v1.schema.json +25 -0
  180. package/src/identity/__tests__/fixtures/registry-journey.ts +53 -0
  181. package/src/identity/__tests__/registration.test.ts +64 -0
  182. package/src/identity/__tests__/registry.test.ts +70 -0
  183. package/src/identity/__tests__/transfer.test.ts +198 -0
  184. package/src/identity/index.ts +23 -0
  185. package/src/identity/registration.ts +72 -0
  186. package/src/identity/registry.ts +172 -0
  187. package/src/identity/transfer.ts +187 -0
  188. package/src/identity/tsconfig.json +6 -0
  189. package/src/index.ts +1 -0
  190. package/src/keys/.spec/api.d.ts +74 -0
  191. package/src/keys/.spec/architecture.md +13 -0
  192. package/src/keys/.spec/laws/keys.ts +43 -0
  193. package/src/keys/.spec/layout.ts +6 -0
  194. package/src/keys/__tests__/algorithm.test.ts +39 -0
  195. package/src/keys/__tests__/keys.test.ts +168 -0
  196. package/src/keys/algorithm.ts +33 -0
  197. package/src/keys/credential.ts +112 -0
  198. package/src/keys/index.ts +14 -0
  199. package/src/keys/pair.ts +219 -0
  200. package/src/keys/tsconfig.json +6 -0
  201. package/src/lib/__tests__/admin-target.test.ts +67 -7
  202. package/src/lib/__tests__/binary.test.ts +2 -3
  203. package/src/lib/__tests__/command-dx.test.ts +2 -2
  204. package/src/lib/__tests__/config.test.ts +3 -2
  205. package/src/lib/__tests__/idp.test.ts +4 -6
  206. package/src/lib/__tests__/instance-target.test.ts +7 -4
  207. package/src/lib/__tests__/local-status.test.ts +1 -1
  208. package/src/lib/__tests__/log.test.ts +31 -0
  209. package/src/lib/__tests__/meta.test.ts +55 -0
  210. package/src/lib/__tests__/sdk-deps.test.ts +1 -1
  211. package/src/lib/__tests__/self.test.ts +8 -230
  212. package/src/lib/__tests__/use-target.test.ts +1 -1
  213. package/src/lib/__tests__/view-open-intent.test.ts +78 -104
  214. package/src/lib/__tests__/view-session.test.ts +99 -0
  215. package/src/lib/admin-domain.ts +39 -25
  216. package/src/lib/admin-instance.ts +38 -43
  217. package/src/lib/admin-target.ts +67 -13
  218. package/src/lib/binary.ts +13 -34
  219. package/src/lib/browser.ts +1 -1
  220. package/src/{kernel → lib}/ca-fetch.ts +35 -27
  221. package/src/lib/config.ts +1 -1
  222. package/src/lib/idp-session.ts +1 -1
  223. package/src/lib/idp.ts +3 -8
  224. package/src/lib/instance-target.ts +12 -8
  225. package/src/lib/instance.ts +34 -2
  226. package/src/lib/local-status.ts +2 -2
  227. package/src/lib/log.ts +3 -1
  228. package/src/lib/login-flow.ts +32 -5
  229. package/src/lib/meta.ts +15 -11
  230. package/src/lib/provision-instance.ts +4 -12
  231. package/src/lib/self.ts +5 -147
  232. package/src/lib/update.ts +1 -1
  233. package/src/lib/use-target.ts +1 -1
  234. package/src/lib/validation.ts +2 -2
  235. package/src/lib/view/open-intent.ts +7 -37
  236. package/src/lib/view/port-allocation.ts +1 -1
  237. package/src/lib/view/resolve.ts +52 -45
  238. package/src/lib/view/server.ts +16 -33
  239. package/src/lib/view/session.ts +51 -22
  240. package/src/paths/.spec/api.d.ts +13 -0
  241. package/src/paths/.spec/architecture.md +5 -0
  242. package/src/paths/.spec/layout.ts +3 -0
  243. package/src/paths/index.ts +13 -0
  244. package/src/program/.spec/api.d.ts +42 -0
  245. package/src/program/.spec/architecture.md +18 -0
  246. package/src/program/.spec/laws/program.ts +29 -0
  247. package/src/program/.spec/layout.ts +14 -0
  248. package/src/program/__tests__/program.test.ts +359 -0
  249. package/src/program/build.ts +190 -0
  250. package/src/program/command.ts +42 -0
  251. package/src/program/index.ts +2 -0
  252. package/src/program/options.ts +40 -0
  253. package/src/{registry.ts → program/registry.ts} +5 -1
  254. package/src/program/tsconfig.json +6 -0
  255. package/src/setup/steps/admin.ts +18 -3
  256. package/src/setup/steps/instance.ts +1 -1
  257. package/src/state/.spec/api.d.ts +133 -0
  258. package/src/state/.spec/architecture.md +31 -0
  259. package/src/state/.spec/capabilities/state.ts +7 -0
  260. package/src/state/.spec/flows/identity-update.ts +22 -0
  261. package/src/state/.spec/laws/state.ts +73 -0
  262. package/src/state/.spec/layout.ts +14 -0
  263. package/src/state/__tests__/exchange-credentials.test.ts +188 -0
  264. package/src/state/__tests__/files.test.ts +113 -0
  265. package/src/state/__tests__/fixtures/identity-transition.ts +37 -0
  266. package/src/state/__tests__/identities.test.ts +142 -0
  267. package/src/state/__tests__/paths.test.ts +27 -0
  268. package/src/state/exchange-credentials.ts +219 -0
  269. package/src/state/files.ts +129 -0
  270. package/src/state/identities.ts +236 -0
  271. package/src/state/index.ts +29 -0
  272. package/src/state/paths.ts +61 -0
  273. package/src/state/tsconfig.json +6 -0
  274. package/src/telemetry/__tests__/redact.test.ts +3 -3
  275. package/src/telemetry/__tests__/trigger.test.ts +1 -1
  276. package/src/telemetry/settings.ts +1 -1
  277. package/src/telemetry/store.ts +1 -1
  278. package/src/test-utils.ts +1 -1
  279. package/studio/client/dist/assets/elk-api-D0cBetPW.js +1 -0
  280. package/studio/client/dist/assets/elk-worker.min-C9JGDOE-.js +6312 -0
  281. package/studio/client/dist/assets/index-BQnJ5sgd.css +1 -0
  282. package/studio/client/dist/assets/index-ChOr3yP0.css +1 -0
  283. package/studio/client/dist/assets/index-Dspir4w7.js +81 -0
  284. package/studio/client/dist/assets/index-bVD2KJgz.js +8 -0
  285. package/studio/client/dist/index.html +2 -2
  286. package/studio/package.json +10 -1
  287. package/studio/server/introspect/extractor.ts +1 -1
  288. package/studio/server/state/views.test.ts +3 -7
  289. package/studio/server/state/views.ts +23 -30
  290. package/studio/tsconfig.json +23 -0
  291. package/tsconfig.json +1 -1
  292. package/viewer/dist/main.js +65 -39
  293. package/viewer/tsconfig.json +13 -0
  294. package/src/command.ts +0 -42
  295. package/src/commands/__tests__/help-contract.test.ts +0 -164
  296. package/src/kernel/__tests__/client-owned-lookup.test.ts +0 -51
  297. package/src/kernel/__tests__/errors.test.ts +0 -43
  298. package/src/kernel/__tests__/expand.test.ts +0 -123
  299. package/src/kernel/client.ts +0 -214
  300. package/src/kernel/expand.ts +0 -192
  301. package/src/kernel/graph.ts +0 -96
  302. package/src/kernel/index.ts +0 -29
  303. package/src/kernel/options.ts +0 -22
  304. package/src/kernel/types.ts +0 -14
  305. package/src/lib/__tests__/domain-identity.test.ts +0 -60
  306. package/src/lib/__tests__/fs-atomic.test.ts +0 -104
  307. package/src/lib/__tests__/identity.test.ts +0 -79
  308. package/src/lib/__tests__/keys.test.ts +0 -129
  309. package/src/lib/domain-identity.ts +0 -49
  310. package/src/lib/env.ts +0 -49
  311. package/src/lib/fs-atomic.ts +0 -126
  312. package/src/lib/identity.ts +0 -256
  313. package/src/lib/keys.ts +0 -294
  314. package/src/lib/paths.ts +0 -11
  315. package/src/program.ts +0 -213
  316. package/studio/client/dist/assets/index-BaqEuIQJ.js +0 -109
  317. package/studio/client/dist/assets/index-jRdExahh.css +0 -1
@@ -1,41 +1,53 @@
1
1
  import { afterEach, beforeEach, describe, expect, mock, test } from 'bun:test'
2
2
 
3
- type Wire = { nodes: unknown[]; edges: unknown[]; roots: string[]; next?: Record<string, unknown> }
3
+ type GraphContext = {
4
+ graph: {
5
+ query: typeof queryMock
6
+ getOrThrow: typeof getOrThrowMock
7
+ mutate: typeof mutateMock
8
+ }
9
+ }
10
+
4
11
  type RunOpts = {
5
12
  opts: Record<string, unknown>
6
- fn: (ctx: { client: { call: typeof clientCallMock } }) => Promise<unknown>
7
- format?: (result: unknown, opts: Record<string, unknown>, isRaw: boolean) => void | Promise<void>
13
+ fn: (context: GraphContext) => Promise<unknown>
14
+ format?: (
15
+ result: unknown,
16
+ opts: Record<string, unknown>,
17
+ machine: boolean,
18
+ ) => void | Promise<void>
8
19
  }
9
20
 
10
21
  class ExitError extends Error {
11
22
  constructor(readonly code: string | number | null | undefined) {
12
- super('process.exit(' + String(code) + ')')
23
+ super(`process.exit(${String(code)})`)
13
24
  }
14
25
  }
15
26
 
16
- const emptyWire = (): Wire => ({ nodes: [], edges: [], roots: [] })
17
-
18
27
  let stdout = ''
19
28
  let errors: string[] = []
20
29
  let originalStdoutWrite: typeof process.stdout.write
21
30
  let originalConsoleError: typeof console.error
22
31
  let originalExit: typeof process.exit
23
- let queryAsts: unknown[] = []
24
- let getPaths: string[] = []
25
- let queryResult: { wire: Wire } = { wire: emptyWire() }
26
- let getResult: unknown = null
27
- let clientCallResult: unknown = { rows: [] }
28
-
29
- const clientCallMock = mock(async () => clientCallResult)
30
- const queryMock = mock(async (ast: unknown) => {
31
- queryAsts.push(ast)
32
+ let queryCalls: Array<{ ast: unknown; options: unknown }> = []
33
+ let getTargets: string[] = []
34
+ let mutations: unknown[] = []
35
+ let queryResult: unknown
36
+ let getResult: unknown
37
+ let mutationResult: unknown
38
+
39
+ const queryMock = mock(async (ast: unknown, options?: unknown) => {
40
+ queryCalls.push({ ast, options })
32
41
  return queryResult
33
42
  })
34
- const getMock = mock(async (path: string) => {
35
- getPaths.push(path)
43
+ const getOrThrowMock = mock(async (target: { readonly raw?: string; toString(): string }) => {
44
+ getTargets.push(target.raw ?? target.toString())
36
45
  return getResult
37
46
  })
38
- const bindGraphMock = mock(() => ({ query: queryMock, get: getMock }))
47
+ const mutateMock = mock(async (mutation: unknown) => {
48
+ mutations.push(mutation)
49
+ return mutationResult
50
+ })
39
51
  const expandSelfInPathMock = mock(async (path: string) => {
40
52
  if (path === '@self') {
41
53
  return {
@@ -45,14 +57,15 @@ const expandSelfInPathMock = mock(async (path: string) => {
45
57
  }
46
58
  return { path, meta: undefined }
47
59
  })
48
- const withSelfHintMock = mock(async (fn: () => Promise<unknown>) => fn())
60
+ const withSelfHintMock = mock(async (action: () => Promise<unknown>) => action())
49
61
  const runKernelCommandMock = mock(async (run: RunOpts) => {
50
- const result = await run.fn({ client: { call: clientCallMock } })
51
- if (run.format) await run.format(result, run.opts, true)
62
+ const result = await run.fn({
63
+ graph: { query: queryMock, getOrThrow: getOrThrowMock, mutate: mutateMock },
64
+ })
65
+ await run.format?.(result, run.opts, true)
52
66
  })
53
67
 
54
- mock.module('../../kernel', () => ({
55
- bindGraph: bindGraphMock,
68
+ mock.module('../../connection', () => ({
56
69
  expandSelfInPath: expandSelfInPathMock,
57
70
  runKernelCommand: runKernelCommandMock,
58
71
  withSelfHint: withSelfHintMock,
@@ -61,28 +74,35 @@ mock.module('../../kernel', () => ({
61
74
  beforeEach(() => {
62
75
  stdout = ''
63
76
  errors = []
64
- queryAsts = []
65
- getPaths = []
66
- queryResult = { wire: emptyWire() }
67
- getResult = null
68
- clientCallResult = { rows: [] }
69
- clientCallMock.mockClear()
77
+ queryCalls = []
78
+ getTargets = []
79
+ mutations = []
80
+ queryResult = {
81
+ result: {
82
+ kind: 'graph',
83
+ graph: { nodes: [], edges: [] },
84
+ selection: { kind: 'node', ids: [] },
85
+ },
86
+ page: {},
87
+ }
88
+ getResult = undefined
89
+ mutationResult = { createdNodes: {} }
70
90
  queryMock.mockClear()
71
- getMock.mockClear()
72
- bindGraphMock.mockClear()
91
+ getOrThrowMock.mockClear()
92
+ mutateMock.mockClear()
73
93
  expandSelfInPathMock.mockClear()
74
94
  withSelfHintMock.mockClear()
75
95
  runKernelCommandMock.mockClear()
76
96
 
77
- originalStdoutWrite = process.stdout.write.bind(process.stdout)
97
+ originalStdoutWrite = process.stdout.write
78
98
  originalConsoleError = console.error
79
99
  originalExit = process.exit
80
100
  process.stdout.write = ((chunk: string | Uint8Array) => {
81
101
  stdout += typeof chunk === 'string' ? chunk : Buffer.from(chunk).toString('utf8')
82
102
  return true
83
103
  }) as typeof process.stdout.write
84
- console.error = ((...args: unknown[]) => {
85
- errors.push(args.map(String).join(' '))
104
+ console.error = ((...values: unknown[]) => {
105
+ errors.push(values.map(String).join(' '))
86
106
  }) as typeof console.error
87
107
  process.exit = ((code?: string | number | null) => {
88
108
  throw new ExitError(code)
@@ -96,106 +116,156 @@ afterEach(() => {
96
116
  })
97
117
 
98
118
  describe('query command', () => {
99
- test('lowers roots and selectors to the expected QueryASTInput', async () => {
119
+ /** @evidence TEST-CLI-QUERY-DISPATCHES-CANONICAL-V6 */
120
+ test('dispatches the supported source/edge subset as Query V6 with its cursor', async () => {
100
121
  const { queryCommand } = await import('../query')
101
122
 
102
- await queryCommand(['/root', '@self'], {
123
+ await queryCommand(['/:notes.example.dev:class.Note'], {
103
124
  json: true,
104
- depth: '2',
105
- children:
106
- '{"classes":["/:kernel.astrale.ai:class.Folder"],"limit":50,"cursor":"child-cursor","order":{"by":"id","dir":"asc"}}',
107
- edges:
108
- '[{"as":"perm","classes":["/:kernel.astrale.ai:class.has_perm"],"direction":"out","limit":10,"cursor":"edge-cursor","order":{"by":"id","dir":"desc"}},{"direction":"both"}]',
125
+ edge: '/:notes.example.dev:class.references',
126
+ direction: 'incoming',
127
+ limit: '25',
128
+ cursor: 'next-page',
109
129
  })
110
130
 
111
- expect(queryAsts).toEqual([
131
+ expect(JSON.parse(JSON.stringify(queryCalls))).toEqual([
112
132
  {
113
- version: 1,
114
- from: ['/root', '@expanded-self'],
115
- steps: [
116
- {
117
- expand: {
118
- edge: 'has_parent',
119
- dir: 'in',
120
- depth: 2,
121
- filter: { class: ['/:kernel.astrale.ai:class.Folder'] },
122
- page: { limit: 50, cursor: 'child-cursor' },
123
- order: { by: 'id', dir: 'asc' },
124
- },
133
+ ast: {
134
+ format: 'astrale.graph.query',
135
+ version: 'v6',
136
+ source: {
137
+ kind: 'node',
138
+ terms: [{ kind: 'path', path: '/:notes.example.dev:class.Note' }],
139
+ binding: 'n0',
125
140
  },
126
- {
127
- expand: {
128
- edge: ['/:kernel.astrale.ai:class.has_perm'],
129
- dir: 'out',
130
- as: 'perm',
131
- page: { limit: 10, cursor: 'edge-cursor' },
132
- order: { by: 'id', dir: 'desc' },
141
+ steps: [
142
+ {
143
+ op: 'expand',
144
+ from: 'n0',
145
+ via: [{ origin: 'notes.example.dev', kind: 'class', name: 'references' }],
146
+ direction: 'incoming',
147
+ bindings: { edge: 'e0', node: 'n1' },
133
148
  },
134
- },
135
- { expand: { dir: 'both', as: 'e1' } },
136
- ],
149
+ ],
150
+ select: { kind: 'graph', binding: 'n1' },
151
+ },
152
+ options: { page: { size: 25, after: 'next-page' } },
137
153
  },
138
154
  ])
139
- expect(expandSelfInPathMock.mock.calls.map((call) => call[0])).toEqual(['/root', '@self'])
140
- expect(withSelfHintMock).toHaveBeenCalledTimes(1)
141
155
  })
142
156
 
143
- test('rejects an invalid children selector before dispatch', async () => {
157
+ test('authors a Definition source through the same canonical Query V6 call', async () => {
144
158
  const { queryCommand } = await import('../query')
145
159
 
146
- await expect(queryCommand(['/'], { json: true, children: '{"bogus":true}' })).rejects.toEqual(
147
- new ExitError(1),
148
- )
160
+ await queryCommand([], {
161
+ json: true,
162
+ definition: '/:issues.astrale.ai:class.Issue',
163
+ limit: '20',
164
+ })
149
165
 
150
- expect(errors.join('\n')).toContain('--children invalid selector')
151
- expect(queryMock).not.toHaveBeenCalled()
166
+ expect(JSON.parse(JSON.stringify(queryCalls))).toEqual([
167
+ {
168
+ ast: {
169
+ format: 'astrale.graph.query',
170
+ version: 'v6',
171
+ source: {
172
+ kind: 'node',
173
+ terms: [
174
+ {
175
+ kind: 'definition',
176
+ definition: { origin: 'issues.astrale.ai', kind: 'class', name: 'Issue' },
177
+ },
178
+ ],
179
+ binding: 'n0',
180
+ },
181
+ steps: [],
182
+ select: { kind: 'graph', binding: 'n0' },
183
+ },
184
+ options: { page: { size: 20 } },
185
+ },
186
+ ])
152
187
  })
153
188
 
154
- test('rejects mixing --ast with positional roots', async () => {
189
+ /** @evidence TEST-CLI-QUERY-REJECTS-V1-BEFORE-CONNECTION */
190
+ test('rejects a legacy AST before opening the command connection', async () => {
155
191
  const { queryCommand } = await import('../query')
156
192
 
157
193
  await expect(
158
- queryCommand(['/'], { json: true, ast: '{"version":1,"from":["/"]}' }),
194
+ queryCommand([], { json: true, ast: '{"version":1,"from":["/"]}' }),
159
195
  ).rejects.toEqual(new ExitError(1))
160
196
 
161
- expect(errors.join('\n')).toContain(
162
- '--ast cannot be used with positional roots or --depth/--children/--edges',
163
- )
164
- expect(queryMock).not.toHaveBeenCalled()
197
+ expect(runKernelCommandMock).not.toHaveBeenCalled()
198
+ expect(errors.join('\n')).toContain('expected "astrale.graph.query" at /format')
165
199
  })
166
200
  })
167
201
 
168
202
  describe('get command', () => {
169
- test('dispatches a point read and prints the flat wire projection', async () => {
203
+ /** @evidence TEST-CLI-GET-RETURNS-CANONICAL-NODE */
204
+ test('point-reads the target and does not synthesize Path or backend fields', async () => {
170
205
  const { getCommand } = await import('../get')
171
- const row = {
172
- id: 'n1',
173
- class: '/:demo:class.Widget',
174
- path: '/demo/widget',
175
- props: { name: 'Widget' },
176
- __labels: ['Node', 'Widget'],
177
- classId: 'class-1',
206
+ getResult = {
207
+ id: 'note-1',
208
+ class: '/:notes.example.dev:class.Note',
209
+ props: { 'notes.example.dev:class.Note.property.title': 'Hello' },
178
210
  }
179
- getResult = row
180
211
 
181
- await getCommand('/demo/widget', { json: true })
212
+ await getCommand('/:notes.example.dev:class.Note', { json: true })
182
213
 
183
- expect(getPaths).toEqual(['/demo/widget'])
184
- expect(JSON.parse(stdout)).toEqual({
185
- id: 'n1',
186
- class: '/:demo:class.Widget',
187
- path: '/demo/widget',
188
- props: { name: 'Widget' },
189
- })
214
+ expect(getTargets).toEqual(['/:notes.example.dev:class.Note'])
215
+ expect(JSON.parse(stdout)).toEqual(getResult)
216
+ expect(stdout).not.toContain('__labels')
217
+ expect(stdout).not.toContain('classId')
190
218
  })
219
+ })
191
220
 
192
- test('errors with exit 1 when no node resolves', async () => {
193
- const { getCommand } = await import('../get')
194
- getResult = null
221
+ describe('ls command', () => {
222
+ /** @evidence TEST-CLI-LS-REQUIRES-EXACT-EDGE-BEFORE-CONNECTION */
223
+ test('rejects a generic child listing before opening the command connection', async () => {
224
+ const { lsCommand } = await import('../ls')
225
+
226
+ await expect(lsCommand('@node', { json: true })).rejects.toEqual(new ExitError(1))
227
+
228
+ expect(runKernelCommandMock).not.toHaveBeenCalled()
229
+ expect(errors.join('\n')).toContain('Kernel V2 has no universal child relation')
230
+ })
231
+ })
195
232
 
196
- await expect(getCommand('/missing', { json: true })).rejects.toEqual(new ExitError(1))
233
+ describe('mutate command', () => {
234
+ /** @evidence TEST-CLI-MUTATE-DISPATCHES-CANONICAL-V3 */
235
+ test('admits authoring input and dispatches one canonical Mutation V3 document', async () => {
236
+ const { mutateCommand } = await import('../mutate')
237
+ mutationResult = { createdNodes: { note: 'note-1' } }
197
238
 
198
- expect(getPaths).toEqual(['/missing'])
199
- expect(errors.join('\n')).toContain('node "/missing" not found or not visible')
239
+ await mutateCommand({
240
+ json: true,
241
+ data: JSON.stringify({
242
+ preconditions: [],
243
+ operations: [
244
+ {
245
+ op: 'node.create',
246
+ as: 'note',
247
+ class: '/:notes.example.dev:class.Note',
248
+ props: {},
249
+ },
250
+ ],
251
+ }),
252
+ })
253
+
254
+ expect(JSON.parse(JSON.stringify(mutations))).toEqual([
255
+ {
256
+ format: 'astrale.graph.mutation',
257
+ version: 'v3',
258
+ preconditions: [],
259
+ operations: [
260
+ {
261
+ op: 'node.create',
262
+ as: 'note',
263
+ class: '/:notes.example.dev:class.Note',
264
+ props: {},
265
+ },
266
+ ],
267
+ },
268
+ ])
269
+ expect(JSON.parse(stdout)).toEqual({ createdNodes: { note: 'note-1' } })
200
270
  })
201
271
  })
@@ -1,23 +1,28 @@
1
+ import type { ResolvedView } from '@astrale-os/shell'
2
+
3
+ import { Path } from '@astrale-os/sdk/graph/path'
1
4
  import { beforeEach, describe, expect, mock, test } from 'bun:test'
2
5
 
3
- const getMock = mock(async (_path: string) => ({ id: 'issue-id' }))
4
- const clientCallMock = mock(async (): Promise<unknown> => {
5
- throw new Error('View:resolve must not run for a bare --view-url')
6
- })
6
+ const viewsForMock = mock(async (_target: unknown): Promise<unknown> => ({ views: [] }))
7
7
  const abMock = mock(async (_args: string[]) => ({
8
8
  ok: true,
9
9
  data: { snapshot: '- button "Ready" [ref=e1]' },
10
10
  error: null,
11
11
  }))
12
12
 
13
- mock.module('../../kernel', () => ({
14
- bindGraph: () => ({ get: getMock }),
13
+ mock.module('../../connection', () => ({
15
14
  expandSelfInPath: async (path: string) => ({ path }),
16
- resolveKernelTarget: mock(),
17
- withKernelClient: async (
15
+ withClientSession: async (
18
16
  _opts: unknown,
19
- run: (ctx: { client: { call: typeof clientCallMock } }) => Promise<unknown>,
20
- ) => run({ client: { call: clientCallMock } }),
17
+ run: (ctx: {
18
+ session: { viewsFor: typeof viewsForMock }
19
+ target: { url: string; kernelIssuer: string }
20
+ }) => Promise<unknown>,
21
+ ) =>
22
+ run({
23
+ session: { viewsFor: viewsForMock },
24
+ target: { url: 'https://kernel.test', kernelIssuer: 'https://kernel.test' },
25
+ }),
21
26
  }))
22
27
 
23
28
  mock.module('../../lib/browser', () => ({
@@ -28,62 +33,106 @@ mock.module('../../lib/browser', () => ({
28
33
  }))
29
34
 
30
35
  beforeEach(() => {
31
- getMock.mockClear()
32
- clientCallMock.mockClear()
36
+ viewsForMock.mockClear()
33
37
  abMock.mockClear()
34
38
  })
35
39
 
40
+ const route = (value: unknown) => value as ResolvedView['route']
41
+
42
+ const resolved = [
43
+ route({
44
+ key: 'ai-gateway.astrale.ai:view.chat',
45
+ href: 'https://ai-gateway.astrale.ai/ui/chat',
46
+ handshake: 'shell' as const,
47
+ issuer: 'https://ai-gateway.astrale.ai',
48
+ etag: `sha256:${'a'.repeat(64)}`,
49
+ revision: `sha256:${'b'.repeat(64)}`,
50
+ declaration: {
51
+ target: {
52
+ kind: 'definition' as const,
53
+ definitions: [{ origin: 'ai-gateway.astrale.ai', kind: 'class' as const, name: 'Model' }],
54
+ },
55
+ auth: 'required' as const,
56
+ },
57
+ }),
58
+ route({
59
+ key: 'ai-gateway.astrale.ai:view.model',
60
+ href: 'https://ai-gateway.astrale.ai/ui/model',
61
+ handshake: 'none' as const,
62
+ issuer: 'https://ai-gateway.astrale.ai',
63
+ etag: `sha256:${'c'.repeat(64)}`,
64
+ revision: `sha256:${'d'.repeat(64)}`,
65
+ declaration: { target: { kind: 'domain' as const }, auth: 'public' as const },
66
+ }),
67
+ ]
68
+
36
69
  describe('view session resolution', () => {
37
- test('uses a bare --view-url target only as shell context', async () => {
70
+ /** @evidence TEST-CLI-VIEW-PRESERVES-HOST-PROVENANCE */
71
+ test('returns one exact target-bound placement without split mount coordinates', async () => {
38
72
  const { resolveSession } = await import('../view')
39
- const targetPath = '/users/user-1/issue-1'
73
+ viewsForMock.mockImplementationOnce(async () => ({ views: resolved }))
40
74
 
41
- const result = await resolveSession(undefined, {
42
- viewUrl: 'http://127.0.0.1:5173/ui/issues',
43
- handshake: 'shell',
44
- target: targetPath,
45
- })
75
+ const result = await resolveSession('@model-id', { view: 'chat' })
46
76
 
47
- expect(getMock).toHaveBeenCalledWith(targetPath)
48
- expect(clientCallMock).not.toHaveBeenCalled()
49
- expect(result).toEqual({
50
- view: {
51
- url: 'http://127.0.0.1:5173/ui/issues',
52
- functionId: 'dev-view',
53
- handshake: 'shell',
54
- name: 'dev',
55
- },
56
- target: { id: 'issue-id', path: targetPath },
57
- candidates: [],
58
- })
77
+ expect(viewsForMock).toHaveBeenCalledTimes(1)
78
+ expect(String(viewsForMock.mock.calls[0]?.[0])).toBe('@model-id')
79
+ expect(result.view).toEqual({ target: Path.parse('@model-id').raw, route: resolved[0] })
80
+ expect(result.view).not.toHaveProperty('url')
81
+ expect(result.view).not.toHaveProperty('functionId')
82
+ expect(result.view).not.toHaveProperty('handshake')
59
83
  })
60
84
 
61
- test('lists every candidate without requiring a view selection', async () => {
85
+ test('lists every candidate with its complete placement provenance', async () => {
62
86
  const { resolveSession } = await import('../view')
63
- const targetPath = '/domains/ai-gateway.astrale.ai/core/gemini-3-5-flash'
64
- const candidates = [
65
- {
66
- id: 'chat-id',
67
- path: '/domains/ai-gateway.astrale.ai/views/chat',
87
+ viewsForMock.mockImplementationOnce(async () => ({ views: resolved }))
88
+
89
+ const result = await resolveSession('@model-id', { list: true })
90
+
91
+ expect(result.view).toBeUndefined()
92
+ expect(result.candidates).toEqual([
93
+ expect.objectContaining({
94
+ target: '@model-id',
95
+ route: resolved[0],
96
+ id: 'ai-gateway.astrale.ai:view.chat',
97
+ path: '/:ai-gateway.astrale.ai:view.chat',
68
98
  url: 'https://ai-gateway.astrale.ai/ui/chat',
69
- origin: 'class' as const,
70
- },
71
- {
72
- id: 'model-id',
73
- path: '/domains/ai-gateway.astrale.ai/views/model',
74
- url: 'https://ai-gateway.astrale.ai/ui/model',
75
- origin: 'class' as const,
76
- },
77
- ]
78
- clientCallMock.mockImplementationOnce(async () => candidates)
99
+ name: 'chat',
100
+ handshake: 'shell',
101
+ origin: 'class',
102
+ }),
103
+ expect.objectContaining({
104
+ target: '@model-id',
105
+ route: resolved[1],
106
+ id: 'ai-gateway.astrale.ai:view.model',
107
+ handshake: 'none',
108
+ origin: 'self',
109
+ }),
110
+ ])
111
+ })
79
112
 
80
- const result = await resolveSession(targetPath, { list: true })
113
+ test('resolves an explicit ViewPath against its Domain owner when no target is supplied', async () => {
114
+ const { resolveSession } = await import('../view')
115
+ viewsForMock.mockImplementationOnce(async () => ({ views: resolved }))
116
+
117
+ const result = await resolveSession('/:ai-gateway.astrale.ai:view.model', {})
81
118
 
82
- expect(result).toEqual({
83
- target: { id: 'issue-id', path: targetPath },
84
- candidates,
119
+ expect(String(viewsForMock.mock.calls[0]?.[0])).toBe('/:ai-gateway.astrale.ai')
120
+ expect(result.view).toEqual({
121
+ target: Path.parse('/:ai-gateway.astrale.ai').raw,
122
+ route: resolved[1],
85
123
  })
86
124
  })
125
+
126
+ test('retains legacy override flags but refuses to forge V2 placement provenance', async () => {
127
+ const { rejectUnrepresentableOverrides } = await import('../view')
128
+
129
+ expect(() => rejectUnrepresentableOverrides({ viewUrl: 'http://localhost:8787' })).toThrow(
130
+ 'verified View placement',
131
+ )
132
+ expect(() => rejectUnrepresentableOverrides({ handshake: 'none' })).toThrow(
133
+ 'verified View placement',
134
+ )
135
+ })
87
136
  })
88
137
 
89
138
  describe('view capture timing', () => {
@@ -1,10 +1,10 @@
1
1
  import chalk from 'chalk'
2
2
 
3
- import type { CommandDefinition } from '../../command'
3
+ import type { CommandDefinition } from '../../program/index'
4
4
 
5
+ import { getDefault } from '../../identity/index'
5
6
  import { resolveAdminTargetFromStore } from '../../lib/admin-target'
6
7
  import { readConfig } from '../../lib/config'
7
- import { getDefault } from '../../lib/identity'
8
8
  import { readInstances } from '../../lib/instance'
9
9
  import { fatal, log } from '../../lib/log'
10
10
  import { isMachine, output, RAW_OUTPUT_OPTIONS, type RawOutputOpts } from '../../lib/output'
@@ -46,7 +46,7 @@ export default {
46
46
 
47
47
  console.log(chalk.bold('Admin'))
48
48
  console.log(` ${chalk.bold(target.name)} ${chalk.dim(target.url)}`)
49
- console.log(` issuer: ${chalk.dim(target.issuer)}`)
49
+ console.log(` kernel issuer: ${chalk.dim(target.kernelIssuer)}`)
50
50
  console.log(` source: ${target.source}`)
51
51
  if (status.identity) {
52
52
  const mark = status.identity.registered ? chalk.green('yes') : chalk.yellow('no')
@@ -1,6 +1,10 @@
1
- import type { CommandDefinition } from '../../command'
1
+ import type { CommandDefinition } from '../../program/index'
2
2
 
3
- import { AdminTargetConfigSchema, DEFAULT_ADMIN_TARGET_NAME } from '../../lib/admin-target'
3
+ import {
4
+ AdminTargetConfigSchema,
5
+ DEFAULT_ADMIN_DOMAIN_ISSUER,
6
+ DEFAULT_ADMIN_TARGET_NAME,
7
+ } from '../../lib/admin-target'
4
8
  import { readConfig, writeConfig } from '../../lib/config'
5
9
  import { resolveInstance } from '../../lib/instance'
6
10
  import { fatal, log } from '../../lib/log'
@@ -8,7 +12,8 @@ import { fatal, log } from '../../lib/log'
8
12
  type AdminUseOpts = {
9
13
  url?: string
10
14
  name?: string
11
- issuer?: string
15
+ kernelIssuer?: string
16
+ domainIssuer?: string
12
17
  }
13
18
 
14
19
  export default {
@@ -23,7 +28,7 @@ Behavior:
23
28
 
24
29
  Examples:
25
30
  $ astrale admin use admin
26
- $ astrale admin use --url https://admin.eu.astrale.ai/api
31
+ $ astrale admin use --url https://admin.eu.astrale.ai/api --domain-issuer https://admin.beta.astrale.ai
27
32
  $ astrale admin status
28
33
  `,
29
34
  arguments: [{ name: 'bookmark', description: 'Bookmarked admin instance', required: false }],
@@ -33,7 +38,14 @@ Examples:
33
38
  flags: '--name <name>',
34
39
  description: 'Friendly name / registration slug for direct URL admin target',
35
40
  },
36
- { flags: '--issuer <url>', description: 'Admin kernel issuer/audience if different from URL' },
41
+ {
42
+ flags: '--kernel-issuer <url>',
43
+ description: 'Admin Kernel issuer/audience if different from URL',
44
+ },
45
+ {
46
+ flags: '--domain-issuer <url>',
47
+ description: 'Exact native Admin Domain issuer used for token exchange',
48
+ },
37
49
  ],
38
50
  action: async (bookmark: string | undefined, opts: AdminUseOpts) => {
39
51
  try {
@@ -43,13 +55,17 @@ Examples:
43
55
 
44
56
  const config = await readConfig()
45
57
  if (opts.url) {
58
+ if (opts.domainIssuer === undefined) {
59
+ fatal('Direct Admin targets require --domain-issuer <url> for token exchange.')
60
+ }
46
61
  const name = opts.name ?? DEFAULT_ADMIN_TARGET_NAME
47
62
  await writeConfig({
48
63
  ...config,
49
64
  admin: AdminTargetConfigSchema.parse({
50
65
  name,
51
66
  url: opts.url,
52
- issuer: opts.issuer ?? opts.url,
67
+ kernelIssuer: opts.kernelIssuer ?? opts.url,
68
+ domainIssuer: opts.domainIssuer,
53
69
  }),
54
70
  })
55
71
  log.success(`Admin target: ${name} (${opts.url})`)
@@ -63,10 +79,17 @@ Examples:
63
79
  }
64
80
 
65
81
  const resolved = await resolveInstance(bookmark)
82
+ if (resolved.domainIssuer === undefined) {
83
+ fatal(
84
+ `Admin bookmark "${resolved.name}" has no Domain issuer. Re-bookmark it with --domain-issuer <url>.`,
85
+ )
86
+ }
66
87
  await writeConfig({
67
88
  ...config,
68
89
  admin: AdminTargetConfigSchema.parse({
69
90
  instance: resolved.name,
91
+ domainIssuer:
92
+ opts.domainIssuer ?? config.admin.domainIssuer ?? DEFAULT_ADMIN_DOMAIN_ISSUER,
70
93
  }),
71
94
  })
72
95
  log.success(`Admin target: ${resolved.name} (${resolved.url})`)
@@ -1,4 +1,4 @@
1
- import type { CommandDefinition } from '../../command'
1
+ import type { CommandDefinition } from '../../program/index'
2
2
 
3
3
  import { type IdpSession } from '../../lib/idp'
4
4
  import { log } from '../../lib/log'