@astrale-os/sdk 0.1.10 → 0.3.0

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 (186) hide show
  1. package/LICENSE +202 -0
  2. package/dist/auth/check.d.ts +1 -2
  3. package/dist/auth/check.d.ts.map +1 -1
  4. package/dist/auth/check.js +2 -6
  5. package/dist/auth/check.js.map +1 -1
  6. package/dist/auth/index.d.ts +3 -1
  7. package/dist/auth/index.d.ts.map +1 -1
  8. package/dist/auth/index.js +2 -1
  9. package/dist/auth/index.js.map +1 -1
  10. package/dist/auth/issuer-mint.d.ts +53 -0
  11. package/dist/auth/issuer-mint.d.ts.map +1 -0
  12. package/dist/auth/issuer-mint.js +35 -0
  13. package/dist/auth/issuer-mint.js.map +1 -0
  14. package/dist/auth/kernel-client.d.ts +8 -2
  15. package/dist/auth/kernel-client.d.ts.map +1 -1
  16. package/dist/auth/kernel-client.js +22 -46
  17. package/dist/auth/kernel-client.js.map +1 -1
  18. package/dist/cli/dotenv.d.ts +8 -0
  19. package/dist/cli/dotenv.d.ts.map +1 -1
  20. package/dist/cli/dotenv.js +18 -0
  21. package/dist/cli/dotenv.js.map +1 -1
  22. package/dist/cli/index.d.ts +1 -1
  23. package/dist/cli/index.d.ts.map +1 -1
  24. package/dist/cli/index.js +1 -1
  25. package/dist/cli/index.js.map +1 -1
  26. package/dist/cli/log.d.ts +11 -0
  27. package/dist/cli/log.d.ts.map +1 -0
  28. package/dist/cli/log.js +17 -0
  29. package/dist/cli/log.js.map +1 -0
  30. package/dist/cli/publish.d.ts +35 -0
  31. package/dist/cli/publish.d.ts.map +1 -0
  32. package/dist/cli/publish.js +271 -0
  33. package/dist/cli/publish.js.map +1 -0
  34. package/dist/cli/run.d.ts +17 -2
  35. package/dist/cli/run.d.ts.map +1 -1
  36. package/dist/cli/run.js +102 -111
  37. package/dist/cli/run.js.map +1 -1
  38. package/dist/cli/spec.d.ts.map +1 -1
  39. package/dist/cli/spec.js +1 -0
  40. package/dist/cli/spec.js.map +1 -1
  41. package/dist/config/adapter.d.ts +12 -11
  42. package/dist/config/adapter.d.ts.map +1 -1
  43. package/dist/config/adapter.js.map +1 -1
  44. package/dist/config/define-domain.d.ts +88 -27
  45. package/dist/config/define-domain.d.ts.map +1 -1
  46. package/dist/config/define-domain.js +79 -33
  47. package/dist/config/define-domain.js.map +1 -1
  48. package/dist/config/deploy.d.ts +1 -1
  49. package/dist/config/deploy.js +1 -1
  50. package/dist/config/index.d.ts +1 -1
  51. package/dist/config/index.d.ts.map +1 -1
  52. package/dist/define/remote-function.d.ts +32 -17
  53. package/dist/define/remote-function.d.ts.map +1 -1
  54. package/dist/define/remote-function.js +10 -9
  55. package/dist/define/remote-function.js.map +1 -1
  56. package/dist/define/view.d.ts +8 -6
  57. package/dist/define/view.d.ts.map +1 -1
  58. package/dist/define/view.js +8 -6
  59. package/dist/define/view.js.map +1 -1
  60. package/dist/deploy/check.d.ts +6 -6
  61. package/dist/deploy/check.d.ts.map +1 -1
  62. package/dist/deploy/check.js +1 -1
  63. package/dist/deploy/check.js.map +1 -1
  64. package/dist/deploy/index.d.ts +0 -1
  65. package/dist/deploy/index.d.ts.map +1 -1
  66. package/dist/deploy/index.js +0 -1
  67. package/dist/deploy/index.js.map +1 -1
  68. package/dist/deploy/meta.d.ts +11 -0
  69. package/dist/deploy/meta.d.ts.map +1 -1
  70. package/dist/deploy/meta.js +23 -0
  71. package/dist/deploy/meta.js.map +1 -1
  72. package/dist/dispatch/dispatcher.d.ts +17 -4
  73. package/dist/dispatch/dispatcher.d.ts.map +1 -1
  74. package/dist/dispatch/dispatcher.js +15 -3
  75. package/dist/dispatch/dispatcher.js.map +1 -1
  76. package/dist/dispatch/execute.d.ts +6 -2
  77. package/dist/dispatch/execute.d.ts.map +1 -1
  78. package/dist/dispatch/execute.js +1 -0
  79. package/dist/dispatch/execute.js.map +1 -1
  80. package/dist/dispatch/identity.d.ts +12 -10
  81. package/dist/dispatch/identity.d.ts.map +1 -1
  82. package/dist/dispatch/identity.js +10 -15
  83. package/dist/dispatch/identity.js.map +1 -1
  84. package/dist/dispatch/index.d.ts +1 -1
  85. package/dist/dispatch/index.d.ts.map +1 -1
  86. package/dist/dispatch/index.js +1 -1
  87. package/dist/dispatch/index.js.map +1 -1
  88. package/dist/dispatch/self.d.ts +25 -37
  89. package/dist/dispatch/self.d.ts.map +1 -1
  90. package/dist/dispatch/self.js +60 -30
  91. package/dist/dispatch/self.js.map +1 -1
  92. package/dist/domain/binding.d.ts +18 -0
  93. package/dist/domain/binding.d.ts.map +1 -0
  94. package/dist/domain/binding.js +29 -0
  95. package/dist/domain/binding.js.map +1 -0
  96. package/dist/domain/build-spec.d.ts.map +1 -1
  97. package/dist/domain/build-spec.js +22 -7
  98. package/dist/domain/build-spec.js.map +1 -1
  99. package/dist/domain/define.d.ts +28 -43
  100. package/dist/domain/define.d.ts.map +1 -1
  101. package/dist/domain/define.js +23 -60
  102. package/dist/domain/define.js.map +1 -1
  103. package/dist/domain/extend-functions.d.ts +48 -0
  104. package/dist/domain/extend-functions.d.ts.map +1 -0
  105. package/dist/domain/extend-functions.js +84 -0
  106. package/dist/domain/extend-functions.js.map +1 -0
  107. package/dist/domain/extend-views.d.ts +45 -0
  108. package/dist/domain/extend-views.d.ts.map +1 -0
  109. package/dist/domain/extend-views.js +116 -0
  110. package/dist/domain/extend-views.js.map +1 -0
  111. package/dist/index.d.ts +8 -4
  112. package/dist/index.d.ts.map +1 -1
  113. package/dist/index.js +13 -5
  114. package/dist/index.js.map +1 -1
  115. package/dist/method/class.d.ts +2 -2
  116. package/dist/method/class.d.ts.map +1 -1
  117. package/dist/method/class.js.map +1 -1
  118. package/dist/method/context.d.ts +18 -3
  119. package/dist/method/context.d.ts.map +1 -1
  120. package/dist/method/index.d.ts +2 -2
  121. package/dist/method/index.d.ts.map +1 -1
  122. package/dist/method/index.js.map +1 -1
  123. package/dist/method/single.d.ts +25 -38
  124. package/dist/method/single.d.ts.map +1 -1
  125. package/dist/method/single.js +1 -11
  126. package/dist/method/single.js.map +1 -1
  127. package/dist/server/auxiliary-routes.d.ts.map +1 -1
  128. package/dist/server/auxiliary-routes.js +14 -3
  129. package/dist/server/auxiliary-routes.js.map +1 -1
  130. package/dist/server/create.d.ts +2 -15
  131. package/dist/server/create.d.ts.map +1 -1
  132. package/dist/server/create.js +15 -51
  133. package/dist/server/create.js.map +1 -1
  134. package/dist/server/domain-entry.d.ts +10 -0
  135. package/dist/server/domain-entry.d.ts.map +1 -1
  136. package/dist/server/domain-entry.js +2 -0
  137. package/dist/server/domain-entry.js.map +1 -1
  138. package/dist/server/index.d.ts +0 -1
  139. package/dist/server/index.d.ts.map +1 -1
  140. package/dist/server/index.js +0 -1
  141. package/dist/server/index.js.map +1 -1
  142. package/dist/server/worker-entry.d.ts +3 -3
  143. package/dist/server/worker-entry.js +3 -3
  144. package/package.json +10 -11
  145. package/src/auth/check.ts +2 -6
  146. package/src/auth/index.ts +3 -1
  147. package/src/auth/issuer-mint.ts +75 -0
  148. package/src/auth/kernel-client.ts +31 -63
  149. package/src/cli/dotenv.ts +20 -0
  150. package/src/cli/index.ts +1 -1
  151. package/src/cli/log.ts +18 -0
  152. package/src/cli/publish.ts +341 -0
  153. package/src/cli/run.ts +109 -125
  154. package/src/cli/spec.ts +1 -0
  155. package/src/config/adapter.ts +9 -11
  156. package/src/config/define-domain.ts +184 -53
  157. package/src/config/deploy.ts +1 -1
  158. package/src/config/index.ts +1 -1
  159. package/src/define/remote-function.ts +56 -21
  160. package/src/define/view.ts +8 -6
  161. package/src/deploy/check.ts +7 -7
  162. package/src/deploy/index.ts +0 -1
  163. package/src/deploy/meta.ts +25 -0
  164. package/src/dispatch/dispatcher.ts +32 -9
  165. package/src/dispatch/execute.ts +7 -2
  166. package/src/dispatch/identity.ts +17 -22
  167. package/src/dispatch/index.ts +1 -1
  168. package/src/dispatch/self.ts +117 -41
  169. package/src/domain/binding.ts +37 -0
  170. package/src/domain/build-spec.ts +32 -7
  171. package/src/domain/define.ts +71 -78
  172. package/src/domain/extend-functions.ts +106 -0
  173. package/src/domain/extend-views.ts +151 -0
  174. package/src/index.ts +36 -7
  175. package/src/method/class.ts +11 -7
  176. package/src/method/context.ts +19 -3
  177. package/src/method/index.ts +8 -2
  178. package/src/method/single.ts +35 -42
  179. package/src/server/auxiliary-routes.ts +14 -3
  180. package/src/server/create.ts +15 -51
  181. package/src/server/domain-entry.ts +12 -0
  182. package/src/server/index.ts +0 -1
  183. package/src/server/worker-entry.ts +3 -3
  184. package/src/deploy/hash-spec.ts +0 -31
  185. package/src/domain/extend-core.ts +0 -301
  186. package/src/server/worker-meta.ts +0 -25
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * `defineDomain` — the WORKER-SAFE definition of a domain: what the domain *is*
3
- * (its `schema`, `methods`, `deps`, `views`, standalone `functions`, `client`
4
- * SPA) plus its addressing identity (`origin`, `requires`, `postInstall`). It
3
+ * (its `schema`, `methods`, `deps`, `views`, standalone `functions`) plus its
4
+ * addressing identity (`origin`, `requires`, `postInstall`). It
5
5
  * deliberately carries NO deployment adapter — the adapter (`cloudflare(...)`,
6
6
  * `astrale(...)`) is node-only code (filesystem, wrangler) that must never enter
