@astrale-os/cli 1.0.0-beta.1 → 1.0.0-beta.11

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 (329) hide show
  1. package/README.md +23 -12
  2. package/dist/astrale.js +48048 -56798
  3. package/dist/public/connect-core.js +2011 -30765
  4. package/dist/public/keys/index.js +884 -29483
  5. package/dist/public/paths/index.js +13101 -41459
  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 +11 -36
  28. package/dist/types/lib/config.d.ts +7 -59
  29. package/dist/types/lib/failure-debug.d.ts +1 -0
  30. package/dist/types/lib/idp.d.ts +26 -168
  31. package/dist/types/lib/instance-target.d.ts +1 -1
  32. package/dist/types/lib/instance.d.ts +30 -88
  33. package/dist/types/lib/log.d.ts +5 -3
  34. package/dist/types/lib/meta.d.ts +2 -2
  35. package/dist/types/lib/proc.d.ts +43 -0
  36. package/dist/types/lib/self.d.ts +9 -0
  37. package/dist/types/lib/update.d.ts +91 -0
  38. package/dist/types/lib/validation.d.ts +4 -1
  39. package/package.json +16 -19
  40. package/src/__tests__/fixtures/publication.ts +20 -0
  41. package/src/admin/__tests__/binding.test.ts +31 -0
  42. package/src/admin/__tests__/fixture.ts +101 -0
  43. package/src/admin/binding.ts +98 -0
  44. package/src/admin/catalog/.spec/api.d.ts +2 -2
  45. package/src/admin/catalog/__tests__/client.test.ts +20 -49
  46. package/src/admin/catalog/client.ts +36 -32
  47. package/src/admin/catalog/model.ts +3 -4
  48. package/src/admin/graph/.spec/api.d.ts +4 -10
  49. package/src/admin/graph/nodes.ts +1 -1
  50. package/src/admin/instance/.spec/api.d.ts +2 -2
  51. package/src/admin/instance/__tests__/client.test.ts +39 -164
  52. package/src/admin/instance/client.ts +35 -124
  53. package/src/admin/instance/index.ts +1 -2
  54. package/src/admin/instance/model.ts +11 -20
  55. package/src/commands/__tests__/admin-instance.test.ts +11 -8
  56. package/src/commands/__tests__/auth-login.test.ts +54 -1
  57. package/src/commands/__tests__/call-describe.test.ts +46 -33
  58. package/src/commands/__tests__/call.test.ts +5 -1
  59. package/src/commands/__tests__/domain-install-operation.test.ts +12 -7
  60. package/src/commands/__tests__/domain-install-owned.test.ts +6 -2
  61. package/src/commands/__tests__/domain-list.test.ts +63 -4
  62. package/src/commands/__tests__/install-identity-override.test.ts +19 -19
  63. package/src/commands/__tests__/instance-list-rows.test.ts +1 -1
  64. package/src/commands/__tests__/instance-status.test.ts +139 -0
  65. package/src/commands/__tests__/read-commands.test.ts +35 -10
  66. package/src/commands/__tests__/studio-descriptor.test.ts +22 -0
  67. package/src/commands/__tests__/update.test.ts +48 -19
  68. package/src/commands/call-describe.ts +60 -68
  69. package/src/commands/call.ts +43 -56
  70. package/src/commands/domain/install.ts +13 -17
  71. package/src/commands/domain/list.ts +18 -13
  72. package/src/commands/domain/publish.ts +14 -11
  73. package/src/commands/domain/uninstall.ts +2 -2
  74. package/src/commands/get.ts +14 -15
  75. package/src/commands/identity/__tests__/register.test.ts +3 -3
  76. package/src/commands/identity/register.ts +5 -4
  77. package/src/commands/instance/create.ts +8 -16
  78. package/src/commands/instance/delete.ts +12 -9
  79. package/src/commands/instance/list.ts +2 -2
  80. package/src/commands/instance/status.ts +66 -20
  81. package/src/commands/instance/use.ts +16 -12
  82. package/src/commands/introspect.ts +9 -16
  83. package/src/commands/logs.ts +24 -25
  84. package/src/commands/mutate.ts +6 -5
  85. package/src/commands/query.ts +27 -30
  86. package/src/commands/studio.ts +20 -0
  87. package/src/commands/token.ts +8 -7
  88. package/src/commands/update.ts +38 -14
  89. package/src/commands/view.ts +7 -6
  90. package/src/connection/.spec/api.d.ts +7 -8
  91. package/src/connection/.spec/architecture.md +5 -4
  92. package/src/connection/.spec/flows/session.ts +2 -2
  93. package/src/connection/.spec/laws/connection.ts +6 -2
  94. package/src/connection/.spec/layout.ts +1 -0
  95. package/src/connection/__tests__/credential.test.ts +20 -1
  96. package/src/connection/__tests__/errors.test.ts +43 -32
  97. package/src/connection/__tests__/exchange.test.ts +36 -6
  98. package/src/connection/__tests__/failure-fixtures.ts +39 -0
  99. package/src/connection/__tests__/failure-safety.test.ts +75 -0
  100. package/src/connection/__tests__/self.test.ts +31 -11
  101. package/src/connection/__tests__/session.test.ts +3 -3
  102. package/src/connection/__tests__/target.test.ts +1 -0
  103. package/src/connection/auth.ts +13 -0
  104. package/src/connection/call.ts +1 -1
  105. package/src/connection/credential.ts +2 -2
  106. package/src/connection/errors.ts +1 -355
  107. package/src/connection/exchange.ts +52 -27
  108. package/src/connection/failure/classify.ts +110 -0
  109. package/src/connection/failure/index.ts +18 -0
  110. package/src/connection/failure/model.ts +28 -0
  111. package/src/connection/failure/render.ts +116 -0
  112. package/src/connection/reasons.ts +16 -0
  113. package/src/connection/self.ts +39 -21
  114. package/src/connection/session.ts +19 -8
  115. package/src/errors.ts +3 -2
  116. package/src/graph/.spec/api.d.ts +12 -5
  117. package/src/graph/.spec/layout.ts +9 -1
  118. package/src/graph/__tests__/mutation.test.ts +2 -2
  119. package/src/graph/__tests__/query.test.ts +25 -6
  120. package/src/graph/class.ts +30 -0
  121. package/src/graph/index.ts +1 -0
  122. package/src/graph/query.ts +21 -32
  123. package/src/identity/.spec/api.d.ts +2 -2
  124. package/src/identity/__tests__/registration.test.ts +1 -1
  125. package/src/identity/registration.ts +3 -3
  126. package/src/keys/__tests__/keys.test.ts +4 -0
  127. package/src/keys/credential.ts +1 -0
  128. package/src/lib/__tests__/admin-target.test.ts +12 -0
  129. package/src/lib/__tests__/command-dx.test.ts +1 -1
  130. package/src/lib/__tests__/domain-publication.test.ts +138 -0
  131. package/src/lib/__tests__/idp-session.test.ts +22 -0
  132. package/src/lib/__tests__/idp.test.ts +36 -4
  133. package/src/lib/__tests__/instance-candidates.test.ts +1 -3
  134. package/src/lib/__tests__/instance-target.test.ts +7 -39
  135. package/src/lib/__tests__/instance.test.ts +6 -0
  136. package/src/lib/__tests__/log-errors.test.ts +64 -0
  137. package/src/lib/__tests__/studio-server-deps.test.ts +53 -16
  138. package/src/lib/__tests__/update.test.ts +113 -8
  139. package/src/lib/__tests__/view-open-intent.test.ts +0 -2
  140. package/src/lib/__tests__/view-server.test.ts +18 -3
  141. package/src/lib/__tests__/view-session.test.ts +1 -1
  142. package/src/lib/admin-instance.ts +14 -8
  143. package/src/lib/admin-target.ts +21 -2
  144. package/src/lib/command-dx.ts +9 -9
  145. package/src/lib/domain-publication.ts +104 -0
  146. package/src/lib/failure-debug.ts +26 -0
  147. package/src/lib/idp.ts +39 -4
  148. package/src/lib/instance-candidates.ts +2 -2
  149. package/src/lib/instance-target.ts +3 -14
  150. package/src/lib/instance.ts +22 -12
  151. package/src/lib/log.ts +16 -9
  152. package/src/lib/login-flow.ts +13 -3
  153. package/src/lib/meta.ts +2 -2
  154. package/src/lib/provision-instance.ts +34 -57
  155. package/src/lib/update.ts +124 -30
  156. package/src/lib/validation.ts +2 -2
  157. package/src/lib/view/resolve.ts +1 -1
  158. package/src/lib/view/server.ts +17 -3
  159. package/src/program/__tests__/program.test.ts +20 -4
  160. package/src/program/build.ts +2 -2
  161. package/src/setup/__tests__/instance-step.test.ts +27 -10
  162. package/src/setup/steps/instance.ts +22 -15
  163. package/studio/client/dist/assets/elk-api-DjmKsHXc.js +1 -0
  164. package/studio/client/dist/assets/index-7YHdeCxp.js +81 -0
  165. package/studio/client/dist/assets/index-C0FAnwNw.css +2 -0
  166. package/studio/client/dist/assets/rolldown-runtime-Dd_uD5pT.js +1 -0
  167. package/studio/client/dist/assets/schema-studio-BilUNb6Z.css +1 -0
  168. package/studio/client/dist/assets/schema-studio-CABpTfvH.js +8 -0
  169. package/studio/client/dist/index.html +3 -2
  170. package/studio/package.json +10 -14
  171. package/studio/server/agent/ask.test.ts +1 -1
  172. package/studio/server/agent/bridge/grant.test.ts +1 -1
  173. package/studio/server/agent/bridge/routes.test.ts +1 -1
  174. package/studio/server/agent/bridge/stdio.ts +8 -1
  175. package/studio/server/agent/conversation.test.ts +10 -5
  176. package/studio/server/agent/conversation.ts +56 -8
  177. package/studio/server/agent/harness/claude/adapter.test.ts +15 -5
  178. package/studio/server/agent/harness/codex/loadout.ts +18 -21
  179. package/studio/server/agent/harness/gateway/config.ts +20 -18
  180. package/studio/server/agent/harness/selection.ts +9 -1
  181. package/studio/server/agent/prompts/anchors.test.ts +32 -36
  182. package/studio/server/agent/prompts/anchors.ts +20 -49
  183. package/studio/server/agent/prompts/system.test.ts +3 -2
  184. package/studio/server/agent/prompts/system.ts +6 -5
  185. package/studio/server/agent/prompts/turn.ts +13 -4
  186. package/studio/server/agent/routes.test.ts +1 -1
  187. package/studio/server/agent/run/completion.ts +2 -2
  188. package/studio/server/agent/run/coordinator.test.ts +1 -1
  189. package/studio/server/agent/run/preparation.ts +6 -5
  190. package/studio/server/agent/run/transcript.test.ts +35 -1
  191. package/studio/server/agent/run/transcript.ts +169 -3
  192. package/studio/server/agent/run/usage.test.ts +24 -0
  193. package/studio/server/agent/run/usage.ts +26 -1
  194. package/studio/server/api/canvas.ts +43 -0
  195. package/studio/server/api/comments.ts +85 -0
  196. package/studio/server/api/context.ts +61 -0
  197. package/studio/server/api/deployment.ts +39 -0
  198. package/studio/server/api/documents.ts +55 -0
  199. package/studio/server/api/domain.ts +59 -0
  200. package/studio/server/api/http.ts +45 -0
  201. package/studio/server/api/project.ts +57 -0
  202. package/studio/server/api/schema.ts +12 -0
  203. package/studio/server/api/views.ts +57 -0
  204. package/studio/server/api/workspace.ts +60 -0
  205. package/studio/server/api-agent-loadout.test.ts +1 -1
  206. package/studio/server/api.test.ts +147 -0
  207. package/studio/server/api.ts +20 -422
  208. package/studio/server/cache.test.ts +122 -0
  209. package/studio/server/cache.ts +162 -11
  210. package/studio/server/cli-consumers.test.ts +494 -0
  211. package/studio/server/cli.test.ts +88 -0
  212. package/studio/server/cli.ts +240 -0
  213. package/studio/server/client-package.ts +25 -1
  214. package/studio/server/domain.test.ts +50 -40
  215. package/studio/server/domain.ts +83 -52
  216. package/studio/server/environment/dotenv-preview.test.ts +39 -0
  217. package/studio/server/environment/dotenv-preview.ts +27 -0
  218. package/studio/server/environment/files.test.ts +53 -0
  219. package/studio/server/{state/env.ts → environment/files.ts} +7 -48
  220. package/studio/server/{state → handoff}/copy.ts +21 -6
  221. package/studio/server/handoff/service.test.ts +22 -0
  222. package/studio/server/{state/handoff.ts → handoff/service.ts} +21 -14
  223. package/studio/server/index.ts +1 -1
  224. package/studio/server/instances/active.ts +72 -0
  225. package/studio/server/instances/deploy-record.test.ts +31 -0
  226. package/studio/server/instances/deploy-record.ts +37 -0
  227. package/studio/server/instances/deploy.ts +56 -0
  228. package/studio/server/{state/instance.test.ts → instances/probe.test.ts} +25 -21
  229. package/studio/server/instances/probe.ts +67 -0
  230. package/studio/server/instances/status.test.ts +12 -0
  231. package/studio/server/instances/status.ts +49 -0
  232. package/studio/server/introspect/anatomy/client-tree.ts +57 -0
  233. package/studio/server/introspect/anatomy/env-fields.ts +66 -0
  234. package/studio/server/introspect/anatomy/schema-definition.ts +53 -0
  235. package/studio/server/introspect/anatomy/source.ts +181 -0
  236. package/studio/server/introspect/anatomy/views/routes.ts +114 -0
  237. package/studio/server/introspect/anatomy/views.ts +53 -0
  238. package/studio/server/introspect/anatomy-extras.test.ts +106 -35
  239. package/studio/server/introspect/anatomy-extras.ts +7 -726
  240. package/studio/server/introspect/anatomy.test.ts +22 -14
  241. package/studio/server/introspect/anatomy.ts +52 -28
  242. package/studio/server/introspect/bundle.ts +15 -9
  243. package/studio/server/introspect/canonical-schema.test.ts +93 -309
  244. package/studio/server/introspect/canonical-schema.ts +257 -425
  245. package/studio/server/introspect/config-preview.test.ts +32 -0
  246. package/studio/server/introspect/config-preview.ts +119 -0
  247. package/studio/server/introspect/core-extractor.ts +18 -127
  248. package/studio/server/introspect/core.ts +1 -2
  249. package/studio/server/introspect/diff.test.ts +18 -18
  250. package/studio/server/introspect/diff.ts +44 -90
  251. package/studio/server/introspect/extractor.ts +23 -116
  252. package/studio/server/introspect/hash.ts +5 -2
  253. package/studio/server/introspect/overlay-tsmorph.test.ts +119 -240
  254. package/studio/server/introspect/overlay-tsmorph.ts +7 -1305
  255. package/studio/server/introspect/overlay.test.ts +26 -54
  256. package/studio/server/introspect/overlay.ts +10 -37
  257. package/studio/server/introspect/revision.test.ts +54 -0
  258. package/studio/server/introspect/revision.ts +49 -0
  259. package/studio/server/introspect/runtime.test.ts +83 -143
  260. package/studio/server/introspect/runtime.ts +40 -11
  261. package/studio/server/introspect/schema-ir-json.test.ts +70 -0
  262. package/studio/server/introspect/schema-ir-json.ts +151 -0
  263. package/studio/server/introspect/schema-refs.test.ts +43 -88
  264. package/studio/server/introspect/schema-refs.ts +14 -49
  265. package/studio/server/introspect/source-overlay/annotations.ts +14 -0
  266. package/studio/server/introspect/source-overlay/handlers.ts +161 -0
  267. package/studio/server/introspect/source-overlay/kernel-calls.ts +46 -0
  268. package/studio/server/introspect/source-overlay/project.ts +248 -0
  269. package/studio/server/introspect/source-overlay/spans.ts +330 -0
  270. package/studio/server/json.ts +46 -0
  271. package/studio/server/lifecycle.ts +5 -1
  272. package/studio/server/sse.test.ts +26 -0
  273. package/studio/server/sse.ts +4 -1
  274. package/studio/server/state/baseline.test.ts +223 -7
  275. package/studio/server/state/baseline.ts +148 -34
  276. package/studio/server/state/comments.test.ts +66 -1
  277. package/studio/server/state/comments.ts +173 -10
  278. package/studio/server/state/context.ts +34 -2
  279. package/studio/server/state/documents.ts +42 -1
  280. package/studio/server/state/integrations.ts +27 -1
  281. package/studio/server/state/layout.test.ts +34 -0
  282. package/studio/server/state/layout.ts +44 -17
  283. package/studio/server/state/settings.ts +29 -22
  284. package/studio/server/state/store.test.ts +122 -0
  285. package/studio/server/state/store.ts +66 -17
  286. package/studio/server/state/visibility.ts +22 -11
  287. package/studio/server/views/model.test.ts +57 -0
  288. package/studio/server/views/model.ts +66 -0
  289. package/studio/server/views/runtime.ts +40 -0
  290. package/studio/server/views/selection-repository.test.ts +31 -0
  291. package/studio/server/views/selection-repository.ts +60 -0
  292. package/studio/server/views/session.test.ts +73 -0
  293. package/studio/server/views/session.ts +166 -0
  294. package/studio/server/views/target.test.ts +78 -0
  295. package/studio/server/views/target.ts +222 -0
  296. package/studio/server/watch.test.ts +11 -11
  297. package/studio/server/watch.ts +9 -5
  298. package/studio/server/workspace/catalog.test.ts +17 -0
  299. package/studio/server/workspace/catalog.ts +43 -0
  300. package/studio/server/{state → workspace}/create.test.ts +1 -1
  301. package/studio/server/{state → workspace}/create.ts +1 -1
  302. package/studio/server/{state → workspace}/git.ts +1 -1
  303. package/studio/server/workspace/updates.ts +83 -0
  304. package/studio/server/workspace-state.ts +1 -1
  305. package/studio/server/workspace-watch.ts +2 -2
  306. package/studio/shared/contracts/README.md +16 -0
  307. package/studio/shared/contracts/agent.ts +270 -0
  308. package/studio/shared/contracts/runtime.ts +50 -0
  309. package/studio/shared/contracts/schema.ts +393 -0
  310. package/studio/shared/contracts/surface.test.ts +317 -0
  311. package/studio/shared/contracts/workspace.ts +265 -0
  312. package/studio/shared/layout.test.ts +116 -0
  313. package/studio/shared/schema/identity.test.ts +34 -0
  314. package/studio/shared/schema/identity.ts +68 -0
  315. package/studio/shared/types.ts +11 -1099
  316. package/studio/tsconfig.json +1 -1
  317. package/viewer/dist/main.js +40 -61
  318. package/.check-workspace.cjs +0 -40
  319. package/src/commands/__tests__/instance-create-hosts.test.ts +0 -29
  320. package/studio/client/dist/assets/elk-api-D2xgMJvi.js +0 -1
  321. package/studio/client/dist/assets/index-BMdnsIJA.css +0 -1
  322. package/studio/client/dist/assets/index-ChOr3yP0.css +0 -1
  323. package/studio/client/dist/assets/index-D-vRV8w7.js +0 -8
  324. package/studio/client/dist/assets/index-LGSWRrk8.js +0 -81
  325. package/studio/server/state/catalog.ts +0 -117
  326. package/studio/server/state/instance.ts +0 -280
  327. package/studio/server/state/updates.ts +0 -63
  328. package/studio/server/state/views.test.ts +0 -286
  329. package/studio/server/state/views.ts +0 -535
