@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,18 +1,17 @@
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
+ * Pure projection from an SDK-admitted portable DomainSchema into Studio's
3
+ * intentionally lossy render model. The extractor calls the authored Domain's
4
+ * own installed SDK for admission before this module interprets the document.
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,
@@ -21,7 +20,7 @@ import type {
21
20
  StudioCore,
22
21
  } from '../../shared/types'
23
22
 
24
- import { definitionRefKey, isIrDefinitionRef, schemaRefKey } from '../../shared/schema/identity'
23
+ import { classRefKey, isIrClassRef, schemaRefKey } from '../../shared/schema/identity'
25
24
  import { isSchemaRevision } from '../../shared/types'
26
25
 
27
26
  type AnyRecord = Record<string, unknown>
@@ -33,22 +32,21 @@ export type CanonicalDomainSchemaV1 = AnyRecord & {
33
32
  }
34
33
 
35
34
  export interface CanonicalSchemaProjection {
36
- ir: SchemaIR
37
- importedInterfaces: Record<string, IrInterface>
35
+ readonly ir: SchemaIR
38
36
  }
39
37
 
40
38
  export type CanonicalSchemaAdmission =
41
39
  | {
42
- status: 'admitted'
43
- root: CanonicalDomainSchemaV1
44
- closure: CanonicalDomainSchemaV1[]
45
- revision: `sha256:${string}`
40
+ readonly status: 'admitted'
41
+ readonly root: CanonicalDomainSchemaV1
42
+ readonly closure: CanonicalDomainSchemaV1[]
43
+ readonly revision: `sha256:${string}`
46
44
  }
47
45
  | {
48
- status: 'preview'
49
- root: CanonicalDomainSchemaV1
50
- closure: CanonicalDomainSchemaV1[]
51
- revision: null
46
+ readonly status: 'preview'
47
+ readonly root: CanonicalDomainSchemaV1
48
+ readonly closure: CanonicalDomainSchemaV1[]
49
+ readonly revision: null
52
50
  }
53
51
 
54
52
  export function isCanonicalDomainSchemaV1(value: unknown): value is CanonicalDomainSchemaV1 {
@@ -67,63 +65,32 @@ export function findCanonicalDomainSchemaExport(
67
65
  ): CanonicalDomainSchemaV1 | null {
68
66
  if (isCanonicalDomainSchemaV1(moduleExports.schema)) return moduleExports.schema
69
67
  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
68
+ return (
69
+ Object.entries(moduleExports)
70
+ .filter((entry): entry is [string, CanonicalDomainSchemaV1] =>
71
+ isCanonicalDomainSchemaV1(entry[1]),
72
+ )
73
+ .sort(([left], [right]) => left.localeCompare(right))[0]?.[1] ?? null
74
+ )
77
75
  }
78
76
 
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
- */
77
+ /** Read the exact retained dependency closure through the authored SDK. */
84
78
  export function closureFromSdk(
85
79
  sdkModule: Record<string, unknown>,
86
80
  root: CanonicalDomainSchemaV1,
87
81
  ): 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
- }
82
+ const create = asRecord(sdkModule.bundle)?.create
83
+ if (typeof create !== 'function') return []
84
+ try {
85
+ const candidate = asRecord(Reflect.apply(create, undefined, [root]))
86
+ if (candidate?.root !== root) return []
87
+ return admitClosure(candidate.closure, root) ?? []
88
+ } catch {
89
+ return []
116
90
  }
117
-
118
- return []
119
91
  }
120
92
 