7
7
  * the worker bundle. The author wires this in a `domain.ts` the generated worker
@@ -11,16 +11,16 @@
11
11
  * The modules are wired EXPLICITLY here — imported and passed in — not
12
12
  * discovered from magic folder names. A renamed or mistyped module is a compile
13
13
  * error at this call site, never a silently-missing worker route. The adapter
14
- * reads this one definition for everything it codegens; there is no second
15
- * filesystem probe to drift from it. `defineDomain` itself builds no server and
16
- * boots no kernel — it validates and packages the declaration.
14
+ * reads this one definition for domain-side codegen; frontend source folders
15
+ * live in adapter env config. `defineDomain` itself builds no server and boots
16
+ * no kernel — it validates and packages the declaration.
17
17
  */
18
18
 
19
19
  import type { Schema } from '@astrale-os/kernel-dsl'
20
20
 
21
- import { DomainOrigin, extractDomainSlug } from '@astrale-os/kernel-core/domain'
21
+ import { DomainOrigin } from '@astrale-os/kernel-core/domain'
22
22
 
23
- import type { RemoteFunctionDef, ViewDef } from '../define'
23
+ import type { AnyRemoteFunctionDef, ViewDef } from '../define'
24
24
  import type { SchemaMethodsImpl } from '../method'
