@astrale-os/cli 1.0.0-beta.3 → 1.0.0-beta.31

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 (345) hide show
  1. package/README.md +84 -6
  2. package/dist/astrale.js +57586 -60753
  3. package/dist/public/connect-core.js +2158 -30706
  4. package/dist/public/keys/index.js +1016 -29541
  5. package/dist/public/paths/index.js +370 -28654
  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 +13 -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/dist/types/state/exchange-credentials.d.ts +6 -2
  40. package/dist/types/state/files.d.ts +2 -0
  41. package/dist/types/state/index.d.ts +3 -2
  42. package/dist/types/state/paths.d.ts +2 -0
  43. package/dist/types/state/session-routes.d.ts +10 -0
  44. package/package.json +16 -19
  45. package/src/__tests__/fixtures/publication.ts +20 -0
  46. package/src/admin/.spec/architecture.md +12 -5
  47. package/src/admin/__tests__/fixture.ts +25 -0
  48. package/src/admin/catalog/.spec/api.d.ts +1 -3
  49. package/src/admin/catalog/.spec/architecture.md +5 -4
  50. package/src/admin/catalog/__tests__/client.test.ts +143 -69
  51. package/src/admin/catalog/client.ts +40 -54
  52. package/src/admin/catalog/model.ts +3 -4
  53. package/src/admin/contract.ts +47 -0
  54. package/src/admin/graph/.spec/api.d.ts +4 -10
  55. package/src/admin/graph/nodes.ts +1 -1
  56. package/src/admin/instance/.spec/api.d.ts +5 -9
  57. package/src/admin/instance/.spec/architecture.md +7 -7
  58. package/src/admin/instance/__tests__/client.test.ts +157 -175
  59. package/src/admin/instance/client.ts +39 -128
  60. package/src/admin/instance/index.ts +1 -2
  61. package/src/admin/instance/model.ts +12 -18
  62. package/src/commands/__tests__/admin-instance.test.ts +11 -8
  63. package/src/commands/__tests__/call-describe.test.ts +46 -33
  64. package/src/commands/__tests__/call.test.ts +34 -0
  65. package/src/commands/__tests__/domain-install-operation.test.ts +30 -22
  66. package/src/commands/__tests__/domain-install-owned.test.ts +1 -1
  67. package/src/commands/__tests__/domain-list.test.ts +5 -25
  68. package/src/commands/__tests__/install-direct.test.ts +115 -8
  69. package/src/commands/__tests__/install-identity-override.test.ts +6 -17
  70. package/src/commands/__tests__/instance-list-rows.test.ts +1 -1
  71. package/src/commands/__tests__/instance-status.test.ts +139 -0
  72. package/src/commands/__tests__/introspect-parse.test.ts +80 -1
  73. package/src/commands/__tests__/logs.test.ts +250 -2
  74. package/src/commands/__tests__/read-commands.test.ts +119 -11
  75. package/src/commands/__tests__/token-ttl.test.ts +49 -4
  76. package/src/commands/__tests__/update.test.ts +25 -3
  77. package/src/commands/__tests__/view.test.ts +50 -2
  78. package/src/commands/auth/logout.ts +2 -1
  79. package/src/commands/browser.ts +29 -0
  80. package/src/commands/call-describe.ts +60 -68
  81. package/src/commands/call.ts +71 -59
  82. package/src/commands/domain/install.ts +73 -46
  83. package/src/commands/domain/list.ts +1 -1
  84. package/src/commands/domain/publish.ts +14 -11
  85. package/src/commands/domain/uninstall.ts +2 -2
  86. package/src/commands/get.ts +15 -16
  87. package/src/commands/identity/__tests__/register.test.ts +3 -3
  88. package/src/commands/identity/register.ts +8 -8
  89. package/src/commands/instance/create.ts +8 -16
  90. package/src/commands/instance/delete.ts +12 -9
  91. package/src/commands/instance/list.ts +2 -2
  92. package/src/commands/instance/status.ts +66 -20
  93. package/src/commands/instance/use.ts +16 -12
  94. package/src/commands/introspect.ts +53 -41
  95. package/src/commands/logs.ts +123 -36
  96. package/src/commands/mutate.ts +6 -5
  97. package/src/commands/query.ts +39 -36
  98. package/src/commands/session/analyze.ts +26 -4
  99. package/src/commands/token.ts +42 -27
  100. package/src/commands/ui/__tests__/commands.test.ts +152 -0
  101. package/src/commands/ui/add.ts +51 -0
  102. package/src/commands/ui/doctor.ts +21 -0
  103. package/src/commands/ui/init.ts +38 -0
  104. package/src/commands/ui/list.ts +26 -0
  105. package/src/commands/ui/preset-apply.ts +19 -0
  106. package/src/commands/ui/preset-list.ts +12 -0
  107. package/src/commands/ui/shared.ts +25 -0
  108. package/src/commands/update.ts +90 -48
  109. package/src/commands/view.ts +82 -37
  110. package/src/connection/.spec/api.d.ts +7 -8
  111. package/src/connection/.spec/architecture.md +14 -5
  112. package/src/connection/.spec/flows/session.ts +2 -2
  113. package/src/connection/.spec/laws/connection.ts +6 -2
  114. package/src/connection/.spec/layout.ts +1 -0
  115. package/src/connection/__tests__/auth.test.ts +1 -0
  116. package/src/connection/__tests__/credential.test.ts +21 -1
  117. package/src/connection/__tests__/errors.test.ts +27 -20
  118. package/src/connection/__tests__/exchange.test.ts +64 -11
  119. package/src/connection/__tests__/failure-fixtures.ts +39 -0
  120. package/src/connection/__tests__/failure-safety.test.ts +75 -0
  121. package/src/connection/__tests__/self.test.ts +31 -11
  122. package/src/connection/__tests__/session.test.ts +8 -4
  123. package/src/connection/__tests__/target.test.ts +2 -0
  124. package/src/connection/auth.ts +13 -0
  125. package/src/connection/call.ts +1 -1
  126. package/src/connection/credential.ts +2 -2
  127. package/src/connection/errors.ts +1 -355
  128. package/src/connection/exchange.ts +110 -61
  129. package/src/connection/failure/classify.ts +110 -0
  130. package/src/connection/failure/index.ts +18 -0
  131. package/src/connection/failure/model.ts +28 -0
  132. package/src/connection/failure/render.ts +116 -0
  133. package/src/connection/reasons.ts +16 -0
  134. package/src/connection/self.ts +39 -21
  135. package/src/connection/session.ts +27 -9
  136. package/src/errors.ts +3 -2
  137. package/src/graph/.spec/api.d.ts +13 -6
  138. package/src/graph/.spec/laws/documents.ts +7 -7
  139. package/src/graph/.spec/layout.ts +9 -1
  140. package/src/graph/__tests__/mutation.test.ts +2 -2
  141. package/src/graph/__tests__/query.test.ts +35 -9
  142. package/src/graph/class.ts +30 -0
  143. package/src/graph/index.ts +1 -0
  144. package/src/graph/query.ts +27 -40
  145. package/src/identity/.spec/api.d.ts +2 -2
  146. package/src/identity/__tests__/fixtures/registry-journey.ts +13 -1
  147. package/src/identity/__tests__/registration.test.ts +1 -1
  148. package/src/identity/__tests__/registry.test.ts +4 -0
  149. package/src/identity/registration.ts +3 -3
  150. package/src/identity/registry.ts +2 -0
  151. package/src/keys/__tests__/keys.test.ts +4 -0
  152. package/src/keys/credential.ts +1 -0
  153. package/src/lib/__tests__/admin-target.test.ts +12 -0
  154. package/src/lib/__tests__/binary.test.ts +16 -1
  155. package/src/lib/__tests__/browser-retention.test.ts +212 -0
  156. package/src/lib/__tests__/command-dx.test.ts +1 -1
  157. package/src/lib/__tests__/config.test.ts +27 -0
  158. package/src/lib/__tests__/domain-publication.test.ts +10 -16
  159. package/src/lib/__tests__/idp.test.ts +34 -4
  160. package/src/lib/__tests__/instance-candidates.test.ts +1 -3
  161. package/src/lib/__tests__/instance-target.test.ts +7 -39
  162. package/src/lib/__tests__/log-errors.test.ts +64 -0
  163. package/src/lib/__tests__/skills.test.ts +834 -0
  164. package/src/lib/__tests__/studio-server-deps.test.ts +1 -1
  165. package/src/lib/__tests__/update.test.ts +264 -11
  166. package/src/lib/__tests__/view-assets.test.ts +25 -0
  167. package/src/lib/__tests__/view-external-open-intent.test.ts +150 -0
  168. package/src/lib/__tests__/view-external-open-origins.test.ts +27 -0
  169. package/src/lib/__tests__/view-open-intent.test.ts +0 -2
  170. package/src/lib/__tests__/view-server.test.ts +26 -3
  171. package/src/lib/__tests__/view-session.test.ts +2 -1
  172. package/src/lib/admin-instance.ts +14 -8
  173. package/src/lib/admin-target.ts +21 -2
  174. package/src/lib/binary.ts +22 -5
  175. package/src/lib/browser-retention.ts +210 -0
  176. package/src/lib/command-dx.ts +9 -9
  177. package/src/lib/config.ts +12 -1
  178. package/src/lib/domain-publication.ts +9 -7
  179. package/src/lib/failure-debug.ts +26 -0
  180. package/src/lib/idp.ts +34 -3
  181. package/src/lib/instance-candidates.ts +2 -2
  182. package/src/lib/instance-target.ts +3 -14
  183. package/src/lib/instance.ts +16 -11
  184. package/src/lib/log.ts +16 -9
  185. package/src/lib/output.ts +1 -1
  186. package/src/lib/proc.ts +7 -2
  187. package/src/lib/provision-instance.ts +34 -57
  188. package/src/lib/skills/lock.ts +117 -0
  189. package/src/lib/skills/sync.ts +878 -0
  190. package/src/lib/skills.ts +1 -52
  191. package/src/lib/update.ts +298 -45
  192. package/src/lib/validation.ts +2 -2
  193. package/src/lib/view/assets.ts +11 -2
  194. package/src/lib/view/external-open-intent.ts +41 -0
  195. package/src/lib/view/external-open-origins.ts +37 -0
  196. package/src/lib/view/host-capabilities.ts +17 -0
  197. package/src/lib/view/resolve.ts +1 -1
  198. package/src/lib/view/server.ts +18 -3
  199. package/src/lib/view/session.ts +2 -0
  200. package/src/program/.spec/api.d.ts +1 -0
  201. package/src/program/__tests__/program.test.ts +201 -7
  202. package/src/program/argv.ts +14 -0
  203. package/src/program/build.ts +26 -4
  204. package/src/program/command.ts +1 -0
  205. package/src/program/index.ts +1 -0
  206. package/src/program/registry.ts +2 -1
  207. package/src/setup/__tests__/instance-step.test.ts +27 -10
  208. package/src/setup/steps/instance.ts +22 -15
  209. package/src/setup/steps/skills.ts +40 -38
  210. package/src/state/.spec/api.d.ts +21 -2
  211. package/src/state/.spec/architecture.md +18 -4
  212. package/src/state/.spec/layout.ts +1 -0
  213. package/src/state/__tests__/exchange-credentials.test.ts +88 -42
  214. package/src/state/__tests__/files.test.ts +24 -1
  215. package/src/state/__tests__/fixtures/session-route-process.ts +93 -0
  216. package/src/state/__tests__/paths.test.ts +1 -0
  217. package/src/state/__tests__/session-routes.test.ts +138 -0
  218. package/src/state/exchange-credentials.ts +22 -9
  219. package/src/state/files.ts +41 -0
  220. package/src/state/index.ts +3 -1
  221. package/src/state/paths.ts +3 -0
  222. package/src/state/session-routes.ts +34 -0
  223. package/src/telemetry/__tests__/analyze-log.test.ts +38 -0
  224. package/src/telemetry/__tests__/recorder.test.ts +1 -0
  225. package/src/telemetry/__tests__/retention.test.ts +267 -0
  226. package/src/telemetry/__tests__/settings.test.ts +102 -0
  227. package/src/telemetry/__tests__/store-scan.test.ts +128 -0
  228. package/src/telemetry/__tests__/trigger.test.ts +33 -4
  229. package/src/telemetry/analyze.ts +24 -2
  230. package/src/telemetry/recorder.ts +15 -4
  231. package/src/telemetry/retention.ts +176 -0
  232. package/src/telemetry/session.ts +18 -12
  233. package/src/telemetry/settings.ts +64 -11
  234. package/src/telemetry/store.ts +92 -9
  235. package/src/telemetry/trigger.ts +16 -28
  236. package/src/ui/.spec/api.d.ts +14 -0
  237. package/src/ui/.spec/architecture.md +14 -0
  238. package/src/ui/.spec/laws.ts +50 -0
  239. package/src/ui/.spec/layout.ts +16 -0
  240. package/src/ui/__tests__/ui.test.ts +1742 -0
  241. package/src/ui/index.ts +13 -0
  242. package/src/ui/lock.ts +89 -0
  243. package/src/ui/model.ts +83 -0
  244. package/src/ui/operations.ts +1044 -0
  245. package/src/ui/project.ts +333 -0
  246. package/src/ui/release.ts +398 -0
  247. package/src/ui/runner.ts +18 -0
  248. package/studio/client/dist/assets/elk-api-DjmKsHXc.js +1 -0
  249. package/studio/client/dist/assets/index-BFVFs_8x.js +81 -0
  250. package/studio/client/dist/assets/index-DuB9iUdu.css +2 -0
  251. package/studio/client/dist/assets/rolldown-runtime-Dd_uD5pT.js +1 -0
  252. package/studio/client/dist/assets/schema-studio--a0kX3QU.css +1 -0
  253. package/studio/client/dist/assets/schema-studio-DH6oEWME.js +8 -0
  254. package/studio/client/dist/index.html +4 -3
  255. package/studio/package.json +8 -14
  256. package/studio/server/agent/ask.test.ts +1 -1
  257. package/studio/server/agent/bridge/grant.test.ts +1 -1
  258. package/studio/server/agent/bridge/routes.test.ts +1 -1
  259. package/studio/server/agent/harness/claude/adapter.test.ts +15 -5
  260. package/studio/server/agent/harness/gateway/token.test.ts +1 -1
  261. package/studio/server/agent/harness/gateway/token.ts +3 -3
  262. package/studio/server/agent/prompts/anchors.test.ts +41 -32
  263. package/studio/server/agent/prompts/anchors.ts +23 -51
  264. package/studio/server/agent/prompts/system.test.ts +4 -4
  265. package/studio/server/agent/prompts/system.ts +5 -5
  266. package/studio/server/agent/routes.test.ts +1 -1
  267. package/studio/server/agent/run/coordinator.test.ts +1 -1
  268. package/studio/server/agent/run/preparation.ts +1 -1
  269. package/studio/server/api/canvas.ts +0 -1
  270. package/studio/server/api/context.ts +1 -1
  271. package/studio/server/api/deployment.ts +1 -1
  272. package/studio/server/api/views.ts +2 -2
  273. package/studio/server/api/workspace.ts +1 -1
  274. package/studio/server/api-agent-loadout.test.ts +1 -1
  275. package/studio/server/api.test.ts +1 -1
  276. package/studio/server/cache.test.ts +24 -18
  277. package/studio/server/cache.ts +10 -54
  278. package/studio/server/cli-consumers.test.ts +32 -4
  279. package/studio/server/cli.test.ts +2 -2
  280. package/studio/server/domain.test.ts +50 -68
  281. package/studio/server/domain.ts +82 -63
  282. package/studio/server/handoff/copy.ts +2 -2
  283. package/studio/server/index.ts +1 -1
  284. package/studio/server/instances/probe.test.ts +1 -1
  285. package/studio/server/introspect/anatomy/views/routes.ts +46 -37
  286. package/studio/server/introspect/anatomy/views.ts +10 -16
  287. package/studio/server/introspect/anatomy-extras.test.ts +29 -56
  288. package/studio/server/introspect/anatomy.test.ts +22 -14
  289. package/studio/server/introspect/anatomy.ts +6 -9
  290. package/studio/server/introspect/bundle.ts +0 -3
  291. package/studio/server/introspect/canonical-schema.test.ts +155 -424
  292. package/studio/server/introspect/canonical-schema.ts +248 -601
  293. package/studio/server/introspect/core.ts +14 -22
  294. package/studio/server/introspect/diff.test.ts +21 -9
  295. package/studio/server/introspect/diff.ts +19 -64
  296. package/studio/server/introspect/extractor.ts +23 -122
  297. package/studio/server/introspect/overlay-tsmorph.test.ts +116 -241
  298. package/studio/server/introspect/overlay-tsmorph.ts +0 -1
  299. package/studio/server/introspect/overlay.ts +3 -51
  300. package/studio/server/introspect/revision.test.ts +24 -28
  301. package/studio/server/introspect/revision.ts +11 -22
  302. package/studio/server/introspect/runtime.test.ts +77 -195
  303. package/studio/server/introspect/runtime.ts +5 -67
  304. package/studio/server/introspect/schema-ir-json.test.ts +81 -0
  305. package/studio/server/introspect/schema-ir-json.ts +39 -69
  306. package/studio/server/introspect/schema-refs.test.ts +43 -88
  307. package/studio/server/introspect/schema-refs.ts +14 -49
  308. package/studio/server/introspect/source-overlay/handlers.ts +116 -636
  309. package/studio/server/introspect/source-overlay/kernel-calls.ts +0 -3
  310. package/studio/server/introspect/source-overlay/spans.ts +15 -45
  311. package/studio/server/lifecycle.ts +4 -1
  312. package/studio/server/state/baseline.test.ts +14 -9
  313. package/studio/server/state/baseline.ts +3 -5
  314. package/studio/server/state/documents.test.ts +69 -0
  315. package/studio/server/state/documents.ts +57 -10
  316. package/studio/server/state/visibility.ts +1 -5
  317. package/studio/server/views/model.ts +3 -3
  318. package/studio/server/views/target.test.ts +18 -76
  319. package/studio/server/views/target.ts +25 -51
  320. package/studio/server/watch.test.ts +11 -11
  321. package/studio/server/watch.ts +8 -4
  322. package/studio/server/workspace/updates.ts +23 -1
  323. package/studio/server/workspace-watch.ts +2 -2
  324. package/studio/shared/contracts/runtime.ts +4 -0
  325. package/studio/shared/contracts/schema.ts +44 -131
  326. package/studio/shared/contracts/surface.test.ts +15 -17
  327. package/studio/shared/contracts/workspace.ts +5 -11
  328. package/studio/shared/schema/identity.test.ts +20 -44
  329. package/studio/shared/schema/identity.ts +9 -24
  330. package/studio/tsconfig.json +2 -1
  331. package/tsconfig.json +1 -1
  332. package/viewer/dist/index.html +21 -0
  333. package/viewer/dist/main.js +41 -62
  334. package/.check-workspace.cjs +0 -40
  335. package/src/admin/binding.ts +0 -168
  336. package/src/commands/__tests__/instance-create-hosts.test.ts +0 -29
  337. package/studio/client/dist/assets/elk-api-lwhFo7vB.js +0 -1
  338. package/studio/client/dist/assets/index-B-c-smo9.js +0 -8
  339. package/studio/client/dist/assets/index-BckHuAWk.js +0 -81
  340. package/studio/client/dist/assets/index-C4aNQ6dz.css +0 -1
  341. package/studio/client/dist/assets/index-ChOr3yP0.css +0 -1
  342. package/studio/server/introspect/anatomy/views/legacy.ts +0 -232
  343. package/studio/server/introspect/core-extractor.ts +0 -139
  344. package/studio/server/introspect/overlay.test.ts +0 -72
  345. package/studio/server/introspect/source-overlay/annotations.ts +0 -14
