@astrale-os/cli 1.0.0-beta.2 → 1.0.0-beta.20

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 (282) hide show
  1. package/README.md +16 -1
  2. package/dist/astrale.js +51105 -58057
  3. package/dist/public/connect-core.js +2085 -30702
  4. package/dist/public/keys/index.js +884 -29476
  5. package/dist/public/paths/index.js +13101 -41452
  6. package/dist/types/admin/contract.d.ts +26 -0
  7. package/dist/types/admin/instance/client.d.ts +20 -0
  8. package/dist/types/admin/instance/index.d.ts +2 -0
  9. package/dist/types/admin/instance/model.d.ts +6 -10
  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 +25 -173
  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/output.d.ts +1 -1
  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 +109 -0
  38. package/dist/types/lib/validation.d.ts +4 -1
  39. package/package.json +15 -20
  40. package/src/__tests__/fixtures/publication.ts +20 -0
  41. package/src/admin/.spec/architecture.md +12 -5
  42. package/src/admin/__tests__/fixture.ts +25 -0
  43. package/src/admin/catalog/.spec/api.d.ts +1 -3
  44. package/src/admin/catalog/.spec/architecture.md +5 -4
  45. package/src/admin/catalog/__tests__/client.test.ts +143 -69
  46. package/src/admin/catalog/client.ts +40 -54
  47. package/src/admin/catalog/model.ts +3 -4
  48. package/src/admin/contract.ts +46 -0
  49. package/src/admin/graph/.spec/api.d.ts +4 -10
  50. package/src/admin/graph/nodes.ts +1 -1
  51. package/src/admin/instance/.spec/api.d.ts +5 -9
  52. package/src/admin/instance/.spec/architecture.md +7 -7
  53. package/src/admin/instance/__tests__/client.test.ts +157 -175
  54. package/src/admin/instance/client.ts +39 -128
  55. package/src/admin/instance/index.ts +1 -2
  56. package/src/admin/instance/model.ts +12 -18
  57. package/src/commands/__tests__/admin-instance.test.ts +11 -8
  58. package/src/commands/__tests__/call-describe.test.ts +46 -33
  59. package/src/commands/__tests__/call.test.ts +34 -0
  60. package/src/commands/__tests__/domain-install-operation.test.ts +7 -22
  61. package/src/commands/__tests__/domain-install-owned.test.ts +1 -1
  62. package/src/commands/__tests__/domain-list.test.ts +5 -25
  63. package/src/commands/__tests__/install-direct.test.ts +115 -8
  64. package/src/commands/__tests__/install-identity-override.test.ts +3 -14
  65. package/src/commands/__tests__/instance-list-rows.test.ts +1 -1
  66. package/src/commands/__tests__/instance-status.test.ts +139 -0
  67. package/src/commands/__tests__/introspect-parse.test.ts +80 -1
  68. package/src/commands/__tests__/logs.test.ts +250 -2
  69. package/src/commands/__tests__/read-commands.test.ts +62 -10
  70. package/src/commands/__tests__/token-ttl.test.ts +49 -4
  71. package/src/commands/__tests__/update.test.ts +25 -3
  72. package/src/commands/__tests__/view.test.ts +49 -0
  73. package/src/commands/call-describe.ts +60 -68
  74. package/src/commands/call.ts +71 -59
  75. package/src/commands/domain/install.ts +54 -35
  76. package/src/commands/domain/list.ts +1 -1
  77. package/src/commands/domain/publish.ts +14 -11
  78. package/src/commands/domain/uninstall.ts +2 -2
  79. package/src/commands/get.ts +14 -15
  80. package/src/commands/identity/__tests__/register.test.ts +3 -3
  81. package/src/commands/identity/register.ts +5 -4
  82. package/src/commands/instance/create.ts +8 -16
  83. package/src/commands/instance/delete.ts +12 -9
  84. package/src/commands/instance/list.ts +2 -2
  85. package/src/commands/instance/status.ts +66 -20
  86. package/src/commands/instance/use.ts +16 -12
  87. package/src/commands/introspect.ts +48 -35
  88. package/src/commands/logs.ts +123 -33
  89. package/src/commands/mutate.ts +6 -5
  90. package/src/commands/query.ts +35 -32
  91. package/src/commands/token.ts +42 -27
  92. package/src/commands/ui/__tests__/commands.test.ts +152 -0
  93. package/src/commands/ui/add.ts +51 -0
  94. package/src/commands/ui/doctor.ts +13 -0
  95. package/src/commands/ui/init.ts +38 -0
  96. package/src/commands/ui/list.ts +26 -0
  97. package/src/commands/ui/preset-apply.ts +19 -0
  98. package/src/commands/ui/preset-list.ts +12 -0
  99. package/src/commands/ui/shared.ts +25 -0
  100. package/src/commands/update.ts +31 -11
  101. package/src/commands/view.ts +82 -37
  102. package/src/connection/.spec/api.d.ts +7 -8
  103. package/src/connection/.spec/architecture.md +5 -4
  104. package/src/connection/.spec/flows/session.ts +2 -2
  105. package/src/connection/.spec/laws/connection.ts +6 -2
  106. package/src/connection/.spec/layout.ts +1 -0
  107. package/src/connection/__tests__/credential.test.ts +20 -1
  108. package/src/connection/__tests__/errors.test.ts +27 -20
  109. package/src/connection/__tests__/exchange.test.ts +30 -1
  110. package/src/connection/__tests__/failure-fixtures.ts +39 -0
  111. package/src/connection/__tests__/failure-safety.test.ts +75 -0
  112. package/src/connection/__tests__/self.test.ts +31 -11
  113. package/src/connection/__tests__/session.test.ts +3 -3
  114. package/src/connection/__tests__/target.test.ts +1 -0
  115. package/src/connection/auth.ts +13 -0
  116. package/src/connection/call.ts +1 -1
  117. package/src/connection/credential.ts +2 -2
  118. package/src/connection/errors.ts +1 -355
  119. package/src/connection/exchange.ts +47 -23
  120. package/src/connection/failure/classify.ts +110 -0
  121. package/src/connection/failure/index.ts +18 -0
  122. package/src/connection/failure/model.ts +28 -0
  123. package/src/connection/failure/render.ts +116 -0
  124. package/src/connection/reasons.ts +16 -0
  125. package/src/connection/self.ts +39 -21
  126. package/src/connection/session.ts +19 -8
  127. package/src/errors.ts +3 -2
  128. package/src/graph/.spec/api.d.ts +12 -5
  129. package/src/graph/.spec/layout.ts +9 -1
  130. package/src/graph/__tests__/mutation.test.ts +2 -2
  131. package/src/graph/__tests__/query.test.ts +25 -6
  132. package/src/graph/class.ts +30 -0
  133. package/src/graph/index.ts +1 -0
  134. package/src/graph/query.ts +21 -32
  135. package/src/identity/.spec/api.d.ts +2 -2
  136. package/src/identity/__tests__/registration.test.ts +1 -1
  137. package/src/identity/registration.ts +3 -3
  138. package/src/keys/__tests__/keys.test.ts +4 -0
  139. package/src/keys/credential.ts +1 -0
  140. package/src/lib/__tests__/admin-target.test.ts +12 -0
  141. package/src/lib/__tests__/binary.test.ts +16 -1
  142. package/src/lib/__tests__/command-dx.test.ts +1 -1
  143. package/src/lib/__tests__/domain-publication.test.ts +10 -16
  144. package/src/lib/__tests__/idp.test.ts +34 -4
  145. package/src/lib/__tests__/instance-candidates.test.ts +1 -3
  146. package/src/lib/__tests__/instance-target.test.ts +7 -39
  147. package/src/lib/__tests__/log-errors.test.ts +64 -0
  148. package/src/lib/__tests__/studio-server-deps.test.ts +1 -1
  149. package/src/lib/__tests__/update.test.ts +264 -11
  150. package/src/lib/__tests__/view-assets.test.ts +14 -0
  151. package/src/lib/__tests__/view-external-open-intent.test.ts +150 -0
  152. package/src/lib/__tests__/view-external-open-origins.test.ts +27 -0
  153. package/src/lib/__tests__/view-open-intent.test.ts +0 -2
  154. package/src/lib/__tests__/view-server.test.ts +26 -3
  155. package/src/lib/__tests__/view-session.test.ts +2 -1
  156. package/src/lib/admin-instance.ts +14 -8
  157. package/src/lib/admin-target.ts +21 -2
  158. package/src/lib/binary.ts +22 -5
  159. package/src/lib/command-dx.ts +9 -9
  160. package/src/lib/domain-publication.ts +9 -7
  161. package/src/lib/failure-debug.ts +26 -0
  162. package/src/lib/idp.ts +34 -3
  163. package/src/lib/instance-candidates.ts +2 -2
  164. package/src/lib/instance-target.ts +3 -14
  165. package/src/lib/instance.ts +16 -11
  166. package/src/lib/log.ts +16 -9
  167. package/src/lib/output.ts +1 -1
  168. package/src/lib/proc.ts +7 -2
  169. package/src/lib/provision-instance.ts +34 -57
  170. package/src/lib/update.ts +298 -45
  171. package/src/lib/validation.ts +2 -2
  172. package/src/lib/view/assets.ts +11 -2
  173. package/src/lib/view/external-open-intent.ts +41 -0
  174. package/src/lib/view/external-open-origins.ts +37 -0
  175. package/src/lib/view/host-capabilities.ts +17 -0
  176. package/src/lib/view/resolve.ts +1 -1
  177. package/src/lib/view/server.ts +18 -3
  178. package/src/lib/view/session.ts +2 -0
  179. package/src/program/.spec/api.d.ts +1 -0
  180. package/src/program/__tests__/program.test.ts +163 -6
  181. package/src/program/argv.ts +14 -0
  182. package/src/program/build.ts +26 -4
  183. package/src/program/command.ts +1 -0
  184. package/src/program/index.ts +1 -0
  185. package/src/program/registry.ts +2 -1
  186. package/src/setup/__tests__/instance-step.test.ts +27 -10
  187. package/src/setup/steps/instance.ts +22 -15
  188. package/src/telemetry/__tests__/recorder.test.ts +1 -0
  189. package/src/telemetry/recorder.ts +9 -1
  190. package/src/ui/.spec/api.d.ts +14 -0
  191. package/src/ui/.spec/architecture.md +10 -0
  192. package/src/ui/.spec/laws.ts +36 -0
  193. package/src/ui/.spec/layout.ts +16 -0
  194. package/src/ui/__tests__/ui.test.ts +981 -0
  195. package/src/ui/index.ts +13 -0
  196. package/src/ui/lock.ts +87 -0
  197. package/src/ui/model.ts +83 -0
  198. package/src/ui/operations.ts +710 -0
  199. package/src/ui/project.ts +219 -0
  200. package/src/ui/release.ts +321 -0
  201. package/src/ui/runner.ts +18 -0
  202. package/studio/client/dist/assets/elk-api-DjmKsHXc.js +1 -0
  203. package/studio/client/dist/assets/index-7YHdeCxp.js +81 -0
  204. package/studio/client/dist/assets/index-C0FAnwNw.css +2 -0
  205. package/studio/client/dist/assets/rolldown-runtime-Dd_uD5pT.js +1 -0
  206. package/studio/client/dist/assets/schema-studio-BilUNb6Z.css +1 -0
  207. package/studio/client/dist/assets/schema-studio-CABpTfvH.js +8 -0
  208. package/studio/client/dist/index.html +3 -2
  209. package/studio/package.json +7 -13
  210. package/studio/server/agent/ask.test.ts +1 -1
  211. package/studio/server/agent/bridge/grant.test.ts +1 -1
  212. package/studio/server/agent/bridge/routes.test.ts +1 -1
  213. package/studio/server/agent/harness/claude/adapter.test.ts +15 -5
  214. package/studio/server/agent/harness/gateway/token.test.ts +1 -1
  215. package/studio/server/agent/harness/gateway/token.ts +3 -3
  216. package/studio/server/agent/prompts/anchors.test.ts +32 -36
  217. package/studio/server/agent/prompts/anchors.ts +20 -49
  218. package/studio/server/agent/prompts/system.test.ts +3 -2
  219. package/studio/server/agent/prompts/system.ts +3 -3
  220. package/studio/server/agent/routes.test.ts +1 -1
  221. package/studio/server/agent/run/coordinator.test.ts +1 -1
  222. package/studio/server/api/canvas.ts +0 -1
  223. package/studio/server/api-agent-loadout.test.ts +1 -1
  224. package/studio/server/api.test.ts +1 -1
  225. package/studio/server/cache.test.ts +23 -12
  226. package/studio/server/cache.ts +12 -15
  227. package/studio/server/domain.test.ts +50 -68
  228. package/studio/server/domain.ts +82 -63
  229. package/studio/server/handoff/copy.ts +1 -1
  230. package/studio/server/index.ts +1 -1
  231. package/studio/server/instances/probe.test.ts +1 -1
  232. package/studio/server/introspect/anatomy/views/routes.ts +46 -37
  233. package/studio/server/introspect/anatomy/views.ts +9 -14
  234. package/studio/server/introspect/anatomy-extras.test.ts +26 -42
  235. package/studio/server/introspect/anatomy.test.ts +22 -14
  236. package/studio/server/introspect/anatomy.ts +6 -9
  237. package/studio/server/introspect/bundle.ts +0 -3
  238. package/studio/server/introspect/canonical-schema.test.ts +94 -389
  239. package/studio/server/introspect/canonical-schema.ts +227 -457
  240. package/studio/server/introspect/core-extractor.ts +18 -127
  241. package/studio/server/introspect/core.ts +1 -2
  242. package/studio/server/introspect/diff.test.ts +11 -7
  243. package/studio/server/introspect/diff.ts +18 -55
  244. package/studio/server/introspect/extractor.ts +22 -117
  245. package/studio/server/introspect/overlay-tsmorph.test.ts +119 -240
  246. package/studio/server/introspect/overlay.test.ts +26 -54
  247. package/studio/server/introspect/overlay.ts +10 -37
  248. package/studio/server/introspect/revision.test.ts +1 -1
  249. package/studio/server/introspect/revision.ts +1 -1
  250. package/studio/server/introspect/runtime.test.ts +65 -183
  251. package/studio/server/introspect/runtime.ts +8 -25
  252. package/studio/server/introspect/schema-ir-json.test.ts +70 -0
  253. package/studio/server/introspect/schema-ir-json.ts +38 -68
  254. package/studio/server/introspect/schema-refs.test.ts +43 -88
  255. package/studio/server/introspect/schema-refs.ts +14 -49
  256. package/studio/server/introspect/source-overlay/handlers.ts +116 -636
  257. package/studio/server/introspect/source-overlay/kernel-calls.ts +0 -3
  258. package/studio/server/introspect/source-overlay/spans.ts +15 -45
  259. package/studio/server/state/baseline.test.ts +14 -9
  260. package/studio/server/state/baseline.ts +3 -5
  261. package/studio/server/state/visibility.ts +1 -5
  262. package/studio/server/views/model.ts +3 -3
  263. package/studio/server/views/target.test.ts +17 -74
  264. package/studio/server/views/target.ts +24 -50
  265. package/studio/server/watch.test.ts +11 -11
  266. package/studio/server/watch.ts +8 -4
  267. package/studio/server/workspace-watch.ts +2 -2
  268. package/studio/shared/contracts/schema.ts +38 -107
  269. package/studio/shared/contracts/surface.test.ts +13 -15
  270. package/studio/shared/contracts/workspace.ts +2 -11
  271. package/studio/shared/schema/identity.test.ts +20 -44
  272. package/studio/shared/schema/identity.ts +9 -23
  273. package/viewer/dist/main.js +30 -51
  274. package/.check-workspace.cjs +0 -40
  275. package/src/admin/binding.ts +0 -168
  276. package/src/commands/__tests__/instance-create-hosts.test.ts +0 -29
  277. package/studio/client/dist/assets/elk-api-lwhFo7vB.js +0 -1
  278. package/studio/client/dist/assets/index-B-c-smo9.js +0 -8
  279. package/studio/client/dist/assets/index-BckHuAWk.js +0 -81
  280. package/studio/client/dist/assets/index-C4aNQ6dz.css +0 -1
  281. package/studio/client/dist/assets/index-ChOr3yP0.css +0 -1
  282. package/studio/server/introspect/anatomy/views/legacy.ts +0 -232