25
25
 
26
26
  // A registry holds views / functions of every deps + param/result shape. Their
@@ -28,19 +28,69 @@ import type { SchemaMethodsImpl } from '../method'
28
28
  // site; here they're held loosely and, crucially, kept OUT of `TDeps` inference
29
29
  // — so `TDeps` is fixed by `methods` alone (the authoritative deps source) and a
30
30
  // view authored with the default `unknown` deps can't fight `methods`'s `Env`.
31
- // oxlint-disable no-explicit-any
31
+ // oxlint-disable-next-line no-explicit-any
32
32
  type AnyViewDef = ViewDef<any>
33
- type AnyFunctionDef = RemoteFunctionDef<any, any, any>
34
- // oxlint-enable no-explicit-any
33
+ // The function bag is the canonical, variance-correct one from `../define` (it
34
+ // must accept functions of any auth policy — see the note there). Aliased to
35
+ // keep the local usages below terse.
36
+ type AnyFunctionDef = AnyRemoteFunctionDef
35
37
 
36
38
  /**
37
- * The domain's client SPA binding. Its presence is the whole signal there is
38
- * no `existsSync('client/')` probe. `dir` is the project-relative source folder
39
- * (e.g. `'client'`); the worker serves its built SPA under `/ui` via its Assets
40
- * binding. Always written out explicitly (`client: { dir: 'client' }`) — there
41
- * is no boolean shorthand, so the source folder is never implicit.
39
+ * Optional presentation metadata for the domain, served verbatim on the
40
+ * worker's `/meta` endpoint (see `../deploy/meta`) so a host UI can brand a
41
+ * freshly-installed domain. Both the block and every field are optional.
42
42
  */
43
- export type ClientBinding = { dir: string }
43
+ export interface DomainManifest {
44
+ /** Domain logo — an inline SVG string or a `data:` URL (no extra validation). */
45
+ logo?: string
46
+ /**
47
+ * Slug of one of THIS domain's own views to open first — the domain's entry
48
+ * surface. This is the SERVED form: a plain slug, stamped verbatim onto
49
+ * `/meta`. Authors don't write the slug directly — they pass the view OBJECT
50
+ * to `defineDomain` (see {@link DomainManifestConfig.entrypoint}), which
51
+ * resolves it to this slug. Pinned at app install to the `View` node at
52
+ * `/<origin>/views/<slug>` via the app's `entrypoint` edge.
53
+ */
54
+ entrypoint?: string
55
+ /**
56
+ * The ROLES this domain declares — named capability identities a workspace
57
+ * registers when an app for this domain is installed, then assigns to users
58
+ * (`default: true` roles are auto-assigned to the installing user). The
59
+ * domain itself grants each role its resource permissions (typically in its
60
+ * `postInstall`); declaring a role here only names it. Slugs are validated
61
+ * (lowercase slug charset, unique) at `defineDomain`.
62
+ */
63
+ roles?: readonly DomainRoleDecl[]
64
+ }
65
+
66
+ /** One declared role of the domain (see {@link DomainManifest.roles}). */
67
+ export interface DomainRoleDecl {
68
+ /** Stable role slug, unique within the domain (e.g. `"editor"`). */
69
+ slug: string
70
+ /** Display name; defaults to the slug. */
71
+ name?: string
72
+ /** What holding this role means — shown in permission UIs. */
73
+ description?: string
74
+ /** Auto-assign this role to the user installing an app for this domain. */
75
+ default?: boolean
76
+ }
77
+
78
+ /**
79
+ * The author-facing manifest accepted by {@link defineDomain} — identical to the
80
+ * served {@link DomainManifest} except `entrypoint` is the view OBJECT (a value
81
+ * of the `views` map) rather than its slug. `defineDomain` resolves it to the
82
+ * slug by identity, so a renamed or dropped view is a compile error at the
83
+ * reference site, never a stale string (the same guarantee `postInstall` gives
84
+ * for functions).
85
+ */
86
+ export interface DomainManifestConfig extends Omit<DomainManifest, 'entrypoint'> {
87
+ /**
88
+ * The view to open first — the domain's entry surface. Pass the view OBJECT
89
+ * from this domain's own `views` map (e.g. `entrypoint: views.welcome`).
90
+ * Resolved to its slug here and served as {@link DomainManifest.entrypoint}.
91
+ */
92
+ entrypoint?: AnyViewDef
93
+ }
44
94
 
