@astrale-os/cli 1.0.0-beta.0 → 1.0.0-beta.10

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 (322) hide show
  1. package/README.md +23 -12
  2. package/dist/astrale.js +51494 -56307
  3. package/dist/public/connect-core.js +2028 -26846
  4. package/dist/public/keys/index.js +1755 -40694
  5. package/dist/public/paths/index.js +859 -39518
  6. package/dist/types/admin/binding.d.ts +19 -0
  7. package/dist/types/admin/instance/client.d.ts +22 -0
  8. package/dist/types/admin/instance/index.d.ts +2 -0
  9. package/dist/types/admin/instance/model.d.ts +5 -12
  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 +6 -2
  28. package/dist/types/lib/failure-debug.d.ts +1 -0
  29. package/dist/types/lib/idp.d.ts +7 -0
  30. package/dist/types/lib/instance-target.d.ts +1 -1
  31. package/dist/types/lib/instance.d.ts +10 -2
  32. package/dist/types/lib/log.d.ts +5 -3
  33. package/dist/types/lib/meta.d.ts +2 -2
  34. package/dist/types/lib/proc.d.ts +43 -0
  35. package/dist/types/lib/self.d.ts +9 -0
  36. package/dist/types/lib/update.d.ts +131 -0
  37. package/package.json +8 -10
  38. package/src/__tests__/fixtures/publication.ts +20 -0
  39. package/src/admin/__tests__/binding.test.ts +31 -0
  40. package/src/admin/__tests__/fixture.ts +101 -0
  41. package/src/admin/binding.ts +98 -0
  42. package/src/admin/catalog/.spec/api.d.ts +2 -2
  43. package/src/admin/catalog/__tests__/client.test.ts +20 -49
  44. package/src/admin/catalog/client.ts +36 -32
  45. package/src/admin/catalog/model.ts +3 -4
  46. package/src/admin/graph/.spec/api.d.ts +4 -10
  47. package/src/admin/graph/nodes.ts +1 -1
  48. package/src/admin/instance/.spec/api.d.ts +2 -2
  49. package/src/admin/instance/__tests__/client.test.ts +40 -163
  50. package/src/admin/instance/client.ts +35 -137
  51. package/src/admin/instance/index.ts +1 -2
  52. package/src/admin/instance/model.ts +11 -20
  53. package/src/commands/__tests__/admin-instance.test.ts +11 -8
  54. package/src/commands/__tests__/auth-login.test.ts +54 -1
  55. package/src/commands/__tests__/call-describe.test.ts +46 -33
  56. package/src/commands/__tests__/call.test.ts +5 -1
  57. package/src/commands/__tests__/domain-install-operation.test.ts +12 -7
  58. package/src/commands/__tests__/domain-install-owned.test.ts +6 -2
  59. package/src/commands/__tests__/domain-list.test.ts +63 -4
  60. package/src/commands/__tests__/install-identity-override.test.ts +19 -19
  61. package/src/commands/__tests__/instance-list-rows.test.ts +1 -1
  62. package/src/commands/__tests__/instance-status.test.ts +139 -0
  63. package/src/commands/__tests__/read-commands.test.ts +35 -10
  64. package/src/commands/__tests__/studio-descriptor.test.ts +22 -0
  65. package/src/commands/__tests__/update.test.ts +95 -0
  66. package/src/commands/call-describe.ts +60 -68
  67. package/src/commands/call.ts +43 -56
  68. package/src/commands/domain/install.ts +13 -17
  69. package/src/commands/domain/list.ts +18 -13
  70. package/src/commands/domain/publish.ts +14 -11
  71. package/src/commands/domain/uninstall.ts +2 -2
  72. package/src/commands/get.ts +14 -15
  73. package/src/commands/identity/__tests__/register.test.ts +3 -3
  74. package/src/commands/identity/register.ts +5 -4
  75. package/src/commands/instance/create.ts +8 -16
  76. package/src/commands/instance/delete.ts +12 -9
  77. package/src/commands/instance/list.ts +2 -2
  78. package/src/commands/instance/status.ts +66 -20
  79. package/src/commands/instance/use.ts +16 -12
  80. package/src/commands/introspect.ts +9 -16
  81. package/src/commands/logs.ts +24 -25
  82. package/src/commands/mutate.ts +6 -5
  83. package/src/commands/query.ts +27 -30
  84. package/src/commands/studio.ts +20 -0
  85. package/src/commands/token.ts +8 -7
  86. package/src/commands/update.ts +68 -22
  87. package/src/commands/view.ts +7 -6
  88. package/src/connection/.spec/api.d.ts +7 -8
  89. package/src/connection/.spec/architecture.md +5 -4
  90. package/src/connection/.spec/flows/session.ts +2 -2
  91. package/src/connection/.spec/laws/connection.ts +6 -2
  92. package/src/connection/.spec/layout.ts +1 -0
  93. package/src/connection/__tests__/credential.test.ts +20 -1
  94. package/src/connection/__tests__/errors.test.ts +43 -32
  95. package/src/connection/__tests__/exchange.test.ts +36 -6
  96. package/src/connection/__tests__/failure-fixtures.ts +39 -0
  97. package/src/connection/__tests__/failure-safety.test.ts +75 -0
  98. package/src/connection/__tests__/self.test.ts +31 -11
  99. package/src/connection/__tests__/session.test.ts +3 -3
  100. package/src/connection/__tests__/target.test.ts +1 -0
  101. package/src/connection/auth.ts +13 -0
  102. package/src/connection/call.ts +1 -1
  103. package/src/connection/credential.ts +2 -2
  104. package/src/connection/errors.ts +1 -355
  105. package/src/connection/exchange.ts +52 -27
  106. package/src/connection/failure/classify.ts +110 -0
  107. package/src/connection/failure/index.ts +18 -0
  108. package/src/connection/failure/model.ts +28 -0
  109. package/src/connection/failure/render.ts +116 -0
  110. package/src/connection/reasons.ts +16 -0
  111. package/src/connection/self.ts +39 -21
  112. package/src/connection/session.ts +19 -8
  113. package/src/errors.ts +3 -2
  114. package/src/graph/.spec/api.d.ts +12 -5
  115. package/src/graph/.spec/layout.ts +9 -1
  116. package/src/graph/__tests__/mutation.test.ts +2 -2
  117. package/src/graph/__tests__/query.test.ts +25 -6
  118. package/src/graph/class.ts +30 -0
  119. package/src/graph/index.ts +1 -0
  120. package/src/graph/query.ts +21 -32
  121. package/src/identity/.spec/api.d.ts +2 -2
  122. package/src/identity/__tests__/registration.test.ts +1 -1
  123. package/src/identity/registration.ts +3 -3
  124. package/src/keys/__tests__/keys.test.ts +4 -0
  125. package/src/keys/credential.ts +1 -0
  126. package/src/lib/__tests__/admin-target.test.ts +12 -0
  127. package/src/lib/__tests__/command-dx.test.ts +1 -1
  128. package/src/lib/__tests__/domain-publication.test.ts +138 -0
  129. package/src/lib/__tests__/idp-session.test.ts +22 -0
  130. package/src/lib/__tests__/idp.test.ts +36 -4
  131. package/src/lib/__tests__/instance-candidates.test.ts +1 -3
  132. package/src/lib/__tests__/instance-target.test.ts +7 -39
  133. package/src/lib/__tests__/instance.test.ts +6 -0
  134. package/src/lib/__tests__/log-errors.test.ts +64 -0
  135. package/src/lib/__tests__/studio-server-deps.test.ts +53 -16
  136. package/src/lib/__tests__/update.test.ts +113 -8
  137. package/src/lib/__tests__/view-open-intent.test.ts +0 -2
  138. package/src/lib/__tests__/view-server.test.ts +1 -1
  139. package/src/lib/__tests__/view-session.test.ts +1 -1
  140. package/src/lib/admin-instance.ts +14 -8
  141. package/src/lib/admin-target.ts +21 -2
  142. package/src/lib/command-dx.ts +9 -9
  143. package/src/lib/domain-publication.ts +104 -0
  144. package/src/lib/failure-debug.ts +26 -0
  145. package/src/lib/idp.ts +39 -4
  146. package/src/lib/instance-candidates.ts +2 -2
  147. package/src/lib/instance-target.ts +3 -14
  148. package/src/lib/instance.ts +22 -12
  149. package/src/lib/log.ts +16 -9
  150. package/src/lib/login-flow.ts +13 -3
  151. package/src/lib/meta.ts +2 -2
  152. package/src/lib/provision-instance.ts +34 -57
  153. package/src/lib/update.ts +124 -30
  154. package/src/lib/validation.ts +2 -2
  155. package/src/lib/view/resolve.ts +1 -1
  156. package/src/program/__tests__/program.test.ts +20 -4
  157. package/src/program/build.ts +2 -2
  158. package/src/setup/__tests__/instance-step.test.ts +27 -10
  159. package/src/setup/steps/instance.ts +22 -15
  160. package/studio/client/dist/assets/{elk-api-D2xgMJvi.js → elk-api-ByzoDZWH.js} +1 -1
  161. package/studio/client/dist/assets/index-B0RCgUOT.js +8 -0
  162. package/studio/client/dist/assets/index-BiGR8Oo9.css +1 -0
  163. package/studio/client/dist/assets/index-N8J8EKlB.js +81 -0
  164. package/studio/client/dist/index.html +2 -2
  165. package/studio/package.json +5 -10
  166. package/studio/server/agent/ask.test.ts +1 -1
  167. package/studio/server/agent/bridge/grant.test.ts +1 -1
  168. package/studio/server/agent/bridge/routes.test.ts +1 -1
  169. package/studio/server/agent/bridge/stdio.ts +8 -1
  170. package/studio/server/agent/conversation.test.ts +10 -5
  171. package/studio/server/agent/conversation.ts +56 -8
  172. package/studio/server/agent/harness/claude/adapter.test.ts +15 -5
  173. package/studio/server/agent/harness/codex/loadout.ts +18 -21
  174. package/studio/server/agent/harness/gateway/config.ts +20 -18
  175. package/studio/server/agent/harness/selection.ts +9 -1
  176. package/studio/server/agent/prompts/anchors.test.ts +32 -36
  177. package/studio/server/agent/prompts/anchors.ts +20 -49
  178. package/studio/server/agent/prompts/system.test.ts +3 -2
  179. package/studio/server/agent/prompts/system.ts +6 -5
  180. package/studio/server/agent/prompts/turn.ts +13 -4
  181. package/studio/server/agent/routes.test.ts +1 -1
  182. package/studio/server/agent/run/completion.ts +2 -2
  183. package/studio/server/agent/run/coordinator.test.ts +1 -1
  184. package/studio/server/agent/run/preparation.ts +6 -5
  185. package/studio/server/agent/run/transcript.test.ts +35 -1
  186. package/studio/server/agent/run/transcript.ts +169 -3
  187. package/studio/server/agent/run/usage.test.ts +24 -0
  188. package/studio/server/agent/run/usage.ts +26 -1
  189. package/studio/server/api/canvas.ts +43 -0
  190. package/studio/server/api/comments.ts +85 -0
  191. package/studio/server/api/context.ts +61 -0
  192. package/studio/server/api/deployment.ts +39 -0
  193. package/studio/server/api/documents.ts +55 -0
  194. package/studio/server/api/domain.ts +59 -0
  195. package/studio/server/api/http.ts +45 -0
  196. package/studio/server/api/project.ts +57 -0
  197. package/studio/server/api/schema.ts +12 -0
  198. package/studio/server/api/views.ts +57 -0
  199. package/studio/server/api/workspace.ts +60 -0
  200. package/studio/server/api-agent-loadout.test.ts +1 -1
  201. package/studio/server/api.test.ts +147 -0
  202. package/studio/server/api.ts +20 -422
  203. package/studio/server/cache.test.ts +122 -0
  204. package/studio/server/cache.ts +162 -11
  205. package/studio/server/cli-consumers.test.ts +494 -0
  206. package/studio/server/cli.test.ts +88 -0
  207. package/studio/server/cli.ts +240 -0
  208. package/studio/server/client-package.ts +25 -1
  209. package/studio/server/domain.test.ts +50 -40
  210. package/studio/server/domain.ts +83 -52
  211. package/studio/server/environment/dotenv-preview.test.ts +39 -0
  212. package/studio/server/environment/dotenv-preview.ts +27 -0
  213. package/studio/server/environment/files.test.ts +53 -0
  214. package/studio/server/{state/env.ts → environment/files.ts} +7 -48
  215. package/studio/server/{state → handoff}/copy.ts +21 -6
  216. package/studio/server/handoff/service.test.ts +22 -0
  217. package/studio/server/{state/handoff.ts → handoff/service.ts} +21 -14
  218. package/studio/server/index.ts +1 -1
  219. package/studio/server/instances/active.ts +72 -0
  220. package/studio/server/instances/deploy-record.test.ts +31 -0
  221. package/studio/server/instances/deploy-record.ts +37 -0
  222. package/studio/server/instances/deploy.ts +56 -0
  223. package/studio/server/{state/instance.test.ts → instances/probe.test.ts} +25 -21
  224. package/studio/server/instances/probe.ts +67 -0
  225. package/studio/server/instances/status.test.ts +12 -0
  226. package/studio/server/instances/status.ts +49 -0
  227. package/studio/server/introspect/anatomy/client-tree.ts +57 -0
  228. package/studio/server/introspect/anatomy/env-fields.ts +66 -0
  229. package/studio/server/introspect/anatomy/schema-definition.ts +53 -0
  230. package/studio/server/introspect/anatomy/source.ts +181 -0
  231. package/studio/server/introspect/anatomy/views/routes.ts +114 -0
  232. package/studio/server/introspect/anatomy/views.ts +53 -0
  233. package/studio/server/introspect/anatomy-extras.test.ts +106 -35
  234. package/studio/server/introspect/anatomy-extras.ts +7 -726
  235. package/studio/server/introspect/anatomy.test.ts +22 -14
  236. package/studio/server/introspect/anatomy.ts +52 -28
  237. package/studio/server/introspect/bundle.ts +15 -9
  238. package/studio/server/introspect/canonical-schema.test.ts +93 -309
  239. package/studio/server/introspect/canonical-schema.ts +257 -425
  240. package/studio/server/introspect/config-preview.test.ts +32 -0
  241. package/studio/server/introspect/config-preview.ts +119 -0
  242. package/studio/server/introspect/core-extractor.ts +18 -127
  243. package/studio/server/introspect/core.ts +1 -2
  244. package/studio/server/introspect/diff.test.ts +18 -18
  245. package/studio/server/introspect/diff.ts +44 -90
  246. package/studio/server/introspect/extractor.ts +23 -116
  247. package/studio/server/introspect/hash.ts +5 -2
  248. package/studio/server/introspect/overlay-tsmorph.test.ts +119 -240
  249. package/studio/server/introspect/overlay-tsmorph.ts +7 -1305
  250. package/studio/server/introspect/overlay.test.ts +26 -54
  251. package/studio/server/introspect/overlay.ts +10 -37
  252. package/studio/server/introspect/revision.test.ts +54 -0
  253. package/studio/server/introspect/revision.ts +49 -0
  254. package/studio/server/introspect/runtime.test.ts +83 -143
  255. package/studio/server/introspect/runtime.ts +40 -11
  256. package/studio/server/introspect/schema-ir-json.test.ts +70 -0
  257. package/studio/server/introspect/schema-ir-json.ts +151 -0
  258. package/studio/server/introspect/schema-refs.test.ts +43 -88
  259. package/studio/server/introspect/schema-refs.ts +14 -49
  260. package/studio/server/introspect/source-overlay/annotations.ts +14 -0
  261. package/studio/server/introspect/source-overlay/handlers.ts +161 -0
  262. package/studio/server/introspect/source-overlay/kernel-calls.ts +46 -0
  263. package/studio/server/introspect/source-overlay/project.ts +248 -0
  264. package/studio/server/introspect/source-overlay/spans.ts +330 -0
  265. package/studio/server/json.ts +46 -0
  266. package/studio/server/lifecycle.ts +5 -1
  267. package/studio/server/sse.test.ts +26 -0
  268. package/studio/server/sse.ts +4 -1
  269. package/studio/server/state/baseline.test.ts +223 -7
  270. package/studio/server/state/baseline.ts +148 -34
  271. package/studio/server/state/comments.test.ts +66 -1
  272. package/studio/server/state/comments.ts +173 -10
  273. package/studio/server/state/context.ts +34 -2
  274. package/studio/server/state/documents.ts +42 -1
  275. package/studio/server/state/integrations.ts +27 -1
  276. package/studio/server/state/layout.test.ts +34 -0
  277. package/studio/server/state/layout.ts +44 -17
  278. package/studio/server/state/settings.ts +29 -22
  279. package/studio/server/state/store.test.ts +122 -0
  280. package/studio/server/state/store.ts +66 -17
  281. package/studio/server/state/visibility.ts +22 -11
  282. package/studio/server/views/model.test.ts +57 -0
  283. package/studio/server/views/model.ts +66 -0
  284. package/studio/server/views/runtime.ts +40 -0
  285. package/studio/server/views/selection-repository.test.ts +31 -0
  286. package/studio/server/views/selection-repository.ts +60 -0
  287. package/studio/server/views/session.test.ts +73 -0
  288. package/studio/server/views/session.ts +166 -0
  289. package/studio/server/views/target.test.ts +78 -0
  290. package/studio/server/views/target.ts +222 -0
  291. package/studio/server/watch.test.ts +11 -11
  292. package/studio/server/watch.ts +9 -5
  293. package/studio/server/workspace/catalog.test.ts +17 -0
  294. package/studio/server/workspace/catalog.ts +43 -0
  295. package/studio/server/{state → workspace}/create.test.ts +1 -1
  296. package/studio/server/{state → workspace}/create.ts +1 -1
  297. package/studio/server/{state → workspace}/git.ts +1 -1
  298. package/studio/server/workspace/updates.ts +83 -0
  299. package/studio/server/workspace-state.ts +1 -1
  300. package/studio/server/workspace-watch.ts +2 -2
  301. package/studio/shared/contracts/README.md +16 -0
  302. package/studio/shared/contracts/agent.ts +270 -0
  303. package/studio/shared/contracts/runtime.ts +50 -0
  304. package/studio/shared/contracts/schema.ts +393 -0
  305. package/studio/shared/contracts/surface.test.ts +317 -0
  306. package/studio/shared/contracts/workspace.ts +265 -0
  307. package/studio/shared/layout.test.ts +116 -0
  308. package/studio/shared/schema/identity.test.ts +34 -0
  309. package/studio/shared/schema/identity.ts +68 -0
  310. package/studio/shared/types.ts +11 -1099
  311. package/studio/tsconfig.json +1 -1
  312. package/viewer/dist/main.js +40 -61
  313. package/.check-workspace.cjs +0 -40
  314. package/src/commands/__tests__/instance-create-hosts.test.ts +0 -29
  315. package/studio/client/dist/assets/index-BMdnsIJA.css +0 -1
  316. package/studio/client/dist/assets/index-D-vRV8w7.js +0 -8
  317. package/studio/client/dist/assets/index-LGSWRrk8.js +0 -81
  318. package/studio/server/state/catalog.ts +0 -117
  319. package/studio/server/state/instance.ts +0 -280
  320. package/studio/server/state/updates.ts +0 -63
  321. package/studio/server/state/views.test.ts +0 -286
  322. package/studio/server/state/views.ts +0 -535