@@ -81,11 +81,56 @@ describe('auth login command', () => {
81
81
  expect(result.exitCode).toBe(0)
82
82
  const session = JSON.parse(await readFile(join(tmp, 'idp-sessions', 'alice.json'), 'utf-8'))
83
83
  expect(session.audience).toBe('https://kernel.example.com')
84
+ expect(session.clientId).toBe('client_123')
84
85
  expect(session.subject).toBe('user_123')
85
86
  } finally {
86
87
  await server.stop()
87
88
  }
88
89
  })
90
+
91
+ test('persists an explicit client id and reuses it for a named login', async () => {
92
+ const server = oauthServer({
93
+ accessToken: unsignedJwt({
94
+ iss: 'http://127.0.0.1',
95
+ sub: 'user_123',
96
+ exp: 1893456000,
97
+ }),
98
+ })
99
+ try {
100
+ await writeIdpConfig(server.url)
101
+
102
+ const explicit = await runAuthLogin(
103
+ '--idp',
104
+ 'test',
105
+ '--name',
106
+ 'alice',
107
+ '--client-id',
108
+ 'client_override',
109
+ '--client-credentials',
110
+ '--client-secret-env',
111
+ 'TEST_CLIENT_SECRET',
112
+ '--raw',
113
+ )
114
+ const reused = await runAuthLogin(
115
+ '--idp',
116
+ 'test',
117
+ '--name',
118
+ 'alice',
119
+ '--client-credentials',
120
+ '--client-secret-env',
121
+ 'TEST_CLIENT_SECRET',
122
+ '--raw',
123
+ )
124
+
125
+ expect(explicit.exitCode).toBe(0)
126
+ expect(reused.exitCode).toBe(0)
127
+ expect(server.clientIds()).toEqual(['client_override', 'client_override'])
128
+ const session = JSON.parse(await readFile(join(tmp, 'idp-sessions', 'alice.json'), 'utf-8'))
129
+ expect(session.clientId).toBe('client_override')
130
+ } finally {
131
+ await server.stop()
132
+ }
133
+ })
89
134
  })
