@astrale-os/cli 1.0.0-beta.1 → 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 +51471 -56290
  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 +39 -164
  50. package/src/admin/instance/client.ts +35 -124
  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 +48 -19
  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 +38 -14
  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
@@ -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
+ })
@@ -36,6 +36,8 @@ let mutations: unknown[] = []
36
36
  let queryResult: unknown
37
37
  let getResult: unknown
38
38
  let mutationResult: unknown
39
+ let selfFailure: unknown
40
+ let selfContexts: unknown[] = []
39
41
 
40
42
  const queryMock = mock(async (ast: unknown, options?: unknown) => {
41
43
  queryCalls.push({ ast, options })
@@ -49,7 +51,9 @@ const mutateMock = mock(async (mutation: unknown) => {
49
51
  mutations.push(mutation)
50
52
  return mutationResult
51
53
  })
52
- const expandSelfInPathMock = mock(async (path: string) => {
54
+ const expandSelfInPathMock = mock(async (path: string, context: unknown) => {
55
+ selfContexts.push(context)
56
+ if (selfFailure !== undefined) throw selfFailure
53
57
  if (path === '@self') {
54
58
  return {
55
59
  path: '@expanded-self',
@@ -59,10 +63,11 @@ const expandSelfInPathMock = mock(async (path: string) => {
59
63
  return { path, meta: undefined }
60
64
  })
61
65
  const withSelfHintMock = mock(async (action: () => Promise<unknown>) => action())
66
+ const commandContext = {
67
+ graph: { query: queryMock, getOrThrow: getOrThrowMock, mutate: mutateMock },
68
+ }
62
69
  const runKernelCommandMock = mock(async (run: RunOpts) => {
63
- const result = await run.fn({
64
- graph: { query: queryMock, getOrThrow: getOrThrowMock, mutate: mutateMock },
65
- })
70
+ const result = await run.fn(commandContext)
66
71
  await run.format?.(result, run.opts, true)
67
72
  })
68
73
 
@@ -88,6 +93,8 @@ beforeEach(() => {
88
93
  }
89
94
  getResult = undefined
90
95
  mutationResult = { createdNodes: {} }
96
+ selfFailure = undefined
97
+ selfContexts = []
91
98
  queryMock.mockClear()
92
99
  getOrThrowMock.mockClear()
93
100
  mutateMock.mockClear()
@@ -161,7 +168,7 @@ describe('query command', () => {
161
168
  ])
162
169
  })
163
170
 
164
- test('authors a Definition source through the same canonical Query V6 call', async () => {
171
+ test('authors a Class source through the same canonical Query V6 call', async () => {
165
172
  const { queryCommand } = await import('../query')
166
173
 
167
174
  await queryCommand([], {
@@ -179,8 +186,8 @@ describe('query command', () => {
179
186
  kind: 'node',
180
187
  terms: [
181
188
  {
182
- kind: 'definition',
183
- definition: { origin: 'issues.astrale.ai', kind: 'class', name: 'Issue' },
189
+ kind: 'class',
190
+ class: { origin: 'issues.astrale.ai', kind: 'class', name: 'Issue' },
184
191
  },
185
192
  ],
186
193
  binding: 'n0',
@@ -193,6 +200,17 @@ describe('query command', () => {
193
200
  ])
194
201
  })
195
202
 
203
+ test('resolves @self inside the command session and never relabels transport failure as input', async () => {
204
+ const { queryCommand } = await import('../query')
205
+ await queryCommand(['@self'], { json: true, limit: '2' })
206
+ expect(selfContexts).toEqual([commandContext])
207
+
208
+ const aggregate = new AggregateError([], '')
209
+ selfFailure = aggregate
210
+ await expect(queryCommand(['@self'], { json: true, limit: '2' })).rejects.toBe(aggregate)
211
+ expect(errors.join('\n')).not.toContain('INVALID_INPUT')
212
+ })
213
+
196
214
  /** @evidence TEST-CLI-QUERY-REJECTS-V1-BEFORE-CONNECTION */
197
215
  test('rejects a legacy AST before opening the command connection', async () => {
198
216
  const { queryCommand } = await import('../query')
@@ -215,7 +233,7 @@ describe('get command', () => {
215
233
  const { getCommand } = await import('../get')
216
234
  getResult = {
217
235
  id: 'note-1',
218
- class: '/:notes.example.dev:class.Note',
236
+ class: 'notes.example.dev:class.Note',
219
237
  props: { 'notes.example.dev:class.Note.property.title': 'Hello' },
220
238
  }
221
239
 
@@ -226,6 +244,13 @@ describe('get command', () => {
226
244
  expect(stdout).not.toContain('__labels')
227
245
  expect(stdout).not.toContain('classId')
228
246
  })
247
+
248
+ test('resolves @self through the same point-read command context', async () => {
249
+ const { getCommand } = await import('../get')
250
+ await getCommand('@self', { json: true })
251
+ expect(selfContexts).toEqual([commandContext])
252
+ expect(getTargets).toEqual(['@expanded-self'])
253
+ })
229
254
  })
230
255
 
231
256
  describe('mutate command', () => {
@@ -242,7 +267,7 @@ describe('mutate command', () => {
242
267
  {
243
268
  op: 'node.create',
244
269
  as: 'note',
245
- class: '/:notes.example.dev:class.Note',
270
+ class: 'notes.example.dev:class.Note',
246
271
  props: {},
247
272
  },
248
273
  ],
@@ -258,7 +283,7 @@ describe('mutate command', () => {
258
283
  {
259
284
  op: 'node.create',
260
285
  as: 'note',
261
- class: '/:notes.example.dev:class.Note',
286
+ class: 'notes.example.dev:class.Note',
262
287
  props: {},
263
288
  },
264
289
  ],
@@ -0,0 +1,22 @@
1
+ import { expect, test } from 'bun:test'
2
+ import { realpathSync } from 'node:fs'
3
+
4
+ import { encodeStudioCliDescriptor } from '../studio'
5
+
6
+ test('Studio descriptor preserves the exact runtime and script entrypoint', () => {
7
+ expect(JSON.parse(encodeStudioCliDescriptor(process.execPath, import.meta.path))).toEqual({
8
+ version: 1,
9
+ executable: process.execPath,
10
+ args: [realpathSync(import.meta.path)],
11
+ })
12
+ })
13
+
14
+ test('Studio descriptor reinvokes a compiled executable without its virtual bunfs entry', () => {
15
+ expect(JSON.parse(encodeStudioCliDescriptor('/opt/bin/astrale', '/$bunfs/root/astrale'))).toEqual(
16
+ {
17
+ version: 1,
18
+ executable: '/opt/bin/astrale',
19
+ args: [],
20
+ },
21
+ )
22
+ })
@@ -6,14 +6,17 @@ describe('CLI update staleness', () => {
6
6
  test('trusts the release manifest for a script install without consulting npm latest', async () => {
7
7
  const fetchPackageVersion = mock(async () => '0.8.1-alpha.7')
8
8
  const result = await cliStale(
9
- { channel: 'beta' },
9
+ {},
10
10
  {
11
- update: async () => ({
12
- status: 'up-to-date',
13
- currentVersion: '1.0.0-beta.0',
14
- latestVersion: '1.0.0-beta.0',
15
- channel: 'beta',
16
- }),
11
+ update: async ({ channel }) => {
12
+ expect(channel).toBe('beta')
13
+ return {
14
+ status: 'up-to-date',
15
+ currentVersion: '1.0.0-beta.0',
16
+ latestVersion: '1.0.0-beta.0',
17
+ channel: 'beta',
18
+ }
19
+ },
17
20
  fetchPackageVersion,
18
21
  },
19
22
  )
@@ -28,37 +31,63 @@ describe('CLI update staleness', () => {
28
31
  expect(fetchPackageVersion).not.toHaveBeenCalled()
29
32
  })
30
33
 
31
- test('uses the selected npm dist-tag only for package-managed installs', async () => {
34
+ test('uses an explicit npm dist-tag only for package-managed installs', async () => {
32
35
  const result = await cliStale(
33
- { channel: 'beta' },
36
+ { channel: 'canary' },
34
37
  {
35
- update: async () => {
36
- throw new Error('package managed')
37
- },
38
+ update: async () => ({
39
+ status: 'managed',
40
+ currentVersion: '1.0.0-beta.0',
41
+ executable: '/opt/homebrew/bin/node',
42
+ }),
38
43
  fetchPackageVersion: async ({ channel }) => {
39
- expect(channel).toBe('beta')
40
- return '1.0.0-beta.0'
44
+ expect(channel).toBe('canary')
45
+ return '1.0.0-canary.0'
41
46
  },
42
47
  },
43
48
  )
44
49
 
45
50
  expect(result).toMatchObject({
46
51
  managed: true,
47
- latest: '1.0.0-beta.0',
52
+ latest: '1.0.0-canary.0',
48
53
  channel: 'npm',
49
54
  })
50
55
  })
51
56
 
52
- test('maps the stable channel to the npm latest dist-tag', async () => {
57
+ test('does not misclassify a script update failure as package-managed', async () => {
58
+ const fetchPackageVersion = mock(async () => '9.9.9')
59
+ const result = await cliStale(
60
+ {},
61
+ {
62
+ update: async () => {
63
+ throw new Error('release endpoint unavailable')
64
+ },
65
+ fetchPackageVersion,
66
+ },
67
+ )
68
+
69
+ expect(result).toMatchObject({
70
+ stale: false,
71
+ managed: false,
72
+ error: 'release endpoint unavailable',
73
+ })
74
+ expect(fetchPackageVersion).not.toHaveBeenCalled()
75
+ })
76
+
77
+ test('maps the default to beta and the stable channel to npm latest', async () => {
53
78
  const originalFetch = globalThis.fetch
54
79
  const fetchMock = mock(async (input: RequestInfo | URL) => {
55
- expect(String(input)).toBe('https://registry.npmjs.org/@astrale-os/cli/latest')
56
- return Response.json({ version: '1.0.0' })
80
+ const target = String(input).split('/').at(-1)
81
+ return Response.json({ version: target === 'beta' ? '1.0.0-beta.0' : '1.0.0' })
57
82
  })
58
83
  globalThis.fetch = fetchMock as unknown as typeof fetch
59
84
  try {
85
+ expect(await fetchNpmTargetVersion({})).toBe('1.0.0-beta.0')
60
86
  expect(await fetchNpmTargetVersion({ channel: 'stable' })).toBe('1.0.0')
61
- expect(fetchMock).toHaveBeenCalledTimes(1)
87
+ expect(fetchMock.mock.calls.map(([input]) => String(input))).toEqual([
88
+ 'https://registry.npmjs.org/@astrale-os/cli/beta',
89
+ 'https://registry.npmjs.org/@astrale-os/cli/latest',
90
+ ])
62
91
  } finally {
63
92
  globalThis.fetch = originalFetch
64
93
  }