@astrale-os/cli 1.0.0-beta.0 → 1.0.0-beta.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (322) hide show
  1. package/README.md +23 -12
  2. package/dist/astrale.js +51494 -56307
  3. package/dist/public/connect-core.js +2028 -26846
  4. package/dist/public/keys/index.js +1755 -40694
  5. package/dist/public/paths/index.js +859 -39518
  6. package/dist/types/admin/binding.d.ts +19 -0
  7. package/dist/types/admin/instance/client.d.ts +22 -0
  8. package/dist/types/admin/instance/index.d.ts +2 -0
  9. package/dist/types/admin/instance/model.d.ts +5 -12
  10. package/dist/types/connection/auth.d.ts +3 -0
  11. package/dist/types/connection/call.d.ts +3 -0
  12. package/dist/types/connection/command.d.ts +27 -0
  13. package/dist/types/connection/credential.d.ts +14 -0
  14. package/dist/types/connection/errors.d.ts +1 -0
  15. package/dist/types/connection/exchange.d.ts +9 -0
  16. package/dist/types/connection/failure/classify.d.ts +2 -0
  17. package/dist/types/connection/failure/index.d.ts +5 -0
  18. package/dist/types/connection/failure/model.d.ts +25 -0
  19. package/dist/types/connection/failure/render.d.ts +3 -0
  20. package/dist/types/connection/index.d.ts +8 -0
  21. package/dist/types/connection/reasons.d.ts +37 -0
  22. package/dist/types/connection/self.d.ts +37 -0
  23. package/dist/types/connection/session.d.ts +31 -0
  24. package/dist/types/connection/target.d.ts +38 -0
  25. package/dist/types/errors.d.ts +1 -1
  26. package/dist/types/lib/admin-instance.d.ts +17 -0
  27. package/dist/types/lib/admin-target.d.ts +6 -2
  28. package/dist/types/lib/failure-debug.d.ts +1 -0
  29. package/dist/types/lib/idp.d.ts +7 -0
  30. package/dist/types/lib/instance-target.d.ts +1 -1
  31. package/dist/types/lib/instance.d.ts +10 -2
  32. package/dist/types/lib/log.d.ts +5 -3
  33. package/dist/types/lib/meta.d.ts +2 -2
  34. package/dist/types/lib/proc.d.ts +43 -0
  35. package/dist/types/lib/self.d.ts +9 -0
  36. package/dist/types/lib/update.d.ts +131 -0
  37. package/package.json +8 -10
  38. package/src/__tests__/fixtures/publication.ts +20 -0
  39. package/src/admin/__tests__/binding.test.ts +31 -0
  40. package/src/admin/__tests__/fixture.ts +101 -0
  41. package/src/admin/binding.ts +98 -0
  42. package/src/admin/catalog/.spec/api.d.ts +2 -2
  43. package/src/admin/catalog/__tests__/client.test.ts +20 -49
  44. package/src/admin/catalog/client.ts +36 -32
  45. package/src/admin/catalog/model.ts +3 -4
  46. package/src/admin/graph/.spec/api.d.ts +4 -10
  47. package/src/admin/graph/nodes.ts +1 -1
  48. package/src/admin/instance/.spec/api.d.ts +2 -2
  49. package/src/admin/instance/__tests__/client.test.ts +40 -163
  50. package/src/admin/instance/client.ts +35 -137
  51. package/src/admin/instance/index.ts +1 -2
  52. package/src/admin/instance/model.ts +11 -20
  53. package/src/commands/__tests__/admin-instance.test.ts +11 -8
  54. package/src/commands/__tests__/auth-login.test.ts +54 -1
  55. package/src/commands/__tests__/call-describe.test.ts +46 -33
  56. package/src/commands/__tests__/call.test.ts +5 -1
  57. package/src/commands/__tests__/domain-install-operation.test.ts +12 -7
  58. package/src/commands/__tests__/domain-install-owned.test.ts +6 -2
  59. package/src/commands/__tests__/domain-list.test.ts +63 -4
  60. package/src/commands/__tests__/install-identity-override.test.ts +19 -19
  61. package/src/commands/__tests__/instance-list-rows.test.ts +1 -1
  62. package/src/commands/__tests__/instance-status.test.ts +139 -0
  63. package/src/commands/__tests__/read-commands.test.ts +35 -10
  64. package/src/commands/__tests__/studio-descriptor.test.ts +22 -0
  65. package/src/commands/__tests__/update.test.ts +95 -0
  66. package/src/commands/call-describe.ts +60 -68
  67. package/src/commands/call.ts +43 -56
  68. package/src/commands/domain/install.ts +13 -17
  69. package/src/commands/domain/list.ts +18 -13
  70. package/src/commands/domain/publish.ts +14 -11
  71. package/src/commands/domain/uninstall.ts +2 -2
  72. package/src/commands/get.ts +14 -15
  73. package/src/commands/identity/__tests__/register.test.ts +3 -3
  74. package/src/commands/identity/register.ts +5 -4
  75. package/src/commands/instance/create.ts +8 -16
  76. package/src/commands/instance/delete.ts +12 -9
  77. package/src/commands/instance/list.ts +2 -2
  78. package/src/commands/instance/status.ts +66 -20
  79. package/src/commands/instance/use.ts +16 -12
  80. package/src/commands/introspect.ts +9 -16
  81. package/src/commands/logs.ts +24 -25
  82. package/src/commands/mutate.ts +6 -5
  83. package/src/commands/query.ts +27 -30
  84. package/src/commands/studio.ts +20 -0
  85. package/src/commands/token.ts +8 -7
  86. package/src/commands/update.ts +68 -22
  87. package/src/commands/view.ts +7 -6
  88. package/src/connection/.spec/api.d.ts +7 -8
  89. package/src/connection/.spec/architecture.md +5 -4
  90. package/src/connection/.spec/flows/session.ts +2 -2
  91. package/src/connection/.spec/laws/connection.ts +6 -2
  92. package/src/connection/.spec/layout.ts +1 -0
  93. package/src/connection/__tests__/credential.test.ts +20 -1
  94. package/src/connection/__tests__/errors.test.ts +43 -32
  95. package/src/connection/__tests__/exchange.test.ts +36 -6
  96. package/src/connection/__tests__/failure-fixtures.ts +39 -0
  97. package/src/connection/__tests__/failure-safety.test.ts +75 -0
  98. package/src/connection/__tests__/self.test.ts +31 -11
  99. package/src/connection/__tests__/session.test.ts +3 -3
  100. package/src/connection/__tests__/target.test.ts +1 -0
  101. package/src/connection/auth.ts +13 -0
  102. package/src/connection/call.ts +1 -1
  103. package/src/connection/credential.ts +2 -2
  104. package/src/connection/errors.ts +1 -355
  105. package/src/connection/exchange.ts +52 -27
  106. package/src/connection/failure/classify.ts +110 -0
  107. package/src/connection/failure/index.ts +18 -0
  108. package/src/connection/failure/model.ts +28 -0
  109. package/src/connection/failure/render.ts +116 -0
  110. package/src/connection/reasons.ts +16 -0
  111. package/src/connection/self.ts +39 -21
  112. package/src/connection/session.ts +19 -8
  113. package/src/errors.ts +3 -2
  114. package/src/graph/.spec/api.d.ts +12 -5
  115. package/src/graph/.spec/layout.ts +9 -1
  116. package/src/graph/__tests__/mutation.test.ts +2 -2
  117. package/src/graph/__tests__/query.test.ts +25 -6
  118. package/src/graph/class.ts +30 -0
  119. package/src/graph/index.ts +1 -0
  120. package/src/graph/query.ts +21 -32
  121. package/src/identity/.spec/api.d.ts +2 -2
  122. package/src/identity/__tests__/registration.test.ts +1 -1
  123. package/src/identity/registration.ts +3 -3
  124. package/src/keys/__tests__/keys.test.ts +4 -0
  125. package/src/keys/credential.ts +1 -0
  126. package/src/lib/__tests__/admin-target.test.ts +12 -0
  127. package/src/lib/__tests__/command-dx.test.ts +1 -1
  128. package/src/lib/__tests__/domain-publication.test.ts +138 -0
  129. package/src/lib/__tests__/idp-session.test.ts +22 -0
  130. package/src/lib/__tests__/idp.test.ts +36 -4
  131. package/src/lib/__tests__/instance-candidates.test.ts +1 -3
  132. package/src/lib/__tests__/instance-target.test.ts +7 -39
  133. package/src/lib/__tests__/instance.test.ts +6 -0
  134. package/src/lib/__tests__/log-errors.test.ts +64 -0
  135. package/src/lib/__tests__/studio-server-deps.test.ts +53 -16
  136. package/src/lib/__tests__/update.test.ts +113 -8
  137. package/src/lib/__tests__/view-open-intent.test.ts +0 -2
  138. package/src/lib/__tests__/view-server.test.ts +1 -1
  139. package/src/lib/__tests__/view-session.test.ts +1 -1
  140. package/src/lib/admin-instance.ts +14 -8
  141. package/src/lib/admin-target.ts +21 -2
  142. package/src/lib/command-dx.ts +9 -9
  143. package/src/lib/domain-publication.ts +104 -0
  144. package/src/lib/failure-debug.ts +26 -0
  145. package/src/lib/idp.ts +39 -4
  146. package/src/lib/instance-candidates.ts +2 -2
  147. package/src/lib/instance-target.ts +3 -14
  148. package/src/lib/instance.ts +22 -12
  149. package/src/lib/log.ts +16 -9
  150. package/src/lib/login-flow.ts +13 -3
  151. package/src/lib/meta.ts +2 -2
  152. package/src/lib/provision-instance.ts +34 -57
  153. package/src/lib/update.ts +124 -30
  154. package/src/lib/validation.ts +2 -2
  155. package/src/lib/view/resolve.ts +1 -1
  156. package/src/program/__tests__/program.test.ts +20 -4
  157. package/src/program/build.ts +2 -2
  158. package/src/setup/__tests__/instance-step.test.ts +27 -10
  159. package/src/setup/steps/instance.ts +22 -15
  160. package/studio/client/dist/assets/{elk-api-D2xgMJvi.js → elk-api-ByzoDZWH.js} +1 -1
  161. package/studio/client/dist/assets/index-B0RCgUOT.js +8 -0
  162. package/studio/client/dist/assets/index-BiGR8Oo9.css +1 -0
  163. package/studio/client/dist/assets/index-N8J8EKlB.js +81 -0
  164. package/studio/client/dist/index.html +2 -2
  165. package/studio/package.json +5 -10
  166. package/studio/server/agent/ask.test.ts +1 -1
  167. package/studio/server/agent/bridge/grant.test.ts +1 -1
  168. package/studio/server/agent/bridge/routes.test.ts +1 -1
  169. package/studio/server/agent/bridge/stdio.ts +8 -1
  170. package/studio/server/agent/conversation.test.ts +10 -5
  171. package/studio/server/agent/conversation.ts +56 -8
  172. package/studio/server/agent/harness/claude/adapter.test.ts +15 -5
  173. package/studio/server/agent/harness/codex/loadout.ts +18 -21
  174. package/studio/server/agent/harness/gateway/config.ts +20 -18
  175. package/studio/server/agent/harness/selection.ts +9 -1
  176. package/studio/server/agent/prompts/anchors.test.ts +32 -36
  177. package/studio/server/agent/prompts/anchors.ts +20 -49
  178. package/studio/server/agent/prompts/system.test.ts +3 -2
  179. package/studio/server/agent/prompts/system.ts +6 -5
  180. package/studio/server/agent/prompts/turn.ts +13 -4
  181. package/studio/server/agent/routes.test.ts +1 -1
  182. package/studio/server/agent/run/completion.ts +2 -2
  183. package/studio/server/agent/run/coordinator.test.ts +1 -1
  184. package/studio/server/agent/run/preparation.ts +6 -5
  185. package/studio/server/agent/run/transcript.test.ts +35 -1
  186. package/studio/server/agent/run/transcript.ts +169 -3
  187. package/studio/server/agent/run/usage.test.ts +24 -0
  188. package/studio/server/agent/run/usage.ts +26 -1
  189. package/studio/server/api/canvas.ts +43 -0
  190. package/studio/server/api/comments.ts +85 -0
  191. package/studio/server/api/context.ts +61 -0
  192. package/studio/server/api/deployment.ts +39 -0
  193. package/studio/server/api/documents.ts +55 -0
  194. package/studio/server/api/domain.ts +59 -0
  195. package/studio/server/api/http.ts +45 -0
  196. package/studio/server/api/project.ts +57 -0
  197. package/studio/server/api/schema.ts +12 -0
  198. package/studio/server/api/views.ts +57 -0
  199. package/studio/server/api/workspace.ts +60 -0
  200. package/studio/server/api-agent-loadout.test.ts +1 -1
  201. package/studio/server/api.test.ts +147 -0
  202. package/studio/server/api.ts +20 -422
  203. package/studio/server/cache.test.ts +122 -0
  204. package/studio/server/cache.ts +162 -11
  205. package/studio/server/cli-consumers.test.ts +494 -0
  206. package/studio/server/cli.test.ts +88 -0
  207. package/studio/server/cli.ts +240 -0
  208. package/studio/server/client-package.ts +25 -1
  209. package/studio/server/domain.test.ts +50 -40
  210. package/studio/server/domain.ts +83 -52
  211. package/studio/server/environment/dotenv-preview.test.ts +39 -0
  212. package/studio/server/environment/dotenv-preview.ts +27 -0
  213. package/studio/server/environment/files.test.ts +53 -0
  214. package/studio/server/{state/env.ts → environment/files.ts} +7 -48
  215. package/studio/server/{state → handoff}/copy.ts +21 -6
  216. package/studio/server/handoff/service.test.ts +22 -0
  217. package/studio/server/{state/handoff.ts → handoff/service.ts} +21 -14
  218. package/studio/server/index.ts +1 -1
  219. package/studio/server/instances/active.ts +72 -0
  220. package/studio/server/instances/deploy-record.test.ts +31 -0
  221. package/studio/server/instances/deploy-record.ts +37 -0
  222. package/studio/server/instances/deploy.ts +56 -0
  223. package/studio/server/{state/instance.test.ts → instances/probe.test.ts} +25 -21
  224. package/studio/server/instances/probe.ts +67 -0
  225. package/studio/server/instances/status.test.ts +12 -0
  226. package/studio/server/instances/status.ts +49 -0
  227. package/studio/server/introspect/anatomy/client-tree.ts +57 -0
  228. package/studio/server/introspect/anatomy/env-fields.ts +66 -0
  229. package/studio/server/introspect/anatomy/schema-definition.ts +53 -0
  230. package/studio/server/introspect/anatomy/source.ts +181 -0
  231. package/studio/server/introspect/anatomy/views/routes.ts +114 -0
  232. package/studio/server/introspect/anatomy/views.ts +53 -0
  233. package/studio/server/introspect/anatomy-extras.test.ts +106 -35
  234. package/studio/server/introspect/anatomy-extras.ts +7 -726
  235. package/studio/server/introspect/anatomy.test.ts +22 -14
  236. package/studio/server/introspect/anatomy.ts +52 -28
  237. package/studio/server/introspect/bundle.ts +15 -9
  238. package/studio/server/introspect/canonical-schema.test.ts +93 -309
  239. package/studio/server/introspect/canonical-schema.ts +257 -425
  240. package/studio/server/introspect/config-preview.test.ts +32 -0
  241. package/studio/server/introspect/config-preview.ts +119 -0
  242. package/studio/server/introspect/core-extractor.ts +18 -127
  243. package/studio/server/introspect/core.ts +1 -2
  244. package/studio/server/introspect/diff.test.ts +18 -18
  245. package/studio/server/introspect/diff.ts +44 -90
  246. package/studio/server/introspect/extractor.ts +23 -116
  247. package/studio/server/introspect/hash.ts +5 -2
  248. package/studio/server/introspect/overlay-tsmorph.test.ts +119 -240
  249. package/studio/server/introspect/overlay-tsmorph.ts +7 -1305
  250. package/studio/server/introspect/overlay.test.ts +26 -54
  251. package/studio/server/introspect/overlay.ts +10 -37
  252. package/studio/server/introspect/revision.test.ts +54 -0
  253. package/studio/server/introspect/revision.ts +49 -0
  254. package/studio/server/introspect/runtime.test.ts +83 -143
  255. package/studio/server/introspect/runtime.ts +40 -11
  256. package/studio/server/introspect/schema-ir-json.test.ts +70 -0
  257. package/studio/server/introspect/schema-ir-json.ts +151 -0
  258. package/studio/server/introspect/schema-refs.test.ts +43 -88
  259. package/studio/server/introspect/schema-refs.ts +14 -49
  260. package/studio/server/introspect/source-overlay/annotations.ts +14 -0
  261. package/studio/server/introspect/source-overlay/handlers.ts +161 -0
  262. package/studio/server/introspect/source-overlay/kernel-calls.ts +46 -0
  263. package/studio/server/introspect/source-overlay/project.ts +248 -0
  264. package/studio/server/introspect/source-overlay/spans.ts +330 -0
  265. package/studio/server/json.ts +46 -0
  266. package/studio/server/lifecycle.ts +5 -1
  267. package/studio/server/sse.test.ts +26 -0
  268. package/studio/server/sse.ts +4 -1
  269. package/studio/server/state/baseline.test.ts +223 -7
  270. package/studio/server/state/baseline.ts +148 -34
  271. package/studio/server/state/comments.test.ts +66 -1
  272. package/studio/server/state/comments.ts +173 -10
  273. package/studio/server/state/context.ts +34 -2
  274. package/studio/server/state/documents.ts +42 -1
  275. package/studio/server/state/integrations.ts +27 -1
  276. package/studio/server/state/layout.test.ts +34 -0
  277. package/studio/server/state/layout.ts +44 -17
  278. package/studio/server/state/settings.ts +29 -22
  279. package/studio/server/state/store.test.ts +122 -0
  280. package/studio/server/state/store.ts +66 -17
  281. package/studio/server/state/visibility.ts +22 -11
  282. package/studio/server/views/model.test.ts +57 -0
  283. package/studio/server/views/model.ts +66 -0
  284. package/studio/server/views/runtime.ts +40 -0
  285. package/studio/server/views/selection-repository.test.ts +31 -0
  286. package/studio/server/views/selection-repository.ts +60 -0
  287. package/studio/server/views/session.test.ts +73 -0
  288. package/studio/server/views/session.ts +166 -0
  289. package/studio/server/views/target.test.ts +78 -0
  290. package/studio/server/views/target.ts +222 -0
  291. package/studio/server/watch.test.ts +11 -11
  292. package/studio/server/watch.ts +9 -5
  293. package/studio/server/workspace/catalog.test.ts +17 -0
  294. package/studio/server/workspace/catalog.ts +43 -0
  295. package/studio/server/{state → workspace}/create.test.ts +1 -1
  296. package/studio/server/{state → workspace}/create.ts +1 -1
  297. package/studio/server/{state → workspace}/git.ts +1 -1
  298. package/studio/server/workspace/updates.ts +83 -0
  299. package/studio/server/workspace-state.ts +1 -1
  300. package/studio/server/workspace-watch.ts +2 -2
  301. package/studio/shared/contracts/README.md +16 -0
  302. package/studio/shared/contracts/agent.ts +270 -0
  303. package/studio/shared/contracts/runtime.ts +50 -0
  304. package/studio/shared/contracts/schema.ts +393 -0
  305. package/studio/shared/contracts/surface.test.ts +317 -0
  306. package/studio/shared/contracts/workspace.ts +265 -0
  307. package/studio/shared/layout.test.ts +116 -0
  308. package/studio/shared/schema/identity.test.ts +34 -0
  309. package/studio/shared/schema/identity.ts +68 -0
  310. package/studio/shared/types.ts +11 -1099
  311. package/studio/tsconfig.json +1 -1
  312. package/viewer/dist/main.js +40 -61
  313. package/.check-workspace.cjs +0 -40
  314. package/src/commands/__tests__/instance-create-hosts.test.ts +0 -29
  315. package/studio/client/dist/assets/index-BMdnsIJA.css +0 -1
  316. package/studio/client/dist/assets/index-D-vRV8w7.js +0 -8
  317. package/studio/client/dist/assets/index-LGSWRrk8.js +0 -81
  318. package/studio/server/state/catalog.ts +0 -117
  319. package/studio/server/state/instance.ts +0 -280
  320. package/studio/server/state/updates.ts +0 -63
  321. package/studio/server/state/views.test.ts +0 -286
  322. package/studio/server/state/views.ts +0 -535
