@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,6 +1,14 @@
1
1
  import { defineLayout } from '@astrale-os/spec/authoring'
2
2
 
3
3
  export default defineLayout({
4
- entries: ['__tests__/', 'index.ts', 'mutation.ts', 'projection.ts', 'query.ts', 'tsconfig.json'],
4
+ entries: [
5
+ '__tests__/',
6
+ 'class.ts',
7
+ 'index.ts',
8
+ 'mutation.ts',
9
+ 'projection.ts',
10
+ 'query.ts',
11
+ 'tsconfig.json',
12
+ ],
5
13
  exact: true,
6
14
  })
@@ -14,7 +14,7 @@ describe('prepareMutation', () => {
14
14
  {
15
15
  op: 'node.create',
16
16
  as: 'note',
17
- class: '/:notes.example.dev:class.Note',
17
+ class: 'notes.example.dev:class.Note',
18
18
  props: {},
19
19
  },
20
20
  ],
@@ -29,7 +29,7 @@ describe('prepareMutation', () => {
29
29
  {
30
30
  op: 'node.create',
31
31
  as: 'note',
32
- class: '/:notes.example.dev:class.Note',
32
+ class: 'notes.example.dev:class.Note',
33
33
  props: {},
34
34
  },
35
35
  ],
@@ -38,7 +38,7 @@ describe('prepareQuery', () => {
38
38
  })
39
39
 
40
40
  /** @evidence TEST-CLI-GRAPH-AUTHORS-DEFINITION-QUERY */
41
- test('authors an exact Definition source without backend query text', () => {
41
+ test('authors an exact Class source without backend query text', () => {
42
42
  const prepared = prepareQuery({
43
43
  sources: [],
44
44
  definition: '/:issues.astrale.ai:class.Issue',
@@ -52,8 +52,8 @@ describe('prepareQuery', () => {
52
52
  kind: 'node',
53
53
  terms: [
54
54
  {
55
- kind: 'definition',
56
- definition: { origin: 'issues.astrale.ai', kind: 'class', name: 'Issue' },
55
+ kind: 'class',
56
+ class: { origin: 'issues.astrale.ai', kind: 'class', name: 'Issue' },
57
57
  },
58
58
  ],
59
59
  binding: 'n0',
@@ -64,6 +64,25 @@ describe('prepareQuery', () => {
64
64
  expect(prepared.page).toEqual({ size: 201 })
65
65
  })
66
66
 
67
+ test('unions positional Paths and one Class source in authored order', () => {
68
+ const prepared = prepareQuery({
69
+ sources: ['@note'],
70
+ definition: '/:issues.astrale.ai:class.Issue',
71
+ })
72
+
73
+ expect(JSON.parse(JSON.stringify(prepared.ast.source))).toEqual({
74
+ kind: 'node',
75
+ terms: [
76
+ { kind: 'path', path: '@note' },
77
+ {
78
+ kind: 'class',
79
+ class: { origin: 'issues.astrale.ai', kind: 'class', name: 'Issue' },
80
+ },
81
+ ],
82
+ binding: 'n0',
83
+ })
84
+ })
85
+
67
86
  /** @evidence TEST-CLI-GRAPH-ADMITS-QUERY-V6-ORDERING */
68
87
  test('admits one exact Property-ordered Query V6 document', () => {
69
88
  const ast = {
@@ -73,8 +92,8 @@ describe('prepareQuery', () => {
73
92
  kind: 'node',
74
93
  terms: [
75
94
  {
76
- kind: 'definition',
77
- definition: { origin: 'notes.example.dev', kind: 'class', name: 'Note' },
95
+ kind: 'class',
96
+ class: { origin: 'notes.example.dev', kind: 'class', name: 'Note' },
78
97
  },
79
98
  ],
80
99
  binding: 'n0',
@@ -149,6 +168,6 @@ describe('prepareQuery', () => {
149
168
  ).toThrow('--limit must be a positive integer')
150
169
  expect(() =>
151
170
  prepareQuery({ sources: [], definition: '/:notes.example.dev:view.note' }),
152
- ).toThrow('--definition must be one canonical Class or Interface Path')
171
+ ).toThrow('--definition must be one canonical Class Path')
153
172
  })
154
173
  })
@@ -0,0 +1,30 @@
1
+ import type { ClassKey as ClassKeyValue, ClassRef } from '@astrale-os/sdk/graph/class'
2
+
3
+ import { ClassKey } from '@astrale-os/sdk/graph/class'
4
+ import { Path } from '@astrale-os/sdk/graph/path'
5
+
6
+ /** Admit one canonical Domain-rooted Class Path into its DSL coordinate. */
7
+ export function classReference(input: string, label: string): ClassRef {
8
+ const path = Path.parse(input)
9
+ const step = path.ast.steps[0]
10
+ if (
11
+ path.ast.anchor.kind !== 'domain' ||
12
+ path.ast.steps.length !== 1 ||
13
+ step?.kind !== 'projection' ||
14
+ step.projection.kind !== 'class'
15
+ ) {
16
+ throw new TypeError(`${label} must be one canonical Class Path`)
17
+ }
18
+ return ClassKey.ref(
19
+ ClassKey.of({
20
+ origin: path.ast.anchor.origin,
21
+ kind: 'class',
22
+ name: step.projection.name,
23
+ }),
24
+ )
25
+ }
26
+
27
+ /** Admit one canonical Domain-rooted Class Path into its exact string identity. */
28
+ export function classKey(input: string, label: string): ClassKeyValue {
29
+ return ClassKey.of(classReference(input, label))
30
+ }
@@ -1,3 +1,4 @@
1
+ export { classKey, classReference } from './class'
1
2
  export { prepareMutation } from './mutation'
2
3
  export { nodeProperty, unqualifyProperty } from './projection'
3
4
  export { prepareQuery, type PreparedQuery, type QueryCommandInput } from './query'
@@ -1,12 +1,15 @@
1
1
  import type {
2
+ NodeQueryBuilder,
2
3
  QueryAST as QueryASTValue,
3
- QueryDefinitionRef,
4
4
  QueryDirection,
5
+ QueryNodeInput,
5
6
  } from '@astrale-os/sdk/query'
6
7
 
7
8
  import { Path } from '@astrale-os/sdk/graph/path'
8
9
  import { Query, QueryAST } from '@astrale-os/sdk/query'
9
10
 
11
+ import { classReference } from './class'
12
+
10
13
  export interface QueryCommandInput {
11
14
  readonly sources: readonly string[]
12
15
  readonly definition?: string
@@ -51,43 +54,29 @@ export function prepareQuery(input: QueryCommandInput): PreparedQuery {
51
54
  }
52
55
 
53
56
  const paths = input.sources.map((source) => Path.parse(source))
54
- const definition = input.definition === undefined ? undefined : definitionRef(input.definition)
55
- const query =
56
- paths.length > 0
57
- ? Query.from({
58
- kind: 'node',
59
- paths: paths as [Path, ...Path[]],
60
- ...(definition === undefined ? {} : { definitions: [definition] }),
61
- })
62
- : Query.from({ kind: 'node', definitions: [definition!] })
57
+ const definition =
58
+ input.definition === undefined ? undefined : classReference(input.definition, '--definition')
59
+ const nodes = definition === undefined ? paths : [...paths, definition]
60
+ const query: NodeQueryBuilder<unknown> = Query.from({
61
+ nodes: nodes as [QueryNodeInput, ...QueryNodeInput[]],
62
+ })
63
63
  const ast =
64
64
  input.edge === undefined
65
- ? query.select({ kind: 'graph' })
66
- : query
67
- .expand({
68
- via: [definitionRef(input.edge)],
69
- direction: input.direction ?? 'outgoing',
70
- })
71
- .select({ kind: 'graph' })
65
+ ? query.select({ kind: 'graph', binding: query.node })
66
+ : expanded(query, input.edge, input.direction)
72
67
  return withPage(ast, limit, input.cursor)
73
68
  }
74
69
 
75
- function definitionRef(input: string): QueryDefinitionRef {
76
- const path = Path.parse(input)
77
- const step = path.ast.steps[0]
78
- if (
79
- path.ast.anchor.kind !== 'domain' ||
80
- path.ast.steps.length !== 1 ||
81
- step?.kind !== 'projection' ||
82
- (step.projection.kind !== 'class' && step.projection.kind !== 'interface')
83
- ) {
84
- throw new TypeError('--definition must be one canonical Class or Interface Path')
85
- }
86
- return Object.freeze({
87
- origin: path.ast.anchor.origin,
88
- kind: step.projection.kind,
89
- name: step.projection.name,
70
+ function expanded(
71
+ query: NodeQueryBuilder<unknown>,
72
+ edge: string,
73
+ direction: QueryDirection | undefined,
74
+ ): QueryASTValue {
75
+ const result = query.expand({
76
+ via: [classReference(edge, '--edge')],
77
+ direction: direction ?? 'outgoing',
90
78
  })
79
+ return result.select({ kind: 'graph', binding: result.node })
91
80
  }
92
81
 
93
82
  function positiveInteger(raw: string | undefined, name: string, fallback: number): number {
@@ -1,6 +1,6 @@
1
- import type { Call } from '@astrale-os/kernel-client'
2
1
  import type { ProvisionRequest } from '@astrale-os/sdk/auth'
3
- import type { LocalBinding } from '@astrale-os/sdk/graph/model'
2
+ import type { Call } from '@astrale-os/sdk/client'
3
+ import type { LocalBinding } from '@astrale-os/sdk/graph'
4
4
  import type { JWK } from 'jose'
5
5
 
6
6
  import type {
@@ -1,6 +1,6 @@
1
1
  import type { ProvisionRequest } from '@astrale-os/sdk/auth'
2
2
 
3
- import { LocalBinding } from '@astrale-os/sdk/graph/model'
3
+ import { LocalBinding } from '@astrale-os/sdk/graph'
4
4
  import { expect, test } from 'bun:test'
5
5
 
6
6
  import { submitIdentityProvision } from '../registration'
@@ -1,9 +1,9 @@
1
- import type { Call } from '@astrale-os/kernel-client'
2
1
  import type { ProvisionRequest } from '@astrale-os/sdk/auth'
3
- import type { LocalBinding } from '@astrale-os/sdk/graph/model'
2
+ import type { Call } from '@astrale-os/sdk/client'
3
+ import type { LocalBinding } from '@astrale-os/sdk/graph'
4
4
 
5
- import { call } from '@astrale-os/kernel-client'
6
5
  import { issuer } from '@astrale-os/sdk/auth'
6
+ import { call } from '@astrale-os/sdk/client'
7
7
  import { NodeId } from '@astrale-os/sdk/graph/node'
8
8
  import { Path } from '@astrale-os/sdk/graph/path'
9
9
 
@@ -62,6 +62,10 @@ describe('DESIGN — per-identity keys', () => {
62
62
  await persistKeypair('alice', { keysDir: tmp })
63
63
  const jwt = await signAs('alice', tmp)
64
64
  expect(jwt.split('.').length).toBe(3)
65
+ const claims = decodeJwt(jwt)
66
+ expect(claims.iat).toBeUndefined()
67
+ expect(claims.exp).toBeNumber()
68
+ expect((claims.exp as number) * 1_000).toBeGreaterThan(Date.now() + 299_000)
65
69
  })
66
70
 
67
71
  /** @evidence TEST-CLI-KEYS-DISTINGUISHES-KERNEL-ROOT-GRANT */
@@ -47,6 +47,7 @@ async function signIdentityCredential(options: {
47
47
  .setIssuer(options.issuer)
48
48
  .setSubject(options.subject)
49
49
  .setAudience(options.audience)
50
+ .setExpirationTime('5m')
50
51
  .sign(privateKey)
51
52
  }
52
53
 
@@ -6,6 +6,7 @@ import {
6
6
  DEFAULT_ADMIN_TARGET_NAME,
7
7
  DEFAULT_ADMIN_DOMAIN_ISSUER,
8
8
  DEFAULT_ADMIN_TARGET_URL,
9
+ defaultAdminTargetForChannel,
9
10
  resolveAdminTargetFromStore,
10
11
  } from '../admin-target'
11
12
  import { DEFAULT_CONFIG, type AstraleConfig } from '../config'
@@ -32,6 +33,17 @@ const instances: InstanceStore = {
32
33
  }
33
34
 
34
35
  describe('resolveAdminTargetFromStore', () => {
36
+ test('derives hosted Admin defaults from the release channel', () => {
37
+ expect(defaultAdminTargetForChannel('beta')).toEqual({
38
+ url: 'https://admin.eu.beta.astrale.ai/api',
39
+ domainIssuer: 'https://admin.beta.astrale.ai',
40
+ })
41
+ expect(defaultAdminTargetForChannel('stable')).toEqual({
42
+ url: 'https://admin.eu.astrale.ai/api',
43
+ domainIssuer: 'https://admin.astrale.ai',
44
+ })
45
+ })
46
+
35
47
  test('uses hosted admin default without active instance coupling', () => {
36
48
  expect(resolveAdminTargetFromStore({}, DEFAULT_CONFIG, instances)).toEqual({
37
49
  name: DEFAULT_ADMIN_TARGET_NAME,
@@ -16,7 +16,7 @@ function dx(
16
16
  error: CommanderError,
17
17
  argv: string[],
18
18
  ) {
19
- const rendered = JSON.parse(stripAnsi(renderCommanderError(program, error, argv))) as {
19
+ const rendered = JSON.parse(stripAnsi(renderCommanderError(program, error, argv, true))) as {
20
20
  error: string
21
21
  message: string
22
22
  detail: string
@@ -0,0 +1,138 @@
1
+ import { publication } from '@astrale-os/sdk/publication'
2
+ import { defineSchema } from '@astrale-os/sdk/schema'
3
+ import { describe, expect, test } from 'bun:test'
4
+
5
+ import { releaseFor } from '../../__tests__/fixtures/publication'
6
+ import { domainPublicationUrl, fetchDomainPublication } from '../domain-publication'
7
+
8
+ const ROOT = 'https://publication-reader.example.dev'
9
+ const PUBLICATION_URL = `${ROOT}/.well-known/astrale/domain.json`
10
+ const MAXIMUM_PUBLICATION_BYTES = 1024 * 1024
11
+ const schema = defineSchema('publication-reader.example.dev', {})
12
+ const deployed = releaseFor(schema, ROOT).publication
13
+
14
+ describe('Domain Publication reader', () => {
15
+ test('fetches only the canonical path with redirects disabled', async () => {
16
+ const requests: Array<{ readonly url: string; readonly redirect?: RequestRedirect }> = []
17
+ const fetch = async (input: RequestInfo | URL, init?: RequestInit): Promise<Response> => {
18
+ requests.push({ url: String(input), redirect: init?.redirect })
19
+ return Response.json(deployed)
20
+ }
21
+
22
+ await expect(fetchDomainPublication(`${ROOT}/`, undefined, fetch)).resolves.toEqual(deployed)
23
+ expect(requests).toEqual([{ url: PUBLICATION_URL, redirect: 'error' }])
24
+ })
25
+
26
+ test('normalizes an origin and admits the exact canonical Publication URL', () => {
27
+ expect(domainPublicationUrl(ROOT).href).toBe(PUBLICATION_URL)
28
+ expect(domainPublicationUrl(PUBLICATION_URL).href).toBe(PUBLICATION_URL)
29
+ })
30
+
31
+ test('propagates redirect rejection instead of accepting another target', async () => {
32
+ const fetch = async (_input: RequestInfo | URL, init?: RequestInit): Promise<Response> => {
33
+ expect(init?.redirect).toBe('error')
34
+ throw new TypeError('redirect rejected')
35
+ }
36
+
37
+ await expect(fetchDomainPublication(ROOT, undefined, fetch)).rejects.toThrow(
38
+ 'redirect rejected',
39
+ )
40
+ })
41
+
42
+ test('stops at one MiB and cancels an over-limit response stream', async () => {
43
+ let pulls = 0
44
+ let cancelled = false
45
+ const body = new ReadableStream<Uint8Array>(
46
+ {
47
+ pull(controller) {
48
+ pulls += 1
49
+ if (pulls === 1) {
50
+ controller.enqueue(new Uint8Array(MAXIMUM_PUBLICATION_BYTES))
51
+ return
52
+ }
53
+ if (pulls === 2) {
54
+ controller.enqueue(Uint8Array.of(0))
55
+ return
56
+ }
57
+ controller.error(new Error('reader consumed beyond its byte limit'))
58
+ },
59
+ cancel() {
60
+ cancelled = true
61
+ },
62
+ },
63
+ { highWaterMark: 0 },
64
+ )
65
+ const fetch = async (): Promise<Response> => new Response(body)
66
+
67
+ await expect(fetchDomainPublication(ROOT, undefined, fetch)).rejects.toThrow(
68
+ `exceeded ${MAXIMUM_PUBLICATION_BYTES} bytes`,
69
+ )
70
+ expect(pulls).toBe(2)
71
+ expect(cancelled).toBe(true)
72
+ })
73
+
74
+ test('rejects an oversized Content-Length before pulling and cancels the body', async () => {
75
+ let pulls = 0
76
+ let cancelled = false
77
+ const body = new ReadableStream<Uint8Array>(
78
+ {
79
+ pull() {
80
+ pulls += 1
81
+ },
82
+ cancel() {
83
+ cancelled = true
84
+ },
85
+ },
86
+ { highWaterMark: 0 },
87
+ )
88
+ const fetch = async (): Promise<Response> =>
89
+ new Response(body, {
90
+ headers: { 'content-length': String(MAXIMUM_PUBLICATION_BYTES + 1) },
91
+ })
92
+
93
+ await expect(fetchDomainPublication(ROOT, undefined, fetch)).rejects.toThrow(
94
+ `exceeded ${MAXIMUM_PUBLICATION_BYTES} bytes`,
95
+ )
96
+ expect(pulls).toBe(0)
97
+ expect(cancelled).toBe(true)
98
+ })
99
+
100
+ test.each([
101
+ ['invalid UTF-8', Uint8Array.of(0xc3, 0x28), TypeError],
102
+ ['invalid JSON', new TextEncoder().encode('{'), SyntaxError],
103
+ ['invalid Publication', new TextEncoder().encode('{}'), publication.PublicationError],
104
+ ] as const)('rejects %s before returning a Publication', async (_label, bytes, Cause) => {
105
+ const fetch = async (): Promise<Response> => new Response(bytes)
106
+ const error = await rejection(fetchDomainPublication(ROOT, undefined, fetch))
107
+
108
+ expect(error.message).toBe(`GET ${PUBLICATION_URL} returned an invalid Domain Publication`)
109
+ expect(error.cause).toBeInstanceOf(Cause)
110
+ })
111
+
112
+ test.each([
113
+ `${ROOT}/nested`,
114
+ `${ROOT}?deployment=current`,
115
+ `${ROOT}#fragment`,
116
+ 'https://user:secret@publication-reader.example.dev',
117
+ 'ftp://publication-reader.example.dev',
118
+ ])('rejects a non-origin deployment URL before fetch: %s', async (input) => {
119
+ let called = false
120
+ const fetch = async (): Promise<Response> => {
121
+ called = true
122
+ return Response.json(deployed)
123
+ }
124
+
125
+ await expect(fetchDomainPublication(input, undefined, fetch)).rejects.toBeInstanceOf(TypeError)
126
+ expect(called).toBe(false)
127
+ })
128
+ })
129
+
130
+ async function rejection(input: Promise<unknown>): Promise<Error> {
131
+ try {
132
+ await input
133
+ } catch (cause) {
134
+ if (cause instanceof Error) return cause
135
+ throw new Error('Expected an Error rejection.', { cause })
136
+ }
137
+ throw new Error('Expected the promise to reject.')
138
+ }
@@ -68,11 +68,29 @@ describe('ensureFreshSession', () => {
68
68
  expect(result.orgHintCalls).toBe(1)
69
69
  expect(server.refreshCount()).toBe(1)
70
70
  const session = await readSession()
71
+ expect(session.clientId).toBe('c_1')
71
72
  expect(session.refresh_token).toBe('rt-1')
72
73
  expect((session.tokens as Record<string, { access_token: string }>)[AUD].access_token).toBe(
73
74
  result.token as string,
74
75
  )
75
76
  expect(server.lastOrganizationId()).toBe('org_1')
77
+ expect(server.lastClientId()).toBe('c_1')
78
+ } finally {
79
+ await server.stop()
80
+ }
81
+ })
82
+
83
+ test('refreshes with the client that issued the session instead of the IdP default', async () => {
84
+ const server = rotationServer()
85
+ try {
86
+ await writeIdpConfig(server.url)
87
+ await writeSession({ clientId: 'c_session', expires_at: past() })
88
+
89
+ const result = await runDriver('ensure', { DRIVER_AUDIENCE: AUD })
90
+
91
+ expect(result.ok).toBe(true)
92
+ expect(server.lastClientId()).toBe('c_session')
93
+ expect((await readSession()).clientId).toBe('c_session')
76
94
  } finally {
77
95
  await server.stop()
78
96
  }
@@ -273,6 +291,7 @@ function rotationServer(opts?: {
273
291
  }): {
274
292
  url: string
275
293
  refreshCount: () => number
294
+ lastClientId: () => string | undefined
276
295
  lastOrganizationId: () => string | undefined
277
296
  stop: () => Promise<void>
278
297
  } {
@@ -280,6 +299,7 @@ function rotationServer(opts?: {
280
299
  // refresh token is invalid_grant.
281
300
  let current = 'rt-0'
282
301
  let count = 0
302
+ let lastClientId: string | undefined
283
303
  let lastOrgId: string | undefined
284
304
  const server = Bun.serve({
285
305
  port: 0,
@@ -300,6 +320,7 @@ function rotationServer(opts?: {
300
320
  if (opts?.delayMs) await new Promise((r) => setTimeout(r, opts.delayMs))
301
321
  count += 1
302
322
  current = `rt-${count}`
323
+ lastClientId = form.get('client_id') ?? undefined
303
324
  lastOrgId = form.get('organization_id') ?? undefined
304
325
  return Response.json({
305
326
  access_token: unsignedJwt({ aud: AUD, exp: futureEpoch() }),
@@ -312,6 +333,7 @@ function rotationServer(opts?: {
312
333
  return {
313
334
  url: `http://${server.hostname}:${server.port}`,
314
335
  refreshCount: () => count,
336
+ lastClientId: () => lastClientId,
315
337
  lastOrganizationId: () => lastOrgId,
316
338
  stop: () => server.stop(true),
317
339
  }
@@ -9,6 +9,7 @@ import {
9
9
  IdpSessionSchema,
10
10
  isSessionExpired,
11
11
  issuerFromToken,
12
+ legacyBuiltinWorkosReplacement,
12
13
  normalizeTokenResponse,
13
14
  OAuthTokenError,
14
15
  OidcMetadataSchema,
@@ -63,8 +64,11 @@ describe('IdP schemas and token helpers', () => {
63
64
  expect(config?.client.token_request_format).toBe('json')
64
65
  })
65
66
 
66
- test('does not invent a WorkOS environment when no client id is configured', () => {
67
- expect(builtinIdpConfig('workos', undefined, {})).toBeNull()
67
+ test('builds the built-in WorkOS IdP with the public client id by default', () => {
68
+ const config = builtinIdpConfig('workos', undefined, {})
69
+
70
+ expect(config?.entry.builtIn).toBe(true)
71
+ expect(config?.client.client_id).toBe('client_01KC29HET5F3QAQ8GNTPZ7F320')
68
72
  })
69
73
 
70
74
  test('reads WorkOS client id from WORKOS_CLIENT_ID before VITE_WORKOS_CLIENT_ID', () => {
@@ -75,8 +79,34 @@ describe('IdP schemas and token helpers', () => {
75
79
  }),
76
80
  ).toBe('client_primary')
77
81
  expect(workosClientIdFromEnv({ VITE_WORKOS_CLIENT_ID: 'client_vite' })).toBe('client_vite')
78
- expect(workosClientIdFromEnv({})).toBeUndefined()
79
- expect(workosClientIdFromEnv({ WORKOS_CLIENT_ID: ' ' })).toBeUndefined()
82
+ expect(workosClientIdFromEnv({})).toBe('client_01KC29HET5F3QAQ8GNTPZ7F320')
83
+ expect(workosClientIdFromEnv({ WORKOS_CLIENT_ID: ' ' })).toBe(
84
+ 'client_01KC29HET5F3QAQ8GNTPZ7F320',
85
+ )
86
+ })
87
+
88
+ test('replaces only the legacy persisted built-in WorkOS client', () => {
89
+ const legacy = builtinIdpConfig('workos', 'client_01KC29HEGD7B40TV2C4QZ436BG', {})!
90
+ const replacement = legacyBuiltinWorkosReplacement(legacy, undefined, {})
91
+
92
+ expect(replacement?.client.client_id).toBe('client_01KC29HET5F3QAQ8GNTPZ7F320')
93
+ expect(replacement?.metadata.jwks_uri).toBe(
94
+ 'https://api.workos.com/sso/jwks/client_01KC29HET5F3QAQ8GNTPZ7F320',
95
+ )
96
+ expect(
97
+ legacyBuiltinWorkosReplacement(
98
+ { ...legacy, entry: { ...legacy.entry, builtIn: false } },
99
+ undefined,
100
+ {},
101
+ ),
102
+ ).toBeUndefined()
103
+ expect(
104
+ legacyBuiltinWorkosReplacement(
105
+ { ...legacy, client: { ...legacy.client, client_id: 'client_custom' } },
106
+ undefined,
107
+ {},
108
+ ),
109
+ ).toBeUndefined()
80
110
  })
81
111
 
82
112
  test('stores client secret env names, not secret material', () => {
@@ -100,6 +130,7 @@ describe('IdP schemas and token helpers', () => {
100
130
  idp: 'workos',
101
131
  issuer: 'https://example.authkit.app',
102
132
  subject: 'user_123',
133
+ clientId: 'client_123',
103
134
  access_token: 'opaque-token',
104
135
  refresh_token: 'refresh-token',
105
136
  token_type: 'Bearer',
@@ -110,6 +141,7 @@ describe('IdP schemas and token helpers', () => {
110
141
  })
111
142
 
112
143
  expect(session.claims?.email).toBe('alice@example.com')
144
+ expect(session.clientId).toBe('client_123')
113
145
  expect(session.refresh_token).toBe('refresh-token')
114
146
  })
115
147
 
@@ -18,7 +18,6 @@ const managedDemo: OwnedInstanceInfo = {
18
18
  slug: 'demo',
19
19
  url: 'https://demo.eu.astrale.ai',
20
20
  state: 'ready',
21
- region: 'eu',
22
21
  }
23
22
 
24
23
  describe('collectInstanceCandidates', () => {
@@ -138,11 +137,10 @@ describe('describeInstanceCandidate', () => {
138
137
  slug: 'twin',
139
138
  url: 'https://twin.eu.astrale.ai',
140
139
  state: 'ready',
141
- region: 'eu',
142
140
  },
143
141
  ])
144
142
  expect(describeInstanceCandidate(managed!)).toBe(
145
- 'twin (managed) https://twin.eu.astrale.ai/api — eu',
143
+ 'twin (managed) https://twin.eu.astrale.ai/api',
146
144
  )
147
145
  })
148
146
  })