@@ -1,55 +1,94 @@
1
1
  /**
2
- * Pure projection of the canonical, portable DomainSchema V1 document into the
3
- * Studio's backward-compatible render IR. This module deliberately imports no
4
- * Kernel or SDK package: extractor islands must interpret the schema emitted by
5
- * the domain's own SDK cohort, not a second copy installed beside Studio.
2
+ * Project the DSL's portable Schema into Studio's deliberately smaller render
3
+ * model. Admission, dependency reachability, and reference resolution remain
4
+ * owned by the Domain's installed DSL.
6
5
  */
7
6
  import type {
8
7
  IrCallableAuth,
9
8
  IrCallableOutput,
10
9
  IrClass,
11
- IrDefinitionKey,
10
+ IrClassKey,
11
+ IrClassRef,
12
12
  IrEndpoint,
13
13
  IrFunction,
14
14
  IrImportDescriptor,
15
- IrInterface,
16
15
  IrMethod,
17
16
  IrSchemaRef,
18
17
  IrView,
19
18
  JsonSchema,
20
19
  SchemaIR,
20
+ SchemaRevision,
21
21
  StudioCore,
22
22
  } from '../../shared/types'
23
23
 
24
- import { definitionRefKey, isIrDefinitionRef, schemaRefKey } from '../../shared/schema/identity'
25
24
  import { isSchemaRevision } from '../../shared/types'