45
95
  export interface DefineDomainConfig<S extends Schema, TDeps, TEnv = unknown> {
46
96
  /** The domain schema (from `schema/`). Its `.domain` seeds the default origin. */
@@ -77,11 +127,14 @@ export interface DefineDomainConfig<S extends Schema, TDeps, TEnv = unknown> {
77
127
  */
78
128
  functions?: Record<string, AnyFunctionDef>
79
129
  /**
80
- * The domain's client SPA, e.g. `{ dir: 'client' }`. Its presence enables it
81
- * (no folder probing); `dir` is the project-relative source folder, built and
82
- * served under `/ui`. Omit for a domain with no SPA.
130
+ * Optional presentation metadata stamped onto `/meta`. When `manifest.entrypoint`
131
+ * is set it MUST be a view from this domain's own `views` map passed as the
132
+ * OBJECT (`entrypoint: views.welcome`), resolved to its slug here. A view that
133
+ * isn't in `views` is a compile error at the reference site, and the
134
+ * resolution throws loudly if it's somehow absent — so a typo can never dangle
135
+ * to a NOT_FOUND view path at install.
83
136
  */
84
- client?: ClientBinding
137
+ manifest?: DomainManifestConfig
85
138
  /**
86
139
  * The domain's **addressing name** (the graph slug it mounts under, e.g.
87
140
  * `'crm.acme.dev'`). Defaults to `schema.domain`. Must be a name, never a
@@ -94,11 +147,25 @@ export interface DefineDomainConfig<S extends Schema, TDeps, TEnv = unknown> {
94
147
  /** Cross-domain deps, by origin. Verified present on the instance at install. */
95
148
  requires?: readonly string[]
96
149
  /**
97
- * Astrale Path (usually an AbsolutePath to a `functions/` entry) the kernel
98
- * calls once after install, as __SYSTEM__ — where the domain posts its own
99
- * grants / seed.
150
+ * Where the Domain node physically lives in the graph TREE — an absolute tree
151
+ * path whose LAST segment is the origin (e.g. `'/domains/crm.acme.dev'`).
152
+ * Optional; defaults to `/domains/<origin>`. This moves ONLY the physical
153
+ * `has_parent` position: the domain's `installed_in` edge and EVERY typed
154
+ * address (`/:<origin>`, `/:<origin>:Class`, …) stay ROOT-mounted, so
155
+ * addressing is unchanged. A platform domain that must stay top-level sets its
156
+ * own origin path (e.g. `'/workspace.astrale.ai'`).
100
157
  */
101
- postInstall?: string
158
+ path?: string
159
+ /**
160
+ * The function the kernel runs once after install, as __SYSTEM__ — where the
161
+ * domain seeds itself / posts its own grants. Reference it from the `functions`
162
+ * map: `postInstall: functions.seed`. The SDK derives its path by identity, so a
163
+ * typo or a renamed key is a compile error here, never a stale string. It is
164
+ * always a standalone function (a domain bootstrap belongs to the domain, not to
165
+ * a class) under THIS domain — you never write the origin, and the kernel
166
+ * resolves it relative to wherever the domain is installed.
167
+ */
168
+ postInstall?: AnyFunctionDef
102
169
  }
103
170
 
104
171
  export interface DomainDefinition {
@@ -114,11 +181,13 @@ export interface DomainDefinition {
114
181
  deps?: (env: any, url: string) => any
115
182
  views?: Record<string, ViewDef>
116
183
  functions?: Record<string, AnyFunctionDef>
117
- /** Normalized client binding (resolved `dir`), or absent when the domain has no SPA. */
118
- client?: { dir: string }
184
+ /** Presentation metadata for `/meta`, validated at definition time. */
185
+ manifest?: DomainManifest
119
186
  origin: string
120
187
  requires: readonly string[]
121
188
  postInstall?: string
189
+ /** Physical tree path for the Domain node; default `/domains/<origin>`. */
190
+ path?: string
122
191
  }
123
192
 
124
193
  export function defineDomain<S extends Schema, TDeps, TEnv = unknown>(
@@ -159,57 +228,119 @@ export function defineDomain<S extends Schema, TDeps, TEnv = unknown>(
159
228
  } catch {
160
229
  throw new Error(
161
230
  `defineDomain: invalid \`requires\` entry "${dep}". Use the dependency's origin slug ` +
162
- '(lowercase FQDN-like, e.g. "dist.astrale.ai"), not a URL.',
231
+ '(lowercase FQDN-like, e.g. "example.astrale.ai"), not a URL.',
163
232
  )
164
233
  }
165
234
  })
166
235
 
236
+ // `path`, when set, is the Domain's physical tree path. Validate the
237
+ // two invariants the serializer/kernel rely on, string-only (no AbsolutePath
238
+ // import — this module must stay worker-safe): it is absolute with at least one
239
+ // segment, and its LAST segment equals the origin (the kernel derives origin
240
+ // from the mount basename in a few places, e.g. has_parent slug + get-access-
241
+ // token origin recovery). Full structural parse happens at build time.
242
+ if (config.path !== undefined) {
243
+ const loc = config.path
244
+ if (!loc.startsWith('/') || loc === '/') {
245
+ throw new Error(
246
+ `defineDomain: \`path\` must be an absolute graph path with at least ` +
247
+ `one segment (e.g. "/domains/${origin}"), not "${loc}".`,
248
+ )
249
+ }
250
+ if (loc.split('/').pop() !== origin) {
251
+ throw new Error(
252
+ `defineDomain: \`path\` last segment must equal the origin "${origin}" ` +
253
+ `(e.g. "/domains/${origin}" or "/${origin}"), got "${loc}".`,
254
+ )
255
+ }
256
+ }
257
+
258
+ // A `manifest.entrypoint` view is resolved to its slug below (in the return),
259
+ // where a view absent from `views` throws — see `resolveManifest`.
260
+
261
+ // Declared roles must carry valid, unique slugs — a role slug becomes a graph
262
+ // path segment and an identity subject, so the kernel's reserved separators
263
+ // (`/`, `:`, `@`) can never appear in one. Fail at authoring time.
264
+ if (config.manifest?.roles !== undefined) {
265
+ const seen = new Set<string>()
266
+ for (const role of config.manifest.roles) {
267
+ if (!/^[a-z0-9][a-z0-9_-]*$/.test(role.slug)) {
268
+ throw new Error(
269
+ `defineDomain: invalid \`manifest.roles\` slug "${role.slug}". ` +
270
+ 'Use a lowercase slug (letters, digits, "_", "-"), e.g. "editor".',
271
+ )
272
+ }
273
+ if (seen.has(role.slug)) {
274
+ throw new Error(`defineDomain: duplicate \`manifest.roles\` slug "${role.slug}".`)
275
+ }
276
+ seen.add(role.slug)
277
+ }
278
+ }
279
+
167
280
  return {
168
281
  schema: config.schema,
169
282
  methods: config.methods as DomainDefinition['methods'],
170
283
  ...(config.deps ? { deps: config.deps as DomainDefinition['deps'] } : {}),
171
284
  ...(config.views ? { views: config.views as Record<string, ViewDef> } : {}),
172
285
  ...(config.functions ? { functions: config.functions } : {}),
173
- ...(config.client ? { client: config.client } : {}),
286
+ ...(config.manifest ? { manifest: resolveManifest(config.manifest, config.views) } : {}),
174
287
  origin,
175
288
  requires,
176
- ...(config.postInstall
177
- ? { postInstall: normalizePostInstall(config.postInstall, origin) }
289
+ ...(config.path ? { path: config.path } : {}),
290
+ ...(config.postInstall !== undefined
291
+ ? { postInstall: normalizePostInstall(config.postInstall, origin, config.functions) }
178
292
  : {}),
179
293
  }
180
294
  }
181
295
 
182
296
  /**
183
- * Validate a `postInstall` hook path and align its leading origin segment with
184
- * the canonical (lowercased) origin. Only the typed colon forms are accepted
185
- * (`/:<origin>:class.X:seed`, `/:<origin>:interface.Ops:seed`)mirroring the
186
- * kernel's own origin guard, which refuses absolute tree paths because they
187
- * cannot prove their origin from the string alone. The hook is often authored
188
- * as `` `/:${schema.domain}:…` `` where `schema.domain` keeps its source
189
- * casing, but the kernel stores graph nodes (and runs its guard) under the
190
- * lowercased origin — so the origin segment is re-stamped canonical. Rejects a
191
- * tree path or one pointing at a different domain (the kernel calls the hook
192
- * as __SYSTEM__ and refuses a foreign target).
297
+ * Lower the author-facing manifest (view OBJECT for `entrypoint`) to the served
298
+ * {@link DomainManifest} (slug for `entrypoint`). The entrypoint view is matched
299
+ * to its `views`-map key by IDENTITY the same mechanism `normalizePostInstall`
300
+ * uses for functions so a view absent from `views` (one from another domain,
301
+ * or dropped) throws here instead of dangling to a NOT_FOUND view path at install.
193
302
  */
194
- function normalizePostInstall(postInstall: string, origin: string): string {
195
- const slug = extractDomainSlug(postInstall)
196
- if (slug === null) {
303
+ function resolveManifest(
304
+ manifest: DomainManifestConfig,
305
+ views: Record<string, AnyViewDef> | undefined,
306
+ ): DomainManifest {
307
+ const { entrypoint, ...rest } = manifest
308
+ if (entrypoint === undefined) return rest
309
+ const slug = views ? Object.entries(views).find(([, def]) => def === entrypoint)?.[0] : undefined
310
+ if (slug === undefined) {
311
+ const viewKeys = Object.keys(views ?? {})
197
312
  throw new Error(
198
- `defineDomain: \`postInstall\` must be a typed colon-path under "/:${origin}" ` +
199
- `(e.g. "/:${origin}:class.Note:seed" or "/:${origin}:interface.Ops:seed"); ` +
200
- `absolute tree paths are not accepted got "${postInstall}".`,
313
+ "defineDomain: `manifest.entrypoint` must be one of this domain's own views " +
314
+ '(pass the view object, e.g. `entrypoint: views.welcome`). ' +
315
+ `Available views: ${viewKeys.length > 0 ? viewKeys.map((k) => `"${k}"`).join(', ') : '(none)'}.`,
201
316
  )
202
317
  }
203
- if (slug.toLowerCase() !== origin) {
318
+ return { ...rest, entrypoint: slug }
319
+ }
320
+
321
+ /**
322
+ * Resolve a `postInstall` function reference to the colon-path the bundle carries.
323
+ * The slug is the `functions` map key the reference is registered under (found by
324
+ * identity), so a renamed key is a compile error at the reference site AND the
325
+ * derived path follows the rename. The origin is never the author's to supply
326
+ * (postInstall is always a standalone function of THIS domain) and the path is
327
+ * mount-agnostic — the kernel resolves it wherever the domain is installed.
328
+ */
329
+ function normalizePostInstall(
330
+ postInstall: AnyFunctionDef,
331
+ origin: string,
332
+ functions: Record<string, AnyFunctionDef> | undefined,
333
+ ): string {
334
+ const slug = functions
335
+ ? Object.entries(functions).find(([, def]) => def === postInstall)?.[0]
336
+ : undefined
337
+ if (slug === undefined) {
204
338
  throw new Error(
205
- `defineDomain: \`postInstall\` "${postInstall}" must resolve under the domain origin ` +
206
- `"/${origin}" — the kernel calls it as __SYSTEM__ and refuses a hook pointing at another domain.`,
339
+ "defineDomain: `postInstall` must reference a function from this domain's `functions` map " +
340
+ '(e.g. `postInstall: functions.seed`).',
207
341
  )
208
342
  }
209
- // Re-stamp the origin segment with its canonical (lowercased) form.
210
- return postInstall.startsWith('/:')
211
- ? `/:${origin}${postInstall.slice(2 + slug.length)}`
212
- : `/${origin}${postInstall.slice(1 + slug.length)}`
343
+ return `/:${origin}:function.${slug}`
213
344
  }
214
345
 
215
346
  function schemaDomain(schema: Schema): string | undefined {
@@ -9,7 +9,7 @@
9
9
  * is authored in `astrale.config.ts` — a Node-only module the CLI loads but the
10
10
  * worker never imports — so the adapter stays out of the bundle.
11
11
  *
12
- * export const domain = defineDomain({ schema, methods, deps, views, client })
12
+ * export const domain = defineDomain({ schema, methods, deps, views })
13
13
  *
14
14
  * import { domain } from './domain'
15
15
  * export default deploy(domain, cloudflare({ dev, prod }))
@@ -14,7 +14,7 @@
14
14
  */
15
15
 
16
16
  export { defineDomain } from './define-domain'
17
- export type { ClientBinding, DefineDomainConfig, DomainDefinition } from './define-domain'
17
+ export type { DefineDomainConfig, DomainDefinition, DomainManifest } from './define-domain'
18
18
 
19
19
  export { deploy } from './deploy'
20
20
  export type { DeployConfig } from './deploy'
@@ -1,19 +1,20 @@
1
1
  /**
2
2
  * Authoring a standalone remote function — a callable not bound to a class.
3
3
  * (The verb keeps the `defineRemoteFunction` name; the node it materializes is
4
- * the canonical kernel `Function` class, the former distribution `RemoteFunction`.)
4
+ * the canonical kernel `Function` class, replacing the former remote-function wrapper.)
5
5
  *
6
6
  * Each entry in `defineRemoteDomain({ remoteFunctions: { ... } })` becomes:
7
- * - a graph node at `/${origin}/core/<functionsFolder>/<slug>`, materialized
8
- * as the kernel `Function` class by `buildCorePath` so kernel discovery /
9
- * `View.resolve` can list it. The `core/` anchor appears in the GRAPH path only.
7
+ * - a graph node at `/${origin}/functions/<slug>` — a first-class domain MEMBER,
8
+ * materialized as the kernel `Function` class and attached to the Domain via an
9
+ * `of_domain` edge (slug `function.<slug>`), so it is addressable by the
10
+ * semantic path `/:${origin}:function.<slug>` (the form a `postInstall` uses).
10
11
  * - a Hono route on the worker at the path implied by `binding`
11
- * (`/<functionsFolder>/<slug>` POST by default — no `core/` in the URL).
12
+ * (`/<functionsFolder>/<slug>` POST by default — the route URL is decoupled
13
+ * from the graph layout).
12
14
  *
13
- * The slug = the map key (single source of truth, no duplication).
14
- *
15
- * `ref` is auto-derived as `function.<slug>` if omittedused as
16
- * `Function.ref` on the graph node and as the dispatch key.
15
+ * The slug = the map key (single source of truth, no duplication). The member
16
+ * ref (`function.<slug>`), the layout (`/<origin>/functions/<slug>`), and the
17
+ * `of_domain` edge slug are all DERIVED from it there is nothing else to name.
17
18
  */
18
19
 
19
20
  import type { AuthPolicy, FunctionBinding } from '@astrale-os/kernel-api/routed'
@@ -22,6 +23,7 @@ import type { BoundClientSessionView } from '@astrale-os/kernel-client/session'
22
23
  import type { Context } from 'hono'
23
24
  import type { z } from 'zod'
24
25
 
26
+ import type { DomainIssuer } from '../auth/issuer-mint'
25
27
  import type { CallRemoteFn } from '../dispatch/call-remote'
26
28
  import type { AuthForPolicy, KernelForAuth } from '../method/context'
27
29
 
@@ -46,12 +48,20 @@ export type RemoteFunctionContext<
46
48
  /**
47
49
  * `BoundClientSessionView` to the parent kernel, bound to the composed
48
50
  * credential `union(delegation, self)` — same shape as `RemoteContext.kernel`
49
- * for `remoteMethod`. Nullability follows {@link KernelForAuth} of the
51
+ * for `remoteMethod`, including the typed graph read/write sugar flattened onto
52
+ * it (`kernel.get` / `kernel.children` / `kernel.query`,
53
+ * `kernel.createNode` / `kernel.mutate(patch)`, …) over the `function.get` /
54
+ * `function.mutate` syscalls. Nullability follows {@link KernelForAuth} of the
50
55
  * function's `auth`: non-null for the default `'required'`, `… | null` for
51
56
  * `'optional'`, `null` for `'public'` (use {@link RemoteFunctionContext.selfKernel}
52
57
  * there, after verifying the upstream).
53
58
  */
54
59
  kernel: TKernel
60
+ /**
61
+ * Mint seam for identities THIS DOMAIN issues — same contract as
62
+ * {@link RemoteContext.issuer}.
63
+ */
64
+ issuer: DomainIssuer
55
65
  /**
56
66
  * Call another worker's remote method, re-minting the credential for the
57
67
  * target's audience. Same contract as {@link RemoteContext.callRemote}: throws
@@ -77,12 +87,6 @@ export type RemoteFunctionDef<
77
87
  TDeps = unknown,
78
88
  TAuth extends AuthPolicy = 'required',
79
89
  > = {
80
- /**
81
- * Canonical callable identity. Auto-derived as `function.<slug>` (where
82
- * `<slug>` is the map key) when omitted. Stored as `Function.ref` on the
83
- * graph node and used by the kernel dispatcher to route the call.
84
- */
85
- ref?: string
86
90
  /** Zod schema for the call's parameters. */
87
91
  inputSchema: z.ZodType<TParams>
88
92
  /** Zod schema for the call's result. */
@@ -118,11 +122,42 @@ export type RemoteFunctionDef<
118
122
  description?: string
119
123
  }
120
124
 
121
- // `TAuth = AuthPolicy` (the full union, not the `'required'` default) keeps this
122
- // permissive across any declared policy; `ctx.kernel` widens to
123
- // `BoundClientSessionView<FnMap> | null`. Used where the concrete policy is erased.
124
- // oxlint-disable-next-line no-explicit-any
125
- export type AnyRemoteFunctionDef = RemoteFunctionDef<any, any, any, AuthPolicy>
125
+ // Loosely-typed "bag" for the per-domain function maps. The PRECISE typing is
126
+ // enforced at each `defineRemoteFunction` call site; this only has to HOLD defs
127
+ // of ANY auth policy. We deliberately do NOT pin a single concrete `TAuth`:
128
+ // `RemoteFunctionContext` carries `kernel: KernelForAuth<TAuth>` (an
129
+ // auth-dependent type that resolves to a concrete union), so any single concrete
130
+ // policy — `'required'` OR the full `AuthPolicy` union — rejects the others when
131
+ // a handler is assigned into the bag under parameter contravariance (a public
132
+ // handler wants `kernel: null`, a required one `kernel: Kernel`; neither fits a
133
+ // fixed bag). Relaxing the `authorize`/`execute` PARAMS to `any` is what makes
134
+ // the bag accept every policy; soundness is unchanged — the bag was already
135
+ // `any, any, any`, and the real contract is checked at the call site.
136
+ // oxlint-disable no-explicit-any
137
+ export type AnyRemoteFunctionDef = Omit<
138
+ RemoteFunctionDef<any, any, any, AuthPolicy>,
139
+ 'authorize' | 'execute'
140
+ > & {
141
+ authorize?: (ctx: any) => void | Promise<void>
142
+ execute: (ctx: any) => any
143
+ }
144
+ // oxlint-enable no-explicit-any
145
+
146
+ // Compile-time regression guards (pure `type` aliases — zero runtime emit, but
147
+ // checked by `pnpm typecheck` since this file is under `src/`). Each asserts a
148
+ // function of the given auth policy still FITS the bag. If `AnyRemoteFunctionDef`
149
+ // ever narrows back and rejects a policy, the matching `_Assert<false>` errors.
150
+ type _AssertTrue<T extends true> = T
151
+ type _FnFitsBag<D> = D extends AnyRemoteFunctionDef ? true : false
152
+ // Exported only so `noUnusedLocals` treats the assertions as used — it is NOT
153
+ // re-exported by `../define`, so it never reaches the package's public surface.
154
+ // A regression that makes the bag reject a policy turns the matching
155
+ // `_AssertTrue<false>` into a compile error here.
156
+ export type _AuthPolicyBagGuards = [
157
+ _AssertTrue<_FnFitsBag<RemoteFunctionDef<unknown, unknown, unknown, 'public'>>>,
158
+ _AssertTrue<_FnFitsBag<RemoteFunctionDef<unknown, unknown, unknown, 'optional'>>>,
159
+ _AssertTrue<_FnFitsBag<RemoteFunctionDef<unknown, unknown, unknown, 'required'>>>,
160
+ ]
126
161
 
127
162
  /**
128
163
  * Identity helper for authoring a RemoteFunction. Returns its argument
@@ -2,14 +2,16 @@
2
2
  * Authoring a `View` — iframe-mountable callable served by the domain worker.
3
3
  *
4
4
  * Each entry in `defineRemoteDomain({ views: { ... } })` becomes both:
5
- * - a graph node at `/${origin}/core/<viewsFolder>/<slug>` (auto-materialized
6
- * by the SDK; the `core/` segment is the universal anchor injected by
7
- * `buildCorePath`. `<slug>` is the map key, so it lives in exactly one place)
5
+ * - a first-class domain MEMBER: a `View` node at `/${origin}/views/<slug>`
6
+ * attached to the Domain via an `of_domain` edge (slug `view.<slug>`),
7
+ * addressable as `/:${origin}:view.<slug>`. `<slug>` is the map key, so it
8
+ * lives in exactly one place.
8
9
  * - a Hono route on the worker at the path implied by `binding`
9
- * (`/<viewsFolder>/<slug>` by default — note: no `core/` in the URL).
10
+ * (`/<viewsFolder>/<slug>` by default).
10
11
  *
11
- * The `core/` segment appears in the GRAPH path only; the URL path that
12
- * lands in `Function.binding.remoteUrl` is `${url}/<viewsFolder>/<slug>`.
12
+ * The graph layout (`/<origin>/views/<slug>`) is a fixed kernel convention; the
13
+ * URL path that lands in `Function.binding.remoteUrl` is
14
+ * `${url}/<viewsFolder>/<slug>` — DECOUPLED from the graph layout.
13
15
  *
14
16
  * The author can override the URL via `binding` — host and/or path
15
17
  * placeholders are supported (the kernel's `route` mechanism does the
@@ -6,14 +6,14 @@ import type { Meta } from './meta'
6
6
  import { MetaSchema } from './meta'
7
7
 
8
8
  export type DeployCheckOptions = {
9
- /** Deployed worker URL (e.g. `https://dist.astrale.ai`). */
9
+ /** Deployed worker URL (e.g. `https://example.astrale.ai`). */
10
10
  url: string
11
11
  /**
12
- * Expected `meta.schemaHash` — the hash of the locally-built `spec.json`
13
- * (via `hashSpecFile`). REQUIRED to verify schema drift: when the worker
14
- * advertises a `schemaHash` but this is absent, the check fails loudly rather
15
- * than silently "passing" an unverified deploy. Per-domain deploy scripts
16
- * compute it (`hashSpecFile('./spec.json')`) and pass it.
12
+ * Expected `meta.schemaHash` — the hash of the domain's install graph (via
13
+ * `buildInstallGraphHash(domain, url)`). REQUIRED to verify schema drift: when
14
+ * the worker advertises a `schemaHash` but this is absent, the check fails
15
+ * loudly rather than silently "passing" an unverified deploy. Per-domain
16
+ * deploy scripts compute it (`buildInstallGraphHash(...)`) and pass it.
17
17
  */
18
18
  expectedSchemaHash?: string
19
19
  /**
@@ -96,7 +96,7 @@ async function checkSchemaHash(
96
96
  if (!opts.expectedSchemaHash) {
97
97
  throw new Error(
98
98
  `worker advertises schemaHash=${meta.schemaHash} but no expectedSchemaHash was provided — ` +
99
- `cannot verify schema drift (pass expectedSchemaHash, e.g. hashSpecFile('./spec.json'))`,
99
+ `cannot verify schema drift (pass expectedSchemaHash, e.g. buildInstallGraphHash(domain, url))`,
100
100
  )
101
101
  }
102
102
  if (meta.schemaHash !== opts.expectedSchemaHash) {
@@ -1,3 +1,2 @@
1
1
  export { MetaSchema, type Meta } from './meta'
2
- export { hashSpecFile } from './hash-spec'
3
2
  export { deployCheck, type DeployCheckOptions } from './check'
@@ -11,6 +11,8 @@
11
11
  import { z } from 'zod'
12
12
 
13
13
  export const MetaSchema = z.object({
14
+ // The version of the meta payload. Incremented on any change to the payload
15
+ version: z.number().int().min(1).default(1),
14
16
  /** Base URL where JWKS is published. Always stamped by `createRemoteServer`
15
17
  * and required by `deployCheck` to verify JWKS reachability. */
16
18
  iss: z.string().min(1),
@@ -20,6 +22,29 @@ export const MetaSchema = z.object({
20
22
  schemaHash: z.string().optional(),
21
23
  /** Local directory name under `kernel/domains/<...>/` — used to auto-resolve the local spec. */
22
24
  domainName: z.string().optional(),
25
+ /**
26
+ * Optional presentation metadata declared on the domain (`defineDomain`):
27
+ * `logo` (inline SVG / `data:` URL), `entrypoint` (a view slug — the domain's
28
+ * entry surface), and the domain's declared `roles` (registered by a workspace
29
+ * at app install). Served verbatim so a host UI can brand a freshly-installed
30
+ * domain.
31
+ */
32
+ manifest: z
33
+ .object({
34
+ logo: z.string().optional(),
35
+ entrypoint: z.string().optional(),
36
+ roles: z
37
+ .array(
38
+ z.object({
39
+ slug: z.string().min(1),
40
+ name: z.string().optional(),
41
+ description: z.string().optional(),
42
+ default: z.boolean().optional(),
43
+ }),
44
+ )
45
+ .optional(),
46
+ })
47
+ .optional(),
23
48
  })
24
49
 
25
50
  export type Meta = z.infer<typeof MetaSchema>