@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
@@ -0,0 +1,70 @@
1
+ import { expect, test } from 'bun:test'
2
+
3
+ import { decodeSchemaIR } from './schema-ir-json'
4
+
5
+ function canonicalIr() {
6
+ return {
7
+ format: 'astrale.dsl',
8
+ version: 'v1',
9
+ domain: 'example.test',
10
+ classes: {
11
+ Document: {
12
+ type: 'node',
13
+ name: 'Document',
14
+ origin: 'example.test',
15
+ ref: { origin: 'example.test', kind: 'class', name: 'Document' },
16
+ properties: {},
17
+ methods: {
18
+ rename: {
19
+ name: 'rename',
20
+ input: { type: 'object', properties: {} },
21
+ output: { mode: 'value', schema: { type: 'boolean' } },
22
+ static: false,
23
+ inheritance: 'default',
24
+ auth: 'authorized',
25
+ },
26
+ },
27
+ },
28
+ },
29
+ importsByKey: {},
30
+ importedClassesByKey: {},
31
+ functions: {
32
+ search: {
33
+ name: 'search',
34
+ input: { type: 'object', properties: {} },
35
+ output: { mode: 'stream', item: { type: 'string' } },
36
+ auth: 'authenticated',
37
+ },
38
+ },
39
+ views: {},
40
+ policies: {},
41
+ dependencies: [],
42
+ core: {},
43
+ }
44
+ }
45
+
46
+ test('admits the complete canonical Studio projection', () => {
47
+ expect(decodeSchemaIR(canonicalIr())).toMatchObject({
48
+ format: 'astrale.dsl',
49
+ domain: 'example.test',
50
+ })
51
+ })
52
+
53
+ test('rejects stale projections that lost canonical callable contracts', () => {
54
+ const input = canonicalIr()
55
+ const rename = input.classes.Document.methods.rename as Record<string, unknown>
56
+ delete rename.input
57
+ delete rename.output
58
+ Object.assign(rename, { params: {}, returns: { type: 'boolean' } })
59
+ expect(decodeSchemaIR(input)).toBeUndefined()
60
+ })
61
+
62
+ test('rejects unversioned and incomplete projection roots', () => {
63
+ const withoutFormat = canonicalIr() as Record<string, unknown>
64
+ delete withoutFormat.format
65
+ expect(decodeSchemaIR(withoutFormat)).toBeUndefined()
66
+
67
+ const withoutViews = canonicalIr() as Record<string, unknown>
68
+ delete withoutViews.views
69
+ expect(decodeSchemaIR(withoutViews)).toBeUndefined()
70
+ })
@@ -0,0 +1,151 @@
1
+ /** Structural admission for persisted Studio render IR. SDK admission is separate. */
2
+
3
+ import type {
4
+ IrClass,
5
+ IrClassRef,
6
+ IrFunction,
7
+ IrMethod,
8
+ IrSchemaRef,
9
+ JsonSchema,
10
+ SchemaIR,
11
+ } from '../../shared/types'
12
+
13
+ import { asJsonRecord, asStringArray } from '../json'
14
+
15
+ function recordOf(value: unknown, guard: (candidate: unknown) => boolean): boolean {
16
+ const record = asJsonRecord(value)
17
+ return !!record && Object.values(record).every(guard)
18
+ }
19
+
20
+ function isJsonSchema(value: unknown): value is JsonSchema {
21
+ return !!asJsonRecord(value)
22
+ }
23
+
24
+ function isSchemaRef(value: unknown): value is IrSchemaRef {
25
+ const record = asJsonRecord(value)
26
+ return (
27
+ typeof record?.origin === 'string' &&
28
+ typeof record.name === 'string' &&
29
+ ['class', 'function', 'policy', 'view', 'core'].includes(String(record.kind))
30
+ )
31
+ }
32
+
33
+ function isClassRef(value: unknown): value is IrClassRef {
34
+ return isSchemaRef(value) && value.kind === 'class'
35
+ }
36
+
37
+ function isOutput(value: unknown): boolean {
38
+ const record = asJsonRecord(value)
39
+ if (!record) return false
40
+ if (record.mode === 'binary') return true
41
+ if (record.mode === 'value') return isJsonSchema(record.schema)
42
+ if (record.mode === 'stream') return isJsonSchema(record.item)
43
+ return false
44
+ }
45
+
46
+ function isMethod(value: unknown): value is IrMethod {
47
+ const record = asJsonRecord(value)
48
+ return (
49
+ typeof record?.name === 'string' &&
50
+ isJsonSchema(record.input) &&
51
+ isOutput(record.output) &&
52
+ typeof record.static === 'boolean' &&
53
+ ['default', 'abstract', 'sealed'].includes(String(record.inheritance))
54
+ )
55
+ }
56
+
57
+ function isEndpoint(value: unknown): boolean {
58
+ const record = asJsonRecord(value)
59
+ if (!record || typeof record.name !== 'string' || !asStringArray(record.types)) return false
60
+ if (
61
+ record.refs !== undefined &&
62
+ (!Array.isArray(record.refs) || !record.refs.every(isSchemaRef))
63
+ ) {
64
+ return false
65
+ }
66
+ if (record.cardinality !== undefined) {
67
+ const cardinality = asJsonRecord(record.cardinality)
68
+ if (
69
+ !cardinality ||
70
+ typeof cardinality.min !== 'number' ||
71
+ !Number.isFinite(cardinality.min) ||
72
+ (cardinality.max !== null &&
73
+ (typeof cardinality.max !== 'number' || !Number.isFinite(cardinality.max)))
74
+ ) {
75
+ return false
76
+ }
77
+ }
78
+ return true
79
+ }
80
+
81
+ function isClass(value: unknown): value is IrClass {
82
+ const record = asJsonRecord(value)
83
+ return (
84
+ (record?.type === 'node' || record?.type === 'edge') &&
85
+ typeof record.name === 'string' &&
86
+ typeof record.origin === 'string' &&
87
+ isClassRef(record.ref) &&
88
+ recordOf(record.properties, isJsonSchema) &&
89
+ recordOf(record.methods, isMethod) &&
90
+ (record.required === undefined || asStringArray(record.required) !== undefined) &&
91
+ (record.extends === undefined || asStringArray(record.extends) !== undefined) &&
92
+ (record.extendsRefs === undefined ||
93
+ (Array.isArray(record.extendsRefs) && record.extendsRefs.every(isClassRef))) &&
94
+ (record.endpoints === undefined ||
95
+ (Array.isArray(record.endpoints) && record.endpoints.every(isEndpoint)))
96
+ )
97
+ }
98
+
99
+ function isFunction(value: unknown): value is IrFunction {
100
+ const record = asJsonRecord(value)
101
+ return typeof record?.name === 'string' && isJsonSchema(record.input) && isOutput(record.output)
102
+ }
103
+
104
+ function isImport(value: unknown): boolean {
105
+ const record = asJsonRecord(value)
106
+ return (
107
+ typeof record?.origin === 'string' &&
108
+ isClassRef(record.ref) &&
109
+ typeof record.key === 'string' &&
110
+ record.key === `${record.ref.origin}:class.${record.ref.name}`
111
+ )
112
+ }
113
+
114
+ function isView(value: unknown): boolean {
115
+ const record = asJsonRecord(value)
116
+ const target = asJsonRecord(record?.target)
117
+ return (
118
+ typeof record?.name === 'string' &&
119
+ ['required', 'optional', 'public'].includes(String(record.auth)) &&
120
+ !!target &&
121
+ (target.kind === 'domain' ||
122
+ (target.kind === 'definition' &&
123
+ Array.isArray(target.definitions) &&
124
+ target.definitions.every(isSchemaRef)))
125
+ )
126
+ }
127
+
128
+ export function decodeSchemaIR(value: unknown): SchemaIR | undefined {
129
+ const record = asJsonRecord(value)
130
+ if (
131
+ !record ||
132
+ record.format !== 'astrale.dsl' ||
133
+ typeof record.version !== 'string' ||
134
+ typeof record.domain !== 'string' ||
135
+ !recordOf(record.classes, isClass) ||
136
+ !recordOf(record.functions, isFunction) ||
137
+ !recordOf(record.importsByKey, isImport) ||
138
+ !recordOf(record.importedClassesByKey, isClass) ||
139
+ !recordOf(record.views, isView) ||
140
+ !asJsonRecord(record.policies) ||
141
+ !Array.isArray(record.dependencies) ||
142
+ !record.dependencies.every((dependency) => {
143
+ const item = asJsonRecord(dependency)
144
+ return typeof item?.origin === 'string' && typeof item.revision === 'string'
145
+ }) ||
146
+ !('core' in record)
147
+ ) {
148
+ return undefined
149
+ }
150
+ return record as unknown as SchemaIR
151
+ }
@@ -1,100 +1,55 @@
1
- import { describe, expect, test } from 'bun:test'
1
+ import { expect, test } from 'bun:test'
2
2
 