@@ -3,6 +3,7 @@ import { describe, expect, test } from 'bun:test'
3
3
  import type { AstraleConfig } from '../config'
4
4
  import type { InstanceStore } from '../instance'
5
5
 
6
+ import { AdminInstanceNotFoundError } from '../../admin/instance/model'
6
7
  import { AstraleError } from '../../errors'
7
8
  import { DEFAULT_CONFIG } from '../config'
8
9
  import {
@@ -112,6 +113,7 @@ describe('resolveInstanceTarget', () => {
112
113
  id: 'node-1',
113
114
  slug,
114
115
  url: `https://${slug}.eu.astrale.ai`,
116
+ state: 'ready',
115
117
  }),
116
118
  },
117
119
  ),
@@ -124,9 +126,6 @@ describe('resolveInstanceTarget', () => {
124
126
  })
125
127
 
126
128
  test('unknown slugs surface the original instance-not-found error', async () => {
127
- const notFound = new Error('Path not found: "/admin/instances/missing"')
128
- notFound.name = 'NotFoundError'
129
-
130
129
  await expect(
131
130
  resolveInstanceTarget(
132
131
  { source: 'name', name: 'missing' },
@@ -134,7 +133,7 @@ describe('resolveInstanceTarget', () => {
134
133
  config: DEFAULT_CONFIG,
135
134
  instances: emptyStore,
136
135
  managed: async () => {
137
- throw notFound
136
+ throw new AdminInstanceNotFoundError('missing')
138
137
  },
139
138
  },
140
139
  ),
@@ -168,14 +167,8 @@ describe('instance target helpers', () => {
168
167
  expect(couldBeConfiguredAdminInstance('bryan', directAdminConfig)).toBe(false)
169
168
  })
170
169
 
171
- test('treats wrapped remote Instance.info misses as managed lookup not-found', () => {
172
- const wrapped = new Error('Path not found: "/admin/instances/admin"') as Error & {
173
- data?: unknown
174
- }
175
- wrapped.name = 'KernelError'
176
- wrapped.data = { type: 'NotFoundError' }
177
-
178
- expect(isManagedInstanceNotFound(wrapped)).toBe(true)
170
+ test('treats the owner-scoped Admin error as managed lookup not-found', () => {
171
+ expect(isManagedInstanceNotFound(new AdminInstanceNotFoundError('admin'))).toBe(true)
179
172
  })
180
173
 
181
174
  test('does not hide non-not-found managed lookup failures', () => {
@@ -194,37 +187,12 @@ describe('instance target helpers', () => {
194
187
  })
195
188
  })
196
189
 
197
- describe('isManagedInstanceNotFound: kernel InternalKernelError wrap', () => {
198
- test('NOT_FOUND-prefixed InternalKernelError is an instance miss', () => {
199
- const e = new Error('NOT_FOUND: no instance node for "knowledge"')
200
- e.name = 'InternalKernelError'
201
- expect(isManagedInstanceNotFound(e)).toBe(true)
202
- })
203
- test('other InternalKernelError messages are not swallowed', () => {
190
+ describe('isManagedInstanceNotFound: unknown errors', () => {
191
+ test('does not infer a miss from an InternalKernelError message', () => {
204
192
  const e = new Error('DISPATCH_FAILED: handler crashed')
205
193
  e.name = 'InternalKernelError'
206
194
  expect(isManagedInstanceNotFound(e)).toBe(false)
207
195
  })
208
- test('resolveNamedInstanceTarget maps the wrap to typed INSTANCE_NOT_FOUND', async () => {
209
- const managed = async () => {
210
- const e = new Error('NOT_FOUND: no instance node for "ghost"')
211
- e.name = 'InternalKernelError'
212
- throw e
213
- }
214
- const opts = {
215
- config: DEFAULT_CONFIG,
216
- instances: { instances: {} },
217
- managed,
218
- } as unknown as Parameters<typeof resolveInstanceTarget>[1]
219
- let caught: unknown
220
- try {
221
- await resolveInstanceTarget({ source: 'name', name: 'ghost' }, opts)
222
- } catch (e) {
223
- caught = e
224
- }
225
- expect(caught).toBeInstanceOf(AstraleError)
226
- expect((caught as AstraleError).code).toBe('INSTANCE_NOT_FOUND')
227
- })
228
196
 
229
197
  test('maps Admin token-exchange failure to INSTANCE_NOT_FOUND', async () => {
230
198
  const managed = async () => {
@@ -74,6 +74,12 @@ describe('InstanceStoreSchema', () => {
74
74
  expect(normalizeInstanceKernelUrl('https://testmarc.eu.astrale.ai/')).toBe(
75
75
  'https://testmarc.eu.astrale.ai/api',
76
76
  )
77
+ expect(normalizeInstanceKernelUrl('https://testmarc.eu.beta.astrale.ai')).toBe(
78
+ 'https://testmarc.eu.beta.astrale.ai/api',
79
+ )
80
+ expect(normalizeInstanceKernelUrl('https://shell.beta.astrale.ai')).toBe(
81
+ 'https://shell.beta.astrale.ai',
82
+ )
77
83
  })
78
84
 
79
85
  test('does not rewrite explicit kernel or non-managed URLs', () => {
@@ -0,0 +1,64 @@
1
+ import { afterEach, beforeEach, describe, expect, test } from 'bun:test'
2
+
3
+ import { AstraleError } from '../../errors'
4
+ import { failInput, fatal } from '../log'
5
+
6
+ class ExitError extends Error {}
7
+
8
+ let stderr = ''
9
+ let exit: typeof process.exit
10
+ let write: typeof process.stderr.write
11
+
12
+ beforeEach(() => {
13
+ stderr = ''
14
+ exit = process.exit
15
+ write = process.stderr.write
16
+ process.exit = (() => {
17
+ throw new ExitError()
18
+ }) as typeof process.exit
19
+ process.stderr.write = ((chunk: string | Uint8Array) => {
20
+ stderr += String(chunk)
21
+ return true
22
+ }) as typeof process.stderr.write
23
+ })
24
+
25
+ afterEach(() => {
26
+ process.exit = exit
27
+ process.stderr.write = write
28
+ })
29
+
30
+ describe('terminal error safety', () => {
31
+ test('renders an empty aggregate as an honest non-blank unexpected failure', () => {
32
+ expect(() => fatal(new AggregateError([], ''), { json: true })).toThrow(ExitError)
33
+ expect(JSON.parse(stderr)).toEqual({
34
+ error: 'UNEXPECTED_ERROR',
35
+ message: 'The CLI encountered an unexpected internal failure.',
36
+ })
37
+ })
38
+
39
+ test('retains unknown failure evidence only under explicit debug output', () => {
40
+ const failure = new AggregateError([new Error('provider rejected organization scope')], '')
41
+
42
+ expect(() => fatal(failure, { json: true, debug: true })).toThrow(ExitError)
43
+
44
+ expect(stderr).toContain(
45
+ '{"error":"UNEXPECTED_ERROR","message":"The CLI encountered an unexpected internal failure."}',
46
+ )
47
+ expect(stderr).toContain('aggregate: Error: provider rejected organization scope')
48
+ })
49
+
50
+ test('rejects blank admitted CLI diagnostics', () => {
51
+ expect(() => new AstraleError('INVALID_INPUT', ' ')).toThrow(TypeError)
52
+ })
53
+
54
+ test('labels only expected admission failures as invalid input', () => {
55
+ expect(() => failInput(new TypeError('Authored value is invalid.'), { json: true })).toThrow(
56
+ ExitError,
57
+ )
58
+ expect(JSON.parse(stderr)).toMatchObject({ error: 'INVALID_INPUT' })
59
+
60
+ stderr = ''
61
+ expect(() => failInput(new AggregateError([], ''), { json: true })).toThrow(ExitError)
62
+ expect(JSON.parse(stderr)).toMatchObject({ error: 'UNEXPECTED_ERROR' })
63
+ })
64
+ })
@@ -1,6 +1,6 @@
1
1
  import { describe, expect, test } from 'bun:test'
2
- import { readFileSync } from 'node:fs'
3
- import { join } from 'node:path'
2
+ import { readFileSync, readdirSync, statSync } from 'node:fs'
3
+ import { join, relative } from 'node:path'
4
4
  import { Project } from 'ts-morph'
5
5
 
6
6
  /**
@@ -19,6 +19,18 @@ import { Project } from 'ts-morph'
19
19
 
20
20
  const CLI_ROOT = new URL('../../../', import.meta.url).pathname
21
21
 
22
+ interface PackageManifest {
23
+ dependencies?: Record<string, string>
24
+ scripts?: Record<string, string>
25
+ }
26
+
27
+ const cliPackage = JSON.parse(
28
+ readFileSync(join(CLI_ROOT, 'package.json'), 'utf8'),
29
+ ) as PackageManifest
30
+ const studioPackage = JSON.parse(
31
+ readFileSync(join(CLI_ROOT, 'studio/package.json'), 'utf8'),
32
+ ) as PackageManifest
33
+
22
34
  /** Base package of a bare specifier: `@scope/pkg/sub` → `@scope/pkg`, `pkg/sub` → `pkg`. */
23
35
  function basePackage(spec: string): string {
24
36
  const parts = spec.split('/')
@@ -29,26 +41,33 @@ function isBuiltin(spec: string): boolean {
29
41
  return spec.startsWith('node:') || spec === 'bun' || spec.startsWith('bun:')
30
42
  }
31
43
 
44
+ function runtimeFiles(directory: string): string[] {
45
+ return readdirSync(directory).flatMap((entry) => {
46
+ const path = join(directory, entry)
47
+ if (statSync(path).isDirectory()) return runtimeFiles(path)
48
+ return /\.(?:ts|tsx)$/.test(path) && !/\.test\.(?:ts|tsx)$/.test(path) ? [path] : []
49
+ })
50
+ }
51
+
52
+ const studioPath = (path: string) => relative(CLI_ROOT, path).replaceAll('\\', '/')
53
+
32
54
  describe('studio server runtime deps', () => {
33
- const deps = new Set(
34
- Object.keys(
35
- (
36
- JSON.parse(readFileSync(join(CLI_ROOT, 'package.json'), 'utf8')) as {
37
- dependencies?: Record<string, string>
38
- }
39
- ).dependencies ?? {},
40
- ),
41
- )
55
+ const deps = new Set(Object.keys(cliPackage.dependencies ?? {}))
42
56
 
43
57
  const project = new Project({ skipAddingFilesFromTsConfig: true })
44
58
  project.addSourceFilesAtPaths([
45
59
  join(CLI_ROOT, 'studio/server/**/*.ts'),
60
+ join(CLI_ROOT, 'studio/server/**/*.tsx'),
46
61
  join(CLI_ROOT, 'studio/shared/**/*.ts'),
62
+ join(CLI_ROOT, 'studio/shared/**/*.tsx'),
47
63
  ])
64
+ const runtimeSourceFiles = project
65
+ .getSourceFiles()
66
+ .filter((sourceFile) => !/\.test\.(?:ts|tsx)$/.test(sourceFile.getBaseName()))
48
67
 
49
68
  // getImportStringLiterals covers static imports, re-exports, and dynamic import().
50
69
  const offenders: Record<string, string[]> = {}
51
- for (const sf of project.getSourceFiles()) {
70
+ for (const sf of runtimeSourceFiles) {
52
71
  const specs = sf.getImportStringLiterals().map((l) => l.getLiteralValue())
53
72
  for (const spec of specs) {
54
73
  if (spec.startsWith('.') || spec.startsWith('/') || isBuiltin(spec)) continue
@@ -66,9 +85,27 @@ describe('studio server runtime deps', () => {
66
85
  ).toEqual({})
67
86
  })
68
87
 
69
- test('parsed at least the known studio server files', () => {
70
- // Guard against the glob silently matching nothing (which would make the
71
- // invariant vacuously pass).
72
- expect(project.getSourceFiles().length).toBeGreaterThan(10)
88
+ test('parses every shipped Studio server and shared runtime source', () => {
89
+ const expected = [
90
+ ...runtimeFiles(join(CLI_ROOT, 'studio/server')),
91
+ ...runtimeFiles(join(CLI_ROOT, 'studio/shared')),
92
+ ]
93
+ .map(studioPath)
94
+ .sort()
95
+ const parsed = runtimeSourceFiles
96
+ .map((sourceFile) => studioPath(sourceFile.getFilePath()))
97
+ .sort()
98
+
99
+ expect(parsed).toEqual(expected)
100
+ })
101
+ })
102
+
103
+ describe('studio test gate', () => {
104
+ test('the root and Studio suites include all client, server, and shared tests', () => {
105
+ expect(cliPackage.scripts?.test).toBe(
106
+ 'node scripts/qualification/source-boundary.mjs --target && bun test src studio/client/src studio/server studio/shared && node --test scripts/*.test.mjs',
107
+ )
108
+ expect(cliPackage.scripts?.['test:studio']).toBe('pnpm --dir studio test')
109
+ expect(studioPackage.scripts?.test).toBe('bun test client/src server shared')
73
110
  })
74
111
  })
@@ -1,14 +1,20 @@
1
1
  import { describe, expect, test } from 'bun:test'
2
- import { chmod, mkdir, mkdtemp, readFile, writeFile } from 'node:fs/promises'
2
+ import { chmod, mkdir, mkdtemp, readFile, symlink, writeFile } from 'node:fs/promises'
3
3
  import { tmpdir } from 'node:os'
4
4
  import { join } from 'node:path'
5
5
 
6
6
  import {
7
+ admitScriptInstall,
8
+ classifyUpdateExecution,
9
+ DEFAULT_UPDATE_CHANNEL,
10
+ InstallMetadataSchema,
11
+ readInstallMetadata,
7
12
  releaseBase,
8
13
  shouldUpdate,
9
14
  updateAstrale,
10
15
  writeInstallMetadata,
11
16
  type InstallMetadata,
17
+ type UpdateExecution,
12
18
  } from '../update'
13
19
 
14
20
  async function makeFakeRelease(
@@ -63,7 +69,7 @@ async function makeFakeRelease(
63
69
  async function makeInstall(
64
70
  root: string,
65
71
  version: string,
66
- ): Promise<{ meta: InstallMetadata; path: string }> {
72
+ ): Promise<{ meta: InstallMetadata; path: string; execution: UpdateExecution }> {
67
73
  const bin = join(root, 'bin', 'astrale')
68
74
  await mkdir(join(root, 'bin'), { recursive: true })
69
75
  await writeFile(
@@ -80,10 +86,43 @@ async function makeInstall(
80
86
  bin,
81
87
  }
82
88
  await writeInstallMetadata(meta, path)
83
- return { meta, path }
89
+ return { meta, path, execution: { kind: 'standalone', executable: bin } }
84
90
  }
85
91
 
86
92
  describe('update helpers', () => {
93
+ test('classifies only a Bun-compiled executable as standalone', () => {
94
+ expect(
95
+ classifyUpdateExecution({
96
+ bunVersion: '1.3.14',
97
+ executable: '/tmp/astrale',
98
+ entry: '/$bunfs/root/astrale',
99
+ }),
100
+ ).toEqual({ kind: 'standalone', executable: '/tmp/astrale' })
101
+ expect(
102
+ classifyUpdateExecution({
103
+ bunVersion: '1.3.14',
104
+ executable: '/opt/homebrew/bin/bun',
105
+ entry: '/tmp/astrale.ts',
106
+ }),
107
+ ).toEqual({ kind: 'package-managed', executable: '/opt/homebrew/bin/bun' })
108
+ expect(
109
+ classifyUpdateExecution({
110
+ executable: '/opt/homebrew/bin/node',
111
+ entry: '/tmp/astrale.js',
112
+ }),
113
+ ).toEqual({ kind: 'package-managed', executable: '/opt/homebrew/bin/node' })
114
+ })
115
+
116
+ test('defaults missing install metadata to the beta channel', () => {
117
+ expect(DEFAULT_UPDATE_CHANNEL).toBe('beta')
118
+ expect(
119
+ InstallMetadataSchema.parse({
120
+ method: 'script',
121
+ bin: '/tmp/astrale',
122
+ }).channel,
123
+ ).toBe('beta')
124
+ })
125
+
87
126
  test('releaseBase resolves exact versions and channels', () => {
88
127
  const meta: InstallMetadata = {
89
128
  method: 'script',
@@ -106,10 +145,70 @@ describe('update helpers', () => {
106
145
  })
107
146
  })
108
147
 
148
+ describe('script install admission', () => {
149
+ test('rejects malformed JSON with the stable metadata error', async () => {
150
+ const root = await mkdtemp(join(tmpdir(), 'astrale-update-test-'))
151
+ const path = join(root, 'install.json')
152
+ await writeFile(path, '{')
153
+
154
+ await expect(readInstallMetadata(path)).rejects.toMatchObject({
155
+ code: 'UPDATE_BAD_INSTALL_METADATA',
156
+ })
157
+ })
158
+
159
+ test('admits a symlink only when it resolves to the recorded binary', async () => {
160
+ const root = await mkdtemp(join(tmpdir(), 'astrale-update-test-'))
161
+ const { meta } = await makeInstall(root, '1.0.0')
162
+ const alias = join(root, 'astrale-alias')
163
+ await symlink(meta.bin, alias)
164
+
165
+ const admitted = await admitScriptInstall(meta, {
166
+ kind: 'standalone',
167
+ executable: alias,
168
+ })
169
+
170
+ expect(admitted.metadata).toEqual(meta)
171
+ })
172
+
173
+ test('rejects a standalone binary that does not own the recorded target', async () => {
174
+ const root = await mkdtemp(join(tmpdir(), 'astrale-update-test-'))
175
+ const { meta } = await makeInstall(root, '1.0.0')
176
+ const other = join(root, 'other-astrale')
177
+ await writeFile(other, '#!/bin/sh\n')
178
+
179
+ await expect(
180
+ admitScriptInstall(meta, { kind: 'standalone', executable: other }),
181
+ ).rejects.toMatchObject({ code: 'UPDATE_INSTALL_MISMATCH' })
182
+ })
183
+ })
184
+
109
185
  describe('updateAstrale', () => {
186
+ test('a package-managed process never consults or mutates a coexisting script install', async () => {
187
+ const root = await mkdtemp(join(tmpdir(), 'astrale-update-test-'))
188
+ const { path, meta } = await makeInstall(root, '1.0.0')
189
+ const before = await readFile(meta.bin, 'utf8')
190
+ process.env.ASTRALE_UPDATE_BASE = 'file:///definitely-not-a-release'
191
+ try {
192
+ const result = await updateAstrale({
193
+ currentVersion: '2.0.0',
194
+ installPath: path,
195
+ execution: { kind: 'package-managed', executable: '/opt/homebrew/bin/node' },
196
+ })
197
+
198
+ expect(result).toEqual({
199
+ status: 'managed',
200
+ currentVersion: '2.0.0',
201
+ executable: '/opt/homebrew/bin/node',
202
+ })
203
+ expect(await readFile(meta.bin, 'utf8')).toBe(before)
204
+ } finally {
205
+ delete process.env.ASTRALE_UPDATE_BASE
206
+ }
207
+ })
208
+
110
209
  test('check compares the installed release identity from metadata', async () => {
111
210
  const root = await mkdtemp(join(tmpdir(), 'astrale-update-test-'))
112
- const { path } = await makeInstall(root, 'main-abc123')
211
+ const { path, execution } = await makeInstall(root, 'main-abc123')
113
212
  const release = await makeFakeRelease(root, 'main-abc123', { binaryVersion: '1.0.0' })
114
213
  process.env.ASTRALE_UPDATE_BASE = `file://${release}`
115
214
  try {
@@ -118,6 +217,7 @@ describe('updateAstrale', () => {
118
217
  currentVersion: '1.0.0',
119
218
  platform: { os: 'darwin', arch: 'arm64' },
120
219
  installPath: path,
220
+ execution,
121
221
  })
122
222
 
123
223
  expect(result).toMatchObject({
@@ -132,7 +232,7 @@ describe('updateAstrale', () => {
132
232
 
133
233
  test('check reports available update without replacing the binary', async () => {
134
234
  const root = await mkdtemp(join(tmpdir(), 'astrale-update-test-'))
135
- const { path, meta } = await makeInstall(root, '1.0.0')
235
+ const { path, meta, execution } = await makeInstall(root, '1.0.0')
136
236
  const release = await makeFakeRelease(root, '1.1.0')
137
237
  process.env.ASTRALE_UPDATE_BASE = `file://${release}`
138
238
  try {
@@ -141,6 +241,7 @@ describe('updateAstrale', () => {
141
241
  currentVersion: '1.0.0',
142
242
  platform: { os: 'darwin', arch: 'arm64' },
143
243
  installPath: path,
244
+ execution,
144
245
  })
145
246
 
146
247
  expect(result).toMatchObject({
@@ -156,7 +257,7 @@ describe('updateAstrale', () => {
156
257
 
157
258
  test('supports legacy string asset manifests', async () => {
158
259
  const root = await mkdtemp(join(tmpdir(), 'astrale-update-test-'))
159
- const { path, meta } = await makeInstall(root, '1.0.0')
260
+ const { path, meta, execution } = await makeInstall(root, '1.0.0')
160
261
  const release = await makeFakeRelease(root, '1.1.0', { legacyManifest: true })
161
262
  process.env.ASTRALE_UPDATE_BASE = `file://${release}`
162
263
  try {
@@ -165,6 +266,7 @@ describe('updateAstrale', () => {
165
266
  currentVersion: '1.0.0',
166
267
  platform: { os: 'darwin', arch: 'arm64' },
167
268
  installPath: path,
269
+ execution,
168
270
  })
169
271
 
170
272
  expect(check).toMatchObject({
@@ -176,6 +278,7 @@ describe('updateAstrale', () => {
176
278
  currentVersion: '1.0.0',
177
279
  platform: { os: 'darwin', arch: 'arm64' },
178
280
  installPath: path,
281
+ execution,
179
282
  })
180
283
 
181
284
  expect(result).toMatchObject({
@@ -192,7 +295,7 @@ describe('updateAstrale', () => {
192
295
 
193
296
  test('updates the binary and install metadata', async () => {
194
297
  const root = await mkdtemp(join(tmpdir(), 'astrale-update-test-'))
195
- const { path, meta } = await makeInstall(root, '1.0.0')
298
+ const { path, meta, execution } = await makeInstall(root, '1.0.0')
196
299
  const release = await makeFakeRelease(root, '1.1.0')
197
300
  process.env.ASTRALE_UPDATE_BASE = `file://${release}`
198
301
  try {
@@ -200,6 +303,7 @@ describe('updateAstrale', () => {
200
303
  currentVersion: '1.0.0',
201
304
  platform: { os: 'darwin', arch: 'arm64' },
202
305
  installPath: path,
306
+ execution,
203
307
  })
204
308
 
205
309
  expect(result).toMatchObject({
@@ -219,7 +323,7 @@ describe('updateAstrale', () => {
219
323
 
220
324
  test('updates canary-style releases whose binary reports the package version', async () => {
221
325
  const root = await mkdtemp(join(tmpdir(), 'astrale-update-test-'))
222
- const { path, meta } = await makeInstall(root, 'main-old123')
326
+ const { path, meta, execution } = await makeInstall(root, 'main-old123')
223
327
  const release = await makeFakeRelease(root, 'main-new456', { binaryVersion: '1.0.0' })
224
328
  process.env.ASTRALE_UPDATE_BASE = `file://${release}`
225
329
  try {
@@ -227,6 +331,7 @@ describe('updateAstrale', () => {
227
331
  currentVersion: '1.0.0',
228
332
  platform: { os: 'darwin', arch: 'arm64' },
229
333
  installPath: path,
334
+ execution,
230
335
  })
231
336
 
232
337
  expect(result).toMatchObject({
@@ -23,7 +23,6 @@ const profile: ResolvedView = {
23
23
  kind: 'definition',
24
24
  definitions: [{ origin: 'shell.test', kind: 'class', name: 'Person' }],
25
25
  },
26
- auth: 'required',
27
26
  },
28
27
  href: 'https://shell.test/profile',
29
28
  handshake: 'shell',
@@ -41,7 +40,6 @@ const card: ResolvedView = {
41
40
  kind: 'definition',
42
41
  definitions: [{ origin: 'shell.test', kind: 'class', name: 'Person' }],
43
42
  },
44
- auth: 'public',
45
43
  },
46
44
  href: 'https://shell.test/card',
47
45
  handshake: 'none',
@@ -29,7 +29,7 @@ describe('view session server credentials', () => {
29
29
  target: target('/:example.test'),
30
30
  route: {
31
31
  key: 'example.test:view.public',
32
- declaration: { target: { kind: 'domain' }, auth: 'required' },
32
+ declaration: { target: { kind: 'domain' } },
33
33
  href: 'https://example.test/ui/public',
34
34
  handshake: 'none',
35
35
  issuer: issuer('https://example.test'),
@@ -58,7 +58,7 @@ describe('view session private state', () => {
58
58
  issuer: issuer('https://example.test'),
59
59
  etag: `sha256:${'a'.repeat(64)}`,
60
60
  revision: revision('b'),
61
- declaration: { target: { kind: 'domain' }, auth: 'required' },
61
+ declaration: { target: { kind: 'domain' } },
62
62
  },
63
63
  },
64
64
  createdAt: '2026-08-12T00:00:00.000Z',
@@ -6,7 +6,7 @@ import { withAdminClientSession } from '../connection'
6
6
  export {
7
7
  AdminInstanceNotFoundError,
8
8
  findOwnedInstance,
9
- formatInstanceLocation,
9
+ formatInstanceState,
10
10
  type InstanceInfo,
11
11
  type InstanceState,
12
12
  type OwnedInstanceInfo,
@@ -19,19 +19,25 @@ export function listOwnedInstances(options: AdminConnectionOptions) {
19
19
  )
20
20
  }
21
21
 
22
+ /** Read caller-visible inventory together with the exact local identity used for the Admin call. */
23
+ export function listOwnedInstancesWithIdentity(options: AdminConnectionOptions) {
24
+ return withAdminClientSession(options, async (context) =>
25
+ Object.freeze({
26
+ instances: await (await connectAdminInstances(context)).list(),
27
+ ...(context.identity === undefined ? {} : { identity: context.identity }),
28
+ }),
29
+ )
30
+ }
31
+
22
32
  /** Reuse an already-open Admin session for a caller-visible Instance inventory. */
23
33
  export async function listOwnedInstancesInContext(context: ConnectionContext) {
24
34
  return (await connectAdminInstances(context)).list()
25
35
  }
26
36
 
27
- /** Provision through Fleet placement or one explicitly resolved Host receiver. */
28
- export function createOwnedInstance(
29
- options: AdminConnectionOptions,
30
- slug: string,
31
- hostId?: string,
32
- ) {
37
+ /** Request one managed Instance; Admin owns infrastructure placement. */
38
+ export function createOwnedInstance(options: AdminConnectionOptions, slug: string) {
33
39
  return withAdminClientSession(options, async (context) =>
34
- (await connectAdminInstances(context)).create(slug, hostId),
40
+ (await connectAdminInstances(context)).create(slug),
35
41
  )
36
42
  }
37
43
 
@@ -5,11 +5,30 @@ import type { InstanceStore } from './instance'
5
5
 
6
6
  import { AstraleError } from '../errors'
7
7
  import { readInstances, resolveInstanceKey } from './instance'
8
+ import { DEFAULT_UPDATE_CHANNEL } from './update'
8
9
  import { isHttpUrl } from './validation'
9
10
 
10
11
  export const DEFAULT_ADMIN_TARGET_NAME = 'admin'
11
- export const DEFAULT_ADMIN_TARGET_URL = 'https://admin.eu.astrale.ai/api'
12
- export const DEFAULT_ADMIN_DOMAIN_ISSUER = 'https://admin.beta.astrale.ai'
12
+
13
+ export function defaultAdminTargetForChannel(channel: string): {
14
+ readonly url: string
15
+ readonly domainIssuer: string
16
+ } {
17
+ return channel === 'stable'
18
+ ? {
19
+ url: 'https://admin.eu.astrale.ai/api',
20
+ domainIssuer: 'https://admin.astrale.ai',
21
+ }
22
+ : {
23
+ url: 'https://admin.eu.beta.astrale.ai/api',
24
+ domainIssuer: 'https://admin.beta.astrale.ai',
25
+ }
26
+ }
27
+
28
+ const DEFAULT_ADMIN_TARGET = defaultAdminTargetForChannel(DEFAULT_UPDATE_CHANNEL)
29
+
30
+ export const DEFAULT_ADMIN_TARGET_URL = DEFAULT_ADMIN_TARGET.url
31
+ export const DEFAULT_ADMIN_DOMAIN_ISSUER = DEFAULT_ADMIN_TARGET.domainIssuer
13
32
 
14
33
  export const DEFAULT_ADMIN_TARGET_CONFIG = {
15
34
  name: DEFAULT_ADMIN_TARGET_NAME,