26
25
 
27
26
  type AnyRecord = Record<string, unknown>
28
27
 
29
- export type CanonicalDomainSchemaV1 = AnyRecord & {
28
+ /** The portable V1 value transported across the extractor process boundary. */
29
+ export interface CanonicalDomainSchemaV1 {
30
30
  readonly format: 'astrale.dsl'
31
31
  readonly version: 'v1'
32
32
  readonly origin: string
33
+ readonly dependencies: Readonly<
34
+ Record<string, { readonly origin: string; readonly revision: string }>
35
+ >
36
+ readonly classes: Readonly<Record<string, unknown>>
37
+ readonly functions: Readonly<Record<string, unknown>>
38
+ readonly policies: Readonly<Record<string, unknown>>
39
+ readonly views: Readonly<Record<string, unknown>>
40
+ readonly core: unknown
41
+ }
42
+
43
+ interface ResolvedSchemaDomain {
44
+ readonly source: CanonicalDomainSchemaV1
45
+ readonly revision: unknown
46
+ definition(ref: IrClassRef): { readonly definition: unknown } | undefined
47
+ }
48
+
49
+ /** Runtime facade supplied by the inspected Domain, not by Studio's package. */
50
+ export interface SchemaSdk {
51
+ readonly bundle: {
52
+ create(input: any): unknown
53
+ accept(input: any): {
54
+ readonly root: CanonicalDomainSchemaV1
55
+ readonly closure: readonly CanonicalDomainSchemaV1[]
56
+ }
57
+ }
58
+ readonly schema: {
59
+ resolve(input: any): ResolvedSchemaDomain
60
+ compareDependencyMeaning(
61
+ source: CanonicalDomainSchemaV1,
62
+ target: CanonicalDomainSchemaV1,
63
+ ): { readonly footprint: readonly unknown[] }
64
+ }
65
+ readonly ClassKey: {
66
+ is(input: unknown): input is IrClassKey
67
+ ref(input: IrClassKey): IrClassRef
68
+ }
33
69
  }
34
70
 
35
- export interface CanonicalSchemaProjection {
36
- ir: SchemaIR
37
- importedInterfaces: Record<string, IrInterface>
71
+ /** Smaller facade used when re-admitting an installed Bundle for drift checks. */
72
+ export interface SchemaAdmissionSdk {
73
+ readonly bundle: {
74
+ accept(input: any): { readonly root: CanonicalDomainSchemaV1 }
75
+ }
76
+ readonly schema: {
77
+ resolve(input: any): { readonly source: unknown; readonly revision: unknown }
78
+ }
38
79
  }
39
80
 
40
- export type CanonicalSchemaAdmission =
41
- | {
42
- status: 'admitted'
43
- root: CanonicalDomainSchemaV1
44
- closure: CanonicalDomainSchemaV1[]
45
- revision: `sha256:${string}`
46
- }
47
- | {
48
- status: 'preview'
49
- root: CanonicalDomainSchemaV1
50
- closure: CanonicalDomainSchemaV1[]
51
- revision: null
52
- }
81
+ export interface CanonicalSchemaExtraction {
82
+ readonly status: 'admitted' | 'preview'
83
+ readonly root: CanonicalDomainSchemaV1
84
+ readonly revision: SchemaRevision | null
85
+ readonly ir: SchemaIR
86
+ }
87
+
88
+ interface ImportProjection {
89
+ importsByKey: Record<IrClassKey, IrImportDescriptor>
90
+ importedClassesByKey: Record<IrClassKey, IrClass>
91
+ }
53
92
 
54
93
  export function isCanonicalDomainSchemaV1(value: unknown): value is CanonicalDomainSchemaV1 {
55
94
  const candidate = asRecord(value)
@@ -67,414 +106,212 @@ export function findCanonicalDomainSchemaExport(
67
106
  ): CanonicalDomainSchemaV1 | null {
68
107
  if (isCanonicalDomainSchemaV1(moduleExports.schema)) return moduleExports.schema
69
108
  if (isCanonicalDomainSchemaV1(moduleExports.default)) return moduleExports.default
70
-
71
- const candidates = Object.entries(moduleExports)
72
- .filter((entry): entry is [string, CanonicalDomainSchemaV1] =>
73
- isCanonicalDomainSchemaV1(entry[1]),
74
- )
75
- .sort(([left], [right]) => left.localeCompare(right))
76
- return candidates[0]?.[1] ?? null
77
- }
78
-
79
- /**
80
- * Read the exact retained dependency closure through the domain's own SDK.
81
- * Calls are optional and fail closed: an unrecognised/substituted result is
82
- * discarded rather than trusted as imported-schema evidence.
83
- */
84
- export function closureFromSdk(
85
- sdkModule: Record<string, unknown>,
86
- root: CanonicalDomainSchemaV1,
87
- ): CanonicalDomainSchemaV1[] {
88
- const bundleApi = asRecord(sdkModule.bundle)
89
- const create = bundleApi?.create
90
- if (typeof create === 'function') {
91
- try {
92
- const candidate = Reflect.apply(create, bundleApi, [root])
93
- const bundle = asRecord(candidate)
94
- if (bundle?.root === root) {
95
- const closure = admitClosure(bundle.closure, root)
96
- if (closure) return closure
97
- }
98
- } catch {
99
- // A structurally V1-looking export may lack retained SDK admission context.
100
- }
101
- }
102
-
103
- const schemaApi = asRecord(sdkModule.schema)
104
- const resolve = schemaApi?.resolve
105
- if (typeof resolve === 'function') {
106
- try {
107
- const candidate = asRecord(Reflect.apply(resolve, schemaApi, [root]))
108
- const resolution = asRecord(candidate?.$)
109
- if (resolution?.schema === root) {
110
- const closure = admitClosure(resolution.closure, root)
111
- if (closure) return closure
112
- }
113
- } catch {
114
- // Missing retained context is a valid degradation: local schema still renders.
115
- }
116
- }
117
-
118
- return []
109
+ return (
110
+ Object.entries(moduleExports)
111
+ .filter((entry): entry is [string, CanonicalDomainSchemaV1] =>
112
+ isCanonicalDomainSchemaV1(entry[1]),
113
+ )
114
+ .sort(([left], [right]) => left.localeCompare(right))[0]?.[1] ?? null
115
+ )
119
116
  }
120
117
 
121
118
  /**
122
- * Re-admit a structurally V1-looking export through the Domain's own SDK cohort.
123
- * Retained Builder context supplies the exact dependency closure. If that proof
124
- * is unavailable or admission fails, Studio may still render the document as a
125
- * structural preview, but it must not assign a canonical revision to it.
119
+ * Re-admit the authored Schema as one exact DSL Bundle and resolve its Domain.
120
+ * A V1-shaped value rejected by the installed DSL remains a best-effort local
121
+ * preview while the author is in the middle of an edit.
126
122
  */
127
- export function admitCanonicalSchemaFromSdk(
128
- sdkModule: Record<string, unknown>,
123
+ export function extractCanonicalSchemaFromSdk(
124
+ sdk: SchemaSdk,
129
125
  candidate: CanonicalDomainSchemaV1,
130
- ): CanonicalSchemaAdmission {
131
- const retainedClosure = closureFromSdk(sdkModule, candidate)
132
- const schemaApi = asRecord(sdkModule.schema)
133
- const accept = schemaApi?.accept
134
- const revision = schemaApi?.revision
135
- if (!schemaApi || typeof accept !== 'function' || typeof revision !== 'function') {
136
- return { status: 'preview', root: candidate, closure: retainedClosure, revision: null }
137
- }
138
-
126
+ ): CanonicalSchemaExtraction {
139
127
  try {
140
- const revisions = new Map<string, string>()
141
- for (const dependency of retainedClosure) {
142
- const value = Reflect.apply(revision, schemaApi, [dependency])
143
- if (!isSchemaRevision(value)) throw new TypeError('SDK returned an invalid schema revision')
144
- revisions.set(dependency.origin, value)
128
+ const admitted = sdk.bundle.accept(sdk.bundle.create(candidate))
129
+ if (admitted.root.origin !== candidate.origin) {
130
+ throw new TypeError('DSL Bundle admission returned another Schema root.')
145
131
  }
146
- const lookup = {
147
- get(origin: string, expected: string): CanonicalDomainSchemaV1 | undefined {
148
- const dependency = retainedClosure.find((entry) => entry.origin === origin)
149
- return dependency && revisions.get(origin) === expected ? dependency : undefined
150
- },
132
+ const domain = sdk.schema.resolve(admitted.root)
133
+ if (domain.source !== admitted.root || !isSchemaRevision(domain.revision)) {
134
+ throw new TypeError('DSL resolution did not retain the admitted Schema root.')
151
135
  }
152
- const accepted = Reflect.apply(accept, schemaApi, [candidate, lookup])
153
- if (!isCanonicalDomainSchemaV1(accepted) || accepted.origin !== candidate.origin) {
154
- throw new TypeError('SDK admission returned a different schema root')
155
- }
156
- const acceptedRevision = Reflect.apply(revision, schemaApi, [accepted])
157
- if (!isSchemaRevision(acceptedRevision)) {
158
- throw new TypeError('SDK returned an invalid schema revision')
159
- }
160
-
161
- // Resolve the newly accepted value once more so its retained closure, rather
162
- // than Studio's structural candidate, is what feeds exact-ref projection.
163
- const resolve = schemaApi.resolve
164
- let closure = retainedClosure
165
- if (typeof resolve === 'function') {
166
- const resolved = asRecord(Reflect.apply(resolve, schemaApi, [accepted]))
167
- const proof = asRecord(resolved?.$)
168
- if (proof?.schema !== accepted) throw new TypeError('SDK resolution did not retain the root')
169
- const admitted = admitClosure(proof.closure, accepted)
170
- if (!admitted) throw new TypeError('SDK resolution returned an invalid dependency closure')
171
- closure = admitted
136
+ return {
137
+ status: 'admitted',
138
+ root: admitted.root,
139
+ revision: domain.revision,
140
+ ir: projectSchema(admitted.root, projectImports(sdk, domain, admitted.closure)),
172
141
  }
173
-
174
- return { status: 'admitted', root: accepted, closure, revision: acceptedRevision }
175
142
  } catch {
176
- return { status: 'preview', root: candidate, closure: retainedClosure, revision: null }
177
- }
178
- }
179
-
180
- /** Add fields required by the current shared contract to a legacy serializer IR. */
181
- export function normalizeLegacySchemaIR(value: unknown): SchemaIR {
182
- const ir = asRecord(value)
183
- if (!ir) throw new TypeError('legacy schema IR must be an object')
184
-
185
- const functions: Record<string, IrFunction> = {}
186
- for (const [name, raw] of entriesOf(ir.functions)) {
187
- const declaration = asRecord(raw) ?? {}
188
- const params = schemaRecord(declaration.params)
189
- const returns = studioSchema(declaration.returns)
190
- const requiredParams =
191
- declaration.input === undefined
192
- ? legacyRequiredParams(params)
193
- : requiredNames(declaration.input)
194
- functions[name] = {
195
- name,
196
- input: studioSchema(declaration.input ?? objectInputFromParams(params, requiredParams)),
197
- params,
198
- requiredParams,
199
- output: callableOutput(declaration.output, returns),
200
- returns,
201
- static: true,
202
- inheritance: 'default',
203
- ...(callableAuth(declaration.auth) ? { auth: callableAuth(declaration.auth) } : {}),
204
- ...(typeof declaration.description === 'string'
205
- ? { description: declaration.description }
206
- : {}),
207
- ...(declaration.policy === undefined ? {} : { policy: jsonCopy(declaration.policy) }),
208
- }
209
- }
210
-
211
- return { ...(ir as unknown as SchemaIR), functions }
212
- }
213
-
214
- export function projectCanonicalSchema(
215
- root: CanonicalDomainSchemaV1,
216
- closure: readonly CanonicalDomainSchemaV1[] = [],
217
- ): CanonicalSchemaProjection {
218
- const origin = root.origin
219
- const interfaces = Object.fromEntries(
220
- entriesOf(root.interfaces).map(([name, declaration]) => [
221
- name,
222
- projectInterface(origin, name, declaration),
223
- ]),
224
- )
225
- const classes = Object.fromEntries(
226
- entriesOf(root.classes).map(([name, declaration]) => [
227
- name,
228
- projectClass(origin, name, declaration),
229
- ]),
230
- )
231
- const functions = Object.fromEntries(
232
- entriesOf(root.functions).map(([name, declaration]) => [
233
- name,
234
- projectFunction(name, declaration),
235
- ]),
236
- )
237
- const views = Object.fromEntries(
238
- entriesOf(root.views).map(([name, declaration]) => [name, projectView(name, declaration)]),
239
- )
240
-
241
- const importsByKey: Record<IrDefinitionKey, IrImportDescriptor> = {}
242
- const importedInterfacesByKey: Record<IrDefinitionKey, IrInterface> = {}
243
- const schemas = new Map(
244
- closure.filter(isCanonicalDomainSchemaV1).map((schema) => [schema.origin, schema]),
245
- )
246
- const pending = collectDefinitionRefs(root)
247
- const visited = new Set<string>()
248
-
249
- for (let index = 0; index < pending.length; index++) {
250
- const ref = pending[index]
251
- if (!isIrDefinitionRef(ref) || ref.origin === origin) continue
252
- const key = definitionRefKey(ref)
253
- if (visited.has(key)) continue
254
- visited.add(key)
255
-
256
- importsByKey[key] = {
257
- origin: ref.origin,
258
- definition: ref.kind,
259
- ref,
260
- key,
261
- }
262
- if (ref.kind !== 'interface' || key in importedInterfacesByKey) continue
263
-
264
- const owner = schemas.get(ref.origin)
265
- const declaration = asRecord(asRecord(owner?.interfaces)?.[ref.name])
266
- if (!owner || !declaration) continue
267
- const projected = projectInterface(owner.origin, ref.name, declaration)
268
- importedInterfacesByKey[key] = projected
269
- pending.push(...collectDefinitionRefsFromDeclaration(declaration))
270
- }
271
-
272
- // Bare member names are retained solely as an unambiguous compatibility
273
- // lookup. The canonical Key maps above remain complete when origins or kinds
274
- // reuse a name, and prevent a traversal-order-dependent "first one wins".
275
- const nameCounts = new Map<string, number>()
276
- for (const descriptor of Object.values(importsByKey)) {
277
- const name = descriptor.ref?.name
278
- if (name) nameCounts.set(name, (nameCounts.get(name) ?? 0) + 1)
279
- }
280
- const localNames = new Set([...Object.keys(interfaces), ...Object.keys(classes)])
281
- const isUnambiguous = (name: string): boolean =>
282
- nameCounts.get(name) === 1 && !localNames.has(name)
283
- const imports = Object.fromEntries(
284
- Object.values(importsByKey).flatMap((descriptor) => {
285
- const name = descriptor.ref?.name
286
- return name && isUnambiguous(name) ? [[name, descriptor]] : []
287
- }),
288
- )
289
- const importedInterfaces = Object.fromEntries(
290
- Object.values(importedInterfacesByKey).flatMap((declaration) =>
291
- isUnambiguous(declaration.name) ? [[declaration.name, declaration]] : [],
292
- ),
293
- )
294
-
295
- const dependencies = arrayOf(root.dependencies)
296
- .map(asRecord)
297
- .filter((dependency): dependency is AnyRecord => dependency !== null)
298
- .flatMap((dependency) =>
299
- typeof dependency.origin === 'string' && typeof dependency.revision === 'string'
300
- ? [{ origin: dependency.origin, revision: dependency.revision }]
301
- : [],
302
- )
303
-
304
- return {
305
- ir: {
306
- format: 'astrale.dsl',
307
- version: 'v1',
308
- domain: origin,
309
- types: schemaRecord(root.types),
310
- interfaces,
311
- classes,
312
- imports,
313
- importsByKey,
314
- importedInterfacesByKey,
315
- functions,
316
- views,
317
- policies: recordCopy(root.policies),
318
- dependencies,
319
- core: jsonCopy(root.core),
320
- },
321
- importedInterfaces,
143
+ return { status: 'preview', root: candidate, revision: null, ir: projectSchema(candidate) }
322
144
  }
323
145
  }
324
146
 
325
- /** Project canonical genesis data without importing the effectful Domain entry. */
147
+ /** Project canonical Core data without importing Application or Runtime modules. */
326
148
  export function projectCanonicalCore(
327
149
  root: CanonicalDomainSchemaV1,
328
150
  ): Pick<StudioCore, 'domain' | 'nodes' | 'edges'> {
329
151
  const core = asRecord(root.core) ?? {}
330
152
  const nodes = entriesOf(core.nodes).map(([slug, raw]) => {
331
153
  const node = asRecord(raw) ?? {}
332
- const classRef = schemaRef(node.class)
333
154
  return {
334
- path: corePath({ origin: root.origin, kind: 'core', name: slug }),
335
- className: classRef?.name ?? '?',
155
+ path: corePath({ origin: root.origin, name: slug }),
156
+ className: classRef(node.class)?.name ?? '?',
336
157
  data: recordCopy(node.properties),
337
158
  }
338
159
  })
339
160
  const edges = arrayOf(core.edges).map((raw) => {
340
161
  const edge = asRecord(raw) ?? {}
341
- const classRef = schemaRef(edge.class)
342
162
  return {
343
163
  from: coreEndpoint(edge.source, root.origin),
344
164
  to: coreEndpoint(edge.target, root.origin),
345
- edgeName: classRef?.name ?? '?',
165
+ edgeName: classRef(edge.class)?.name ?? '?',
346
166
  ...(edge.properties === undefined ? {} : { data: recordCopy(edge.properties) }),
347
167
  }
348
168
  })
349
169
  return { domain: root.origin, nodes, edges }
350
170
  }
351
171
 
352
- function projectInterface(origin: string, name: string, value: unknown): IrInterface {
353
- const declaration = asRecord(value) ?? {}
354
- const family = definitionFamily(declaration.family, 'both')
355
- const extendsRefs = refsOf(declaration.extends, 'interface')
172
+ function projectSchema(root: CanonicalDomainSchemaV1, imports?: ImportProjection): SchemaIR {
356
173
  return {
357
- type: 'interface',
358
- name,
359
- origin,
360
- family,
361
- ref: { origin, kind: 'interface', name },
362
- extends: extendsRefs.map((ref) => ref.name),
363
- extendsRefs,
364
- properties: propertySchemas(declaration),
365
- required: requiredNames(declaration.properties),
366
- methods: projectMethods(declaration.methods),
367
- ...(family === 'edge' ? edgeFields(declaration) : {}),
368
- ...(typeof declaration.description === 'string'
369
- ? { description: declaration.description }
370
- : {}),
371
- ...(asRecord(declaration.propertyMetadata)
372
- ? { propertyMetadata: recordCopy(declaration.propertyMetadata) }
373
- : {}),
374
- ...(dataDeclaration(declaration.data) ? { data: dataDeclaration(declaration.data) } : {}),
174
+ format: 'astrale.dsl',
175
+ version: 'v1',
176
+ domain: root.origin,
177
+ classes: Object.fromEntries(
178
+ entriesOf(root.classes).map(([name, value]) => [
179
+ name,
180
+ projectClass(root.origin, name, value),
181
+ ]),
182
+ ),
183
+ importsByKey: imports?.importsByKey ?? {},
184
+ importedClassesByKey: imports?.importedClassesByKey ?? {},
185
+ functions: Object.fromEntries(
186
+ entriesOf(root.functions).map(([name, value]) => [name, projectFunction(name, value)]),
187
+ ),
188
+ views: Object.fromEntries(
189
+ entriesOf(root.views).map(([name, value]) => [name, projectView(name, value)]),
190
+ ),
191
+ policies: recordCopy(root.policies),
192
+ dependencies: Object.values(root.dependencies ?? {}).map(({ origin, revision }) => ({
193
+ origin,
194
+ revision,
195
+ })),
196
+ core: jsonCopy(root.core),
375
197
  }
376
198
  }
377
199
 
200
+ function projectImports(
201
+ sdk: SchemaSdk,
202
+ domain: ReturnType<SchemaSdk['schema']['resolve']>,
203
+ closure: readonly CanonicalDomainSchemaV1[],
204
+ ): ImportProjection {
205
+ const importsByKey = {} as Record<IrClassKey, IrImportDescriptor>
206
+ const importedClassesByKey = {} as Record<IrClassKey, IrClass>
207
+
208
+ // The DSL computes the exact reachable footprint. Studio no longer recurses
209
+ // through JSON Schemas, policies, Views, and Core declarations to rediscover it.
210
+ for (const dependency of closure) {
211
+ const footprint = sdk.schema.compareDependencyMeaning(domain.source, dependency).footprint
212
+ for (const candidate of footprint) {
213
+ if (!sdk.ClassKey.is(candidate)) continue
214
+ const ref = sdk.ClassKey.ref(candidate)
215
+ const definition = domain.definition(ref)
216
+ if (!definition) continue
217
+ const key = candidate as IrClassKey
218
+ importsByKey[key] = { origin: ref.origin, ref, key }
219
+ importedClassesByKey[key] = projectClass(ref.origin, ref.name, definition.definition)
220
+ }
221
+ }
222
+ return { importsByKey, importedClassesByKey }
223
+ }
224
+
378
225
  function projectClass(origin: string, name: string, value: unknown): IrClass {
379
226
  const declaration = asRecord(value) ?? {}
380
- const family = definitionFamily(declaration.family, 'node') === 'edge' ? 'edge' : 'node'
381
- const implementsRefs = refsOf(declaration.implements, 'interface')
227
+ const kind = declaration.kind === 'edge' ? 'edge' : 'node'
228
+ const extendsRefs = refsOf(declaration.extends)
229
+ const propertyEntries = entriesOf(declaration.properties)
230
+ const propertyMetadata = Object.fromEntries(
231
+ propertyEntries.map(([propertyName, raw]) => {
232
+ const member = { ...(asRecord(raw) ?? {}) }
233
+ delete member.schema
234
+ return [propertyName, jsonCopy(member)]
235
+ }),
236
+ )
382
237
  const policies = Object.fromEntries(
383
238
  entriesOf(declaration.policies).flatMap(([policyName, raw]) => {
384
- const ref = schemaRef(raw)
239
+ const ref = definitionRefOf(raw)
385
240
  return ref ? [[policyName, ref]] : []
386
241
  }),
387
242
  )
388
243
  return {
389
- type: family,
244
+ type: kind,
390
245
  name,
391
246
  origin,
392
247
  ref: { origin, kind: 'class', name },
393
- implements: implementsRefs.map((ref) => ref.name),
394
- implementsRefs,
395
- properties: propertySchemas(declaration),
396
- required: requiredNames(declaration.properties),
397
- methods: projectMethods(declaration.methods),
398
- ...(family === 'edge' ? edgeFields(declaration) : {}),
248
+ extends: extendsRefs.map((ref) => ref.name),
249
+ extendsRefs,
250
+ properties: Object.fromEntries(
251
+ propertyEntries.map(([propertyName, raw]) => [
252
+ propertyName,
253
+ studioSchema(asRecord(raw)?.schema),
254
+ ]),
255
+ ),
256
+ required: propertyEntries.flatMap(([propertyName, raw]) =>
257
+ asRecord(raw)?.required === true ? [propertyName] : [],
258
+ ),
259
+ methods: Object.fromEntries(
260
+ entriesOf(declaration.methods).map(([methodName, raw]) => [
261
+ methodName,
262
+ projectMethod(methodName, raw),
263
+ ]),
264
+ ),
265
+ ...(kind === 'edge' ? edgeFields(declaration) : {}),
399
266
  ...(typeof declaration.icon === 'string' ? { icon: declaration.icon } : {}),
400
267
  ...(typeof declaration.description === 'string'
401
268
  ? { description: declaration.description }
402
269
  : {}),
403
- ...(asRecord(declaration.propertyMetadata)
404
- ? { propertyMetadata: recordCopy(declaration.propertyMetadata) }
405
- : {}),
270
+ // the SDK lets a Class carry its own glyph; without this the schema declared one
271
+ // and every surface still drew the generic box
272
+ ...(typeof declaration.icon === 'string' ? { icon: declaration.icon } : {}),
273
+ ...(Object.keys(propertyMetadata).length === 0 ? {} : { propertyMetadata }),
406
274
  ...(dataDeclaration(declaration.data) ? { data: dataDeclaration(declaration.data) } : {}),
407
- ...(Object.keys(policies).length > 0 ? { policies } : {}),
275
+ ...(Object.keys(policies).length === 0 ? {} : { policies }),
408
276
  }
409
277
  }
410
278
 
411
- function projectMethods(value: unknown): Record<string, IrMethod> {
412
- return Object.fromEntries(
413
- entriesOf(value).map(([name, declaration]) => {
414
- const callable = projectCallable(name, declaration)
415
- const raw = asRecord(declaration) ?? {}
416
- const inheritance =
417
- raw.inheritance === 'abstract' ||
418
- raw.inheritance === 'sealed' ||
419
- raw.inheritance === 'default'
420
- ? raw.inheritance
421
- : 'default'
422
- return [
423
- name,
424
- {
425
- ...callable,
426
- static: raw.static === true,
427
- inheritance,
428
- } satisfies IrMethod,
429
- ]
430
- }),
431
- )
432
- }
433
-
434
- function projectFunction(name: string, declaration: unknown): IrFunction {
279
+ function projectMethod(name: string, value: unknown): IrMethod {
280
+ const declaration = asRecord(value) ?? {}
435
281
  return {
436
- ...projectCallable(name, declaration),
437
- static: true,
438
- inheritance: 'default',
282
+ ...projectFunction(name, declaration),
283
+ static: declaration.static === true,
284
+ inheritance:
285
+ declaration.inheritance === 'abstract' || declaration.inheritance === 'sealed'
286
+ ? declaration.inheritance
287
+ : 'default',
439
288
  }
440
289
  }
441
290
 
442
- function projectCallable(name: string, value: unknown): Omit<IrFunction, 'static' | 'inheritance'> {
291
+ function projectFunction(name: string, value: unknown): IrFunction {
443
292
  const declaration = asRecord(value) ?? {}
444
- const input = studioSchema(declaration.input)
445
- const output = callableOutput(declaration.output, {})
446
293
  const auth = callableAuth(declaration.auth)
447
294
  return {
448
295
  name,
449
- input,
450
- params: callableParams(declaration.input),
451
- requiredParams: requiredNames(declaration.input),
452
- output,
453
- returns: outputValue(output),
296
+ input: studioSchema(declaration.input),
297
+ output: callableOutput(declaration.output),
454
298
  ...(typeof declaration.description === 'string'
455
299
  ? { description: declaration.description }
456
300
  : {}),
457
- ...(auth ? { auth } : {}),
301
+ ...(auth === undefined ? {} : { auth }),
458
302
  ...(declaration.policy === undefined ? {} : { policy: jsonCopy(declaration.policy) }),
459
303
  }
460
304
  }
461
305
 
462
306
  function projectView(name: string, value: unknown): IrView {
463
307
  const declaration = asRecord(value) ?? {}
464
- const rawTarget = asRecord(declaration.target)
465
- const target =
466
- rawTarget?.kind === 'definition'
467
- ? ({
468
- kind: 'definition',
469
- definitions: refsOf(rawTarget.definitions, ['class', 'interface']),
470
- } as const)
471
- : ({ kind: 'domain' } as const)
472
- const auth =
473
- declaration.auth === 'optional' || declaration.auth === 'public' ? declaration.auth : 'required'
308
+ const target = asRecord(declaration.target)
474
309
  return {
475
310
  name,
476
- target,
477
- auth,
311
+ target:
312
+ target?.kind === 'definition'
313
+ ? { kind: 'definition', definitions: refsOf(target.definitions) }
314
+ : { kind: 'domain' },
478
315
  ...(typeof declaration.description === 'string'
479
316
  ? { description: declaration.description }
480
317
  : {}),
@@ -493,71 +330,46 @@ function edgeFields(
493
330
  projectEndpoint(endpoints?.source, 'outgoing'),
494
331
  projectEndpoint(endpoints?.target, 'incoming'),
495
332
  ]
496
- const rawConstraints = asRecord(declaration.constraints)
497
- const constraints = {
498
- ...(rawConstraints?.noSelf === true ? { noSelf: true as const } : {}),
499
- ...(rawConstraints?.acyclic === true ? { acyclic: true as const } : {}),
333
+ const constraints = asRecord(declaration.constraints)
334
+ return {
335
+ endpoints: projected,
336
+ orientation,
337
+ constraints: {
338
+ ...(constraints?.noSelf === true ? { noSelf: true } : {}),
339
+ ...(constraints?.acyclic === true ? { acyclic: true } : {}),
340
+ },
500
341
  }
501
- return { endpoints: projected, orientation, constraints }
502
342
  }
503
343
 
504
344
  function projectEndpoint(
505
345
  value: unknown,
506
- countKey: 'outgoing' | 'incoming' | 'incident',
346
+ cardinality: 'outgoing' | 'incoming' | 'incident',
507
347
  ): IrEndpoint {
508
348
  const endpoint = asRecord(value) ?? {}
509
- const refs = refsOf(endpoint.accepts, ['class', 'interface'])
510
- const cardinality = edgeCount(endpoint[countKey])
349
+ const refs = refsOf(endpoint.accepts)
350
+ const count = endpoint[cardinality]
511
351
  return {
512
352
  name: typeof endpoint.role === 'string' ? endpoint.role : '',
513
353
  types: refs.map((ref) => ref.name),
514
354
  refs,
515
- ...(cardinality ? { cardinality } : {}),
516
- }
517
- }
518
-
519
- function edgeCount(value: unknown): IrEndpoint['cardinality'] | undefined {
520
- switch (value) {
521
- case '0..*':
522
- return { min: 0, max: null }
523
- case '1..*':
524
- return { min: 1, max: null }
525
- case '0..1':
526
- return { min: 0, max: 1 }
527
- case '1':
528
- return { min: 1, max: 1 }
529
- default:
530
- return undefined
355
+ ...(count === '0..*' || count === '1..*' || count === '0..1' || count === '1'
356
+ ? { cardinality: edgeCount(count) }
357
+ : {}),
531
358
  }
532
359
  }
533
360
 
534
- function propertySchemas(declaration: AnyRecord): Record<string, JsonSchema> {
535
- const object = asRecord(declaration.properties) ?? {}
536
- return Object.fromEntries(
537
- entriesOf(object.properties).map(([name, schema]) => [name, studioSchema(schema)]),
538
- )
539
- }
540
-
541
- function callableParams(input: unknown): Record<string, JsonSchema> {
542
- const object = asRecord(input)
543
- if (!object) return {}
544
- return Object.fromEntries(
545
- entriesOf(object.properties).map(([name, schema]) => [name, studioSchema(schema)]),
546
- )
361
+ function edgeCount(value: '0..*' | '1..*' | '0..1' | '1'): IrEndpoint['cardinality'] {
362
+ if (value === '0..*') return { min: 0, max: null }
363
+ if (value === '1..*') return { min: 1, max: null }
364
+ if (value === '0..1') return { min: 0, max: 1 }
365
+ return { min: 1, max: 1 }
547
366
  }
548
367
 
549
- function callableOutput(value: unknown, fallback: JsonSchema): IrCallableOutput {
368
+ function callableOutput(value: unknown): IrCallableOutput {
550
369
  const output = asRecord(value)
551
370
  if (output?.mode === 'stream') return { mode: 'stream', item: studioSchema(output.item) }
552
371
  if (output?.mode === 'binary') return { mode: 'binary' }
553
- if (output?.mode === 'value') return { mode: 'value', schema: studioSchema(output.schema) }
554
- return { mode: 'value', schema: fallback }
555
- }
556
-
557
- function outputValue(output: IrCallableOutput): JsonSchema {
558
- if (output.mode === 'value') return output.schema
559
- if (output.mode === 'stream') return output.item
560
- return {}
372
+ return { mode: 'value', schema: studioSchema(output?.schema) }
561
373
  }
562
374
 
563
375
  function callableAuth(value: unknown): IrCallableAuth | undefined {
@@ -566,8 +378,30 @@ function callableAuth(value: unknown): IrCallableAuth | undefined {
566
378
  : undefined
567
379
  }
568
380
 
569
- function definitionFamily(value: unknown, fallback: 'node' | 'both'): 'node' | 'edge' | 'both' {
570
- return value === 'node' || value === 'edge' || value === 'both' ? value : fallback
381
+ function refsOf(value: unknown): IrClassRef[] {
382
+ return arrayOf(value)
383
+ .map(classRef)
384
+ .filter((ref): ref is IrClassRef => ref !== null)
385
+ }
386
+
387
+ function classRef(value: unknown): IrClassRef | null {
388
+ const ref = definitionRefOf(value)
389
+ return ref?.kind === 'class' ? { origin: ref.origin, kind: 'class', name: ref.name } : null
390
+ }
391
+
392
+ function definitionRefOf(value: unknown): IrSchemaRef | null {
393
+ const ref = asRecord(value)
394
+ if (typeof ref?.origin !== 'string' || typeof ref.name !== 'string') return null
395
+ if (
396
+ ref.kind !== 'class' &&
397
+ ref.kind !== 'function' &&
398
+ ref.kind !== 'policy' &&
399
+ ref.kind !== 'view' &&
400
+ ref.kind !== 'core'
401
+ ) {
402
+ return null
403
+ }
404
+ return { origin: ref.origin, kind: ref.kind, name: ref.name }
571
405
  }
572
406
 
573
407
  function dataDeclaration(
@@ -579,223 +413,36 @@ function dataDeclaration(
579
413
  : undefined
580
414
  }
581
415
 
582
- function refsOf(
583
- value: unknown,
584
- kinds: IrSchemaRef['kind'] | readonly IrSchemaRef['kind'][],
585
- ): IrSchemaRef[] {
586
- const accepted = new Set(Array.isArray(kinds) ? kinds : [kinds])
587
- return arrayOf(value)
588
- .map(schemaRef)
589
- .filter((ref): ref is IrSchemaRef => ref !== null && accepted.has(ref.kind))
590
- }
591
-
592
- function schemaRef(value: unknown): IrSchemaRef | null {
593
- const ref = asRecord(value)
594
- if (!ref || typeof ref.origin !== 'string' || typeof ref.name !== 'string') return null
595
- switch (ref.kind) {
596
- case 'type':
597
- case 'interface':
598
- case 'class':
599
- case 'function':
600
- case 'policy':
601
- case 'view':
602
- case 'core':
603
- return { origin: ref.origin, kind: ref.kind, name: ref.name }
604
- default:
605
- return null
606
- }
607
- }
608
-
609
- function collectDefinitionRefs(root: CanonicalDomainSchemaV1): IrSchemaRef[] {
610
- const refs: IrSchemaRef[] = []
611
- for (const [, declaration] of entriesOf(root.types)) {
612
- collectPathSchemaRefs(declaration, refs)
613
- }
614
- for (const [, declaration] of entriesOf(root.interfaces)) {
615
- refs.push(...collectDefinitionRefsFromDeclaration(declaration))
616
- }
617
- for (const [, declaration] of entriesOf(root.classes)) {
618
- refs.push(...collectDefinitionRefsFromDeclaration(declaration))
619
- }
620
- for (const [, callable] of entriesOf(root.functions)) collectCallableRefs(callable, refs)
621
- for (const [, view] of entriesOf(root.views)) {
622
- const target = asRecord(asRecord(view)?.target)
623
- refs.push(...refsOf(target?.definitions, ['class', 'interface']))
624
- }
625
- const core = asRecord(root.core)
626
- for (const [, node] of entriesOf(core?.nodes)) addRef(asRecord(node)?.class, refs)
627
- for (const edge of arrayOf(core?.edges)) {
628
- const declaration = asRecord(edge)
629
- addRef(declaration?.class, refs)
630
- addRef(declaration?.source, refs)
631
- addRef(declaration?.target, refs)
632
- }
633
- for (const [, policy] of entriesOf(root.policies)) collectPolicyRefs(policy, refs)
634
- return uniqueRefs(refs)
635
- }
636
-
637
- function collectDefinitionRefsFromDeclaration(value: unknown): IrSchemaRef[] {
638
- const declaration = asRecord(value) ?? {}
639
- const refs = [
640
- ...refsOf(declaration.extends, 'interface'),
641
- ...refsOf(declaration.implements, 'interface'),
642
- ]
643
- collectPathSchemaRefs(declaration.properties, refs)
644
- const endpoints = asRecord(declaration.endpoints)
645
- for (const endpoint of [
646
- endpoints?.source,
647
- endpoints?.target,
648
- ...arrayOf(declaration.endpoints),
649
- ]) {
650
- refs.push(...refsOf(asRecord(endpoint)?.accepts, ['class', 'interface']))
651
- }
652
- for (const [, callable] of entriesOf(declaration.methods)) collectCallableRefs(callable, refs)
653
- for (const [, policy] of entriesOf(declaration.policies)) addRef(policy, refs)
654
- return uniqueRefs(refs)
655
- }
656
-
657
- function collectCallableRefs(value: unknown, refs: IrSchemaRef[]): void {
658
- const callable = asRecord(value)
659
- if (!callable) return
660
- collectPathSchemaRefs(callable.input, refs)
661
- const output = asRecord(callable.output)
662
- collectPathSchemaRefs(output?.schema ?? output?.item, refs)
663
- collectPolicyRefs(callable.policy, refs)
664
- }
665
-
666
- /** Embedded value data is opaque; only the declared x-astrale-path vocabulary carries refs. */
667
- function collectPathSchemaRefs(value: unknown, refs: IrSchemaRef[]): void {
668
- if (Array.isArray(value)) {
669
- for (const item of value) collectPathSchemaRefs(item, refs)
670
- return
671
- }
672
- const object = asRecord(value)
673
- if (!object) return
674
- const annotation = asRecord(object['x-astrale-path'])
675
- if (annotation) refs.push(...refsOf(annotation.accepts, ['class', 'interface']))
676
- for (const [key, child] of Object.entries(object)) {
677
- if (key === 'const' || key === 'enum' || key === 'default' || key === 'examples') continue
678
- collectPathSchemaRefs(child, refs)
679
- }
680
- }
681
-
682
- function collectPolicyRefs(value: unknown, refs: IrSchemaRef[]): void {
683
- if (Array.isArray(value)) {
684
- for (const item of value) collectPolicyRefs(item, refs)
685
- return
686
- }
687
- const object = asRecord(value)
688
- if (!object) return
689
- for (const key of ['check', 'class', 'ref']) addRef(object[key], refs)
690
- for (const key of [
691
- 'allOf',
692
- 'anyOf',
693
- 'exists',
694
- 'where',
695
- 'match',
696
- 'nodes',
697
- 'source',
698
- 'target',
699
- 'object',
700
- ]) {
701
- collectPolicyRefs(object[key], refs)
702
- }
703
- }
704
-
705
- function addRef(value: unknown, refs: IrSchemaRef[]): void {
706
- const ref = schemaRef(value)
707
- if (ref) refs.push(ref)
708
- }
709
-
710
- function uniqueRefs(refs: readonly IrSchemaRef[]): IrSchemaRef[] {
711
- const seen = new Set<string>()
712
- return refs.filter((ref) => {
713
- const key = schemaRefKey(ref)
714
- if (seen.has(key)) return false
715
- seen.add(key)
716
- return true
717
- })
718
- }
719
-
720
416
  function coreEndpoint(value: unknown, rootOrigin: string): string {
721
417
  const endpoint = asRecord(value)
722
- if (endpoint?.kind === 'domain') return `/:${rootOrigin}`
723
- const ref = schemaRef(value)
418
+ if (endpoint?.kind === 'domain') {
419
+ return `/:${typeof endpoint.origin === 'string' ? endpoint.origin : rootOrigin}`
420
+ }
421
+ const ref = definitionRefOf(value)
724
422
  return ref?.kind === 'core' ? corePath(ref) : `/:${rootOrigin}`
725
423
  }
726
424
 
727
- function corePath(ref: Pick<IrSchemaRef, 'origin' | 'kind' | 'name'>): string {
425
+ function corePath(ref: Pick<IrSchemaRef, 'origin' | 'name'>): string {
728
426
  return `/:${ref.origin}:core.${ref.name}`
729
427
  }
730
428
 
731
- function admitClosure(
732
- value: unknown,
733
- root: CanonicalDomainSchemaV1,
734
- ): CanonicalDomainSchemaV1[] | null {
735
- if (!Array.isArray(value)) return null
736
- const closure: CanonicalDomainSchemaV1[] = []
737
- const origins = new Set<string>()
738
- for (const schema of value) {
739
- if (!isCanonicalDomainSchemaV1(schema) || schema.origin === root.origin) return null
740
- if (origins.has(schema.origin)) return null
741
- origins.add(schema.origin)
742
- closure.push(schema)
743
- }
744
- return closure
745
- }
746
-
747
429
  function studioSchema(value: unknown): JsonSchema {
748
430
  if (value === true) return {}
749
431
  if (value === false) return { not: {} }
750
432
  return (jsonCopy(asRecord(value) ?? {}) ?? {}) as JsonSchema
751
433
  }
752
434
 
753
- function schemaRecord(value: unknown): Record<string, JsonSchema> {
754
- return Object.fromEntries(entriesOf(value).map(([name, schema]) => [name, studioSchema(schema)]))
755
- }
756
-
757
- function requiredNames(value: unknown): string[] {
758
- const object = asRecord(value)
759
- return arrayOf(object?.required).filter((name): name is string => typeof name === 'string')
760
- }
761
-
762
- function legacyRequiredParams(params: Record<string, JsonSchema>): string[] {
763
- return Object.entries(params).flatMap(([name, schema]) =>
764
- schemaAllowsNull(schema) ? [] : [name],
765
- )
766
- }
767
-
768
- function schemaAllowsNull(schema: JsonSchema): boolean {
769
- if (schema.type === 'null') return true
770
- if (Array.isArray(schema.type) && schema.type.includes('null')) return true
771
- return [...arrayOf(schema.anyOf), ...arrayOf(schema.oneOf)].some(
772
- (candidate) => asRecord(candidate)?.type === 'null',
773
- )
774
- }
775
-
776
- function objectInputFromParams(
777
- params: Record<string, JsonSchema>,
778
- required: readonly string[],
779
- ): JsonSchema {
780
- return {
781
- type: 'object',
782
- properties: params,
783
- required: [...required],
784
- additionalProperties: false,
785
- }
786
- }
787
-
788
- function recordCopy(value: unknown): Record<string, any> {
435
+ function recordCopy(value: unknown): Record<string, unknown> {
789
436
  const record = asRecord(value)
790
- return record ? (jsonCopy(record) as Record<string, any>) : {}
437
+ return record === null ? {} : (jsonCopy(record) as Record<string, unknown>)
791
438
  }
792
439
 
793
440
  function jsonCopy(value: unknown): any {
794
441
  if (Array.isArray(value)) return value.map(jsonCopy)
795
442
  const record = asRecord(value)
796
- if (record)
797
- return Object.fromEntries(Object.entries(record).map(([key, item]) => [key, jsonCopy(item)]))
798
- return value
443
+ return record === null
444
+ ? value
445
+ : Object.fromEntries(Object.entries(record).map(([key, item]) => [key, jsonCopy(item)]))
799
446
  }
800
447
 
801
448
  function entriesOf(value: unknown): [string, unknown][] {