@astrale-os/cli 0.8.1-alpha.0 → 0.8.1-alpha.3

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 (314) hide show
  1. package/dist/astrale.js +63133 -41267
  2. package/dist/public/connect-core.js +55030 -0
  3. package/dist/public/keys/index.js +45631 -0
  4. package/dist/public/paths/index.js +43990 -0
  5. package/dist/types/admin/instance/model.d.ts +40 -0
  6. package/dist/types/connect-core.d.ts +22 -0
  7. package/dist/types/connection/auth.d.ts +33 -0
  8. package/dist/types/errors.d.ts +23 -0
  9. package/dist/types/identity/registry.d.ts +25 -0
  10. package/dist/types/keys/algorithm.d.ts +6 -0
  11. package/dist/types/keys/credential.d.ts +21 -0
  12. package/dist/types/keys/index.d.ts +4 -0
  13. package/dist/types/keys/pair.d.ts +34 -0
  14. package/dist/types/lib/admin-target.d.ts +74 -0
  15. package/dist/types/lib/ca-fetch.d.ts +2 -0
  16. package/dist/types/lib/config.d.ts +71 -0
  17. package/dist/types/lib/format.d.ts +1 -0
  18. package/dist/types/lib/idp-session.d.ts +33 -0
  19. package/dist/types/lib/idp.d.ts +397 -0
  20. package/dist/types/lib/instance-target.d.ts +36 -0
  21. package/dist/types/lib/instance.d.ts +186 -0
  22. package/dist/types/lib/log.d.ts +30 -0
  23. package/dist/types/lib/login-flow.d.ts +49 -0
  24. package/dist/types/lib/meta.d.ts +36 -0
  25. package/dist/types/lib/output.d.ts +83 -0
  26. package/dist/types/lib/table.d.ts +18 -0
  27. package/dist/types/lib/validation.d.ts +11 -0
  28. package/dist/types/paths/index.d.ts +2 -0
  29. package/dist/types/state/exchange-credentials.d.ts +25 -0
  30. package/dist/types/state/files.d.ts +9 -0
  31. package/dist/types/state/identities.d.ts +42 -0
  32. package/dist/types/state/index.d.ts +8 -0
  33. package/dist/types/state/paths.d.ts +32 -0
  34. package/package.json +22 -7
  35. package/src/admin/.spec/api.d.ts +3 -0
  36. package/src/admin/.spec/architecture.md +15 -0
  37. package/src/admin/.spec/icon.svg +3 -0
  38. package/src/admin/.spec/layout.ts +6 -0
  39. package/src/admin/catalog/.spec/api.d.ts +65 -0
  40. package/src/admin/catalog/.spec/architecture.md +6 -0
  41. package/src/admin/catalog/.spec/icon.svg +3 -0
  42. package/src/admin/catalog/.spec/layout.ts +6 -0
  43. package/src/admin/catalog/__tests__/client.test.ts +171 -0
  44. package/src/admin/catalog/client.ts +214 -0
  45. package/src/admin/catalog/index.ts +13 -0
  46. package/src/admin/catalog/model.ts +42 -0
  47. package/src/admin/catalog/tsconfig.json +8 -0
  48. package/src/admin/graph/.spec/api.d.ts +28 -0
  49. package/src/admin/graph/.spec/architecture.md +5 -0
  50. package/src/admin/graph/.spec/icon.svg +3 -0
  51. package/src/admin/graph/.spec/layout.ts +3 -0
  52. package/src/admin/graph/index.ts +6 -0
  53. package/src/admin/graph/nodes.ts +56 -0
  54. package/src/admin/index.ts +2 -0
  55. package/src/admin/instance/.spec/api.d.ts +66 -0
  56. package/src/admin/instance/.spec/architecture.md +10 -0
  57. package/src/admin/instance/.spec/icon.svg +3 -0
  58. package/src/admin/instance/.spec/layout.ts +6 -0
  59. package/src/admin/instance/__tests__/client.test.ts +289 -0
  60. package/src/admin/instance/client.ts +298 -0
  61. package/src/admin/instance/index.ts +16 -0
  62. package/src/admin/instance/model.ts +65 -0
  63. package/src/admin/instance/tsconfig.json +8 -0
  64. package/src/commands/__tests__/admin-instance.test.ts +3 -36
  65. package/src/commands/__tests__/call-describe.test.ts +67 -0
  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__/install-direct.test.ts +20 -0
  70. package/src/commands/__tests__/instance-list-admin.test.ts +26 -0
  71. package/src/commands/__tests__/logs.test.ts +71 -96
  72. package/src/commands/__tests__/ls.test.ts +21 -17
  73. package/src/commands/__tests__/read-commands.test.ts +171 -101
  74. package/src/commands/__tests__/view.test.ts +100 -51
  75. package/src/commands/admin/status.ts +3 -3
  76. package/src/commands/admin/use.ts +29 -6
  77. package/src/commands/auth/login.ts +1 -1
  78. package/src/commands/auth/logout.ts +4 -2
  79. package/src/commands/auth/status.ts +2 -2
  80. package/src/commands/auth/token.ts +2 -2
  81. package/src/commands/browser.ts +1 -1
  82. package/src/commands/call-describe.ts +131 -0
  83. package/src/commands/call.ts +69 -109
  84. package/src/commands/describe.ts +52 -147
  85. package/src/commands/domain/install.ts +106 -49
  86. package/src/commands/domain/list.ts +4 -8
  87. package/src/commands/domain/publish.ts +9 -31
  88. package/src/commands/get.ts +33 -66
  89. package/src/commands/identity/__tests__/register.test.ts +93 -0
  90. package/src/commands/identity/create.ts +2 -2
  91. package/src/commands/identity/delete.ts +2 -2
  92. package/src/commands/identity/export.ts +7 -33
  93. package/src/commands/identity/import.ts +21 -70
  94. package/src/commands/identity/list.ts +2 -2
  95. package/src/commands/identity/register.ts +148 -117
  96. package/src/commands/identity/sync.ts +2 -2
  97. package/src/commands/identity/unsync.ts +2 -2
  98. package/src/commands/identity/use.ts +2 -2
  99. package/src/commands/identity/whoami.ts +2 -2
  100. package/src/commands/idp/add.ts +1 -1
  101. package/src/commands/idp/list.ts +1 -1
  102. package/src/commands/idp/refresh.ts +1 -1
  103. package/src/commands/idp/remove.ts +2 -2
  104. package/src/commands/idp/show.ts +1 -1
  105. package/src/commands/instance/active.ts +2 -2
  106. package/src/commands/instance/bookmark.ts +8 -2
  107. package/src/commands/instance/create.ts +1 -1
  108. package/src/commands/instance/delete.ts +4 -10
  109. package/src/commands/instance/forget.ts +1 -1
  110. package/src/commands/instance/list.ts +39 -6
  111. package/src/commands/instance/status.ts +5 -7
  112. package/src/commands/instance/use.ts +3 -3
  113. package/src/commands/logs.ts +171 -278
  114. package/src/commands/ls.ts +78 -204
  115. package/src/commands/mutate.ts +70 -139
  116. package/src/commands/query.ts +100 -278
  117. package/src/commands/session/analyze.ts +1 -1
  118. package/src/commands/session/list.ts +1 -1
  119. package/src/commands/setup.ts +1 -1
  120. package/src/commands/status.ts +1 -1
  121. package/src/commands/studio.ts +1 -1
  122. package/src/commands/token.ts +26 -21
  123. package/src/commands/update.ts +1 -1
  124. package/src/commands/use.ts +2 -3
  125. package/src/commands/view-serve.ts +1 -1
  126. package/src/commands/view.ts +67 -109
  127. package/src/connect-core.test.ts +38 -0
  128. package/src/connect-core.ts +42 -0
  129. package/src/connection/.spec/api.d.ts +111 -0
  130. package/src/connection/.spec/architecture.md +41 -0
  131. package/src/connection/.spec/capabilities/connection.ts +13 -0
  132. package/src/connection/.spec/examples/call.ts +7 -0
  133. package/src/connection/.spec/flows/command.ts +20 -0
  134. package/src/connection/.spec/flows/session.ts +61 -0
  135. package/src/connection/.spec/laws/connection.ts +181 -0
  136. package/src/connection/.spec/layout.ts +19 -0
  137. package/src/{kernel → connection}/__tests__/auth.test.ts +26 -2
  138. package/src/connection/__tests__/ca-fetch.test.ts +53 -0
  139. package/src/connection/__tests__/call.test.ts +17 -0
  140. package/src/connection/__tests__/credential.test.ts +109 -0
  141. package/src/connection/__tests__/errors.test.ts +96 -0
  142. package/src/connection/__tests__/exchange.test.ts +224 -0
  143. package/src/connection/__tests__/fixtures/localhost-cert.pem +19 -0
  144. package/src/connection/__tests__/fixtures/localhost-key.base64 +1 -0
  145. package/src/connection/__tests__/managed.test.ts +39 -0
  146. package/src/connection/__tests__/self.test.ts +51 -0
  147. package/src/connection/__tests__/session.test.ts +140 -0
  148. package/src/connection/__tests__/target.test.ts +93 -0
  149. package/src/{kernel → connection}/auth.ts +8 -8
  150. package/src/connection/call.ts +7 -0
  151. package/src/{kernel/run.ts → connection/command.ts} +22 -13
  152. package/src/connection/credential.ts +114 -0
  153. package/src/{kernel → connection}/errors.ts +32 -9
  154. package/src/connection/exchange.ts +274 -0
  155. package/src/connection/index.ts +8 -0
  156. package/src/connection/self.ts +120 -0
  157. package/src/connection/session.ts +176 -0
  158. package/src/connection/target.ts +111 -0
  159. package/src/connection/tsconfig.json +6 -0
  160. package/src/graph/.spec/api.d.ts +34 -0
  161. package/src/graph/.spec/laws/documents.ts +24 -0
  162. package/src/graph/.spec/laws/projection.ts +13 -0
  163. package/src/graph/.spec/layout.ts +6 -0
  164. package/src/graph/__tests__/mutation.test.ts +45 -0
  165. package/src/graph/__tests__/projection.test.ts +22 -0
  166. package/src/graph/__tests__/query.test.ts +154 -0
  167. package/src/graph/index.ts +3 -0
  168. package/src/graph/mutation.ts +33 -0
  169. package/src/graph/projection.ts +21 -0
  170. package/src/graph/query.ts +108 -0
  171. package/src/graph/tsconfig.json +6 -0
  172. package/src/identity/.history/ard/bootstrap-root-transfer.md +57 -0
  173. package/src/identity/.history/ard/domain-mediated-registration.md +36 -0
  174. package/src/identity/.spec/api.d.ts +124 -0
  175. package/src/identity/.spec/architecture.md +33 -0
  176. package/src/identity/.spec/capabilities/identity.ts +7 -0
  177. package/src/identity/.spec/flows/bootstrap-root.ts +79 -0
  178. package/src/identity/.spec/flows/domain-mediated-registration.ts +51 -0
  179. package/src/identity/.spec/flows/import.ts +29 -0
  180. package/src/identity/.spec/laws/identity.ts +56 -0
  181. package/src/identity/.spec/layout.ts +13 -0
  182. package/src/identity/.spec/schemas/identity-export-v1.schema.json +25 -0
  183. package/src/identity/__tests__/fixtures/registry-journey.ts +53 -0
  184. package/src/identity/__tests__/registration.test.ts +64 -0
  185. package/src/identity/__tests__/registry.test.ts +70 -0
  186. package/src/identity/__tests__/transfer.test.ts +198 -0
  187. package/src/identity/index.ts +23 -0
  188. package/src/identity/registration.ts +72 -0
  189. package/src/identity/registry.ts +172 -0
  190. package/src/identity/transfer.ts +187 -0
  191. package/src/identity/tsconfig.json +6 -0
  192. package/src/index.ts +1 -0
  193. package/src/keys/.spec/api.d.ts +74 -0
  194. package/src/keys/.spec/architecture.md +13 -0
  195. package/src/keys/.spec/laws/keys.ts +43 -0
  196. package/src/keys/.spec/layout.ts +6 -0
  197. package/src/keys/__tests__/algorithm.test.ts +39 -0
  198. package/src/keys/__tests__/keys.test.ts +168 -0
  199. package/src/keys/algorithm.ts +33 -0
  200. package/src/keys/credential.ts +112 -0
  201. package/src/keys/index.ts +14 -0
  202. package/src/keys/pair.ts +219 -0
  203. package/src/keys/tsconfig.json +6 -0
  204. package/src/lib/__tests__/admin-target.test.ts +67 -7
  205. package/src/lib/__tests__/binary.test.ts +2 -3
  206. package/src/lib/__tests__/command-dx.test.ts +2 -2
  207. package/src/lib/__tests__/config.test.ts +3 -2
  208. package/src/lib/__tests__/idp.test.ts +4 -6
  209. package/src/lib/__tests__/instance-target.test.ts +7 -4
  210. package/src/lib/__tests__/local-status.test.ts +1 -1
  211. package/src/lib/__tests__/log.test.ts +31 -0
  212. package/src/lib/__tests__/meta.test.ts +55 -0
  213. package/src/lib/__tests__/sdk-deps.test.ts +1 -1
  214. package/src/lib/__tests__/self.test.ts +8 -230
  215. package/src/lib/__tests__/use-target.test.ts +1 -1
  216. package/src/lib/__tests__/view-open-intent.test.ts +78 -104
  217. package/src/lib/__tests__/view-session.test.ts +99 -0
  218. package/src/lib/admin-domain.ts +39 -25
  219. package/src/lib/admin-instance.ts +38 -43
  220. package/src/lib/admin-target.ts +67 -13
  221. package/src/lib/binary.ts +13 -34
  222. package/src/lib/browser.ts +1 -1
  223. package/src/{kernel → lib}/ca-fetch.ts +35 -27
  224. package/src/lib/config.ts +1 -1
  225. package/src/lib/idp-session.ts +1 -1
  226. package/src/lib/idp.ts +3 -8
  227. package/src/lib/instance-target.ts +12 -8
  228. package/src/lib/instance.ts +34 -2
  229. package/src/lib/local-status.ts +2 -2
  230. package/src/lib/log.ts +3 -1
  231. package/src/lib/login-flow.ts +32 -5
  232. package/src/lib/meta.ts +15 -11
  233. package/src/lib/provision-instance.ts +4 -12
  234. package/src/lib/self.ts +5 -147
  235. package/src/lib/update.ts +1 -1
  236. package/src/lib/use-target.ts +1 -1
  237. package/src/lib/validation.ts +2 -2
  238. package/src/lib/view/open-intent.ts +7 -37
  239. package/src/lib/view/port-allocation.ts +1 -1
  240. package/src/lib/view/resolve.ts +52 -45
  241. package/src/lib/view/server.ts +16 -33
  242. package/src/lib/view/session.ts +51 -22
  243. package/src/paths/.spec/api.d.ts +13 -0
  244. package/src/paths/.spec/architecture.md +5 -0
  245. package/src/paths/.spec/layout.ts +3 -0
  246. package/src/paths/index.ts +13 -0
  247. package/src/program/.spec/api.d.ts +42 -0
  248. package/src/program/.spec/architecture.md +18 -0
  249. package/src/program/.spec/laws/program.ts +29 -0
  250. package/src/program/.spec/layout.ts +14 -0
  251. package/src/program/__tests__/program.test.ts +359 -0
  252. package/src/program/build.ts +190 -0
  253. package/src/program/command.ts +42 -0
  254. package/src/program/index.ts +2 -0
  255. package/src/program/options.ts +40 -0
  256. package/src/{registry.ts → program/registry.ts} +5 -1
  257. package/src/program/tsconfig.json +6 -0
  258. package/src/setup/steps/admin.ts +18 -3
  259. package/src/setup/steps/instance.ts +1 -1
  260. package/src/state/.spec/api.d.ts +133 -0
  261. package/src/state/.spec/architecture.md +31 -0
  262. package/src/state/.spec/capabilities/state.ts +7 -0
  263. package/src/state/.spec/flows/identity-update.ts +22 -0
  264. package/src/state/.spec/laws/state.ts +73 -0
  265. package/src/state/.spec/layout.ts +14 -0
  266. package/src/state/__tests__/exchange-credentials.test.ts +188 -0
  267. package/src/state/__tests__/files.test.ts +113 -0
  268. package/src/state/__tests__/fixtures/identity-transition.ts +37 -0
  269. package/src/state/__tests__/identities.test.ts +142 -0
  270. package/src/state/__tests__/paths.test.ts +27 -0
  271. package/src/state/exchange-credentials.ts +219 -0
  272. package/src/state/files.ts +129 -0
  273. package/src/state/identities.ts +236 -0
  274. package/src/state/index.ts +29 -0
  275. package/src/state/paths.ts +61 -0
  276. package/src/state/tsconfig.json +6 -0
  277. package/src/telemetry/__tests__/redact.test.ts +3 -3
  278. package/src/telemetry/__tests__/trigger.test.ts +1 -1
  279. package/src/telemetry/settings.ts +1 -1
  280. package/src/telemetry/store.ts +1 -1
  281. package/src/test-utils.ts +1 -1
  282. package/studio/client/dist/assets/{elk-api-DVw5WDTH.js → elk-api-D0cBetPW.js} +1 -1
  283. package/studio/client/dist/assets/index-Dspir4w7.js +81 -0
  284. package/studio/client/dist/assets/{index-Dl709Ra1.js → index-bVD2KJgz.js} +4 -4
  285. package/studio/client/dist/index.html +1 -1
  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/tsconfig.json +1 -1
  291. package/viewer/dist/main.js +65 -39
  292. package/src/command.ts +0 -42
  293. package/src/commands/__tests__/help-contract.test.ts +0 -164
  294. package/src/kernel/__tests__/client-owned-lookup.test.ts +0 -51
  295. package/src/kernel/__tests__/errors.test.ts +0 -43
  296. package/src/kernel/__tests__/expand.test.ts +0 -123
  297. package/src/kernel/client.ts +0 -214
  298. package/src/kernel/expand.ts +0 -192
  299. package/src/kernel/graph.ts +0 -96
  300. package/src/kernel/index.ts +0 -29
  301. package/src/kernel/options.ts +0 -22
  302. package/src/kernel/types.ts +0 -14
  303. package/src/lib/__tests__/domain-identity.test.ts +0 -60
  304. package/src/lib/__tests__/fs-atomic.test.ts +0 -104
  305. package/src/lib/__tests__/identity.test.ts +0 -79
  306. package/src/lib/__tests__/keys.test.ts +0 -129
  307. package/src/lib/domain-identity.ts +0 -49
  308. package/src/lib/env.ts +0 -49
  309. package/src/lib/fs-atomic.ts +0 -126
  310. package/src/lib/identity.ts +0 -256
  311. package/src/lib/keys.ts +0 -294
  312. package/src/lib/paths.ts +0 -11
  313. package/src/program.ts +0 -213
  314. package/studio/client/dist/assets/index-CTbkDp3z.js +0 -81