90
135
 
91
136
  async function writeIdpConfig(baseUrl: string): Promise<void> {
@@ -131,12 +176,19 @@ async function writeIdpConfig(baseUrl: string): Promise<void> {
131
176
  )
132
177
  }
133
178
 
134
- function oauthServer(args: { accessToken: string }): { url: string; stop: () => Promise<void> } {
179
+ function oauthServer(args: { accessToken: string }): {
180
+ url: string
181
+ clientIds: () => Array<string | null>
182
+ stop: () => Promise<void>
183
+ } {
184
+ const clientIds: Array<string | null> = []
135
185
  const server = Bun.serve({
136
186
  port: 0,
137
187
  fetch: async (request) => {
138
188
  const url = new URL(request.url)
139
189
  if (url.pathname !== '/token') return new Response('not found', { status: 404 })
190
+ const form = new URLSearchParams(await request.text())
191
+ clientIds.push(form.get('client_id'))
140
192
  return Response.json({
141
193
  access_token: args.accessToken,
142
194
  token_type: 'Bearer',
@@ -146,6 +198,7 @@ function oauthServer(args: { accessToken: string }): { url: string; stop: () =>
146
198
  })
147
199
  return {
148
200
  url: `http://${server.hostname}:${server.port}`,
201
+ clientIds: () => clientIds,
149
202
  stop: () => server.stop(true),
150
203
  }
151
204
  }
@@ -1,34 +1,47 @@
1
1
  import { Path } from '@astrale-os/sdk/graph/path'
2
+ import { bundle, defineSchema, func, method, nodeClass } from '@astrale-os/sdk/schema'
2
3
  import { describe, expect, test } from 'bun:test'
3
4
 
4
- import { describeCallableFromSchema } from '../call-describe'
5
+ import { describeCallableFromBundle } from '../call-describe'
5
6
 
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
- },
7
+ const createInstance = method({
8
+ auth: 'authenticated',
9
+ description: 'Create one child Instance.',
10
+ input: {
11
+ type: 'object',
12
+ properties: { operationId: { type: 'string' }, slug: { type: 'string' } },
13
+ required: ['operationId', 'slug'],
14
+ additionalProperties: false,
18
15
  },
19
- functions: {
20
- journal: {
21
- description: 'Read the authorized Kernel journal.',
22
- input: { type: 'object' },
23
- },
16
+ output: { type: 'object', additionalProperties: true },
17
+ static: true,
18
+ })
19
+ const inspectInstance = method({
20
+ auth: 'authenticated',
21
+ description: 'Inspect one child Instance.',
22
+ input: { type: 'object', properties: {}, additionalProperties: false },
23
+ output: { type: 'object', additionalProperties: true },
24
+ })
25
+ const journal = func({
26
+ auth: 'authenticated',
27
+ description: 'Read the authorized Kernel journal.',
28
+ input: { type: 'object', properties: {}, additionalProperties: false },
29
+ output: { type: 'object', additionalProperties: true },
30
+ })
31
+ const source = defineSchema('host.astrale.ai', {
32
+ classes: {
33
+ Manager: nodeClass({ methods: { createInstance } }),
34
+ Instance: nodeClass({ methods: { inspectInstance } }),
24
35
  },
25
- }
36
+ functions: { journal },
37
+ })
38
+ const installed = bundle.create(source)
26
39
 
27
- describe('describeCallableFromSchema', () => {
28
- test('reads a static Class method from the Domain schema', () => {
29
- const described = describeCallableFromSchema(
40
+ describe('describeCallableFromBundle', () => {
41
+ test('reads a static Class method from the resolved Domain', () => {
42
+ const described = describeCallableFromBundle(
30
43
  Path.parse('/:host.astrale.ai:class.Manager:createInstance'),
31
- schema,
44
+ installed,
32
45
  )
33
46
  expect(described).toMatchObject({
34
47
  origin: 'host.astrale.ai',
@@ -36,30 +49,30 @@ describe('describeCallableFromSchema', () => {
36
49
  method: 'createInstance',
37
50
  dispatch: 'static',
38
51
  description: 'Create one child Instance.',
39
- auth: 'authorized',
52
+ auth: 'authenticated',
40
53
  })
41
54
  expect(described?.input).toMatchObject({ required: ['operationId', 'slug'] })
42
55
  })
43
56
 
44
- test('finds an instance method when the receiver Path is not a Class projection', () => {
57
+ test('finds one instance method when the receiver Path is not a Class projection', () => {
45
58
  expect(
46
- describeCallableFromSchema(
47
- Path.parse('/:host.astrale.ai:core.manager::createInstance'),
48
- schema,
59
+ describeCallableFromBundle(
60
+ Path.parse('/:host.astrale.ai:core.manager::inspectInstance'),
61
+ installed,
49
62
  ),
50
63
  ).toMatchObject({
51
64
  origin: 'host.astrale.ai',
52
- class: 'Manager',
53
- method: 'createInstance',
65
+ class: 'Instance',
66
+ method: 'inspectInstance',
54
67
  dispatch: 'instance',
55
68
  })
56
69
  })
57
70
 
58
- test('reads a standalone Function from the Domain schema', () => {
71
+ test('reads a standalone Function from the resolved Domain', () => {
59
72
  expect(
60
- describeCallableFromSchema(Path.parse('/:kernel.astrale.ai:function.journal'), schema),
73
+ describeCallableFromBundle(Path.parse('/:host.astrale.ai:function.journal'), installed),
61
74
  ).toMatchObject({
62
- origin: 'kernel.astrale.ai',
75
+ origin: 'host.astrale.ai',
63
76
  function: 'journal',
64
77
  description: 'Read the authorized Kernel journal.',
65
78
  })
@@ -14,7 +14,11 @@ describe('call command result lifetime', () => {
14
14
  }
15
15
 
16
16
  const sessionStream = Object.assign(stream(), { async cancel() {} })
17
- const materialized = await materializeCallResult({ kind: 'stream', stream: sessionStream })
17
+ const materialized = await materializeCallResult({
18
+ kind: 'stream',
19
+ invocation: { source: 'https://kernel.test' as never, id: 'test-stream' },
20
+ stream: sessionStream,
21
+ })
18
22
  sessionOpen = false
19
23
 
20
24
  expect(materialized).toEqual({ kind: 'stream', values: ['first', 'second'] })
@@ -1,5 +1,7 @@
1
+ import { defineSchema } from '@astrale-os/sdk/schema'
1
2
  import { afterEach, describe, expect, mock, test } from 'bun:test'
2
3
 
4
+ import { releaseFor } from '../../__tests__/fixtures/publication'
3
5
  import { installDirect } from '../domain/install'
4
6
 
5
7
  const GENERATED = '4a4c9a18-50f6-4d84-a7b7-2d83e3e45dc8'
@@ -7,6 +9,13 @@ const RETRY = '139137b5-af47-47ce-92b2-b64a2b0c63d7'
7
9
 
8
10
  const originalFetch = globalThis.fetch
9
11
 
12
+ const schema = defineSchema('crm.test', {})
13
+ const deployed = releaseFor(schema, 'https://crm.test').publication
14
+
15
+ function publicationResponse(): Response {
16
+ return Response.json(deployed)
17
+ }
18
+
10
19
  class ExitError extends Error {}
11
20
 
12
21
  afterEach(() => {
@@ -15,9 +24,7 @@ afterEach(() => {
15
24
 
16
25
  describe('direct domain install operation identity', () => {
17
26
  test('generates one operation before transport and exposes it for recovery', async () => {
18
- globalThis.fetch = mock(async () =>
19
- Response.json({ domainName: 'crm.test' }),
20
- ) as unknown as typeof fetch
27
+ globalThis.fetch = mock(async () => publicationResponse()) as unknown as typeof fetch
21
28
  const create = mock(() => GENERATED)
22
29
  const requests: unknown[] = []
23
30
  let recovery: unknown
@@ -54,9 +61,7 @@ describe('direct domain install operation identity', () => {
54
61
  })
55
62
 
56
63
  test('accepts an explicit operation only as the exact retry identity', async () => {
57
- globalThis.fetch = mock(async () =>
58
- Response.json({ domainName: 'crm.test' }),
59
- ) as unknown as typeof fetch
64
+ globalThis.fetch = mock(async () => publicationResponse()) as unknown as typeof fetch
60
65
  const create = mock(() => GENERATED)
61
66
  const accepted: string[] = []
62
67
  const requests: unknown[] = []
@@ -95,7 +100,7 @@ describe('direct domain install operation identity', () => {
95
100
  test('rejects a non-UUID operation before metadata or Kernel transport', async () => {
96
101
  const originalExit = process.exit
97
102
  const originalWrite = process.stderr.write
98
- const fetch = mock(async () => Response.json({ domainName: 'crm.test' }))
103
+ const fetch = mock(async () => publicationResponse())
99
104
  const run = mock(async () => undefined)
100
105
  globalThis.fetch = fetch as unknown as typeof globalThis.fetch
101
106
  process.exit = (() => {
@@ -1,4 +1,4 @@
1
- import { ResponseError } from '@astrale-os/kernel-client'
1
+ import { ResponseError } from '@astrale-os/sdk/client'
2
2
  import { afterEach, beforeEach, describe, expect, mock, test } from 'bun:test'
3
3
 
4
4
  import type { OwnedInstanceInfo } from '../../lib/admin-instance'
@@ -11,6 +11,10 @@ class ExitError extends Error {
11
11
 
12
12
  const calls: Array<{ path: string; params: unknown }> = []
13
13
  let inventory: OwnedInstanceInfo[] = []
14
+ const TEST_INVOCATION = {
15
+ source: 'https://kernel.test',
16
+ id: 'domain-install-owned',
17
+ } as ConstructorParameters<typeof ResponseError>[2]
14
18
 
15
19
  const hostCall = mock(async (call: { target: string; input: unknown }): Promise<unknown> => {
16
20
  const path = call.target
@@ -137,7 +141,7 @@ describe('admin domain install owner boundary', () => {
137
141
  createdAt: '2026-08-20T00:00:00.000Z',
138
142
  updatedAt: '2026-08-20T00:00:00.000Z',
139
143
  }
140
- const failure = new ResponseError(5001, 'Schema operation is not supported.', {
144
+ const failure = new ResponseError(5001, 'Schema operation is not supported.', TEST_INVOCATION, {
141
145
  code: 'SCHEMA_UPGRADE_INCOMPATIBLE',
142
146
  details: {
143
147
  phase: 'upgrade',
@@ -1,8 +1,10 @@
1
- import { describe, expect, test } from 'bun:test'
1
+ import { defineSchema, schema as schemaApi } from '@astrale-os/sdk/schema'
2
+ import { afterAll, describe, expect, test } from 'bun:test'
2
3
 
3
4
  import type { DomainInfo } from '../../lib/admin-domain'
4
5
 
5
- import { byDefaultThenName, domainProjection, type DomainRow } from '../domain/list'
6
+ import { releaseFor } from '../../__tests__/fixtures/publication'
7
+ import { byDefaultThenName, domainProjection, probe, type DomainRow } from '../domain/list'
6
8
 
7
9
  const strip = (s: string): string => s.replace(/\[[0-9;]*m/g, '')
8
10
 
@@ -68,7 +70,64 @@ describe('domain list — projection', () => {
68
70
  const live: DomainRow = { ...base, reachable: true, checkError: null }
69
71
  expect(strip(domainProjection([live]).rows[0].status)).toBe('● live')
70
72
 
71
- const down: DomainRow = { ...base, reachable: false, checkError: 'meta HTTP 502' }
72
- expect(strip(domainProjection([down]).rows[0].status)).toBe('○ meta HTTP 502')
73
+ const down: DomainRow = { ...base, reachable: false, checkError: 'Publication HTTP 502' }
74
+ expect(strip(domainProjection([down]).rows[0].status)).toBe('○ Publication HTTP 502')
75
+ })
76
+ })
77
+
78
+ describe('domain list — canonical Publication check', () => {
79
+ const servers: { stop(): void }[] = []
80
+ afterAll(() => {
81
+ for (const server of servers) server.stop()
82
+ })
83
+
84
+ test('reports the admitted schema revision in machine data', async () => {
85
+ const schema = defineSchema('catalog-check.example.dev', {})
86
+ const deployed = releaseFor(schema, 'https://catalog-check.example.dev').publication
87
+ const server = Bun.serve({
88
+ port: 0,
89
+ fetch(request) {
90
+ return new URL(request.url).pathname === '/.well-known/astrale/domain.json'
91
+ ? Response.json(deployed)
92
+ : new Response('not found', { status: 404 })
93
+ },
94
+ })
95
+ servers.push(server)
96
+
97
+ await expect(
98
+ probe(
99
+ entry({
100
+ origin: schema.origin,
101
+ url: `http://localhost:${server.port}`,
102
+ }),
103
+ ),
104
+ ).resolves.toMatchObject({
105
+ reachable: true,
106
+ schemaRevision: schemaApi.revision(schema),
107
+ checkError: null,
108
+ })
109
+ })
110
+
111
+ test('rejects a Publication whose origin differs from the catalog entry', async () => {
112
+ const schema = defineSchema('deployed.example.dev', {})
113
+ const deployed = releaseFor(schema, 'https://deployed.example.dev').publication
114
+ const server = Bun.serve({
115
+ port: 0,
116
+ fetch: () => Response.json(deployed),
117
+ })
118
+ servers.push(server)
119
+
120
+ await expect(
121
+ probe(
122
+ entry({
123
+ origin: 'catalog.example.dev',
124
+ url: `http://localhost:${server.port}`,
125
+ }),
126
+ ),
127
+ ).resolves.toMatchObject({
128
+ reachable: false,
129
+ checkError:
130
+ 'Domain origin mismatch: deployed=deployed.example.dev expected=catalog.example.dev',
131
+ })
73
132
  })
74
133
  })
@@ -1,5 +1,7 @@
1
+ import { defineSchema } from '@astrale-os/sdk/schema'
1
2
  import { afterAll, describe, expect, test } from 'bun:test'
2
3
 
4
+ import { releaseFor } from '../../__tests__/fixtures/publication'
3
5
  import { domainRefFromTarget, isIdentityOverride, probeDeclaredOrigin } from '../domain/install'
4
6
 
5
7
  describe('admin-path target classification', () => {
@@ -29,46 +31,44 @@ describe('identity-override detection', () => {
29
31
  })
30
32
  })
31
33
 
32
- describe('declared-origin probe (/meta)', () => {
34
+ describe('declared-origin Publication probe', () => {
33
35
  const servers: { stop(): void }[] = []
34
36
  afterAll(() => {
35
37
  for (const s of servers) s.stop()
36
38
  })
37
39
 
38
- function serveMeta(handler: (req: Request) => Response): string {
40
+ function servePublication(handler: (req: Request) => Response): string {
39
41
  const server = Bun.serve({ port: 0, fetch: handler })
40
42
  servers.push(server)
41
43
  return `http://localhost:${server.port}`
42
44
  }
43
45
 
44
- test('reads origin from a well-formed /meta', async () => {
45
- // The exact shape SDK workers serve (adapter-cloudflare worker `/meta`).
46
- const url = serveMeta((req) =>
47
- new URL(req.url).pathname === '/meta'
48
- ? Response.json({
49
- origin: 'crm.acme.dev',
50
- issuer: 'https://crm.acme.dev',
51
- schemaRevision: 'sha256:abc',
52
- deploymentVersion: 'v1',
53
- })
46
+ test('reads origin from a well-formed canonical Publication', async () => {
47
+ const schema = defineSchema('crm.acme.dev', {})
48
+ const deployed = releaseFor(schema, 'https://crm.acme.dev').publication
49
+ const url = servePublication((req) =>
50
+ new URL(req.url).pathname === '/.well-known/astrale/domain.json'
51
+ ? Response.json(deployed)
54
52
  : new Response('nope', { status: 404 }),
55
53
  )
56
54
  expect(await probeDeclaredOrigin(url)).toBe('crm.acme.dev')
57
55
  })
58
56
 
59
- test('falls back to the pre-Kernel-V2 domainName field', async () => {
60
- const legacy = serveMeta(() => Response.json({ iss: 'https://x', domainName: 'crm.acme.dev' }))
61
- expect(await probeDeclaredOrigin(legacy)).toBe('crm.acme.dev')
57
+ test('does not accept the removed pre-Kernel-V2 domainName shape', async () => {
58
+ const legacy = servePublication(() =>
59
+ Response.json({ iss: 'https://x', domainName: 'crm.acme.dev' }),
60
+ )
61
+ expect(await probeDeclaredOrigin(legacy)).toBeUndefined()
62
62
  })
63
63
 
64
- test('degrades to undefined on missing origin, non-200, bad JSON, or dead host', async () => {
65
- const noName = serveMeta(() => Response.json({ iss: 'https://x' }))
64
+ test('degrades to undefined on invalid Publication, non-200, bad JSON, or dead host', async () => {
65
+ const noName = servePublication(() => Response.json({ iss: 'https://x' }))
66
66
  expect(await probeDeclaredOrigin(noName)).toBeUndefined()
67
67
 
68
- const error = serveMeta(() => new Response('boom', { status: 500 }))
68
+ const error = servePublication(() => new Response('boom', { status: 500 }))
69
69
  expect(await probeDeclaredOrigin(error)).toBeUndefined()
70
70
 
71
- const badJson = serveMeta(() => new Response('<html>', { status: 200 }))
71
+ const badJson = servePublication(() => new Response('<html>', { status: 200 }))
72
72
  expect(await probeDeclaredOrigin(badJson)).toBeUndefined()
73
73
 
74
74
  expect(await probeDeclaredOrigin('http://127.0.0.1:1')).toBeUndefined()
@@ -5,7 +5,7 @@ import type { InstanceInfo } from '../../lib/admin-instance'
5
5
  import { buildInstanceRows, type Bookmark } from '../instance/list'
6
6
 
7
7
  const managed: InstanceInfo[] = [
8
- { id: 'demo', slug: 'demo', url: 'https://demo.eu.astrale.ai', region: 'eu' },
8
+ { id: 'demo', slug: 'demo', url: 'https://demo.eu.astrale.ai', state: 'ready' },
9
9
  ]
10
10
 
11
11
  function bookmark(overrides: Partial<Bookmark> = {}): Bookmark {
@@ -0,0 +1,139 @@
1
+ import { afterEach, beforeEach, describe, expect, mock, test } from 'bun:test'
2
+ import { mkdtemp, readFile, rm, writeFile } from 'node:fs/promises'
3
+ import { tmpdir } from 'node:os'
4
+ import { join } from 'node:path'
5
+
6
+ import type { InstanceInfo } from '../../lib/admin-instance'
7
+ import type { ResolvedInstance } from '../../lib/instance'
8
+
9
+ import { resolveStatus, type StatusDependencies } from '../instance/status'
10
+
11
+ const cliRoot = join(import.meta.dir, '../../..')
12
+
13
+ let home: string
14
+ let server: ReturnType<typeof Bun.serve>
15
+
16
+ beforeEach(async () => {
17
+ home = await mkdtemp(join(tmpdir(), 'astrale-instance-status-'))
18
+ server = Bun.serve({
19
+ port: 0,
20
+ fetch(request) {
21
+ const url = new URL(request.url)
22
+ const issuer = `http://127.0.0.1:${server.port}`
23
+ if (url.pathname === '/.well-known/openid-configuration') {
24
+ return Response.json({ issuer, jwks_uri: `${issuer}/.well-known/jwks.json` })
25
+ }
26
+ if (url.pathname === '/.well-known/jwks.json') {
27
+ return Response.json({ keys: [{ kid: 'status-test' }] })
28
+ }
29
+ return new Response('Not found', { status: 404 })
30
+ },
31
+ })
32
+ })
33
+
34
+ afterEach(async () => {
35
+ server.stop(true)
36
+ await rm(home, { recursive: true, force: true })
37
+ })
38
+
39
+ describe('instance status', () => {
40
+ test('keeps managed lifecycle authoritative when the slug is also bookmarked', async () => {
41
+ const managed: InstanceInfo = {
42
+ id: '@managed',
43
+ slug: 'shared',
44
+ url: 'https://managed.example.com',
45
+ state: 'provisioning',
46
+ phase: 'bootstrap',
47
+ }
48
+ const statusOwnedInstance: StatusDependencies['statusOwnedInstance'] = mock(async () => managed)
49
+ const resolveInstance: StatusDependencies['resolveInstance'] = mock(async () => {
50
+ throw new Error('bookmark resolution must not run')
51
+ })
52
+ const probeBookmark: StatusDependencies['probeBookmark'] = mock(async () => {
53
+ throw new Error('bookmark probe must not run')
54
+ })
55
+
56
+ await expect(
57
+ resolveStatus('shared', {}, { statusOwnedInstance, resolveInstance, probeBookmark }),
58
+ ).resolves.toEqual(managed)
59
+ expect(statusOwnedInstance).toHaveBeenCalledWith({}, 'shared')
60
+ expect(resolveInstance).not.toHaveBeenCalled()
61
+ expect(probeBookmark).not.toHaveBeenCalled()
62
+ })
63
+
64
+ test('probes only an explicitly selected bookmark and rejects mixed source flags', async () => {
65
+ const bookmark: ResolvedInstance = {
66
+ name: 'local',
67
+ kind: 'bookmark',
68
+ url: 'https://local.example.com',
69
+ issuer: 'https://issuer.example.com',
70
+ }
71
+ const statusOwnedInstance: StatusDependencies['statusOwnedInstance'] = mock(async () => {
72
+ throw new Error('managed status must not run')
73
+ })
74
+ const resolveInstance: StatusDependencies['resolveInstance'] = mock(async () => bookmark)
75
+ const probeBookmark: StatusDependencies['probeBookmark'] = mock(async () => undefined)
76
+ const dependencies = { statusOwnedInstance, resolveInstance, probeBookmark }
77
+
78
+ await expect(resolveStatus('local', { bookmarked: true }, dependencies)).resolves.toEqual({
79
+ slug: 'local',
80
+ url: bookmark.url,
81
+ issuer: 'https://issuer.example.com',
82
+ kind: 'bookmark',
83
+ state: 'ready',
84
+ })
85
+ expect(resolveInstance).toHaveBeenCalledWith('local', undefined, { persist: false })
86
+ expect(probeBookmark).toHaveBeenCalledWith(bookmark)
87
+ expect(statusOwnedInstance).not.toHaveBeenCalled()
88
+
89
+ await expect(
90
+ resolveStatus('local', { bookmarked: true, admin: 'admin' }, dependencies),
91
+ ).rejects.toMatchObject({ code: 'INVALID_FLAG' })
92
+ })
93
+
94
+ test('probes an explicit local bookmark without rewriting its registry', async () => {
95
+ const issuer = `http://127.0.0.1:${server.port}`
96
+ const registry = `${JSON.stringify({
97
+ active: 'local',
98
+ instances: {
99
+ local: {
100
+ url: issuer,
101
+ issuer,
102
+ mode: 'remote',
103
+ },
104
+ },
105
+ })}\n`
106
+ const path = join(home, 'instances.json')
107
+ await writeFile(path, registry)
108
+
109
+ const process = Bun.spawn({
110
+ cmd: [
111
+ 'bun',
112
+ join(cliRoot, 'bin/astrale.ts'),
113
+ 'instance',
114
+ 'status',
115
+ 'local',
116
+ '--bookmarked',
117
+ '--json',
118
+ ],
119
+ env: { ...Bun.env, ASTRALE_HOME: home },
120
+ stdout: 'pipe',
121
+ stderr: 'pipe',
122
+ })
123
+ const [stdout, stderr, exitCode] = await Promise.all([
124
+ new Response(process.stdout).text(),
125
+ new Response(process.stderr).text(),
126
+ process.exited,
127
+ ])
128
+
129
+ expect(exitCode, stderr).toBe(0)
130
+ expect(JSON.parse(stdout)).toEqual({
131
+ slug: 'local',
132
+ url: issuer,
133
+ issuer,
134
+ kind: 'bookmark',
135
+ state: 'ready',
136
+ })
137
+ expect(await readFile(path, 'utf8')).toBe(registry)
138
+ })
139
+ })