@@ -1,14 +1,14 @@
1
- import { afterEach, expect, test } from 'bun:test'
1
+ import { afterEach, describe, expect, test } from 'bun:test'
2
2
  import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from 'node:fs'
3
3
  import { tmpdir } from 'node:os'
4
4
  import { basename, join } from 'node:path'
5
5
 
6
- import { resolveTarget } from './detect'
7
6
  import {
8
7
  depsInstalled,
9
8
  isDomainDir,
10
9
  registerDomain,
11
- resolveDomainEntry,
10
+ resolveApplicationEntry,
11
+ resolveSchemaEntry,
12
12
  unregisterDomain,
13
13
  } from './domain'
14
14
 
@@ -20,76 +20,58 @@ afterEach(() => {
20
20
  while (roots.length) rmSync(roots.pop()!, { recursive: true, force: true })
21
21
  })
22
22
 
23
- function fixture(entry: 'implementation.ts' | 'domain.ts', parent = tmpdir()): string {
24
- const root = mkdtempSync(join(parent, 'studio-domain-layout-'))
23
+ function fixture(nested = false): string {
24
+ const root = mkdtempSync(join(tmpdir(), 'studio-application-layout-'))
25
25
  roots.push(root)
26
- mkdirSync(join(root, 'schema'), { recursive: true })
27
- writeFileSync(join(root, 'astrale.config.ts'), 'export default {}\n')
28
- writeFileSync(join(root, entry), 'export const domain = {}\n')
29
- writeFileSync(join(root, 'schema/index.ts'), 'export const schema = {}\n')
26
+ const owner = nested ? join(root, 'domain') : root
27
+ mkdirSync(join(owner, 'schema'), { recursive: true })
28
+ writeFileSync(
29
+ join(root, 'astrale.config.ts'),
30
+ nested
31
+ ? "import application from './domain/application.js'\nexport default { application }\n"
32
+ : 'export default {}\n',
33
+ )
34
+ writeFileSync(join(owner, 'application.ts'), 'export default {}\n')
35
+ writeFileSync(join(owner, 'schema/index.ts'), 'export const schema = {}\n')
30
36
  return root
31
37
  }
32
38
 
33
- test('detects the current implementation.ts layout and requires its SDK dependency', () => {
34
- const root = fixture('implementation.ts')
35
-
36
- expect(isDomainDir(root)).toBe(true)
37
- expect(basename(resolveDomainEntry(root)!)).toBe('implementation.ts')
38
- expect(depsInstalled(root)).toBe(false)
39
-
40
- mkdirSync(join(root, 'node_modules', '@astrale-os', 'kernel-core'), { recursive: true })
41
- expect(depsInstalled(root)).toBe(false)
42
- mkdirSync(join(root, 'node_modules', '@astrale-os', 'sdk'), { recursive: true })
43
- expect(depsInstalled(root)).toBe(true)
44
-
45
- const handle = registerDomain(root)!
46
- domainIds.push(handle.id)
47
- expect(basename(handle.domainFile)).toBe('implementation.ts')
48
- expect(resolveTarget(root).map((domain) => domain.root)).toEqual([root])
49
- })
50
-
51
- test('preserves domain.ts and kernel-core as the legacy fallback', () => {
52
- const root = fixture('domain.ts')
53
- mkdirSync(join(root, 'node_modules', '@astrale-os', 'kernel-core'), { recursive: true })
54
-
55
- expect(isDomainDir(root)).toBe(true)
56
- expect(depsInstalled(root)).toBe(true)
57
- const handle = registerDomain(root)!
58
- domainIds.push(handle.id)
59
- expect(basename(handle.domainFile)).toBe('domain.ts')
60
- })
61
-
62
- test('prefers implementation.ts when both composition entries exist', () => {
63
- const root = fixture('domain.ts')
64
- writeFileSync(join(root, 'implementation.ts'), 'export const domain = {}\n')
65
-
66
- expect(basename(resolveDomainEntry(root)!)).toBe('implementation.ts')
67
- })
39
+ describe('SDK V1 project discovery', () => {
40
+ test('discovers conventional root Application and Schema entries', () => {
41
+ const root = fixture()
42
+ expect(isDomainDir(root)).toBe(true)
43
+ expect(basename(resolveApplicationEntry(root)!)).toBe('application.ts')
44
+ expect(basename(resolveSchemaEntry(root, resolveApplicationEntry(root)!)!)).toBe('index.ts')
45
+ const handle = registerDomain(root)!
46
+ domainIds.push(handle.id)
47
+ expect(basename(handle.applicationFile)).toBe('application.ts')
48
+ expect(handle.schemaDirName).toBe('schema')
49
+ })
68
50
 
69
- test('recognizes only an SDK that resolves from the Domain through an autonomous hoist', () => {
70
- const isolatedWorkspace = mkdtempSync(join(tmpdir(), 'studio-domain-no-hoist-'))
71
- roots.push(isolatedWorkspace)
72
- mkdirSync(join(isolatedWorkspace, 'packages'))
73
- const isolatedRoot = fixture('implementation.ts', join(isolatedWorkspace, 'packages'))
74
- expect(depsInstalled(isolatedRoot)).toBe(false)
51
+ test('follows a config-imported nested Application and adjacent Schema', () => {
52
+ const root = fixture(true)
53
+ const application = resolveApplicationEntry(root)!
54
+ expect(application).toBe(join(root, 'domain/application.ts'))
55
+ expect(resolveSchemaEntry(root, application)).toBe(join(root, 'domain/schema/index.ts'))
56
+ })
75
57
 
76
- const hoistedWorkspace = mkdtempSync(join(tmpdir(), 'studio-domain-hoist-'))
77
- roots.push(hoistedWorkspace)
78
- mkdirSync(join(hoistedWorkspace, 'packages'))
79
- mkdirSync(join(hoistedWorkspace, 'node_modules', '@astrale-os', 'sdk'), { recursive: true })
80
- writeFileSync(
81
- join(hoistedWorkspace, 'node_modules', '@astrale-os', 'sdk', 'package.json'),
82
- JSON.stringify({
83
- name: '@astrale-os/sdk',
84
- type: 'module',
85
- exports: { './schema': './schema.js' },
86
- }),
87
- )
88
- writeFileSync(
89
- join(hoistedWorkspace, 'node_modules', '@astrale-os', 'sdk', 'schema.js'),
90
- 'export const schema = {}\n',
91
- )
92
- const hoistedRoot = fixture('implementation.ts', join(hoistedWorkspace, 'packages'))
58
+ test('requires the semantic SDK dependency, not Kernel implementation packages', () => {
59
+ const root = fixture()
60
+ expect(depsInstalled(root)).toBe(false)
61
+ mkdirSync(join(root, 'node_modules', '@astrale-os', 'kernel-core'), { recursive: true })
62
+ expect(depsInstalled(root)).toBe(false)
63
+ mkdirSync(join(root, 'node_modules', '@astrale-os', 'sdk'), { recursive: true })
64
+ expect(depsInstalled(root)).toBe(true)
65
+ })
93
66
 
94
- expect(depsInstalled(hoistedRoot)).toBe(true)
67
+ test('rejects implementation.ts and domain.ts compatibility layouts', () => {
68
+ const root = mkdtempSync(join(tmpdir(), 'studio-legacy-layout-'))
69
+ roots.push(root)
70
+ mkdirSync(join(root, 'schema'))
71
+ writeFileSync(join(root, 'astrale.config.ts'), 'export default {}\n')
72
+ writeFileSync(join(root, 'implementation.ts'), 'export default {}\n')
73
+ writeFileSync(join(root, 'domain.ts'), 'export default {}\n')
74
+ writeFileSync(join(root, 'schema/index.ts'), 'export const schema = {}\n')
75
+ expect(isDomainDir(root)).toBe(false)
76
+ })
95
77
  })
@@ -1,74 +1,95 @@
1
- /**
2
- * domain.ts DomainHandle resolution + the in-process registry. A "domain" is
3
- * confirmed by astrale.config.ts + a composition entry + <schemaDir>/index.ts.
4
- * Current SDK projects use implementation.ts; domain.ts remains the legacy
5
- * fallback. The schema dir is configurable (default 'schema') and threaded
6
- * everywhere.
7
- */
8
- import { existsSync } from 'node:fs'
9
- import { basename, join, resolve } from 'node:path'
1
+ /** SDK V1 project discovery and the in-process Studio registry. */
2
+ import { existsSync, readFileSync } from 'node:fs'
3
+ import { basename, dirname, extname, join, relative, resolve } from 'node:path'
10
4
 
11
5
  export interface DomainHandle {
12
- id: string
13
- root: string
14
- configFile: string
15
- /** Active composition entry (implementation.ts when present, otherwise legacy domain.ts). */
16
- domainFile: string
17
- schemaDirName: string
18
- schemaDir: string
19
- schemaIndex: string
6
+ readonly id: string
7
+ readonly root: string
8
+ readonly configFile: string
9
+ readonly applicationFile: string
10
+ readonly schemaDirName: string
11
+ readonly schemaDir: string
12
+ readonly schemaIndex: string
20
13
  origin?: string
21
14
  }
22
15
 
23
16
  const registry = new Map<string, DomainHandle>()
24
17
 
25
- export const DOMAIN_ENTRY_FILES = ['implementation.ts', 'domain.ts'] as const
26
-
27
18
  export function makeId(root: string): string {
28
19
  return basename(resolve(root)).replace(/[^a-zA-Z0-9_-]/g, '-') || 'domain'
29
20
  }
30
21
 
31
- /** Resolve the current composition entry, preferring the SDK layout. */
32
- export function resolveDomainEntry(root: string): string | null {
33
- const r = resolve(root)
34
- for (const file of DOMAIN_ENTRY_FILES) {
35
- const candidate = join(r, file)
36
- if (existsSync(candidate)) return candidate
22
+ /** Resolve the Application imported by config, with root application.ts as convention. */
23
+ export function resolveApplicationEntry(root: string): string | null {
24
+ const project = resolve(root)
25
+ const conventional = join(project, 'application.ts')
26
+ if (existsSync(conventional)) return conventional
27
+ const config = join(project, 'astrale.config.ts')
28
+ if (!existsSync(config)) return null
29
+ let source: string
30
+ try {
31
+ source = readFileSync(config, 'utf8')
32
+ } catch {
33
+ return null
34
+ }
35
+ for (const match of source.matchAll(/\bfrom\s+['"]([^'"]+)['"]/gu)) {
36
+ const specifier = match[1]
37
+ if (
38
+ !specifier?.startsWith('.') ||
39
+ basename(specifier).replace(/\.[^.]+$/u, '') !== 'application'
40
+ ) {
41
+ continue
42
+ }
43
+ const selected = resolveSourceFile(project, specifier)
44
+ if (selected !== null) return selected
37
45
  }
38
46
  return null
39
47
  }
40
48
 
41
- /** The single definition of "is this dir an Astrale domain": the triple must all exist. */
49
+ /** Resolve schema.ts or schema/index.ts beside the Application, then at project root. */
50
+ export function resolveSchemaEntry(
51
+ root: string,
52
+ applicationFile: string,
53
+ schemaDirName = 'schema',
54
+ ): string | null {
55
+ const project = resolve(root)
56
+ const applicationDir = dirname(applicationFile)
57
+ const candidates = [
58
+ join(applicationDir, 'schema.ts'),
59
+ join(applicationDir, schemaDirName, 'index.ts'),
60
+ join(project, 'schema.ts'),
61
+ join(project, schemaDirName, 'index.ts'),
62
+ ]
63
+ return [...new Set(candidates)].find(existsSync) ?? null
64
+ }
65
+
42
66
  export function isDomainDir(root: string, schemaDirName = 'schema'): boolean {
43
- const r = resolve(root)
44
- return (
45
- existsSync(join(r, 'astrale.config.ts')) &&
46
- resolveDomainEntry(r) !== null &&
47
- existsSync(join(r, schemaDirName, 'index.ts'))
48
- )
67
+ const project = resolve(root)
68
+ if (!existsSync(join(project, 'astrale.config.ts'))) return false
69
+ const application = resolveApplicationEntry(project)
70
+ return application !== null && resolveSchemaEntry(project, application, schemaDirName) !== null
49
71
  }
50
72
 
51
- /** Confirm + register a domain rooted at `root`. Returns null if the triple is incomplete. */
52
73
  export function registerDomain(root: string, schemaDirName = 'schema'): DomainHandle | null {
53
- const r = resolve(root)
54
- if (!isDomainDir(r, schemaDirName)) return null
55
- const domainFile = resolveDomainEntry(r)
56
- if (!domainFile) return null
57
- const schemaDir = join(r, schemaDirName)
74
+ const project = resolve(root)
75
+ const applicationFile = resolveApplicationEntry(project)
76
+ if (applicationFile === null || !existsSync(join(project, 'astrale.config.ts'))) return null
77
+ const schemaIndex = resolveSchemaEntry(project, applicationFile, schemaDirName)
78
+ if (schemaIndex === null) return null
79
+ const schemaDir = dirname(schemaIndex)
58
80
  const handle: DomainHandle = {
59
- id: makeId(r),
60
- root: r,
61
- configFile: join(r, 'astrale.config.ts'),
62
- domainFile,
63
- schemaDirName,
81
+ id: makeId(project),
82
+ root: project,
83
+ configFile: join(project, 'astrale.config.ts'),
84
+ applicationFile,
85
+ schemaDirName: relative(project, schemaDir).replaceAll('\\', '/') || '.',
64
86
  schemaDir,
65
- schemaIndex: join(schemaDir, 'index.ts'),
87
+ schemaIndex,
66
88
  }
67
89
  registry.set(handle.id, handle)
68
90
  return handle
69
91
  }
70
92
 
71
- /** Drop a domain from the registry (its dir/triple is gone). */
72
93
  export function unregisterDomain(id: string): void {
73
94
  registry.delete(id)
74
95
  }
@@ -81,25 +102,23 @@ export function allDomains(): DomainHandle[] {
81
102
  return [...registry.values()]
82
103
  }
83
104
 
84
- /** Does the domain have the dependency cohort required by its project layout installed? */
105
+ /** Studio's current project model always requires the semantic SDK Schema facade. */
85
106
  export function depsInstalled(root: string): boolean {
86
- const installed = (packageDir: string, specifier: string): boolean => {
87
- if (existsSync(join(root, 'node_modules', '@astrale-os', packageDir))) return true
88
- try {
89
- Bun.resolveSync(specifier, root)
90
- return true
91
- } catch {
92
- return false
93
- }
107
+ if (existsSync(join(root, 'node_modules', '@astrale-os', 'sdk'))) return true
108
+ try {
109
+ Bun.resolveSync('@astrale-os/sdk/schema', root)
110
+ return true
111
+ } catch {
112
+ return false
94
113
  }
114
+ }
95
115
 
96
- // A workspace may hoist the package above the Domain root. Check both the
97
- // conventional local link and Bun's real resolver from the Domain boundary.
98
- const sdk = installed('sdk', '@astrale-os/sdk/schema')
99
- const entry = resolveDomainEntry(root)
100
-
101
- // implementation.ts is an SDK boundary by contract. Legacy domain.ts
102
- // projects may predate the SDK facade and depend on kernel-core directly.
103
- if (entry?.endsWith('implementation.ts')) return sdk
104
- return sdk || installed('kernel-core', '@astrale-os/kernel-core')
116
+ function resolveSourceFile(root: string, specifier: string): string | null {
117
+ const absolute = resolve(root, specifier)
118
+ const extension = extname(absolute)
119
+ const candidates =
120
+ extension === ''
121
+ ? [`${absolute}.ts`, join(absolute, 'index.ts')]
122
+ : [absolute, `${absolute.slice(0, -extension.length)}.ts`]
123
+ return candidates.find(existsSync) ?? null
105
124
  }
@@ -10,7 +10,7 @@ import type { Comment, ContextItem, DocMeta, SchemaRevision, ThreadEntry } from
10
10
  interface CopyParts {
11
11
  origin: string
12
12
  root: string
13
- /** Studio render fingerprint retained by the legacy annotate machine-state. */
13
+ /** Studio render fingerprint retained by the handoff machine-state. */
14
14
  renderFingerprint: string
15
15
  schemaRevision?: SchemaRevision
16
16
  openComments: Comment[]
@@ -42,7 +42,7 @@ const domains = resolveTarget(target, schemaDir)
42
42
  if (!domains.length) {
43
43
  console.error(`\n ✗ No Astrale domains found at ${target}`)
44
44
  console.error(
45
- ` (looking for: astrale.config.ts + implementation.ts (or legacy domain.ts) + ${schemaDir}/index.ts)\n`,
45
+ ` (looking for: astrale.config.ts + application.ts + schema.ts or ${schemaDir}/index.ts)\n`,
46
46
  )
47
47
  process.exit(1)
48
48
  }
@@ -21,7 +21,7 @@ describe('installed Domain introspection', () => {
21
21
  ])
22
22
  })
23
23
 
24
- test('retains the complete installed Bundle for cohort SDK admission', () => {
24
+ test('retains the complete installed Bundle for installed SDK admission', () => {
25
25
  const root = {
26
26
  version: 'v1',
27
27
  domain: 'issues.astrale.ai',
@@ -1,9 +1,9 @@
1
- import { existsSync } from 'node:fs'
2
1
  import { join, relative } from 'node:path'
3
2
  import { Node, type SourceFile, SyntaxKind } from 'ts-morph'
4
3
 
5
4
  import type { ViewInfo } from '../../../../shared/types'
6
5
 
6
+ import { resolveApplicationEntry } from '../../../domain'
7
7
  import { defineSchemaCalls, schemaProject } from '../schema-definition'
8
8
  import {
9
9
  addSource,
@@ -44,27 +44,29 @@ export function buildSchemaViewSources(root: string, schemaDirName: string): Map
44
44
  return sources
45
45
  }
46
46
 
47
- function frontendKind(
48
- frontendName: string,
49
- input: Node,
50
- source: SourceFile,
51
- ): { kind: ViewInfo['kind']; external: boolean } {
52
- if (frontendName === 'reactFrontend') return { kind: 'spa', external: false }
53
- const sourceName = callName(objectProperty(input, 'source', source))
54
- if (sourceName === 'generatedFrontend') return { kind: 'inline-html', external: false }
55
- if (sourceName === 'viteFrontend' || sourceName === 'prebuiltFrontend') {
56
- return { kind: 'spa', external: false }
57
- }
58
- if (sourceName === 'externalFrontend') return { kind: 'spa', external: true }
59
- return { kind: 'unknown', external: false }
47
+ function defaultRoute(name: string): string {
48
+ return `/${name
49
+ .replace(/([a-z0-9])([A-Z])/gu, '$1-$2')
50
+ .replace(/[_\s]+/gu, '-')
51
+ .toLowerCase()}`
52
+ }
53
+
54
+ function frontendSource(input: Node, source: SourceFile): { externalOrigin?: string } {
55
+ const value = objectProperty(input, 'source', source)
56
+ if (!value || !Node.isCallExpression(value)) return {}
57
+ if (callName(value) !== 'external') return {}
58
+ const origin = literalString(value.getArguments()[0], source)
59
+ return origin === undefined ? {} : { externalOrigin: origin }
60
60
  }
61
61
 
62
- export function buildFrontendViews(root: string): ViewInfo[] {
62
+ export function buildFrontendViews(
63
+ root: string,
64
+ canonicalViewNames: readonly string[],
65
+ ): ViewInfo[] {
63
66
  const project = makeProject()
64
- const compositionFiles = ['implementation.ts', 'domain.ts']
65
- .map((file) => join(root, file))
66
- .filter((file) => existsSync(file))
67
- const sources = [...new Set([...listSourceFiles(join(root, 'views')), ...compositionFiles])]
67
+ const application = resolveApplicationEntry(root)
68
+ const applicationFiles = application === null ? [] : [application]
69
+ const sources = [...new Set([...listSourceFiles(join(root, 'views')), ...applicationFiles])]
68
70
  .map((file) => addSource(project, file))
69
71
  .filter((source): source is SourceFile => source !== null)
70
72
  const views: ViewInfo[] = []
@@ -72,30 +74,37 @@ export function buildFrontendViews(root: string): ViewInfo[] {
72
74
  for (const source of sources) {
73
75
  for (const call of source.getDescendantsOfKind(SyntaxKind.CallExpression)) {
74
76
  const frontendName = callName(call)
75
- if (frontendName !== 'frontendArtifact' && frontendName !== 'reactFrontend') continue
77
+ if (frontendName !== 'defineFrontend') continue
76
78
  const input = objectValue(call.getArguments()[0], source)
77
79
  if (!input) continue
78
80
  const routes = objectValue(objectProperty(input, 'routes', source) ?? undefined, source)
79
- if (!routes || !Node.isObjectLiteralExpression(routes)) continue
80
- const artifact = frontendKind(frontendName, input, source)
81
-
82
- for (const property of routes.getProperties()) {
83
- const slug = propertySlug(property)
84
- const routeValue = propertyValue(property, source)
85
- if (!slug || !routeValue) continue
86
- const routeName = callName(routeValue)
87
- const routeInput = Node.isCallExpression(routeValue)
88
- ? objectValue(routeValue.getArguments()[0], source)
89
- : objectValue(routeValue, source)
90
- if (!routeInput) continue
81
+ const declared = new Map<string, string>()
82
+ if (routes && Node.isObjectLiteralExpression(routes)) {
83
+ for (const property of routes.getProperties()) {
84
+ const slug = propertySlug(property)
85
+ const routeValue = propertyValue(property, source)
86
+ if (!slug || !routeValue) continue
87
+ const direct = literalString(routeValue, source)
88
+ const routeInput = objectValue(routeValue, source)
89
+ const path =
90
+ direct ??
91
+ (routeInput
92
+ ? literalString(objectProperty(routeInput, 'path', source) ?? undefined, source)
93
+ : undefined)
94
+ if (path) declared.set(slug, path)
95
+ }
96
+ }
97
+ const frontend = frontendSource(input, source)
91
98
 
92
- const path = literalString(objectProperty(routeInput, 'path', source) ?? undefined, source)
93
- const href = literalString(objectProperty(routeInput, 'href', source) ?? undefined, source)
99
+ for (const slug of canonicalViewNames) {
100
+ const path = declared.get(slug) ?? defaultRoute(slug)
101
+ const url = frontend.externalOrigin
102
+ ? new URL(path, `${frontend.externalOrigin}/`).toString()
103
+ : undefined
94
104
  views.push({
95
105
  slug,
96
- kind: routeName === 'reactRoute' ? 'spa' : artifact.kind,
97
- ...(href ? { url: href } : { url: undefined }),
98
- ...(!href && (path || !artifact.external) ? { mount: path ?? `/ui/${slug}` } : {}),
106
+ kind: 'spa',
107
+ ...(url ? { url } : { mount: path }),
99
108
  file: relative(root, source.getFilePath()).replaceAll('\\', '/'),
100
109
  })
101
110
  }
@@ -1,6 +1,5 @@
1
1
  import type { SchemaIR, ViewInfo } from '../../../shared/types'
2
2
 
3
- import { buildLegacyViews } from './views/legacy'
4
3
  import { buildFrontendViews, buildSchemaViewSources } from './views/routes'
5
4
 
6
5
  function mergeRoute(target: ViewInfo, incoming: ViewInfo): void {
@@ -30,7 +29,7 @@ function canonicalViewInfo(slug: string, view: NonNullable<SchemaIR['views']>[st
30
29
  * Join canonical View definitions with route/source metadata. Passing a
31
30
  * canonical map (including an empty map) makes it authoritative: static routes
32
31
  * cannot invent Views or override identity, target, auth, or description.
33
- * Without it, the legacy defineView registry remains the compatibility source.
32
+ * Without admitted canonical Views, Studio does not invent authoring semantics.
34
33
  */
35
34
  export function buildViews(
36
35
  root: string,
@@ -38,21 +37,17 @@ export function buildViews(
38
37
  canonicalViews?: NonNullable<SchemaIR['views']>,
39
38
  ): ViewInfo[] {
40
39
  const merged = new Map<string, ViewInfo>()
41
- if (canonicalViews !== undefined) {
42
- for (const [slug, view] of Object.entries(canonicalViews)) {
43
- merged.set(slug, canonicalViewInfo(slug, view))
44
- }
45
- for (const [slug, file] of buildSchemaViewSources(root, _schemaDirName)) {
46
- const current = merged.get(slug)
47
- if (current) current.file = file
48
- }
49
- } else {
50
- for (const view of buildLegacyViews(root)) merged.set(view.slug, view)
40
+ const admittedViews = canonicalViews ?? {}
41
+ for (const [slug, view] of Object.entries(admittedViews)) {
42
+ merged.set(slug, canonicalViewInfo(slug, view))
51
43
  }
52
- for (const view of buildFrontendViews(root)) {
44
+ for (const [slug, file] of buildSchemaViewSources(root, _schemaDirName)) {
45
+ const current = merged.get(slug)
46
+ if (current) current.file = file
47
+ }
48
+ for (const view of buildFrontendViews(root, Object.keys(admittedViews))) {
53
49
  const current = merged.get(view.slug)
54
50
  if (current) mergeRoute(current, view)
55
- else if (canonicalViews === undefined) merged.set(view.slug, view)
56
51
  }
57
52
  return [...merged.values()]
58
53
  }