@@ -1,23 +1,12 @@
1
- import { describe, expect, mock, test } from 'bun:test'
1
+ import { describe, expect, test } from 'bun:test'
2
2
 
3
- import type { IdentityStore } from '../../lib/identity'
3
+ import type { IdentityStore } from '../../identity/index'
4
4
 
5
5
  import { AuthError } from '../../errors'
6
- import {
7
- ADMIN_INSTANCE,
8
- adminInstanceMethod,
9
- callOwnedInstances,
10
- findOwnedInstance,
11
- type OwnedInstanceInfo,
12
- } from '../../lib/admin-instance'
6
+ import { findOwnedInstance, type OwnedInstanceInfo } from '../../lib/admin-instance'
13
7
  import { assertAlphaCreateIdentity } from '../instance/create'
14
8
 
15
9
  describe('admin-backed instance commands', () => {
16
- test('target the merged Instance class', () => {
17
- expect(ADMIN_INSTANCE).toBe('/:admin.astrale.ai:class.Instance')
18
- expect(adminInstanceMethod('list')).toBe('/:admin.astrale.ai:class.Instance:list')
19
- })
20
-
21
10
  test('findOwnedInstance matches owner inventory by slug or stable node id', () => {
22
11
  const owned = {
23
12
  id: 'instance-node',
@@ -37,28 +26,6 @@ describe('admin-backed instance commands', () => {
37
26
  expect(findOwnedInstance([owned], 'other')).toBeUndefined()
38
27
  })
39
28
 
40
- test('owner-scoped discovery calls listMine with an empty input', async () => {
41
- const owned: OwnedInstanceInfo[] = [
42
- {
43
- id: 'instance-node',
44
- slug: 'demo',
45
- url: 'https://demo.eu.astrale.ai',
46
- state: 'ready',
47
- },
48
- ]
49
- const call = mock(async () => owned)
50
-
51
- await expect(callOwnedInstances({ call })).resolves.toEqual([
52
- {
53
- id: 'instance-node',
54
- slug: 'demo',
55
- url: 'https://demo.eu.astrale.ai',
56
- state: 'ready',
57
- },
58
- ])
59
- expect(call).toHaveBeenCalledWith('/:admin.astrale.ai:class.Instance:listMine', {})
60
- })
61
-
62
29
  test('instance create preflight points fresh installs at WorkOS login', () => {
63
30
  const store: IdentityStore = {
64
31
  default: 'manager',
@@ -0,0 +1,67 @@
1
+ import { Path } from '@astrale-os/sdk/graph/path'
2
+ import { describe, expect, test } from 'bun:test'
3
+
4
+ import { describeCallableFromSchema } from '../call-describe'
5
+
6
+ const schema = {
7
+ classes: {
8
+ Manager: {
9
+ methods: {
10
+ createInstance: {
11
+ auth: 'authorized',
12
+ description: 'Create one child Instance.',
13
+ input: { type: 'object', required: ['operationId', 'slug'] },
14
+ output: { mode: 'value' },
15
+ },
16
+ },
17
+ },
18
+ },
19
+ functions: {
20
+ journal: {
21
+ description: 'Read the authorized Kernel journal.',
22
+ input: { type: 'object' },
23
+ },
24
+ },
25
+ }
26
+
27
+ describe('describeCallableFromSchema', () => {
28
+ test('reads a static Class method from the Domain schema', () => {
29
+ const described = describeCallableFromSchema(
30
+ Path.parse('/:host.astrale.ai:class.Manager:createInstance'),
31
+ schema,
32
+ )
33
+ expect(described).toMatchObject({
34
+ origin: 'host.astrale.ai',
35
+ class: 'Manager',
36
+ method: 'createInstance',
37
+ dispatch: 'static',
38
+ description: 'Create one child Instance.',
39
+ auth: 'authorized',
40
+ })
41
+ expect(described?.input).toMatchObject({ required: ['operationId', 'slug'] })
42
+ })
43
+
44
+ test('finds an instance method when the receiver Path is not a Class projection', () => {
45
+ expect(
46
+ describeCallableFromSchema(
47
+ Path.parse('/:host.astrale.ai:core.manager::createInstance'),
48
+ schema,
49
+ ),
50
+ ).toMatchObject({
51
+ origin: 'host.astrale.ai',
52
+ class: 'Manager',
53
+ method: 'createInstance',
54
+ dispatch: 'instance',
55
+ })
56
+ })
57
+
58
+ test('reads a standalone Function from the Domain schema', () => {
59
+ expect(
60
+ describeCallableFromSchema(Path.parse('/:kernel.astrale.ai:function.journal'), schema),
61
+ ).toMatchObject({
62
+ origin: 'kernel.astrale.ai',
63
+ function: 'journal',
64
+ description: 'Read the authorized Kernel journal.',
65
+ })
66
+ })
67
+ })
@@ -1,72 +1,25 @@
1
1
  import { describe, expect, test } from 'bun:test'
2
2
 
3
- import { parseKeyValue, coerceValue } from '../call'
4
-
5
- describe('coerceValue', () => {
6
- test('parses JSON objects', () => {
7
- expect(coerceValue('{"a":1}')).toEqual({ a: 1 })
8
- })
9
-
10
- test('parses JSON arrays', () => {
11
- expect(coerceValue('[1,2,3]')).toEqual([1, 2, 3])
12
- })
13
-
14
- test('falls through on invalid JSON object', () => {
15
- expect(coerceValue('{not-json}')).toBe('{not-json}')
16
- })
17
-
18
- test('parses booleans', () => {
19
- expect(coerceValue('true')).toBe(true)
20
- expect(coerceValue('false')).toBe(false)
21
- })
22
-
23
- test('parses null', () => {
24
- expect(coerceValue('null')).toBe(null)
25
- })
26
-
27
- test('parses numbers', () => {
28
- expect(coerceValue('42')).toBe(42)
29
- expect(coerceValue('3.14')).toBe(3.14)
30
- expect(coerceValue('-1')).toBe(-1)
31
- expect(coerceValue('0')).toBe(0)
32
- })
33
-
34
- test('returns strings for non-special values', () => {
35
- expect(coerceValue('hello')).toBe('hello')
36
- expect(coerceValue('')).toBe('')
37
- expect(coerceValue('some text')).toBe('some text')
38
- })
39
-
40
- test('preserves hex, octal, and exotic numeric strings as strings', () => {
41
- expect(coerceValue('0x1f')).toBe('0x1f')
42
- expect(coerceValue('0o77')).toBe('0o77')
43
- expect(coerceValue('0b101')).toBe('0b101')
44
- expect(coerceValue('1e308')).toBe('1e308')
45
- expect(coerceValue('Infinity')).toBe('Infinity')
46
- })
47
- })
48
-
49
- describe('parseKeyValue', () => {
50
- test('parses simple key=value pairs', () => {
51
- expect(parseKeyValue(['name=test', 'count=5'])).toEqual({
52
- name: 'test',
53
- count: 5,
54
- })
55
- })
56
-
57
- test('handles values with = in them', () => {
58
- expect(parseKeyValue(['query=a=b'])).toEqual({ query: 'a=b' })
59
- })
60
-
61
- test('throws on missing =', () => {
62
- expect(() => parseKeyValue(['noequals'])).toThrow('expected key=value')
63
- })
64
-
65
- test('parses JSON values', () => {
66
- expect(parseKeyValue(['data={"x":1}'])).toEqual({ data: { x: 1 } })
67
- })
68
-
69
- test('handles empty value', () => {
70
- expect(parseKeyValue(['key='])).toEqual({ key: '' })
3
+ import { materializeCallResult } from '../call'
4
+
5
+ describe('call command result lifetime', () => {
6
+ /** @evidence TEST-CLI-CALL-DRAINS-STREAM-IN-CONNECTION-ACTION */
7
+ test('drains a session-backed stream before returning it for presentation', async () => {
8
+ let sessionOpen = true
9
+ async function* stream() {
10
+ for (const value of ['first', 'second']) {
11
+ if (!sessionOpen) throw new Error('session closed before stream consumption')
12
+ yield value
13
+ }
14
+ }
15
+
16
+ const sessionStream = Object.assign(stream(), { async cancel() {} })
17
+ const materialized = await materializeCallResult({ kind: 'stream', stream: sessionStream })
18
+ sessionOpen = false
19
+
20
+ expect(materialized).toEqual({ kind: 'stream', values: ['first', 'second'] })
21
+ expect(Object.isFrozen(materialized)).toBe(true)
22
+ if (materialized.kind !== 'stream') throw new Error('expected a materialized stream')
23
+ expect(Object.isFrozen(materialized.values)).toBe(true)
71
24
  })
72
25
  })
@@ -2,9 +2,6 @@ import { afterEach, beforeEach, describe, expect, mock, test } from 'bun:test'
2
2
 
3
3
  import type { OwnedInstanceInfo } from '../../lib/admin-instance'
4
4
 
5
- import { adminDomainMethod } from '../../lib/admin-domain'
6
- import { adminInstanceMethod } from '../../lib/admin-instance'
7
-
8
5
  class ExitError extends Error {
9
6
  constructor(readonly code: string | number | null | undefined) {
10
7
  super(`process.exit(${String(code)})`)
@@ -14,21 +11,23 @@ class ExitError extends Error {
14
11
  const calls: Array<{ path: string; params: unknown }> = []
15
12
  let inventory: OwnedInstanceInfo[] = []
16
13
 
17
- const clientCall = mock(async (path: string, params: unknown): Promise<unknown> => {
14
+ const hostCall = mock(async (call: { target: string; input: unknown }): Promise<unknown> => {
15
+ const path = call.target
16
+ const params = call.input
18
17
  calls.push({ path, params })
19
- if (path === adminInstanceMethod('listMine')) return inventory
20
18
  throw new Error(`Unexpected admin call: ${path}`)
21
19
  })
22
20
 
23
- mock.module('../../kernel', () => ({
21
+ mock.module('../../connection', () => ({
22
+ createPathCall: (path: string, input: unknown) => ({
23
+ target: path,
24
+ input,
25
+ }),
24
26
  runKernelCommand: mock(),
25
- }))
26
-
27
- mock.module('../../kernel/client', () => ({
28
- withAdminKernelClient: async (
27
+ withAdminClientSession: async (
29
28
  _opts: unknown,
30
- run: (ctx: { client: { call: typeof clientCall } }) => Promise<unknown>,
31
- ) => run({ client: { call: clientCall } }),
29
+ run: (ctx: { session: { call: typeof hostCall } }) => Promise<unknown>,
30
+ ) => run({ session: { call: hostCall } }),
32
31
  }))
33
32
 
34
33
  let stderr = ''
@@ -39,7 +38,7 @@ beforeEach(() => {
39
38
  calls.length = 0
40
39
  inventory = []
41
40
  stderr = ''
42
- clientCall.mockClear()
41
+ hostCall.mockClear()
43
42
  originalExit = process.exit
44
43
  originalStderrWrite = process.stderr.write.bind(process.stderr)
45
44
  process.exit = ((code?: string | number | null) => {
@@ -57,20 +56,20 @@ afterEach(() => {
57
56
  })
58
57
 
59
58
  async function runInstall(instance: string): Promise<void> {
60
- const command = (await import('../domain/install')).default
61
- const action = command.action as (
62
- target: string | undefined,
63
- opts: Record<string, unknown>,
64
- ) => Promise<void>
65
- await action('crm.acme.dev', {
66
- instance,
67
- json: true,
68
- noPrompt: true,
69
- })
59
+ const { installViaAdmin } = await import('../domain/install')
60
+ await installViaAdmin(
61
+ 'crm.acme.dev',
62
+ {
63
+ instance,
64
+ json: true,
65
+ noPrompt: true,
66
+ },
67
+ { listInstances: async () => inventory },
68
+ )
70
69
  }
71
70
 
72
71
  describe('admin domain install owner boundary', () => {
73
- test('rejects a foreign target after listMine without attempting an install', async () => {
72
+ test('rejects a foreign target after the V2 graph inventory without attempting an install', async () => {
74
73
  inventory = [
75
74
  {
76
75
  id: 'owned-id',
@@ -86,7 +85,7 @@ describe('admin domain install owner boundary', () => {
86
85
  error: 'INSTANCE_NOT_MANAGED',
87
86
  message: 'Instance "foreign" is not admin-managed (managed: owned).',
88
87
  })
89
- expect(calls).toEqual([{ path: adminInstanceMethod('listMine'), params: {} }])
88
+ expect(calls).toEqual([])
90
89
  })
91
90
 
92
91
  test.each([
@@ -116,7 +115,7 @@ describe('admin domain install owner boundary', () => {
116
115
  expect(payload.error).toBe('INSTANCE_NOT_READY')
117
116
  expect(payload.message).toContain(`Instance "owned" is ${status.state}`)
118
117
  expect(payload.hint).toBe(status.error ?? 'Run: astrale instance status owned')
119
- expect(calls).toEqual([{ path: adminInstanceMethod('listMine'), params: {} }])
120
- expect(calls.some((call) => call.path === adminDomainMethod('install'))).toBe(false)
118
+ expect(calls).toEqual([])
119
+ expect(calls).toEqual([])
121
120
  })
122
121
  })
@@ -1,6 +1,7 @@
1
1
  import { describe, expect, test } from 'bun:test'
2
2
 
3
- import { ADMIN_DOMAIN, adminDomainMethod, type DomainInfo } from '../../lib/admin-domain'
3
+ import type { DomainInfo } from '../../lib/admin-domain'
4
+
4
5
  import { byDefaultThenName, domainProjection, type DomainRow } from '../domain/list'
5
6
 
6
7
  const strip = (s: string): string => s.replace(/\[[0-9;]*m/g, '')
@@ -17,11 +18,6 @@ function entry(over: Partial<DomainInfo>): DomainInfo {
17
18
  }
18
19
 
19
20
  describe('domain list — ordering', () => {
20
- test('uses the semantic static method path', () => {
21
- expect(ADMIN_DOMAIN).toBe('/:admin.astrale.ai:class.DomainEntry')
22
- expect(adminDomainMethod('list')).toBe('/:admin.astrale.ai:class.DomainEntry:list')
23
- })
24
-
25
21
  test('install-by-default sorts first, then alphabetical by origin', () => {
26
22
  const rows = [
27
23
  entry({ origin: 'zeta.astrale.ai' }),
@@ -0,0 +1,20 @@
1
+ import { describe, expect, test } from 'bun:test'
2
+
3
+ import { directInstallCallInput } from '../domain/install'
4
+
5
+ describe('directInstallCallInput', () => {
6
+ test('sends the current remote install syscall, not a legacy url list', () => {
7
+ expect(directInstallCallInput('https://tasks.example.test', 'secret', 'op-1')).toEqual({
8
+ operation: 'op-1',
9
+ domains: [
10
+ {
11
+ source: {
12
+ kind: 'remote',
13
+ url: 'https://tasks.example.test',
14
+ token: 'secret',
15
+ },
16
+ },
17
+ ],
18
+ })
19
+ })
20
+ })
@@ -0,0 +1,26 @@
1
+ import { describe, expect, test } from 'bun:test'
2
+
3
+ import { AstraleError } from '../../errors'
4
+ import { adminInventoryUnavailable } from '../instance/list'
5
+
6
+ describe('adminInventoryUnavailable', () => {
7
+ test('turns key-backed Admin exchange failures into a bookmark-only instruction', () => {
8
+ const error = adminInventoryUnavailable(
9
+ new AstraleError(
10
+ 'TOKEN_EXCHANGE_SOURCE_INVALID',
11
+ 'The source identity credential has no valid expiration.',
12
+ ),
13
+ )
14
+ expect(error.code).toBe('ADMIN_INVENTORY_UNAVAILABLE')
15
+ expect(error.message).toContain('Admin is not deployed')
16
+ expect(error.hint).toContain('instance list --bookmarked')
17
+ })
18
+
19
+ test('turns a missing Admin Domain issuer into the same instruction', () => {
20
+ const error = adminInventoryUnavailable(
21
+ new AstraleError('ADMIN_DOMAIN_ISSUER_MISSING', 'no Domain issuer'),
22
+ )
23
+ expect(error.code).toBe('ADMIN_INVENTORY_UNAVAILABLE')
24
+ expect(error.hint).toContain('Key-backed identities cannot mint an Admin Domain token')
25
+ })
26
+ })
@@ -1,117 +1,92 @@
1
1
  import { describe, expect, test } from 'bun:test'
2
2
 
3
- import { buildEventsParams, normalizePage, parseServiceName, parseTimeFlag } from '../logs'
3
+ import { acceptJournalPage, buildJournalInput } from '../logs'
4
4
 
5
- describe('parseTimeFlag', () => {
6
- test('passes through epoch-ms', () => {
7
- expect(parseTimeFlag('--since', '1718841600000')).toBe(1718841600000)
8
- expect(parseTimeFlag('--since', '0')).toBe(0)
9
- expect(parseTimeFlag('--since', '-5')).toBe(-5)
10
- })
11
-
12
- test('parses ISO-8601 to epoch ms', () => {
13
- expect(parseTimeFlag('--since', '2026-06-20T00:00:00Z')).toBe(
14
- Date.parse('2026-06-20T00:00:00Z'),
15
- )
16
- })
17
-
18
- test('throws on garbage', () => {
19
- expect(() => parseTimeFlag('--since', 'not-a-time')).toThrow('--since')
20
- })
21
- })
22
-
23
- describe('buildEventsParams', () => {
24
- test('defaults limit when unset, no other fields', () => {
25
- expect(buildEventsParams({})).toEqual({ limit: 200 })
26
- })
27
-
28
- test('maps typed flags into a JournalFilter', () => {
5
+ describe('buildJournalInput', () => {
6
+ /** @evidence TEST-CLI-LOGS-MAPS-EXACT-JOURNAL-INPUT */
7
+ test('maps flags to the current public journal syscall contract', () => {
29
8
  expect(
30
- buildEventsParams({
31
- topic: 'op:*:failed',
32
- principal: 'id_abc',
33
- since: '1000',
34
- until: '2000',
9
+ buildJournalInput({
10
+ topic: 'op:function.failed',
11
+ topicPrefix: 'security.',
12
+ principal: 'caller',
13
+ since: '2026-08-10T00:00:00Z',
14
+ until: '2026-08-11T00:00:00Z',
15
+ cursor: 'opaque-next',
35
16
  limit: '50',
36
- cursor: '7',
37
17
  }),
38
18
  ).toEqual({
39
- topic: 'op:*:failed',
40
- principal: 'id_abc' as never,
41
- since: 1000,
42
- until: 2000,
19
+ topics: { exact: ['op:function.failed'], prefixes: ['security.'] },
20
+ principal: 'caller',
21
+ since: '2026-08-10T00:00:00Z',
22
+ until: '2026-08-11T00:00:00Z',
23
+ cursor: 'opaque-next',
43
24
  limit: 50,
44
- cursor: 7,
45
25
  })
46
26
  })
47
27
 
48
- test('rejects a non-positive limit', () => {
49
- expect(() => buildEventsParams({ limit: '0' })).toThrow('--limit')
50
- expect(() => buildEventsParams({ limit: '-3' })).toThrow('--limit')
51
- expect(() => buildEventsParams({ limit: 'x' })).toThrow('--limit')
52
- })
53
-
54
- test('rejects a non-positive cursor', () => {
55
- expect(() => buildEventsParams({ cursor: '0' })).toThrow('--cursor')
56
- })
57
-
58
- test('omits empty topic/principal', () => {
59
- expect(buildEventsParams({ topic: '', principal: '' })).toEqual({ limit: 200 })
28
+ test('defaults only the finite limit and rejects invalid values', () => {
29
+ expect(buildJournalInput({})).toEqual({ limit: 200 })
30
+ expect(() => buildJournalInput({ limit: '0' })).toThrow('--limit')
31
+ expect(() => buildJournalInput({ limit: 'all' })).toThrow('--limit')
60
32
  })
61
33
  })
62
34
 
63
- describe('normalizePage', () => {
64
- const entry = (seq: number) => ({
65
- seq,
66
- event: {
67
- id: `e${seq}`,
68
- topic: 'op:x',
69
- payload: {},
70
- metadata: { traceId: 't', timestamp: seq, principal: 'p', root: 'r' },
71
- },
72
- })
73
-
74
- test('wraps a bare entry array and derives nextCursor from max seq', () => {
75
- const page = normalizePage([entry(3), entry(7), entry(5)])
76
- expect(page.entries).toHaveLength(3)
77
- expect(page.nextCursor).toBe(7)
78
- })
79
-
80
- test('empty array → empty entries, null cursor', () => {
81
- expect(normalizePage([])).toEqual({ entries: [], nextCursor: null })
82
- })
83
-
84
- test('accepts a paged { entries, nextCursor } shape', () => {
85
- const page = normalizePage({ entries: [entry(1)], nextCursor: 42 })
86
- expect(page.entries).toHaveLength(1)
87
- expect(page.nextCursor).toBe(42)
88
- })
89
-
90
- test('paged shape without nextCursor falls back to max seq', () => {
91
- expect(normalizePage({ entries: [entry(9)] }).nextCursor).toBe(9)
92
- })
93
-
94
- test('non-array, non-page input → empty', () => {
95
- expect(normalizePage(null)).toEqual({ entries: [], nextCursor: null })
96
- expect(normalizePage(undefined)).toEqual({ entries: [], nextCursor: null })
97
- expect(normalizePage('nope')).toEqual({ entries: [], nextCursor: null })
98
- })
99
- })
100
-
101
- describe('parseServiceName', () => {
102
- test('bare name is returned as-is', () => {
103
- expect(parseServiceName('my-notes')).toBe('my-notes')
104
- })
35
+ describe('acceptJournalPage', () => {
36
+ /** @evidence TEST-CLI-LOGS-ADMITS-OPAQUE-CURSOR-PAGE */
37
+ test('retains current records and an opaque continuation cursor', () => {
38
+ const page = acceptJournalPage({
39
+ records: [
40
+ {
41
+ sequence: 7,
42
+ timestamp: '2026-08-11T12:00:00.000Z',
43
+ topic: 'op:function.completed',
44
+ payload: { durationMs: 4 },
45
+ principal: 'caller',
46
+ },
47
+ ],
48
+ cursor: 'next-page',
49
+ })
105
50
 
106
- test('host → first label', () => {
107
- expect(parseServiceName('my-notes.svc.eu.astrale.ai')).toBe('my-notes')
51
+ expect(page).toEqual({
52
+ records: [
53
+ {
54
+ sequence: 7,
55
+ timestamp: '2026-08-11T12:00:00.000Z',
56
+ topic: 'op:function.completed',
57
+ payload: { durationMs: 4 },
58
+ principal: 'caller',
59
+ },
60
+ ],
61
+ cursor: 'next-page',
62
+ })
108
63
  })
109
64
 
110
- test('full URL hostname first label', () => {
111
- expect(parseServiceName('https://my-notes.example.dev/path')).toBe('my-notes')
65
+ test('rejects malformed record and cursor fields instead of formatting them loosely', () => {
66
+ expect(() => acceptJournalPage({ records: [{}] })).toThrow('record 0')
67
+ expect(() => acceptJournalPage({ records: [], cursor: 7 })).toThrow('cursor')
112
68
  })
113
69
 
114
- test('trims whitespace', () => {
115
- expect(parseServiceName(' my-notes ')).toBe('my-notes')
70
+ test('admits journal v2 records that use occurredAt instead of timestamp', () => {
71
+ const page = acceptJournalPage({
72
+ records: [
73
+ {
74
+ sequence: 10241,
75
+ topic: 'function.invoke',
76
+ occurredAt: '2026-08-19T16:51:10.049Z',
77
+ committedAt: '2026-08-19T16:51:10.070Z',
78
+ payload: { outcome: 'rejected' },
79
+ correlation: { invocationId: 'cf862a64-3aa1-4343-ba86-f9b516c4ff95' },
80
+ },
81
+ ],
82
+ })
83
+ expect(page.records[0]).toMatchObject({
84
+ sequence: 10241,
85
+ topic: 'function.invoke',
86
+ timestamp: '2026-08-19T16:51:10.049Z',
87
+ occurredAt: '2026-08-19T16:51:10.049Z',
88
+ committedAt: '2026-08-19T16:51:10.070Z',
89
+ correlationId: 'cf862a64-3aa1-4343-ba86-f9b516c4ff95',
90
+ })
116
91
  })
117
92
  })
@@ -1,25 +1,29 @@
1
+ import type { Node } from '@astrale-os/sdk/graph/node'
2
+
3
+ import { ClassPath } from '@astrale-os/sdk/graph/class'
4
+ import { NodeId } from '@astrale-os/sdk/graph/node'
5
+ import { normalizeProperties } from '@astrale-os/sdk/graph/properties'
1
6
  import { describe, expect, test } from 'bun:test'
2
7
 
3
- import { basename, classNameOf } from '../ls'
8
+ import { displayName, listProjection } from '../ls'
4
9
 
5
- // Regression: the formatter used to read `item.slug` (never returned by the
6
- // kernel), blanking the name column and breaking `-q`/`-R`. The real fields are
7
- // `path` (absolute) and `class` (serialized ClassPath).
8
- describe('ls — display projection (slug-bug regression)', () => {
9
- test('basename derives the display name from the absolute path', () => {
10
- expect(basename('/example.astrale.ai')).toBe('example.astrale.ai')
11
- expect(basename('/kernel.astrale.ai')).toBe('kernel.astrale.ai')
12
- expect(basename('/')).toBe('/')
13
- expect(basename(undefined)).toBe('')
14
- })
10
+ describe('ls display projection', () => {
11
+ const node = {
12
+ id: NodeId('note-1'),
13
+ class: ClassPath.parse('/:notes.example.dev:class.Note'),
14
+ props: normalizeProperties({ 'notes.example.dev:class.Note.property.title': 'Hello' }),
15
+ } satisfies Node
15
16
 
16
- test('classNameOf parses the kind from the serialized class path', () => {
17
- expect(classNameOf({ class: '/:kernel.astrale.ai:class.Domain' })).toBe('Domain')
18
- expect(classNameOf({ class: '/:kernel.astrale.ai:class.Folder' })).toBe('Folder')
17
+ /** @evidence TEST-CLI-LS-PROJECTS-CANONICAL-NODES */
18
+ test('uses canonical properties for display and @id for pipeable output', () => {
19
+ expect(displayName(node)).toBe('Hello')
20
+ expect(listProjection([node])).toMatchObject({
21
+ rows: [{ name: 'Hello', class: 'Note', id: 'note-1' }],
22
+ paths: ['@note-1'],
23
+ })
19
24
  })
20
25
 
21
- test('classNameOf falls back to the most specific label, then ?', () => {
22
- expect(classNameOf({ __labels: ['Node', 'Domain', 'Container'] })).toBe('Container')
23
- expect(classNameOf({})).toBe('?')
26
+ test('falls back to the canonical Node ID when no display property exists', () => {
27
+ expect(displayName({ ...node, props: normalizeProperties({}) })).toBe('@note-1')
24
28
  })
25
29
  })