121
- /**
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.
126
- */
93
+ /** Re-admit one portable root and prove its revision through the authored SDK. */
127
94
  export function admitCanonicalSchemaFromSdk(
128
95
  sdkModule: Record<string, unknown>,
129
96
  candidate: CanonicalDomainSchemaV1,
@@ -132,7 +99,12 @@ export function admitCanonicalSchemaFromSdk(
132
99
  const schemaApi = asRecord(sdkModule.schema)
133
100
  const accept = schemaApi?.accept
134
101
  const revision = schemaApi?.revision
135
- if (!schemaApi || typeof accept !== 'function' || typeof revision !== 'function') {
102
+ const resolve = schemaApi?.resolve
103
+ if (
104
+ typeof accept !== 'function' ||
105
+ typeof revision !== 'function' ||
106
+ typeof resolve !== 'function'
107
+ ) {
136
108
  return { status: 'preview', root: candidate, closure: retainedClosure, revision: null }
137
109
  }
138
110
 
@@ -140,7 +112,7 @@ export function admitCanonicalSchemaFromSdk(
140
112
  const revisions = new Map<string, string>()
141
113
  for (const dependency of retainedClosure) {
142
114
  const value = Reflect.apply(revision, schemaApi, [dependency])
143
- if (!isSchemaRevision(value)) throw new TypeError('SDK returned an invalid schema revision')
115
+ if (!isSchemaRevision(value)) throw new TypeError('SDK returned an invalid revision.')
144
116
  revisions.set(dependency.origin, value)
145
117
  }
146
118
  const lookup = {
@@ -151,77 +123,27 @@ export function admitCanonicalSchemaFromSdk(
151
123
  }
152
124
  const accepted = Reflect.apply(accept, schemaApi, [candidate, lookup])
153
125
  if (!isCanonicalDomainSchemaV1(accepted) || accepted.origin !== candidate.origin) {
154
- throw new TypeError('SDK admission returned a different schema root')
126
+ throw new TypeError('SDK admission returned another root.')
155
127
  }
156
128
  const acceptedRevision = Reflect.apply(revision, schemaApi, [accepted])
157
- if (!isSchemaRevision(acceptedRevision)) {
158
- throw new TypeError('SDK returned an invalid schema revision')
129
+ if (!isSchemaRevision(acceptedRevision))
130
+ throw new TypeError('SDK returned an invalid revision.')
131
+ const domain = asRecord(Reflect.apply(resolve, schemaApi, [accepted]))
132
+ if (domain?.source !== accepted || domain.origin !== accepted.origin) {
133
+ throw new TypeError('SDK resolution did not retain the admitted root.')
159
134
  }
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
172
- }
173
-
135
+ const closure = closureFromSdk(sdkModule, accepted)
174
136
  return { status: 'admitted', root: accepted, closure, revision: acceptedRevision }
175
137
  } catch {
176
138
  return { status: 'preview', root: candidate, closure: retainedClosure, revision: null }
177
139
  }
178
140
  }
179
141
 
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
142
  export function projectCanonicalSchema(
215
143
  root: CanonicalDomainSchemaV1,
216
144
  closure: readonly CanonicalDomainSchemaV1[] = [],
217
145
  ): CanonicalSchemaProjection {
218
146
  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
147
  const classes = Object.fromEntries(
226
148
  entriesOf(root.classes).map(([name, declaration]) => [
227
149
  name,
@@ -237,193 +159,138 @@ export function projectCanonicalSchema(
237
159
  const views = Object.fromEntries(
238
160
  entriesOf(root.views).map(([name, declaration]) => [name, projectView(name, declaration)]),
239
161
  )
240
-
241
- const importsByKey: Record<IrDefinitionKey, IrImportDescriptor> = {}
242
- const importedInterfacesByKey: Record<IrDefinitionKey, IrInterface> = {}
243
162
  const schemas = new Map(
244
- closure.filter(isCanonicalDomainSchemaV1).map((schema) => [schema.origin, schema]),
163
+ closure.filter(isCanonicalDomainSchemaV1).map((value) => [value.origin, value]),
245
164
  )
246
- const pending = collectDefinitionRefs(root)
165
+ const importsByKey = {} as Record<IrClassKey, IrImportDescriptor>
166
+ const importedClassesByKey = {} as Record<IrClassKey, IrClass>
167
+ const pending = collectClassRefs(root)
247
168
  const visited = new Set<string>()
248
169
 
249
- for (let index = 0; index < pending.length; index++) {
170
+ for (let index = 0; index < pending.length; index += 1) {
250
171
  const ref = pending[index]
251
- if (!isIrDefinitionRef(ref) || ref.origin === origin) continue
252
- const key = definitionRefKey(ref)
172
+ if (ref.origin === origin) continue
173
+ const key = classRefKey(ref)
253
174
  if (visited.has(key)) continue
254
175
  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
-
176
+ importsByKey[key] = { origin: ref.origin, ref, key }
264
177
  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)
178
+ const declaration = asRecord(asRecord(owner?.classes)?.[ref.name])
179
+ if (owner === undefined || declaration === null) continue
180
+ importedClassesByKey[key] = projectClass(owner.origin, ref.name, declaration)
181
+ pending.push(...collectClassRefsFromClass(declaration))
279
182
  }
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
183
 
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
- )
184
+ const dependencies = arrayOf(root.dependencies).flatMap((value) => {
185
+ const dependency = asRecord(value)
186
+ return typeof dependency?.origin === 'string' && typeof dependency.revision === 'string'
187
+ ? [{ origin: dependency.origin, revision: dependency.revision }]
188
+ : []
189
+ })
303
190
 
304
191
  return {
305
192
  ir: {
306
193
  format: 'astrale.dsl',
307
194
  version: 'v1',
308
195
  domain: origin,
309
- types: schemaRecord(root.types),
310
- interfaces,
311
196
  classes,
312
- imports,
313
197
  importsByKey,
314
- importedInterfacesByKey,
198
+ importedClassesByKey,
315
199
  functions,
316
200
  views,
317
201
  policies: recordCopy(root.policies),
318
202
  dependencies,
319
203
  core: jsonCopy(root.core),
320
204
  },
321
- importedInterfaces,
322
205
  }
323
206
  }
324
207
 
325
- /** Project canonical genesis data without importing the effectful Domain entry. */
208
+ /** Project canonical Core data without importing Application or Runtime modules. */
326
209
  export function projectCanonicalCore(
327
210
  root: CanonicalDomainSchemaV1,
328
211
  ): Pick<StudioCore, 'domain' | 'nodes' | 'edges'> {
329
212
  const core = asRecord(root.core) ?? {}
330
213
  const nodes = entriesOf(core.nodes).map(([slug, raw]) => {
331
214
  const node = asRecord(raw) ?? {}
332
- const classRef = schemaRef(node.class)
215
+ const selectedClass = classRef(node.class)
333
216
  return {
334
- path: corePath({ origin: root.origin, kind: 'core', name: slug }),
335
- className: classRef?.name ?? '?',
217
+ path: corePath({ origin: root.origin, name: slug }),
218
+ className: selectedClass?.name ?? '?',
336
219
  data: recordCopy(node.properties),
337
220
  }
338
221
  })
339
222
  const edges = arrayOf(core.edges).map((raw) => {
340
223
  const edge = asRecord(raw) ?? {}
341
- const classRef = schemaRef(edge.class)
342
224
  return {
343
225
  from: coreEndpoint(edge.source, root.origin),
344
226
  to: coreEndpoint(edge.target, root.origin),
345
- edgeName: classRef?.name ?? '?',
227
+ edgeName: classRef(edge.class)?.name ?? '?',
346
228
  ...(edge.properties === undefined ? {} : { data: recordCopy(edge.properties) }),
347
229
  }
348
230
  })
349
231
  return { domain: root.origin, nodes, edges }
350
232
  }
351
233
 
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')
356
- 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) } : {}),
375
- }
376
- }
377
-
378
234
  function projectClass(origin: string, name: string, value: unknown): IrClass {
379
235
  const declaration = asRecord(value) ?? {}
380
- const family = definitionFamily(declaration.family, 'node') === 'edge' ? 'edge' : 'node'
381
- const implementsRefs = refsOf(declaration.implements, 'interface')
236
+ const kind = declaration.kind === 'edge' ? 'edge' : 'node'
237
+ const extendsRefs = refsOf(declaration.extends)
238
+ const propertyEntries = entriesOf(declaration.properties)
239
+ const properties = Object.fromEntries(
240
+ propertyEntries.map(([propertyName, raw]) => [
241
+ propertyName,
242
+ studioSchema(asRecord(raw)?.schema),
243
+ ]),
244
+ )
245
+ const required = propertyEntries.flatMap(([propertyName, raw]) =>
246
+ asRecord(raw)?.required === true ? [propertyName] : [],
247
+ )
248
+ const propertyMetadata = Object.fromEntries(
249
+ propertyEntries.map(([propertyName, raw]) => {
250
+ const member = { ...(asRecord(raw) ?? {}) }
251
+ delete member.schema
252
+ return [propertyName, jsonCopy(member)]
253
+ }),
254
+ )
382
255
  const policies = Object.fromEntries(
383
256
  entriesOf(declaration.policies).flatMap(([policyName, raw]) => {
384
- const ref = schemaRef(raw)
257
+ const ref = definitionRefOf(raw)
385
258
  return ref ? [[policyName, ref]] : []
386
259
  }),
387
260
  )
388
261
  return {
389
- type: family,
262
+ type: kind,
390
263
  name,
391
264
  origin,
392
265
  ref: { origin, kind: 'class', name },
393
- implements: implementsRefs.map((ref) => ref.name),
394
- implementsRefs,
395
- properties: propertySchemas(declaration),
396
- required: requiredNames(declaration.properties),
266
+ extends: extendsRefs.map((ref) => ref.name),
267
+ extendsRefs,
268
+ properties,
269
+ required,
397
270
  methods: projectMethods(declaration.methods),
398
- ...(family === 'edge' ? edgeFields(declaration) : {}),
399
- ...(typeof declaration.icon === 'string' ? { icon: declaration.icon } : {}),
271
+ ...(kind === 'edge' ? edgeFields(declaration) : {}),
400
272
  ...(typeof declaration.description === 'string'
401
273
  ? { description: declaration.description }
402
274
  : {}),
403
- ...(asRecord(declaration.propertyMetadata)
404
- ? { propertyMetadata: recordCopy(declaration.propertyMetadata) }
405
- : {}),
275
+ ...(Object.keys(propertyMetadata).length === 0 ? {} : { propertyMetadata }),
406
276
  ...(dataDeclaration(declaration.data) ? { data: dataDeclaration(declaration.data) } : {}),
407
- ...(Object.keys(policies).length > 0 ? { policies } : {}),
277
+ ...(Object.keys(policies).length === 0 ? {} : { policies }),
408
278
  }
409
279
  }
410
280
 
411
281
  function projectMethods(value: unknown): Record<string, IrMethod> {
412
282
  return Object.fromEntries(
413
- entriesOf(value).map(([name, declaration]) => {
414
- const callable = projectCallable(name, declaration)
415
- const raw = asRecord(declaration) ?? {}
283
+ entriesOf(value).map(([name, raw]) => {
284
+ const declaration = asRecord(raw) ?? {}
416
285
  const inheritance =
417
- raw.inheritance === 'abstract' ||
418
- raw.inheritance === 'sealed' ||
419
- raw.inheritance === 'default'
420
- ? raw.inheritance
286
+ declaration.inheritance === 'abstract' || declaration.inheritance === 'sealed'
287
+ ? declaration.inheritance
421
288
  : 'default'
422
289
  return [
423
290
  name,
424
291
  {
425
- ...callable,
426
- static: raw.static === true,
292
+ ...projectCallable(name, declaration),
293
+ static: declaration.static === true,
427
294
  inheritance,
428
295
  } satisfies IrMethod,
429
296
  ]
@@ -431,50 +298,40 @@ function projectMethods(value: unknown): Record<string, IrMethod> {
431
298
  )
432
299
  }
433
300
 
434
- function projectFunction(name: string, declaration: unknown): IrFunction {
435
- return {
436
- ...projectCallable(name, declaration),
437
- static: true,
438
- inheritance: 'default',
439
- }
301
+ function projectFunction(name: string, value: unknown): IrFunction {
302
+ return projectCallable(name, value)
440
303
  }
441
304
 
442
- function projectCallable(name: string, value: unknown): Omit<IrFunction, 'static' | 'inheritance'> {
305
+ function projectCallable(name: string, value: unknown): IrFunction {
443
306
  const declaration = asRecord(value) ?? {}
444
307
  const input = studioSchema(declaration.input)
445
- const output = callableOutput(declaration.output, {})
308
+ const output = callableOutput(declaration.output)
446
309
  const auth = callableAuth(declaration.auth)
447
310
  return {
448
311
  name,
449
312
  input,
450
- params: callableParams(declaration.input),
451
- requiredParams: requiredNames(declaration.input),
452
313
  output,
453
- returns: outputValue(output),
454
314
  ...(typeof declaration.description === 'string'
455
315
  ? { description: declaration.description }
456
316
  : {}),
457
- ...(auth ? { auth } : {}),
317
+ ...(auth === undefined ? {} : { auth }),
458
318
  ...(declaration.policy === undefined ? {} : { policy: jsonCopy(declaration.policy) }),
459
319
  }
460
320
  }
461
321
 
462
322
  function projectView(name: string, value: unknown): IrView {
463
323
  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'
324
+ const target = asRecord(declaration.target)
474
325
  return {
475
326
  name,
476
- target,
477
- auth,
327
+ target:
328
+ target?.kind === 'definition'
329
+ ? { kind: 'definition', definitions: refsOf(target.definitions) }
330
+ : { kind: 'domain' },
331
+ auth:
332
+ declaration.auth === 'optional' || declaration.auth === 'public'
333
+ ? declaration.auth
334
+ : 'required',
478
335
  ...(typeof declaration.description === 'string'
479
336
  ? { description: declaration.description }
480
337
  : {}),
@@ -493,238 +350,186 @@ function edgeFields(
493
350
  projectEndpoint(endpoints?.source, 'outgoing'),
494
351
  projectEndpoint(endpoints?.target, 'incoming'),
495
352
  ]
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 } : {}),
353
+ const constraints = asRecord(declaration.constraints)
354
+ return {
355
+ endpoints: projected,
356
+ orientation,
357
+ constraints: {
358
+ ...(constraints?.noSelf === true ? { noSelf: true } : {}),
359
+ ...(constraints?.acyclic === true ? { acyclic: true } : {}),
360
+ },
500
361
  }
501
- return { endpoints: projected, orientation, constraints }
502
362
  }
503
363
 
504
364
  function projectEndpoint(
505
365
  value: unknown,
506
- countKey: 'outgoing' | 'incoming' | 'incident',
366
+ cardinality: 'outgoing' | 'incoming' | 'incident',
507
367
  ): IrEndpoint {
508
368
  const endpoint = asRecord(value) ?? {}
509
- const refs = refsOf(endpoint.accepts, ['class', 'interface'])
510
- const cardinality = edgeCount(endpoint[countKey])
369
+ const refs = refsOf(endpoint.accepts)
370
+ const count = edgeCount(endpoint[cardinality])
511
371
  return {
512
372
  name: typeof endpoint.role === 'string' ? endpoint.role : '',
513
373
  types: refs.map((ref) => ref.name),
514
374
  refs,
515
- ...(cardinality ? { cardinality } : {}),
375
+ ...(count === undefined ? {} : { cardinality: count }),
516
376
  }
517
377
  }
518
378
 
519
379
  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
531
- }
532
- }
533
-
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
- )
547
- }
548
-
549
- function callableOutput(value: unknown, fallback: JsonSchema): IrCallableOutput {
550
- const output = asRecord(value)
551
- if (output?.mode === 'stream') return { mode: 'stream', item: studioSchema(output.item) }
552
- 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 {}
561
- }
562
-
563
- function callableAuth(value: unknown): IrCallableAuth | undefined {
564
- return value === 'anonymous' || value === 'authenticated' || value === 'authorized'
565
- ? value
566
- : undefined
567
- }
568
-
569
- function definitionFamily(value: unknown, fallback: 'node' | 'both'): 'node' | 'edge' | 'both' {
570
- return value === 'node' || value === 'edge' || value === 'both' ? value : fallback
571
- }
572
-
573
- function dataDeclaration(
574
- value: unknown,
575
- ): { mediaType: string; [key: string]: unknown } | undefined {
576
- const data = asRecord(value)
577
- return typeof data?.mediaType === 'string'
578
- ? (jsonCopy(data) as { mediaType: string; [key: string]: unknown })
579
- : undefined
380
+ if (value === '0..*') return { min: 0, max: null }
381
+ if (value === '1..*') return { min: 1, max: null }
382
+ if (value === '0..1') return { min: 0, max: 1 }
383
+ if (value === '1') return { min: 1, max: 1 }
384
+ return undefined
580
385
  }
581
386
 
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
- }
387
+ function collectClassRefs(root: CanonicalDomainSchemaV1): IrClassRef[] {
388
+ const refs: IrClassRef[] = []
617
389
  for (const [, declaration] of entriesOf(root.classes)) {
618
- refs.push(...collectDefinitionRefsFromDeclaration(declaration))
390
+ refs.push(...collectClassRefsFromClass(declaration))
619
391
  }
620
392
  for (const [, callable] of entriesOf(root.functions)) collectCallableRefs(callable, refs)
621
393
  for (const [, view] of entriesOf(root.views)) {
622
- const target = asRecord(asRecord(view)?.target)
623
- refs.push(...refsOf(target?.definitions, ['class', 'interface']))
394
+ refs.push(...refsOf(asRecord(asRecord(view)?.target)?.definitions))
624
395
  }
625
396
  const core = asRecord(root.core)
626
- for (const [, node] of entriesOf(core?.nodes)) addRef(asRecord(node)?.class, refs)
397
+ for (const [, node] of entriesOf(core?.nodes)) addClassRef(asRecord(node)?.class, refs)
627
398
  for (const edge of arrayOf(core?.edges)) {
628
399
  const declaration = asRecord(edge)
629
- addRef(declaration?.class, refs)
630
- addRef(declaration?.source, refs)
631
- addRef(declaration?.target, refs)
400
+ addClassRef(declaration?.class, refs)
632
401
  }
633
402
  for (const [, policy] of entriesOf(root.policies)) collectPolicyRefs(policy, refs)
634
- return uniqueRefs(refs)
403
+ return uniqueClassRefs(refs)
635
404
  }
636
405
 
637
- function collectDefinitionRefsFromDeclaration(value: unknown): IrSchemaRef[] {
406
+ function collectClassRefsFromClass(value: unknown): IrClassRef[] {
638
407
  const declaration = asRecord(value) ?? {}
639
- const refs = [
640
- ...refsOf(declaration.extends, 'interface'),
641
- ...refsOf(declaration.implements, 'interface'),
642
- ]
643
- collectPathSchemaRefs(declaration.properties, refs)
408
+ const refs = [...refsOf(declaration.extends)]
644
409
  const endpoints = asRecord(declaration.endpoints)
645
410
  for (const endpoint of [
646
411
  endpoints?.source,
647
412
  endpoints?.target,
648
413
  ...arrayOf(declaration.endpoints),
649
414
  ]) {
650
- refs.push(...refsOf(asRecord(endpoint)?.accepts, ['class', 'interface']))
415
+ refs.push(...refsOf(asRecord(endpoint)?.accepts))
416
+ }
417
+ for (const [, property] of entriesOf(declaration.properties)) {
418
+ collectPathSchemaRefs(asRecord(property)?.schema, refs)
651
419
  }
652
420
  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)
421
+ for (const [, policy] of entriesOf(declaration.policies)) collectPolicyRefs(policy, refs)
422
+ return uniqueClassRefs(refs)
655
423
  }
656
424
 
657
- function collectCallableRefs(value: unknown, refs: IrSchemaRef[]): void {
425
+ function collectCallableRefs(value: unknown, refs: IrClassRef[]): void {
658
426
  const callable = asRecord(value)
659
- if (!callable) return
427
+ if (callable === null) return
660
428
  collectPathSchemaRefs(callable.input, refs)
661
429
  const output = asRecord(callable.output)
662
430
  collectPathSchemaRefs(output?.schema ?? output?.item, refs)
663
431
  collectPolicyRefs(callable.policy, refs)
664
432
  }
665
433
 
666
- /** Embedded value data is opaque; only the declared x-astrale-path vocabulary carries refs. */
667
- function collectPathSchemaRefs(value: unknown, refs: IrSchemaRef[]): void {
434
+ function collectPathSchemaRefs(value: unknown, refs: IrClassRef[]): void {
668
435
  if (Array.isArray(value)) {
669
436
  for (const item of value) collectPathSchemaRefs(item, refs)
670
437
  return
671
438
  }
672
439
  const object = asRecord(value)
673
- if (!object) return
440
+ if (object === null) return
674
441
  const annotation = asRecord(object['x-astrale-path'])
675
- if (annotation) refs.push(...refsOf(annotation.accepts, ['class', 'interface']))
442
+ refs.push(...refsOf(annotation?.accepts))
676
443
  for (const [key, child] of Object.entries(object)) {
677
444
  if (key === 'const' || key === 'enum' || key === 'default' || key === 'examples') continue
678
445
  collectPathSchemaRefs(child, refs)
679
446
  }
680
447
  }
681
448
 
682
- function collectPolicyRefs(value: unknown, refs: IrSchemaRef[]): void {
449
+ function collectPolicyRefs(value: unknown, refs: IrClassRef[]): void {
683
450
  if (Array.isArray(value)) {
684
451
  for (const item of value) collectPolicyRefs(item, refs)
685
452
  return
686
453
  }
687
454
  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
- }
455
+ if (object === null) return
456
+ addClassRef(object.class, refs)
457
+ for (const child of Object.values(object)) collectPolicyRefs(child, refs)
458
+ }
459
+
460
+ function addClassRef(value: unknown, refs: IrClassRef[]): void {
461
+ const ref = classRef(value)
462
+ if (ref !== null) refs.push(ref)
703
463
  }
704
464
 
705
- function addRef(value: unknown, refs: IrSchemaRef[]): void {
706
- const ref = schemaRef(value)
707
- if (ref) refs.push(ref)
465
+ function refsOf(value: unknown): IrClassRef[] {
466
+ return arrayOf(value)
467
+ .map(classRef)
468
+ .filter((ref): ref is IrClassRef => ref !== null)
469
+ }
470
+
471
+ function classRef(value: unknown): IrClassRef | null {
472
+ const ref = definitionRefOf(value)
473
+ return ref !== null && isIrClassRef(ref) ? ref : null
708
474
  }
709
475
 
710
- function uniqueRefs(refs: readonly IrSchemaRef[]): IrSchemaRef[] {
476
+ function definitionRefOf(value: unknown): IrSchemaRef | null {
477
+ const ref = asRecord(value)
478
+ if (typeof ref?.origin !== 'string' || typeof ref.name !== 'string') return null
479
+ if (
480
+ ref.kind !== 'class' &&
481
+ ref.kind !== 'function' &&
482
+ ref.kind !== 'policy' &&
483
+ ref.kind !== 'view' &&
484
+ ref.kind !== 'core'
485
+ ) {
486
+ return null
487
+ }
488
+ return { origin: ref.origin, kind: ref.kind, name: ref.name }
489
+ }
490
+
491
+ function uniqueClassRefs(refs: readonly IrClassRef[]): IrClassRef[] {
711
492
  const seen = new Set<string>()
712
493
  return refs.filter((ref) => {
713
- const key = schemaRefKey(ref)
494
+ const key = classRefKey(ref)
714
495
  if (seen.has(key)) return false
715
496
  seen.add(key)
716
497
  return true
717
498
  })
718
499
  }
719
500
 
501
+ function callableOutput(value: unknown): IrCallableOutput {
502
+ const output = asRecord(value)
503
+ if (output?.mode === 'stream') return { mode: 'stream', item: studioSchema(output.item) }
504
+ if (output?.mode === 'binary') return { mode: 'binary' }
505
+ return { mode: 'value', schema: studioSchema(output?.schema) }
506
+ }
507
+
508
+ function callableAuth(value: unknown): IrCallableAuth | undefined {
509
+ return value === 'anonymous' || value === 'authenticated' || value === 'authorized'
510
+ ? value
511
+ : undefined
512
+ }
513
+
514
+ function dataDeclaration(
515
+ value: unknown,
516
+ ): { mediaType: string; [key: string]: unknown } | undefined {
517
+ const data = asRecord(value)
518
+ return typeof data?.mediaType === 'string'
519
+ ? (jsonCopy(data) as { mediaType: string; [key: string]: unknown })
520
+ : undefined
521
+ }
522
+
720
523
  function coreEndpoint(value: unknown, rootOrigin: string): string {
721
524
  const endpoint = asRecord(value)
722
- if (endpoint?.kind === 'domain') return `/:${rootOrigin}`
723
- const ref = schemaRef(value)
525
+ if (endpoint?.kind === 'domain' && typeof endpoint.origin === 'string') {
526
+ return `/:${endpoint.origin}`
527
+ }
528
+ const ref = definitionRefOf(value)
724
529
  return ref?.kind === 'core' ? corePath(ref) : `/:${rootOrigin}`
725
530
  }
726
531
 
727
- function corePath(ref: Pick<IrSchemaRef, 'origin' | 'kind' | 'name'>): string {
532
+ function corePath(ref: Pick<IrSchemaRef, 'origin' | 'name'>): string {
728
533
  return `/:${ref.origin}:core.${ref.name}`
729
534
  }
730
535
 
@@ -735,11 +540,11 @@ function admitClosure(
735
540
  if (!Array.isArray(value)) return null
736
541
  const closure: CanonicalDomainSchemaV1[] = []
737
542
  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)
543
+ for (const candidate of value) {
544
+ if (!isCanonicalDomainSchemaV1(candidate) || candidate.origin === root.origin) return null
545
+ if (origins.has(candidate.origin)) return null
546
+ origins.add(candidate.origin)
547
+ closure.push(candidate)
743
548
  }
744
549
  return closure
745
550
  }
@@ -750,52 +555,17 @@ function studioSchema(value: unknown): JsonSchema {
750
555
  return (jsonCopy(asRecord(value) ?? {}) ?? {}) as JsonSchema
751
556
  }
752
557
 
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> {
558
+ function recordCopy(value: unknown): Record<string, unknown> {
789
559
  const record = asRecord(value)
790
- return record ? (jsonCopy(record) as Record<string, any>) : {}
560
+ return record === null ? {} : (jsonCopy(record) as Record<string, unknown>)
791
561
  }
792
562
 
793
563
  function jsonCopy(value: unknown): any {
794
564
  if (Array.isArray(value)) return value.map(jsonCopy)
795
565
  const record = asRecord(value)
796
- if (record)
797
- return Object.fromEntries(Object.entries(record).map(([key, item]) => [key, jsonCopy(item)]))
798
- return value
566
+ return record === null
567
+ ? value
568
+ : Object.fromEntries(Object.entries(record).map(([key, item]) => [key, jsonCopy(item)]))
799
569
  }
800
570
 
801
571
  function entriesOf(value: unknown): [string, unknown][] {