@@ -1,21 +1,18 @@
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,
12
- IrDefinitionRef,
10
+ IrClassKey,
11
+ IrClassRef,
13
12
  IrEndpoint,
14
13
  IrFunction,
15
14
  IrImportDescriptor,
16
- IrInterface,
17
15
  IrMethod,
18
- IrSchemaKey,
19
16
  IrSchemaRef,
20
17
  IrView,
21
18
  JsonSchema,
@@ -23,6 +20,9 @@ import type {
23
20
  StudioCore,
24
21
  } from '../../shared/types'
25
22
 
23
+ import { classRefKey, isIrClassRef, schemaRefKey } from '../../shared/schema/identity'
24
+ import { isSchemaRevision } from '../../shared/types'
25
+
26
26
  type AnyRecord = Record<string, unknown>
27
27
 
28
28
  export type CanonicalDomainSchemaV1 = AnyRecord & {
@@ -32,10 +32,23 @@ export type CanonicalDomainSchemaV1 = AnyRecord & {
32
32
  }
33
33
 
34
34
  export interface CanonicalSchemaProjection {
35
- ir: SchemaIR
36
- importedInterfaces: Record<string, IrInterface>
35
+ readonly ir: SchemaIR
37
36
  }
38
37
 
38
+ export type CanonicalSchemaAdmission =
39
+ | {
40
+ readonly status: 'admitted'
41
+ readonly root: CanonicalDomainSchemaV1
42
+ readonly closure: CanonicalDomainSchemaV1[]
43
+ readonly revision: `sha256:${string}`
44
+ }
45
+ | {
46
+ readonly status: 'preview'
47
+ readonly root: CanonicalDomainSchemaV1
48
+ readonly closure: CanonicalDomainSchemaV1[]
49
+ readonly revision: null
50
+ }
51
+
39
52
  export function isCanonicalDomainSchemaV1(value: unknown): value is CanonicalDomainSchemaV1 {
40
53
  const candidate = asRecord(value)
41
54
  return (
@@ -52,89 +65,78 @@ export function findCanonicalDomainSchemaExport(
52
65
  ): CanonicalDomainSchemaV1 | null {
53
66
  if (isCanonicalDomainSchemaV1(moduleExports.schema)) return moduleExports.schema
54
67
  if (isCanonicalDomainSchemaV1(moduleExports.default)) return moduleExports.default
55
-
56
- const candidates = Object.entries(moduleExports)
57
- .filter((entry): entry is [string, CanonicalDomainSchemaV1] =>
58
- isCanonicalDomainSchemaV1(entry[1]),
59
- )
60
- .sort(([left], [right]) => left.localeCompare(right))
61
- 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
+ )
62
75
  }
63
76
 
64
- /**
65
- * Read the exact retained dependency closure through the domain's own SDK.
66
- * Calls are optional and fail closed: an unrecognised/substituted result is
67
- * discarded rather than trusted as imported-schema evidence.
68
- */
77
+ /** Read the exact retained dependency closure through the authored SDK. */
69
78
  export function closureFromSdk(
70
79
  sdkModule: Record<string, unknown>,
71
80
  root: CanonicalDomainSchemaV1,
72
81
  ): CanonicalDomainSchemaV1[] {
73
- const bundleApi = asRecord(sdkModule.bundle)
74
- const create = bundleApi?.create
75
- if (typeof create === 'function') {
76
- try {
77
- const candidate = Reflect.apply(create, bundleApi, [root])
78
- const bundle = asRecord(candidate)
79
- if (bundle?.root === root) {
80
- const closure = admitClosure(bundle.closure, root)
81
- if (closure) return closure
82
- }
83
- } catch {
84
- // A structurally V1-looking export may lack retained SDK admission context.
85
- }
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 []
86
90
  }
91
+ }
87
92
 
93
+ /** Re-admit one portable root and prove its revision through the authored SDK. */
94
+ export function admitCanonicalSchemaFromSdk(
95
+ sdkModule: Record<string, unknown>,
96
+ candidate: CanonicalDomainSchemaV1,
97
+ ): CanonicalSchemaAdmission {
98
+ const retainedClosure = closureFromSdk(sdkModule, candidate)
88
99
  const schemaApi = asRecord(sdkModule.schema)
100
+ const accept = schemaApi?.accept
101
+ const revision = schemaApi?.revision
89
102
  const resolve = schemaApi?.resolve
90
- if (typeof resolve === 'function') {
91
- try {
92
- const candidate = asRecord(Reflect.apply(resolve, schemaApi, [root]))
93
- const resolution = asRecord(candidate?.$)
94
- if (resolution?.schema === root) {
95
- const closure = admitClosure(resolution.closure, root)
96
- if (closure) return closure
97
- }
98
- } catch {
99
- // Missing retained context is a valid degradation: local schema still renders.
100
- }
103
+ if (
104
+ typeof accept !== 'function' ||
105
+ typeof revision !== 'function' ||
106
+ typeof resolve !== 'function'
107
+ ) {
108
+ return { status: 'preview', root: candidate, closure: retainedClosure, revision: null }
101
109
  }
102
110
 
103
- return []
104
- }
105
-
106
- /** Add fields required by the current shared contract to a legacy serializer IR. */
107
- export function normalizeLegacySchemaIR(value: unknown): SchemaIR {
108
- const ir = asRecord(value)
109
- if (!ir) throw new TypeError('legacy schema IR must be an object')
110
-
111
- const functions: Record<string, IrFunction> = {}
112
- for (const [name, raw] of entriesOf(ir.functions)) {
113
- const declaration = asRecord(raw) ?? {}
114
- const params = schemaRecord(declaration.params)
115
- const returns = studioSchema(declaration.returns)
116
- const requiredParams =
117
- declaration.input === undefined
118
- ? legacyRequiredParams(params)
119
- : requiredNames(declaration.input)
120
- functions[name] = {
121
- name,
122
- input: studioSchema(declaration.input ?? objectInputFromParams(params, requiredParams)),
123
- params,
124
- requiredParams,
125
- output: callableOutput(declaration.output, returns),
126
- returns,
127
- static: true,
128
- inheritance: 'default',
129
- ...(callableAuth(declaration.auth) ? { auth: callableAuth(declaration.auth) } : {}),
130
- ...(typeof declaration.description === 'string'
131
- ? { description: declaration.description }
132
- : {}),
133
- ...(declaration.policy === undefined ? {} : { policy: jsonCopy(declaration.policy) }),
111
+ try {
112
+ const revisions = new Map<string, string>()
113
+ for (const dependency of retainedClosure) {
114
+ const value = Reflect.apply(revision, schemaApi, [dependency])
115
+ if (!isSchemaRevision(value)) throw new TypeError('SDK returned an invalid revision.')
116
+ revisions.set(dependency.origin, value)
117
+ }
118
+ const lookup = {
119
+ get(origin: string, expected: string): CanonicalDomainSchemaV1 | undefined {
120
+ const dependency = retainedClosure.find((entry) => entry.origin === origin)
121
+ return dependency && revisions.get(origin) === expected ? dependency : undefined
122
+ },
123
+ }
124
+ const accepted = Reflect.apply(accept, schemaApi, [candidate, lookup])
125
+ if (!isCanonicalDomainSchemaV1(accepted) || accepted.origin !== candidate.origin) {
126
+ throw new TypeError('SDK admission returned another root.')
134
127
  }
128
+ const acceptedRevision = Reflect.apply(revision, schemaApi, [accepted])
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.')
134
+ }
135
+ const closure = closureFromSdk(sdkModule, accepted)
136
+ return { status: 'admitted', root: accepted, closure, revision: acceptedRevision }
137
+ } catch {
138
+ return { status: 'preview', root: candidate, closure: retainedClosure, revision: null }
135
139
  }
136
-
137
- return { ...(ir as unknown as SchemaIR), functions }
138
140
  }
139
141
 
140
142
  export function projectCanonicalSchema(
@@ -142,12 +144,6 @@ export function projectCanonicalSchema(
142
144
  closure: readonly CanonicalDomainSchemaV1[] = [],
143
145
  ): CanonicalSchemaProjection {
144
146
  const origin = root.origin
145
- const interfaces = Object.fromEntries(
146
- entriesOf(root.interfaces).map(([name, declaration]) => [
147
- name,
148
- projectInterface(origin, name, declaration),
149
- ]),
150
- )
151
147
  const classes = Object.fromEntries(
152
148
  entriesOf(root.classes).map(([name, declaration]) => [
153
149
  name,
@@ -163,193 +159,138 @@ export function projectCanonicalSchema(
163
159
  const views = Object.fromEntries(
164
160
  entriesOf(root.views).map(([name, declaration]) => [name, projectView(name, declaration)]),
165
161
  )
166
-
167
- const importsByKey: Record<IrDefinitionKey, IrImportDescriptor> = {}
168
- const importedInterfacesByKey: Record<IrDefinitionKey, IrInterface> = {}
169
162
  const schemas = new Map(
170
- closure.filter(isCanonicalDomainSchemaV1).map((schema) => [schema.origin, schema]),
163
+ closure.filter(isCanonicalDomainSchemaV1).map((value) => [value.origin, value]),
171
164
  )
172
- const pending = collectDefinitionRefs(root)
165
+ const importsByKey = {} as Record<IrClassKey, IrImportDescriptor>
166
+ const importedClassesByKey = {} as Record<IrClassKey, IrClass>
167
+ const pending = collectClassRefs(root)
173
168
  const visited = new Set<string>()
174
169
 
175
- for (let index = 0; index < pending.length; index++) {
170
+ for (let index = 0; index < pending.length; index += 1) {
176
171
  const ref = pending[index]
177
- if (!isDefinitionRef(ref) || ref.origin === origin) continue
178
- const key = definitionKey(ref)
172
+ if (ref.origin === origin) continue
173
+ const key = classRefKey(ref)
179
174
  if (visited.has(key)) continue
180
175
  visited.add(key)
181
-
182
- importsByKey[key] = {
183
- origin: ref.origin,
184
- definition: ref.kind,
185
- ref,
186
- key,
187
- }
188
- if (ref.kind !== 'interface' || key in importedInterfacesByKey) continue
189
-
176
+ importsByKey[key] = { origin: ref.origin, ref, key }
190
177
  const owner = schemas.get(ref.origin)
191
- const declaration = asRecord(asRecord(owner?.interfaces)?.[ref.name])
192
- if (!owner || !declaration) continue
193
- const projected = projectInterface(owner.origin, ref.name, declaration)
194
- importedInterfacesByKey[key] = projected
195
- pending.push(...collectDefinitionRefsFromDeclaration(declaration))
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))
196
182
  }
197
183
 
198
- // Bare member names are retained solely as an unambiguous compatibility
199
- // lookup. The canonical Key maps above remain complete when origins or kinds
200
- // reuse a name, and prevent a traversal-order-dependent "first one wins".
201
- const nameCounts = new Map<string, number>()
202
- for (const descriptor of Object.values(importsByKey)) {
203
- const name = descriptor.ref?.name
204
- if (name) nameCounts.set(name, (nameCounts.get(name) ?? 0) + 1)
205
- }
206
- const localNames = new Set([...Object.keys(interfaces), ...Object.keys(classes)])
207
- const isUnambiguous = (name: string): boolean =>
208
- nameCounts.get(name) === 1 && !localNames.has(name)
209
- const imports = Object.fromEntries(
210
- Object.values(importsByKey).flatMap((descriptor) => {
211
- const name = descriptor.ref?.name
212
- return name && isUnambiguous(name) ? [[name, descriptor]] : []
213
- }),
214
- )
215
- const importedInterfaces = Object.fromEntries(
216
- Object.values(importedInterfacesByKey).flatMap((declaration) =>
217
- isUnambiguous(declaration.name) ? [[declaration.name, declaration]] : [],
218
- ),
219
- )
220
-
221
- const dependencies = arrayOf(root.dependencies)
222
- .map(asRecord)
223
- .filter((dependency): dependency is AnyRecord => dependency !== null)
224
- .flatMap((dependency) =>
225
- typeof dependency.origin === 'string' && typeof dependency.revision === 'string'
226
- ? [{ origin: dependency.origin, revision: dependency.revision }]
227
- : [],
228
- )
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
+ })
229
190
 
230
191
  return {
231
192
  ir: {
232
193
  format: 'astrale.dsl',
233
194
  version: 'v1',
234
195
  domain: origin,
235
- types: schemaRecord(root.types),
236
- interfaces,
237
196
  classes,
238
- imports,
239
197
  importsByKey,
240
- importedInterfacesByKey,
198
+ importedClassesByKey,
241
199
  functions,
242
200
  views,
243
201
  policies: recordCopy(root.policies),
244
202
  dependencies,
245
203
  core: jsonCopy(root.core),
246
204
  },
247
- importedInterfaces,
248
205
  }
249
206
  }
250
207
 
251
- /** Project canonical genesis data without importing the effectful Domain entry. */
208
+ /** Project canonical Core data without importing Application or Runtime modules. */
252
209
  export function projectCanonicalCore(
253
210
  root: CanonicalDomainSchemaV1,
254
211
  ): Pick<StudioCore, 'domain' | 'nodes' | 'edges'> {
255
212
  const core = asRecord(root.core) ?? {}
256
213
  const nodes = entriesOf(core.nodes).map(([slug, raw]) => {
257
214
  const node = asRecord(raw) ?? {}
258
- const classRef = schemaRef(node.class)
215
+ const selectedClass = classRef(node.class)
259
216
  return {
260
- path: corePath({ origin: root.origin, kind: 'core', name: slug }),
261
- className: classRef?.name ?? '?',
217
+ path: corePath({ origin: root.origin, name: slug }),
218
+ className: selectedClass?.name ?? '?',
262
219
  data: recordCopy(node.properties),
263
220
  }
264
221
  })
265
222
  const edges = arrayOf(core.edges).map((raw) => {
266
223
  const edge = asRecord(raw) ?? {}
267
- const classRef = schemaRef(edge.class)
268
224
  return {
269
225
  from: coreEndpoint(edge.source, root.origin),
270
226
  to: coreEndpoint(edge.target, root.origin),
271
- edgeName: classRef?.name ?? '?',
227
+ edgeName: classRef(edge.class)?.name ?? '?',
272
228
  ...(edge.properties === undefined ? {} : { data: recordCopy(edge.properties) }),
273
229
  }
274
230
  })
275
231
  return { domain: root.origin, nodes, edges }
276
232
  }
277
233
 
278
- function projectInterface(origin: string, name: string, value: unknown): IrInterface {
279
- const declaration = asRecord(value) ?? {}
280
- const family = definitionFamily(declaration.family, 'both')
281
- const extendsRefs = refsOf(declaration.extends, 'interface')
282
- return {
283
- type: 'interface',
284
- name,
285
- origin,
286
- family,
287
- ref: { origin, kind: 'interface', name },
288
- extends: extendsRefs.map((ref) => ref.name),
289
- extendsRefs,
290
- properties: propertySchemas(declaration),
291
- required: requiredNames(declaration.properties),
292
- methods: projectMethods(declaration.methods),
293
- ...(family === 'edge' ? edgeFields(declaration) : {}),
294
- ...(typeof declaration.description === 'string'
295
- ? { description: declaration.description }
296
- : {}),
297
- ...(asRecord(declaration.propertyMetadata)
298
- ? { propertyMetadata: recordCopy(declaration.propertyMetadata) }
299
- : {}),
300
- ...(dataDeclaration(declaration.data) ? { data: dataDeclaration(declaration.data) } : {}),
301
- }
302
- }
303
-
304
234
  function projectClass(origin: string, name: string, value: unknown): IrClass {
305
235
  const declaration = asRecord(value) ?? {}
306
- const family = definitionFamily(declaration.family, 'node') === 'edge' ? 'edge' : 'node'
307
- 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
+ )
308
255
  const policies = Object.fromEntries(
309
256
  entriesOf(declaration.policies).flatMap(([policyName, raw]) => {
310
- const ref = schemaRef(raw)
257
+ const ref = definitionRefOf(raw)
311
258
  return ref ? [[policyName, ref]] : []
312
259
  }),
313
260
  )
314
261
  return {
315
- type: family,
262
+ type: kind,
316
263
  name,
317
264
  origin,
318
265
  ref: { origin, kind: 'class', name },
319
- implements: implementsRefs.map((ref) => ref.name),
320
- implementsRefs,
321
- properties: propertySchemas(declaration),
322
- required: requiredNames(declaration.properties),
266
+ extends: extendsRefs.map((ref) => ref.name),
267
+ extendsRefs,
268
+ properties,
269
+ required,
323
270
  methods: projectMethods(declaration.methods),
324
- ...(family === 'edge' ? edgeFields(declaration) : {}),
325
- ...(typeof declaration.icon === 'string' ? { icon: declaration.icon } : {}),
271
+ ...(kind === 'edge' ? edgeFields(declaration) : {}),
326
272
  ...(typeof declaration.description === 'string'
327
273
  ? { description: declaration.description }
328
274
  : {}),
329
- ...(asRecord(declaration.propertyMetadata)
330
- ? { propertyMetadata: recordCopy(declaration.propertyMetadata) }
331
- : {}),
275
+ ...(Object.keys(propertyMetadata).length === 0 ? {} : { propertyMetadata }),
332
276
  ...(dataDeclaration(declaration.data) ? { data: dataDeclaration(declaration.data) } : {}),
333
- ...(Object.keys(policies).length > 0 ? { policies } : {}),
277
+ ...(Object.keys(policies).length === 0 ? {} : { policies }),
334
278
  }
335
279
  }
336
280
 
337
281
  function projectMethods(value: unknown): Record<string, IrMethod> {
338
282
  return Object.fromEntries(
339
- entriesOf(value).map(([name, declaration]) => {
340
- const callable = projectCallable(name, declaration)
341
- const raw = asRecord(declaration) ?? {}
283
+ entriesOf(value).map(([name, raw]) => {
284
+ const declaration = asRecord(raw) ?? {}
342
285
  const inheritance =
343
- raw.inheritance === 'abstract' ||
344
- raw.inheritance === 'sealed' ||
345
- raw.inheritance === 'default'
346
- ? raw.inheritance
286
+ declaration.inheritance === 'abstract' || declaration.inheritance === 'sealed'
287
+ ? declaration.inheritance
347
288
  : 'default'
348
289
  return [
349
290
  name,
350
291
  {
351
- ...callable,
352
- static: raw.static === true,
292
+ ...projectCallable(name, declaration),
293
+ static: declaration.static === true,
353
294
  inheritance,
354
295
  } satisfies IrMethod,
355
296
  ]
@@ -357,50 +298,40 @@ function projectMethods(value: unknown): Record<string, IrMethod> {
357
298
  )
358
299
  }
359
300
 
360
- function projectFunction(name: string, declaration: unknown): IrFunction {
361
- return {
362
- ...projectCallable(name, declaration),
363
- static: true,
364
- inheritance: 'default',
365
- }
301
+ function projectFunction(name: string, value: unknown): IrFunction {
302
+ return projectCallable(name, value)
366
303
  }
367
304
 
368
- function projectCallable(name: string, value: unknown): Omit<IrFunction, 'static' | 'inheritance'> {
305
+ function projectCallable(name: string, value: unknown): IrFunction {
369
306
  const declaration = asRecord(value) ?? {}
370
307
  const input = studioSchema(declaration.input)
371
- const output = callableOutput(declaration.output, {})
308
+ const output = callableOutput(declaration.output)
372
309
  const auth = callableAuth(declaration.auth)
373
310
  return {
374
311
  name,
375
312
  input,
376
- params: callableParams(declaration.input),
377
- requiredParams: requiredNames(declaration.input),
378
313
  output,
379
- returns: outputValue(output),
380
314
  ...(typeof declaration.description === 'string'
381
315
  ? { description: declaration.description }
382
316
  : {}),
383
- ...(auth ? { auth } : {}),
317
+ ...(auth === undefined ? {} : { auth }),
384
318
  ...(declaration.policy === undefined ? {} : { policy: jsonCopy(declaration.policy) }),
385
319
  }
386
320
  }
387
321
 
388
322
  function projectView(name: string, value: unknown): IrView {
389
323
  const declaration = asRecord(value) ?? {}
390
- const rawTarget = asRecord(declaration.target)
391
- const target =
392
- rawTarget?.kind === 'definition'
393
- ? ({
394
- kind: 'definition',
395
- definitions: refsOf(rawTarget.definitions, ['class', 'interface']),
396
- } as const)
397
- : ({ kind: 'domain' } as const)
398
- const auth =
399
- declaration.auth === 'optional' || declaration.auth === 'public' ? declaration.auth : 'required'
324
+ const target = asRecord(declaration.target)
400
325
  return {
401
326
  name,
402
- target,
403
- 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',
404
335
  ...(typeof declaration.description === 'string'
405
336
  ? { description: declaration.description }
406
337
  : {}),
@@ -419,250 +350,186 @@ function edgeFields(
419
350
  projectEndpoint(endpoints?.source, 'outgoing'),
420
351
  projectEndpoint(endpoints?.target, 'incoming'),
421
352
  ]
422
- const rawConstraints = asRecord(declaration.constraints)
423
- const constraints = {
424
- ...(rawConstraints?.noSelf === true ? { noSelf: true as const } : {}),
425
- ...(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
+ },
426
361
  }
427
- return { endpoints: projected, orientation, constraints }
428
362
  }
429
363
 
430
364
  function projectEndpoint(
431
365
  value: unknown,
432
- countKey: 'outgoing' | 'incoming' | 'incident',
366
+ cardinality: 'outgoing' | 'incoming' | 'incident',
433
367
  ): IrEndpoint {
434
368
  const endpoint = asRecord(value) ?? {}
435
- const refs = refsOf(endpoint.accepts, ['class', 'interface'])
436
- const cardinality = edgeCount(endpoint[countKey])
369
+ const refs = refsOf(endpoint.accepts)
370
+ const count = edgeCount(endpoint[cardinality])
437
371
  return {
438
372
  name: typeof endpoint.role === 'string' ? endpoint.role : '',
439
373
  types: refs.map((ref) => ref.name),
440
374
  refs,
441
- ...(cardinality ? { cardinality } : {}),
375
+ ...(count === undefined ? {} : { cardinality: count }),
442
376
  }
443
377
  }
444
378
 
445
379
  function edgeCount(value: unknown): IrEndpoint['cardinality'] | undefined {
446
- switch (value) {
447
- case '0..*':
448
- return { min: 0, max: null }
449
- case '1..*':
450
- return { min: 1, max: null }
451
- case '0..1':
452
- return { min: 0, max: 1 }
453
- case '1':
454
- return { min: 1, max: 1 }
455
- default:
456
- return undefined
457
- }
458
- }
459
-
460
- function propertySchemas(declaration: AnyRecord): Record<string, JsonSchema> {
461
- const object = asRecord(declaration.properties) ?? {}
462
- return Object.fromEntries(
463
- entriesOf(object.properties).map(([name, schema]) => [name, studioSchema(schema)]),
464
- )
465
- }
466
-
467
- function callableParams(input: unknown): Record<string, JsonSchema> {
468
- const object = asRecord(input)
469
- if (!object) return {}
470
- return Object.fromEntries(
471
- entriesOf(object.properties).map(([name, schema]) => [name, studioSchema(schema)]),
472
- )
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
473
385
  }
474
386
 
475
- function callableOutput(value: unknown, fallback: JsonSchema): IrCallableOutput {
476
- const output = asRecord(value)
477
- if (output?.mode === 'stream') return { mode: 'stream', item: studioSchema(output.item) }
478
- if (output?.mode === 'binary') return { mode: 'binary' }
479
- if (output?.mode === 'value') return { mode: 'value', schema: studioSchema(output.schema) }
480
- return { mode: 'value', schema: fallback }
481
- }
482
-
483
- function outputValue(output: IrCallableOutput): JsonSchema {
484
- if (output.mode === 'value') return output.schema
485
- if (output.mode === 'stream') return output.item
486
- return {}
487
- }
488
-
489
- function callableAuth(value: unknown): IrCallableAuth | undefined {
490
- return value === 'anonymous' || value === 'authenticated' || value === 'authorized'
491
- ? value
492
- : undefined
493
- }
494
-
495
- function definitionFamily(value: unknown, fallback: 'node' | 'both'): 'node' | 'edge' | 'both' {
496
- return value === 'node' || value === 'edge' || value === 'both' ? value : fallback
497
- }
498
-
499
- function dataDeclaration(
500
- value: unknown,
501
- ): { mediaType: string; [key: string]: unknown } | undefined {
502
- const data = asRecord(value)
503
- return typeof data?.mediaType === 'string'
504
- ? (jsonCopy(data) as { mediaType: string; [key: string]: unknown })
505
- : undefined
506
- }
507
-
508
- function refsOf(
509
- value: unknown,
510
- kinds: IrSchemaRef['kind'] | readonly IrSchemaRef['kind'][],
511
- ): IrSchemaRef[] {
512
- const accepted = new Set(Array.isArray(kinds) ? kinds : [kinds])
513
- return arrayOf(value)
514
- .map(schemaRef)
515
- .filter((ref): ref is IrSchemaRef => ref !== null && accepted.has(ref.kind))
516
- }
517
-
518
- function schemaRef(value: unknown): IrSchemaRef | null {
519
- const ref = asRecord(value)
520
- if (!ref || typeof ref.origin !== 'string' || typeof ref.name !== 'string') return null
521
- switch (ref.kind) {
522
- case 'type':
523
- case 'interface':
524
- case 'class':
525
- case 'function':
526
- case 'policy':
527
- case 'view':
528
- case 'core':
529
- return { origin: ref.origin, kind: ref.kind, name: ref.name }
530
- default:
531
- return null
532
- }
533
- }
534
-
535
- function collectDefinitionRefs(root: CanonicalDomainSchemaV1): IrSchemaRef[] {
536
- const refs: IrSchemaRef[] = []
537
- for (const [, declaration] of entriesOf(root.types)) {
538
- collectPathSchemaRefs(declaration, refs)
539
- }
540
- for (const [, declaration] of entriesOf(root.interfaces)) {
541
- refs.push(...collectDefinitionRefsFromDeclaration(declaration))
542
- }
387
+ function collectClassRefs(root: CanonicalDomainSchemaV1): IrClassRef[] {
388
+ const refs: IrClassRef[] = []
543
389
  for (const [, declaration] of entriesOf(root.classes)) {
544
- refs.push(...collectDefinitionRefsFromDeclaration(declaration))
390
+ refs.push(...collectClassRefsFromClass(declaration))
545
391
  }
546
392
  for (const [, callable] of entriesOf(root.functions)) collectCallableRefs(callable, refs)
547
393
  for (const [, view] of entriesOf(root.views)) {
548
- const target = asRecord(asRecord(view)?.target)
549
- refs.push(...refsOf(target?.definitions, ['class', 'interface']))
394
+ refs.push(...refsOf(asRecord(asRecord(view)?.target)?.definitions))
550
395
  }
551
396
  const core = asRecord(root.core)
552
- 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)
553
398
  for (const edge of arrayOf(core?.edges)) {
554
399
  const declaration = asRecord(edge)
555
- addRef(declaration?.class, refs)
556
- addRef(declaration?.source, refs)
557
- addRef(declaration?.target, refs)
400
+ addClassRef(declaration?.class, refs)
558
401
  }
559
- collectPolicyRefs(root.policies, refs)
560
- return uniqueRefs(refs)
402
+ for (const [, policy] of entriesOf(root.policies)) collectPolicyRefs(policy, refs)
403
+ return uniqueClassRefs(refs)
561
404
  }
562
405
 
563
- function collectDefinitionRefsFromDeclaration(value: unknown): IrSchemaRef[] {
406
+ function collectClassRefsFromClass(value: unknown): IrClassRef[] {
564
407
  const declaration = asRecord(value) ?? {}
565
- const refs = [
566
- ...refsOf(declaration.extends, 'interface'),
567
- ...refsOf(declaration.implements, 'interface'),
568
- ]
569
- collectPathSchemaRefs(declaration.properties, refs)
408
+ const refs = [...refsOf(declaration.extends)]
570
409
  const endpoints = asRecord(declaration.endpoints)
571
410
  for (const endpoint of [
572
411
  endpoints?.source,
573
412
  endpoints?.target,
574
413
  ...arrayOf(declaration.endpoints),
575
414
  ]) {
576
- 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)
577
419
  }
578
420
  for (const [, callable] of entriesOf(declaration.methods)) collectCallableRefs(callable, refs)
579
- for (const [, policy] of entriesOf(declaration.policies)) addRef(policy, refs)
580
- return uniqueRefs(refs)
421
+ for (const [, policy] of entriesOf(declaration.policies)) collectPolicyRefs(policy, refs)
422
+ return uniqueClassRefs(refs)
581
423
  }
582
424
 
583
- function collectCallableRefs(value: unknown, refs: IrSchemaRef[]): void {
425
+ function collectCallableRefs(value: unknown, refs: IrClassRef[]): void {
584
426
  const callable = asRecord(value)
585
- if (!callable) return
427
+ if (callable === null) return
586
428
  collectPathSchemaRefs(callable.input, refs)
587
429
  const output = asRecord(callable.output)
588
430
  collectPathSchemaRefs(output?.schema ?? output?.item, refs)
589
431
  collectPolicyRefs(callable.policy, refs)
590
432
  }
591
433
 
592
- /** Embedded value data is opaque; only the declared x-astrale-path vocabulary carries refs. */
593
- function collectPathSchemaRefs(value: unknown, refs: IrSchemaRef[]): void {
434
+ function collectPathSchemaRefs(value: unknown, refs: IrClassRef[]): void {
594
435
  if (Array.isArray(value)) {
595
436
  for (const item of value) collectPathSchemaRefs(item, refs)
596
437
  return
597
438
  }
598
439
  const object = asRecord(value)
599
- if (!object) return
440
+ if (object === null) return
600
441
  const annotation = asRecord(object['x-astrale-path'])
601
- if (annotation) refs.push(...refsOf(annotation.accepts, ['class', 'interface']))
442
+ refs.push(...refsOf(annotation?.accepts))
602
443
  for (const [key, child] of Object.entries(object)) {
603
444
  if (key === 'const' || key === 'enum' || key === 'default' || key === 'examples') continue
604
445
  collectPathSchemaRefs(child, refs)
605
446
  }
606
447
  }
607
448
 
608
- function collectPolicyRefs(value: unknown, refs: IrSchemaRef[]): void {
449
+ function collectPolicyRefs(value: unknown, refs: IrClassRef[]): void {
609
450
  if (Array.isArray(value)) {
610
451
  for (const item of value) collectPolicyRefs(item, refs)
611
452
  return
612
453
  }
613
454
  const object = asRecord(value)
614
- if (!object) return
615
- for (const key of ['check', 'class', 'ref']) addRef(object[key], refs)
616
- for (const key of [
617
- 'allOf',
618
- 'anyOf',
619
- 'exists',
620
- 'where',
621
- 'match',
622
- 'nodes',
623
- 'source',
624
- 'target',
625
- 'object',
626
- ]) {
627
- collectPolicyRefs(object[key], refs)
628
- }
455
+ if (object === null) return
456
+ addClassRef(object.class, refs)
457
+ for (const child of Object.values(object)) collectPolicyRefs(child, refs)
629
458
  }
630
459
 
631
- function addRef(value: unknown, refs: IrSchemaRef[]): void {
632
- const ref = schemaRef(value)
633
- if (ref) refs.push(ref)
460
+ function addClassRef(value: unknown, refs: IrClassRef[]): void {
461
+ const ref = classRef(value)
462
+ if (ref !== null) refs.push(ref)
634
463
  }
635
464
 
636
- function uniqueRefs(refs: readonly IrSchemaRef[]): IrSchemaRef[] {
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
474
+ }
475
+
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[] {
637
492
  const seen = new Set<string>()
638
493
  return refs.filter((ref) => {
639
- const key = refKey(ref)
494
+ const key = classRefKey(ref)
640
495
  if (seen.has(key)) return false
641
496
  seen.add(key)
642
497
  return true
643
498
  })
644
499
  }
645
500
 
646
- function refKey(ref: IrSchemaRef): IrSchemaKey {
647
- return `${ref.origin}:${ref.kind}.${ref.name}`
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) }
648
506
  }
649
507
 
650
- function isDefinitionRef(ref: IrSchemaRef): ref is IrDefinitionRef {
651
- return ref.kind === 'class' || ref.kind === 'interface'
508
+ function callableAuth(value: unknown): IrCallableAuth | undefined {
509
+ return value === 'anonymous' || value === 'authenticated' || value === 'authorized'
510
+ ? value
511
+ : undefined
652
512
  }
653
513
 
654
- function definitionKey(ref: IrDefinitionRef): IrDefinitionKey {
655
- return `${ref.origin}:${ref.kind}.${ref.name}`
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
656
521
  }
657
522
 
658
523
  function coreEndpoint(value: unknown, rootOrigin: string): string {
659
524
  const endpoint = asRecord(value)
660
- if (endpoint?.kind === 'domain') return `/:${rootOrigin}`
661
- const ref = schemaRef(value)
525
+ if (endpoint?.kind === 'domain' && typeof endpoint.origin === 'string') {
526
+ return `/:${endpoint.origin}`
527
+ }
528
+ const ref = definitionRefOf(value)
662
529
  return ref?.kind === 'core' ? corePath(ref) : `/:${rootOrigin}`
663
530
  }
664
531
 
665
- function corePath(ref: Pick<IrSchemaRef, 'origin' | 'kind' | 'name'>): string {
532
+ function corePath(ref: Pick<IrSchemaRef, 'origin' | 'name'>): string {
666
533
  return `/:${ref.origin}:core.${ref.name}`
667
534
  }
668
535
 
@@ -673,11 +540,11 @@ function admitClosure(
673
540
  if (!Array.isArray(value)) return null
674
541
  const closure: CanonicalDomainSchemaV1[] = []
675
542
  const origins = new Set<string>()
676
- for (const schema of value) {
677
- if (!isCanonicalDomainSchemaV1(schema) || schema.origin === root.origin) return null
678
- if (origins.has(schema.origin)) return null
679
- origins.add(schema.origin)
680
- 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)
681
548
  }
682
549
  return closure
683
550
  }
@@ -688,52 +555,17 @@ function studioSchema(value: unknown): JsonSchema {
688
555
  return (jsonCopy(asRecord(value) ?? {}) ?? {}) as JsonSchema
689
556
  }
690
557
 
691
- function schemaRecord(value: unknown): Record<string, JsonSchema> {
692
- return Object.fromEntries(entriesOf(value).map(([name, schema]) => [name, studioSchema(schema)]))
693
- }
694
-
695
- function requiredNames(value: unknown): string[] {
696
- const object = asRecord(value)
697
- return arrayOf(object?.required).filter((name): name is string => typeof name === 'string')
698
- }
699
-
700
- function legacyRequiredParams(params: Record<string, JsonSchema>): string[] {
701
- return Object.entries(params).flatMap(([name, schema]) =>
702
- schemaAllowsNull(schema) ? [] : [name],
703
- )
704
- }
705
-
706
- function schemaAllowsNull(schema: JsonSchema): boolean {
707
- if (schema.type === 'null') return true
708
- if (Array.isArray(schema.type) && schema.type.includes('null')) return true
709
- return [...arrayOf(schema.anyOf), ...arrayOf(schema.oneOf)].some(
710
- (candidate) => asRecord(candidate)?.type === 'null',
711
- )
712
- }
713
-
714
- function objectInputFromParams(
715
- params: Record<string, JsonSchema>,
716
- required: readonly string[],
717
- ): JsonSchema {
718
- return {
719
- type: 'object',
720
- properties: params,
721
- required: [...required],
722
- additionalProperties: false,
723
- }
724
- }
725
-
726
- function recordCopy(value: unknown): Record<string, any> {
558
+ function recordCopy(value: unknown): Record<string, unknown> {
727
559
  const record = asRecord(value)
728
- return record ? (jsonCopy(record) as Record<string, any>) : {}
560
+ return record === null ? {} : (jsonCopy(record) as Record<string, unknown>)
729
561
  }
730
562
 
731
563
  function jsonCopy(value: unknown): any {
732
564
  if (Array.isArray(value)) return value.map(jsonCopy)
733
565
  const record = asRecord(value)
734
- if (record)
735
- return Object.fromEntries(Object.entries(record).map(([key, item]) => [key, jsonCopy(item)]))
736
- return value
566
+ return record === null
567
+ ? value
568
+ : Object.fromEntries(Object.entries(record).map(([key, item]) => [key, jsonCopy(item)]))
737
569
  }
738
570
 
739
571
  function entriesOf(value: unknown): [string, unknown][] {