3
- import type { IrInterface, StudioSchemaBundle } from '../../shared/types'
3
+ import type { StudioSchemaBundle } from '../../shared/types'
4
4
 
5
5
  import { schemaRefs } from './schema-refs'
6
6
 
7
- function importedInterface(
8
- name: string,
9
- origin: string,
10
- properties: string[] = [],
11
- methods: string[] = [],
12
- ): IrInterface {
13
- return {
14
- type: 'interface',
15
- name,
16
- origin,
17
- ref: { origin, kind: 'interface', name },
18
- properties: Object.fromEntries(properties.map((property) => [property, { type: 'string' }])),
19
- methods: Object.fromEntries(
20
- methods.map((method) => [
21
- method,
22
- {
23
- name: method,
24
- params: {},
25
- returns: {},
26
- static: false,
27
- inheritance: 'default',
28
- },
29
- ]),
30
- ),
7
+ test('enumerates local and exact imported Class anchors without collapsing homonyms', () => {
8
+ const method = {
9
+ name: 'rename',
10
+ params: {},
11
+ returns: {},
12
+ static: false,
13
+ inheritance: 'default' as const,
31
14
  }
32
- }
33
-
34
- function bundleWithImports(
35
- exact: NonNullable<StudioSchemaBundle['ir']>['importedInterfacesByKey'],
36
- legacy: Record<string, IrInterface>,
37
- ): StudioSchemaBundle {
38
- return {
15
+ const bundle = {
39
16
  ir: {
17
+ version: 'v1',
40
18
  domain: 'root.example.dev',
41
- interfaces: {},
42
- classes: {},
19
+ classes: {
20
+ Document: {
21
+ type: 'node',
22
+ name: 'Document',
23
+ properties: { title: { type: 'string' } },
24
+ methods: { rename: method },
25
+ },
26
+ },
43
27
  functions: {},
44
- ...(exact === undefined ? {} : { importedInterfacesByKey: exact }),
45
- },
46
- importedInterfaces: legacy,
47
- } as unknown as StudioSchemaBundle
48
- }
49
-
50
- describe('schema refs for imported interfaces', () => {
51
- test('enumerates every exact homonym without selecting a name-keyed winner', () => {
52
- const refs = schemaRefs(
53
- bundleWithImports(
54
- {
55
- 'directory.example.dev:interface.Named': importedInterface(
56
- 'Named',
57
- 'directory.example.dev',
58
- ['directoryName'],
59
- ),
60
- 'people.example.dev:interface.Named': importedInterface(
61
- 'Named',
62
- 'people.example.dev',
63
- [],
64
- ['rename'],
65
- ),
28
+ importsByKey: {},
29
+ importedClassesByKey: {
30
+ 'directory.example.dev:class.Named': {
31
+ type: 'node',
32
+ name: 'Named',
33
+ properties: { directoryName: { type: 'string' } },
34
+ methods: {},
66
35
  },
67
- {
68
- Named: importedInterface('Named', 'legacy.invalid', ['legacyOnly']),
36
+ 'people.example.dev:class.Named': {
37
+ type: 'node',
38
+ name: 'Named',
39
+ properties: {},
40
+ methods: { rename: method },
69
41
  },
70
- ),
71
- )
72
-
73
- expect(refs).toContain('interface.directory.example.dev:interface.Named')
74
- expect(refs).toContain('interface.directory.example.dev:interface.Named.property.directoryName')
75
- expect(refs).toContain('interface.people.example.dev:interface.Named.method.rename')
76
- expect(refs).not.toContain('interface.Named')
77
- expect(refs).not.toContain('interface.Named.property.legacyOnly')
78
- })
79
-
80
- test('uses the name-keyed imported interface only when the exact index is absent', () => {
81
- expect(
82
- schemaRefs(
83
- bundleWithImports(undefined, {
84
- Named: importedInterface('Named', 'legacy.example.dev', ['name']),
85
- }),
86
- ),
87
- ).toEqual(['interface.Named', 'interface.Named.property.name'])
42
+ },
43
+ },
44
+ } as unknown as StudioSchemaBundle
88
45
 
89
- expect(
90
- schemaRefs(
91
- bundleWithImports(
92
- {},
93
- {
94
- Named: importedInterface('Named', 'legacy.example.dev', ['name']),
95
- },
96
- ),
97
- ),
98
- ).toEqual([])
99
- })
46
+ expect(schemaRefs(bundle)).toEqual([
47
+ 'class.Document',
48
+ 'class.Document.property.title',
49
+ 'class.Document.method.rename',
50
+ 'class.directory.example.dev:class.Named',
51
+ 'class.directory.example.dev:class.Named.property.directoryName',
52
+ 'class.people.example.dev:class.Named',
53
+ 'class.people.example.dev:class.Named.method.rename',
54
+ ])
100
55
  })
@@ -1,17 +1,6 @@
1
1
  import type { StudioSchemaBundle } from '../../shared/types'
2
2
 
3
- /**
4
- * schema-refs.ts — enumerate every SCHEMA anchor ref a bundle can target, derived
5
- * from the IR (the authority), NOT from `overlay.sourceSpans`.
6
- *
7
- * Source spans are a best-effort ts-morph source-location index: they cover only
8
- * locally-authored members and only the declaration helpers the parser recognises.
9
- * Using their keys as the set of "valid" targets falsely orphans real, commentable
10
- * targets — most notably INHERITED members surfaced in the detail pane's Inherited
11
- * section (kernel mixins / cross-domain interfaces, e.g. `interface.Named.property.name`),
12
- * which live in another package and so have no local span. This walks the IR
13
- * instead, mirroring exactly the refs the detail pane stamps.
14
- */
3
+ /** Enumerate every schema anchor represented by the admitted render IR. */
15
4
  export function schemaRefs(bundle: StudioSchemaBundle): string[] {
16
5
  const ir = bundle.ir
17
6
  if (!ir) return []
@@ -22,46 +11,22 @@ export function schemaRefs(bundle: StudioSchemaBundle): string[] {
22
11
  methods: Record<string, unknown> | undefined,
23
12
  ) => {
24
13
  refs.add(base)
25
- for (const p of Object.keys(properties ?? {})) refs.add(`${base}.property.${p}`)
26
- for (const m of Object.keys(methods ?? {})) refs.add(`${base}.method.${m}`)
14
+ for (const property of Object.keys(properties ?? {})) refs.add(`${base}.property.${property}`)
15
+ for (const method of Object.keys(methods ?? {})) refs.add(`${base}.method.${method}`)
27
16
  }
28
17
 
29
- for (const [name, iface] of Object.entries(ir.interfaces))
30
- addMember(`interface.${name}`, iface.properties, iface.methods)
31
-
32
- for (const [name, cls] of Object.entries(ir.classes)) {
33
- const ns = cls.type === 'edge' ? 'edge' : 'class'
34
- addMember(`${ns}.${name}`, cls.properties, cls.methods)
35
- if (cls.type === 'edge')
36
- for (const ep of cls.endpoints ?? [])
37
- if (ep.name) refs.add(`edge.${name}.endpoint.${ep.name}`)
38
- }
39
-
40
- for (const name of Object.keys(ir.functions ?? {})) refs.add(`function.${name}`)
41
-
42
- // Imported interfaces (kernel mixins + cross-domain) are rendered — and so are
43
- // commentable — in the Inherited section, keyed under the `interface.` namespace.
44
- // Canonical bundles retain every exact `(origin, kind, name)` identity. Iterate
45
- // all of them: a short-name map must never pick one homonym by traversal order.
46
- // The name-keyed bundle field remains a legacy-only fallback.
47
- if (ir.importedInterfacesByKey !== undefined) {
48
- for (const [key, iface] of Object.entries(ir.importedInterfacesByKey)) {
49
- const anchor = importedInterfaceAnchor(key)
50
- if (anchor) addMember(anchor, iface.properties, iface.methods)
18
+ for (const [name, definition] of Object.entries(ir.classes)) {
19
+ const namespace = definition.type === 'edge' ? 'edge' : 'class'
20
+ addMember(`${namespace}.${name}`, definition.properties, definition.methods)
21
+ if (definition.type === 'edge') {
22
+ for (const endpoint of definition.endpoints ?? []) {
23
+ if (endpoint.name) refs.add(`edge.${name}.endpoint.${endpoint.name}`)
24
+ }
51
25
  }
52
- } else {
53
- for (const [name, iface] of Object.entries(bundle.importedInterfaces ?? {}))
54
- addMember(`interface.${name}`, iface.properties, iface.methods)
55
26
  }
56
-
27
+ for (const name of Object.keys(ir.functions)) refs.add(`function.${name}`)
28
+ for (const [key, definition] of Object.entries(ir.importedClassesByKey)) {
29
+ addMember(`class.${key}`, definition.properties, definition.methods)
30
+ }
57
31
  return [...refs]
58
32
  }
59
-
60
- function importedInterfaceAnchor(key: string): string | null {
61
- const separator = key.lastIndexOf(':')
62
- if (separator <= 0) return null
63
- const ref = key.slice(separator + 1)
64
- if (!ref.startsWith('interface.')) return null
65
- const name = ref.slice('interface.'.length)
66
- return /^[A-Za-z_$][\w$]*$/.test(name) ? `interface.${key}` : null
67
- }
@@ -0,0 +1,14 @@
1
+ /**
2
+ * Compatibility owner for Studio schema annotations.
3
+ *
4
+ * The current Kernel admits enum updates against their exact Value Schema, so
5
+ * there are no source-derived annotations today. The serialized overlay field
6
+ * remains consumed by the client and shared contract.
7
+ */
8
+ import type { SchemaAnnotation, SchemaIR } from '../../../shared/types'
9
+
10
+ export function buildSchemaAnnotations(_args: { ir: SchemaIR | null }): SchemaAnnotation[] {
11
+ // The current Kernel admits updated values against their exact Value Schema;
12
+ // enum properties therefore need no Studio-specific compatibility warning.
13
+ return []
14
+ }
@@ -0,0 +1,161 @@
1
+ /** Source links for SDK V1 Action and Workflow declarations. */
2
+ import { readdirSync } from 'node:fs'
3
+ import { join } from 'node:path'
4
+ import { Node, SyntaxKind, type CallExpression } from 'ts-morph'
5
+
6
+ import type { HandlerLink, SchemaIR } from '../../../shared/types'
7
+
8
+ import { scanKernelCalls } from './kernel-calls'
9
+ import { newProject, relToRoot, unwrapExpression, valueOfIdentifier } from './project'
10
+
11
+ const IGNORED_DIRECTORIES = new Set([
12
+ '.git',
13
+ '.history',
14
+ '.astrale',
15
+ '.cache',
16
+ '__tests__',
17
+ 'dist',
18
+ 'node_modules',
19
+ ])
20
+
21
+ function authoredTypeScriptFiles(root: string): string[] {
22
+ const files: string[] = []
23
+ const visit = (directory: string) => {
24
+ let entries
25
+ try {
26
+ entries = readdirSync(directory, { withFileTypes: true })
27
+ } catch {
28
+ return
29
+ }
30
+ for (const entry of entries) {
31
+ if (entry.isSymbolicLink()) continue
32
+ const path = join(directory, entry.name)
33
+ if (entry.isDirectory()) {
34
+ if (!IGNORED_DIRECTORIES.has(entry.name)) visit(path)
35
+ } else if (entry.isFile() && path.endsWith('.ts') && !path.endsWith('.test.ts')) {
36
+ files.push(path)
37
+ }
38
+ }
39
+ }
40
+ visit(root)
41
+ return files
42
+ }
43
+
44
+ interface CallableOwner {
45
+ owner: string
46
+ ownerKind: 'class' | 'function'
47
+ method: string
48
+ static: boolean
49
+ }
50
+
51
+ function declaredCallable(ir: SchemaIR | null, key: string): CallableOwner | undefined {
52
+ if (!ir) return undefined
53
+ const standalone = ir.functions[key]
54
+ if (standalone) {
55
+ return {
56
+ owner: ir.domain,
57
+ ownerKind: 'function',
58
+ method: key,
59
+ static: true,
60
+ }
61
+ }
62
+ const separator = key.lastIndexOf('.')
63
+ if (separator <= 0) return undefined
64
+ const owner = key.slice(0, separator)
65
+ const method = key.slice(separator + 1)
66
+ const definition = ir.classes[owner]?.methods[method]
67
+ return definition
68
+ ? {
69
+ owner,
70
+ ownerKind: 'class',
71
+ method,
72
+ static: definition.static,
73
+ }
74
+ : undefined
75
+ }
76
+
77
+ function declarationKind(call: CallExpression): 'action' | 'workflow' | undefined {
78
+ const expression = unwrapExpression(call.getExpression())
79
+ if (!Node.isCallExpression(expression)) return undefined
80
+ const factory = unwrapExpression(expression.getExpression())
81
+ if (!Node.isIdentifier(factory)) return undefined
82
+ if (factory.getText() === 'defineAction') return 'action'
83
+ if (factory.getText() === 'defineWorkflow') return 'workflow'
84
+ return undefined
85
+ }
86
+
87
+ function stringArgument(call: CallExpression, index: number): string | undefined {
88
+ const argument = call.getArguments()[index]
89
+ return argument &&
90
+ (Node.isStringLiteral(argument) || Node.isNoSubstitutionTemplateLiteral(argument))
91
+ ? argument.getLiteralText()
92
+ : undefined
93
+ }
94
+
95
+ function resolveHandler(node: Node): Node {
96
+ let current = unwrapExpression(node)
97
+ if (Node.isIdentifier(current)) current = unwrapExpression(valueOfIdentifier(current) ?? current)
98
+ return current
99
+ }
100
+
101
+ function isStubHandler(node: Node): boolean {
102
+ if (
103
+ !Node.isArrowFunction(node) &&
104
+ !Node.isFunctionExpression(node) &&
105
+ !Node.isFunctionDeclaration(node) &&
106
+ !Node.isMethodDeclaration(node)
107
+ ) {
108
+ return false
109
+ }
110
+ const body = node.getBody()
111
+ if (!Node.isBlock(body)) return false
112
+ const statements = body.getStatements()
113
+ return statements.length > 0 && statements.every(Node.isThrowStatement)
114
+ }
115
+
116
+ /**
117
+ * Discover modular `defineAction()` and `defineWorkflow()` declarations. Runtime
118
+ * admission remains authoritative for exhaustiveness; this is a source overlay.
119
+ */
120
+ export function buildHandlerLinks(args: {
121
+ ir: SchemaIR | null
122
+ domainRoot: string
123
+ }): HandlerLink[] {
124
+ const { ir, domainRoot } = args
125
+ if (!domainRoot) return []
126
+ const project = newProject()
127
+ for (const file of authoredTypeScriptFiles(domainRoot)) project.addSourceFileAtPath(file)
128
+ const links = new Map<string, HandlerLink>()
129
+
130
+ for (const source of project.getSourceFiles()) {
131
+ for (const call of source.getDescendantsOfKind(SyntaxKind.CallExpression)) {
132
+ const kind = declarationKind(call)
133
+ if (!kind) continue
134
+ const key = stringArgument(call, 0)
135
+ const handlerArgument = call.getArguments()[1]
136
+ if (!key || !handlerArgument) continue
137
+ const callable = declaredCallable(ir, key)
138
+ if (!callable) continue
139
+ const handler = resolveHandler(handlerArgument)
140
+ const handlerFile = relToRoot(domainRoot, handler.getSourceFile().getFilePath())
141
+ const link: HandlerLink = {
142
+ ...callable,
143
+ kind,
144
+ wiringFile: handlerFile,
145
+ wiringLine: call.getStartLineNumber(),
146
+ handlerFile,
147
+ handlerLine: handler.getStartLineNumber(),
148
+ implemented: !isStubHandler(handler),
149
+ }
150
+ const kernelCalls = scanKernelCalls(handler.getSourceFile().getFilePath())
151
+ if (kernelCalls.length > 0) link.kernelCalls = kernelCalls
152
+ links.set(`${callable.ownerKind}:${callable.owner}.${callable.method}`, link)
153
+ }
154
+ }
155
+
156
+ return [...links.values()].sort((left, right) =>
157
+ left.owner === right.owner
158
+ ? left.method.localeCompare(right.method)
159
+ : left.owner.localeCompare(right.owner),
160
+ )
161
+ }
@@ -0,0 +1,46 @@
1
+ /**
2
+ * Kernel-operation token scan for resolved handler source files.
3
+ */
4
+ import { readFileSync } from 'node:fs'
5
+
6
+ type KernelToken = { token: string; label?: string }
7
+
8
+ /** Kernel-op idioms surfaced as `kernelCalls`; entries are longest-first. */
9
+ const KERNEL_TOKENS: KernelToken[] = [
10
+ { token: 'graph.createEdge' },
11
+ { token: 'graph.removeEdge' },
12
+ { token: 'function.mutate' },
13
+ { token: 'graph.children' },
14
+ { token: 'function.get' },
15
+ { token: 'graph.create' },
16
+ { token: 'graph.update' },
17
+ { token: 'graph.remove' },
18
+ { token: 'graph.mutate' },
19
+ { token: 'auth.revoke' },
20
+ { token: 'graph.links' },
21
+ { token: 'auth.grant' },
22
+ { token: 'auth.check' },
23
+ { token: 'graph.tree' },
24
+ { token: 'graph.node' },
25
+ { token: 'graph.get' },
26
+ ]
27
+
28
+ /** Scan handler file text for kernel-op tokens. */
29
+ export function scanKernelCalls(file: string): string[] {
30
+ let text: string
31
+ try {
32
+ text = readFileSync(file, 'utf8')
33
+ } catch {
34
+ return []
35
+ }
36
+ const found: string[] = []
37
+ let work = text
38
+ for (const entry of KERNEL_TOKENS) {
39
+ if (work.includes(entry.token)) {
40
+ found.push(entry.label ?? entry.token)
41
+ // Blank out matches so `::getLinks` doesn't also count as `::getLink`.
42
+ work = work.split(entry.token).join(' '.repeat(entry.token.length))
43
+ }
44
+ }
45
+ return found
46
+ }