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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (282) hide show
  1. package/README.md +16 -1
  2. package/dist/astrale.js +51105 -58057
  3. package/dist/public/connect-core.js +2085 -30702
  4. package/dist/public/keys/index.js +884 -29476
  5. package/dist/public/paths/index.js +13101 -41452
  6. package/dist/types/admin/contract.d.ts +26 -0
  7. package/dist/types/admin/instance/client.d.ts +20 -0
  8. package/dist/types/admin/instance/index.d.ts +2 -0
  9. package/dist/types/admin/instance/model.d.ts +6 -10
  10. package/dist/types/connection/auth.d.ts +3 -0
  11. package/dist/types/connection/call.d.ts +3 -0
  12. package/dist/types/connection/command.d.ts +27 -0
  13. package/dist/types/connection/credential.d.ts +14 -0
  14. package/dist/types/connection/errors.d.ts +1 -0
  15. package/dist/types/connection/exchange.d.ts +9 -0
  16. package/dist/types/connection/failure/classify.d.ts +2 -0
  17. package/dist/types/connection/failure/index.d.ts +5 -0
  18. package/dist/types/connection/failure/model.d.ts +25 -0
  19. package/dist/types/connection/failure/render.d.ts +3 -0
  20. package/dist/types/connection/index.d.ts +8 -0
  21. package/dist/types/connection/reasons.d.ts +37 -0
  22. package/dist/types/connection/self.d.ts +37 -0
  23. package/dist/types/connection/session.d.ts +31 -0
  24. package/dist/types/connection/target.d.ts +38 -0
  25. package/dist/types/errors.d.ts +1 -1
  26. package/dist/types/lib/admin-instance.d.ts +17 -0
  27. package/dist/types/lib/admin-target.d.ts +11 -36
  28. package/dist/types/lib/config.d.ts +7 -59
  29. package/dist/types/lib/failure-debug.d.ts +1 -0
  30. package/dist/types/lib/idp.d.ts +25 -173
  31. package/dist/types/lib/instance-target.d.ts +1 -1
  32. package/dist/types/lib/instance.d.ts +30 -88
  33. package/dist/types/lib/log.d.ts +5 -3
  34. package/dist/types/lib/output.d.ts +1 -1
  35. package/dist/types/lib/proc.d.ts +43 -0
  36. package/dist/types/lib/self.d.ts +9 -0
  37. package/dist/types/lib/update.d.ts +109 -0
  38. package/dist/types/lib/validation.d.ts +4 -1
  39. package/package.json +15 -20
  40. package/src/__tests__/fixtures/publication.ts +20 -0
  41. package/src/admin/.spec/architecture.md +12 -5
  42. package/src/admin/__tests__/fixture.ts +25 -0
  43. package/src/admin/catalog/.spec/api.d.ts +1 -3
  44. package/src/admin/catalog/.spec/architecture.md +5 -4
  45. package/src/admin/catalog/__tests__/client.test.ts +143 -69
  46. package/src/admin/catalog/client.ts +40 -54
  47. package/src/admin/catalog/model.ts +3 -4
  48. package/src/admin/contract.ts +46 -0
  49. package/src/admin/graph/.spec/api.d.ts +4 -10
  50. package/src/admin/graph/nodes.ts +1 -1
  51. package/src/admin/instance/.spec/api.d.ts +5 -9
  52. package/src/admin/instance/.spec/architecture.md +7 -7
  53. package/src/admin/instance/__tests__/client.test.ts +157 -175
  54. package/src/admin/instance/client.ts +39 -128
  55. package/src/admin/instance/index.ts +1 -2
  56. package/src/admin/instance/model.ts +12 -18
  57. package/src/commands/__tests__/admin-instance.test.ts +11 -8
  58. package/src/commands/__tests__/call-describe.test.ts +46 -33
  59. package/src/commands/__tests__/call.test.ts +34 -0
  60. package/src/commands/__tests__/domain-install-operation.test.ts +7 -22
  61. package/src/commands/__tests__/domain-install-owned.test.ts +1 -1
  62. package/src/commands/__tests__/domain-list.test.ts +5 -25
  63. package/src/commands/__tests__/install-direct.test.ts +115 -8
  64. package/src/commands/__tests__/install-identity-override.test.ts +3 -14
  65. package/src/commands/__tests__/instance-list-rows.test.ts +1 -1
  66. package/src/commands/__tests__/instance-status.test.ts +139 -0
  67. package/src/commands/__tests__/introspect-parse.test.ts +80 -1
  68. package/src/commands/__tests__/logs.test.ts +250 -2
  69. package/src/commands/__tests__/read-commands.test.ts +62 -10
  70. package/src/commands/__tests__/token-ttl.test.ts +49 -4
  71. package/src/commands/__tests__/update.test.ts +25 -3
  72. package/src/commands/__tests__/view.test.ts +49 -0
  73. package/src/commands/call-describe.ts +60 -68
  74. package/src/commands/call.ts +71 -59
  75. package/src/commands/domain/install.ts +54 -35
  76. package/src/commands/domain/list.ts +1 -1
  77. package/src/commands/domain/publish.ts +14 -11
  78. package/src/commands/domain/uninstall.ts +2 -2
  79. package/src/commands/get.ts +14 -15
  80. package/src/commands/identity/__tests__/register.test.ts +3 -3
  81. package/src/commands/identity/register.ts +5 -4
  82. package/src/commands/instance/create.ts +8 -16
  83. package/src/commands/instance/delete.ts +12 -9
  84. package/src/commands/instance/list.ts +2 -2
  85. package/src/commands/instance/status.ts +66 -20
  86. package/src/commands/instance/use.ts +16 -12
  87. package/src/commands/introspect.ts +48 -35
  88. package/src/commands/logs.ts +123 -33
  89. package/src/commands/mutate.ts +6 -5
  90. package/src/commands/query.ts +35 -32
  91. package/src/commands/token.ts +42 -27
  92. package/src/commands/ui/__tests__/commands.test.ts +152 -0
  93. package/src/commands/ui/add.ts +51 -0
  94. package/src/commands/ui/doctor.ts +13 -0
  95. package/src/commands/ui/init.ts +38 -0
  96. package/src/commands/ui/list.ts +26 -0
  97. package/src/commands/ui/preset-apply.ts +19 -0
  98. package/src/commands/ui/preset-list.ts +12 -0
  99. package/src/commands/ui/shared.ts +25 -0
  100. package/src/commands/update.ts +31 -11
  101. package/src/commands/view.ts +82 -37
  102. package/src/connection/.spec/api.d.ts +7 -8
  103. package/src/connection/.spec/architecture.md +5 -4
  104. package/src/connection/.spec/flows/session.ts +2 -2
  105. package/src/connection/.spec/laws/connection.ts +6 -2
  106. package/src/connection/.spec/layout.ts +1 -0
  107. package/src/connection/__tests__/credential.test.ts +20 -1
  108. package/src/connection/__tests__/errors.test.ts +27 -20
  109. package/src/connection/__tests__/exchange.test.ts +30 -1
  110. package/src/connection/__tests__/failure-fixtures.ts +39 -0
  111. package/src/connection/__tests__/failure-safety.test.ts +75 -0
  112. package/src/connection/__tests__/self.test.ts +31 -11
  113. package/src/connection/__tests__/session.test.ts +3 -3
  114. package/src/connection/__tests__/target.test.ts +1 -0
  115. package/src/connection/auth.ts +13 -0
  116. package/src/connection/call.ts +1 -1
  117. package/src/connection/credential.ts +2 -2
  118. package/src/connection/errors.ts +1 -355
  119. package/src/connection/exchange.ts +47 -23
  120. package/src/connection/failure/classify.ts +110 -0
  121. package/src/connection/failure/index.ts +18 -0
  122. package/src/connection/failure/model.ts +28 -0
  123. package/src/connection/failure/render.ts +116 -0
  124. package/src/connection/reasons.ts +16 -0
  125. package/src/connection/self.ts +39 -21
  126. package/src/connection/session.ts +19 -8
  127. package/src/errors.ts +3 -2
  128. package/src/graph/.spec/api.d.ts +12 -5
  129. package/src/graph/.spec/layout.ts +9 -1
  130. package/src/graph/__tests__/mutation.test.ts +2 -2
  131. package/src/graph/__tests__/query.test.ts +25 -6
  132. package/src/graph/class.ts +30 -0
  133. package/src/graph/index.ts +1 -0
  134. package/src/graph/query.ts +21 -32
  135. package/src/identity/.spec/api.d.ts +2 -2
  136. package/src/identity/__tests__/registration.test.ts +1 -1
  137. package/src/identity/registration.ts +3 -3
  138. package/src/keys/__tests__/keys.test.ts +4 -0
  139. package/src/keys/credential.ts +1 -0
  140. package/src/lib/__tests__/admin-target.test.ts +12 -0
  141. package/src/lib/__tests__/binary.test.ts +16 -1
  142. package/src/lib/__tests__/command-dx.test.ts +1 -1
  143. package/src/lib/__tests__/domain-publication.test.ts +10 -16
  144. package/src/lib/__tests__/idp.test.ts +34 -4
  145. package/src/lib/__tests__/instance-candidates.test.ts +1 -3
  146. package/src/lib/__tests__/instance-target.test.ts +7 -39
  147. package/src/lib/__tests__/log-errors.test.ts +64 -0
  148. package/src/lib/__tests__/studio-server-deps.test.ts +1 -1
  149. package/src/lib/__tests__/update.test.ts +264 -11
  150. package/src/lib/__tests__/view-assets.test.ts +14 -0
  151. package/src/lib/__tests__/view-external-open-intent.test.ts +150 -0
  152. package/src/lib/__tests__/view-external-open-origins.test.ts +27 -0
  153. package/src/lib/__tests__/view-open-intent.test.ts +0 -2
  154. package/src/lib/__tests__/view-server.test.ts +26 -3
  155. package/src/lib/__tests__/view-session.test.ts +2 -1
  156. package/src/lib/admin-instance.ts +14 -8
  157. package/src/lib/admin-target.ts +21 -2
  158. package/src/lib/binary.ts +22 -5
  159. package/src/lib/command-dx.ts +9 -9
  160. package/src/lib/domain-publication.ts +9 -7
  161. package/src/lib/failure-debug.ts +26 -0
  162. package/src/lib/idp.ts +34 -3
  163. package/src/lib/instance-candidates.ts +2 -2
  164. package/src/lib/instance-target.ts +3 -14
  165. package/src/lib/instance.ts +16 -11
  166. package/src/lib/log.ts +16 -9
  167. package/src/lib/output.ts +1 -1
  168. package/src/lib/proc.ts +7 -2
  169. package/src/lib/provision-instance.ts +34 -57
  170. package/src/lib/update.ts +298 -45
  171. package/src/lib/validation.ts +2 -2
  172. package/src/lib/view/assets.ts +11 -2
  173. package/src/lib/view/external-open-intent.ts +41 -0
  174. package/src/lib/view/external-open-origins.ts +37 -0
  175. package/src/lib/view/host-capabilities.ts +17 -0
  176. package/src/lib/view/resolve.ts +1 -1
  177. package/src/lib/view/server.ts +18 -3
  178. package/src/lib/view/session.ts +2 -0
  179. package/src/program/.spec/api.d.ts +1 -0
  180. package/src/program/__tests__/program.test.ts +163 -6
  181. package/src/program/argv.ts +14 -0
  182. package/src/program/build.ts +26 -4
  183. package/src/program/command.ts +1 -0
  184. package/src/program/index.ts +1 -0
  185. package/src/program/registry.ts +2 -1
  186. package/src/setup/__tests__/instance-step.test.ts +27 -10
  187. package/src/setup/steps/instance.ts +22 -15
  188. package/src/telemetry/__tests__/recorder.test.ts +1 -0
  189. package/src/telemetry/recorder.ts +9 -1
  190. package/src/ui/.spec/api.d.ts +14 -0
  191. package/src/ui/.spec/architecture.md +10 -0
  192. package/src/ui/.spec/laws.ts +36 -0
  193. package/src/ui/.spec/layout.ts +16 -0
  194. package/src/ui/__tests__/ui.test.ts +981 -0
  195. package/src/ui/index.ts +13 -0
  196. package/src/ui/lock.ts +87 -0
  197. package/src/ui/model.ts +83 -0
  198. package/src/ui/operations.ts +710 -0
  199. package/src/ui/project.ts +219 -0
  200. package/src/ui/release.ts +321 -0
  201. package/src/ui/runner.ts +18 -0
  202. package/studio/client/dist/assets/elk-api-DjmKsHXc.js +1 -0
  203. package/studio/client/dist/assets/index-7YHdeCxp.js +81 -0
  204. package/studio/client/dist/assets/index-C0FAnwNw.css +2 -0
  205. package/studio/client/dist/assets/rolldown-runtime-Dd_uD5pT.js +1 -0
  206. package/studio/client/dist/assets/schema-studio-BilUNb6Z.css +1 -0
  207. package/studio/client/dist/assets/schema-studio-CABpTfvH.js +8 -0
  208. package/studio/client/dist/index.html +3 -2
  209. package/studio/package.json +7 -13
  210. package/studio/server/agent/ask.test.ts +1 -1
  211. package/studio/server/agent/bridge/grant.test.ts +1 -1
  212. package/studio/server/agent/bridge/routes.test.ts +1 -1
  213. package/studio/server/agent/harness/claude/adapter.test.ts +15 -5
  214. package/studio/server/agent/harness/gateway/token.test.ts +1 -1
  215. package/studio/server/agent/harness/gateway/token.ts +3 -3
  216. package/studio/server/agent/prompts/anchors.test.ts +32 -36
  217. package/studio/server/agent/prompts/anchors.ts +20 -49
  218. package/studio/server/agent/prompts/system.test.ts +3 -2
  219. package/studio/server/agent/prompts/system.ts +3 -3
  220. package/studio/server/agent/routes.test.ts +1 -1
  221. package/studio/server/agent/run/coordinator.test.ts +1 -1
  222. package/studio/server/api/canvas.ts +0 -1
  223. package/studio/server/api-agent-loadout.test.ts +1 -1
  224. package/studio/server/api.test.ts +1 -1
  225. package/studio/server/cache.test.ts +23 -12
  226. package/studio/server/cache.ts +12 -15
  227. package/studio/server/domain.test.ts +50 -68
  228. package/studio/server/domain.ts +82 -63
  229. package/studio/server/handoff/copy.ts +1 -1
  230. package/studio/server/index.ts +1 -1
  231. package/studio/server/instances/probe.test.ts +1 -1
  232. package/studio/server/introspect/anatomy/views/routes.ts +46 -37
  233. package/studio/server/introspect/anatomy/views.ts +9 -14
  234. package/studio/server/introspect/anatomy-extras.test.ts +26 -42
  235. package/studio/server/introspect/anatomy.test.ts +22 -14
  236. package/studio/server/introspect/anatomy.ts +6 -9
  237. package/studio/server/introspect/bundle.ts +0 -3
  238. package/studio/server/introspect/canonical-schema.test.ts +94 -389
  239. package/studio/server/introspect/canonical-schema.ts +227 -457
  240. package/studio/server/introspect/core-extractor.ts +18 -127
  241. package/studio/server/introspect/core.ts +1 -2
  242. package/studio/server/introspect/diff.test.ts +11 -7
  243. package/studio/server/introspect/diff.ts +18 -55
  244. package/studio/server/introspect/extractor.ts +22 -117
  245. package/studio/server/introspect/overlay-tsmorph.test.ts +119 -240
  246. package/studio/server/introspect/overlay.test.ts +26 -54
  247. package/studio/server/introspect/overlay.ts +10 -37
  248. package/studio/server/introspect/revision.test.ts +1 -1
  249. package/studio/server/introspect/revision.ts +1 -1
  250. package/studio/server/introspect/runtime.test.ts +65 -183
  251. package/studio/server/introspect/runtime.ts +8 -25
  252. package/studio/server/introspect/schema-ir-json.test.ts +70 -0
  253. package/studio/server/introspect/schema-ir-json.ts +38 -68
  254. package/studio/server/introspect/schema-refs.test.ts +43 -88
  255. package/studio/server/introspect/schema-refs.ts +14 -49
  256. package/studio/server/introspect/source-overlay/handlers.ts +116 -636
  257. package/studio/server/introspect/source-overlay/kernel-calls.ts +0 -3
  258. package/studio/server/introspect/source-overlay/spans.ts +15 -45
  259. package/studio/server/state/baseline.test.ts +14 -9
  260. package/studio/server/state/baseline.ts +3 -5
  261. package/studio/server/state/visibility.ts +1 -5
  262. package/studio/server/views/model.ts +3 -3
  263. package/studio/server/views/target.test.ts +17 -74
  264. package/studio/server/views/target.ts +24 -50
  265. package/studio/server/watch.test.ts +11 -11
  266. package/studio/server/watch.ts +8 -4
  267. package/studio/server/workspace-watch.ts +2 -2
  268. package/studio/shared/contracts/schema.ts +38 -107
  269. package/studio/shared/contracts/surface.test.ts +13 -15
  270. package/studio/shared/contracts/workspace.ts +2 -11
  271. package/studio/shared/schema/identity.test.ts +20 -44
  272. package/studio/shared/schema/identity.ts +9 -23
  273. package/viewer/dist/main.js +30 -51
  274. package/.check-workspace.cjs +0 -40
  275. package/src/admin/binding.ts +0 -168
  276. package/src/commands/__tests__/instance-create-hosts.test.ts +0 -29
  277. package/studio/client/dist/assets/elk-api-lwhFo7vB.js +0 -1
  278. package/studio/client/dist/assets/index-B-c-smo9.js +0 -8
  279. package/studio/client/dist/assets/index-BckHuAWk.js +0 -81
  280. package/studio/client/dist/assets/index-C4aNQ6dz.css +0 -1
  281. package/studio/client/dist/assets/index-ChOr3yP0.css +0 -1
  282. package/studio/server/introspect/anatomy/views/legacy.ts +0 -232
@@ -36,6 +36,8 @@ let mutations: unknown[] = []
36
36
  let queryResult: unknown
37
37
  let getResult: unknown
38
38
  let mutationResult: unknown
39
+ let selfFailure: unknown
40
+ let selfContexts: unknown[] = []
39
41
 
40
42
  const queryMock = mock(async (ast: unknown, options?: unknown) => {
41
43
  queryCalls.push({ ast, options })
@@ -49,7 +51,9 @@ const mutateMock = mock(async (mutation: unknown) => {
49
51
  mutations.push(mutation)
50
52
  return mutationResult
51
53
  })
52
- const expandSelfInPathMock = mock(async (path: string) => {
54
+ const expandSelfInPathMock = mock(async (path: string, context: unknown) => {
55
+ selfContexts.push(context)
56
+ if (selfFailure !== undefined) throw selfFailure
53
57
  if (path === '@self') {
54
58
  return {
55
59
  path: '@expanded-self',
@@ -59,10 +63,11 @@ const expandSelfInPathMock = mock(async (path: string) => {
59
63
  return { path, meta: undefined }
60
64
  })
61
65
  const withSelfHintMock = mock(async (action: () => Promise<unknown>) => action())
66
+ const commandContext = {
67
+ graph: { query: queryMock, getOrThrow: getOrThrowMock, mutate: mutateMock },
68
+ }
62
69
  const runKernelCommandMock = mock(async (run: RunOpts) => {
63
- const result = await run.fn({
64
- graph: { query: queryMock, getOrThrow: getOrThrowMock, mutate: mutateMock },
65
- })
70
+ const result = await run.fn(commandContext)
66
71
  await run.format?.(result, run.opts, true)
67
72
  })
68
73
 
@@ -88,6 +93,8 @@ beforeEach(() => {
88
93
  }
89
94
  getResult = undefined
90
95
  mutationResult = { createdNodes: {} }
96
+ selfFailure = undefined
97
+ selfContexts = []
91
98
  queryMock.mockClear()
92
99
  getOrThrowMock.mockClear()
93
100
  mutateMock.mockClear()
@@ -161,7 +168,7 @@ describe('query command', () => {
161
168
  ])
162
169
  })
163
170
 
164
- test('authors a Definition source through the same canonical Query V6 call', async () => {
171
+ test('authors a Class source through the same canonical Query V6 call', async () => {
165
172
  const { queryCommand } = await import('../query')
166
173
 
167
174
  await queryCommand([], {
@@ -179,8 +186,8 @@ describe('query command', () => {
179
186
  kind: 'node',
180
187
  terms: [
181
188
  {
182
- kind: 'definition',
183
- definition: { origin: 'issues.astrale.ai', kind: 'class', name: 'Issue' },
189
+ kind: 'class',
190
+ class: { origin: 'issues.astrale.ai', kind: 'class', name: 'Issue' },
184
191
  },
185
192
  ],
186
193
  binding: 'n0',
@@ -193,6 +200,44 @@ describe('query command', () => {
193
200
  ])
194
201
  })
195
202
 
203
+ test('retains the opaque continuation in machine output without wrapping the graph result', async () => {
204
+ const { queryCommand } = await import('../query')
205
+ const result = {
206
+ kind: 'graph' as const,
207
+ graph: {
208
+ nodes: [{ id: 'issue-1', class: 'issues.astrale.ai:class.Issue', props: {} }],
209
+ edges: [],
210
+ },
211
+ selection: { kind: 'node' as const, ids: ['issue-1'] },
212
+ }
213
+ queryResult = {
214
+ result,
215
+ page: { next: 'opaque-next-page' },
216
+ }
217
+
218
+ await queryCommand([], {
219
+ json: true,
220
+ definition: '/:issues.astrale.ai:class.Issue',
221
+ limit: '1',
222
+ })
223
+
224
+ expect(JSON.parse(stdout)).toEqual({
225
+ ...result,
226
+ page: { next: 'opaque-next-page' },
227
+ })
228
+ })
229
+
230
+ test('resolves @self inside the command session and never relabels transport failure as input', async () => {
231
+ const { queryCommand } = await import('../query')
232
+ await queryCommand(['@self'], { json: true, limit: '2' })
233
+ expect(selfContexts).toEqual([commandContext])
234
+
235
+ const aggregate = new AggregateError([], '')
236
+ selfFailure = aggregate
237
+ await expect(queryCommand(['@self'], { json: true, limit: '2' })).rejects.toBe(aggregate)
238
+ expect(errors.join('\n')).not.toContain('INVALID_INPUT')
239
+ })
240
+
196
241
  /** @evidence TEST-CLI-QUERY-REJECTS-V1-BEFORE-CONNECTION */
197
242
  test('rejects a legacy AST before opening the command connection', async () => {
198
243
  const { queryCommand } = await import('../query')
@@ -215,7 +260,7 @@ describe('get command', () => {
215
260
  const { getCommand } = await import('../get')
216
261
  getResult = {
217
262
  id: 'note-1',
218
- class: '/:notes.example.dev:class.Note',
263
+ class: 'notes.example.dev:class.Note',
219
264
  props: { 'notes.example.dev:class.Note.property.title': 'Hello' },
220
265
  }
221
266
 
@@ -226,6 +271,13 @@ describe('get command', () => {
226
271
  expect(stdout).not.toContain('__labels')
227
272
  expect(stdout).not.toContain('classId')
228
273
  })
274
+
275
+ test('resolves @self through the same point-read command context', async () => {
276
+ const { getCommand } = await import('../get')
277
+ await getCommand('@self', { json: true })
278
+ expect(selfContexts).toEqual([commandContext])
279
+ expect(getTargets).toEqual(['@expanded-self'])
280
+ })
229
281
  })
230
282
 
231
283
  describe('mutate command', () => {
@@ -242,7 +294,7 @@ describe('mutate command', () => {
242
294
  {
243
295
  op: 'node.create',
244
296
  as: 'note',
245
- class: '/:notes.example.dev:class.Note',
297
+ class: 'notes.example.dev:class.Note',
246
298
  props: {},
247
299
  },
248
300
  ],
@@ -258,7 +310,7 @@ describe('mutate command', () => {
258
310
  {
259
311
  op: 'node.create',
260
312
  as: 'note',
261
- class: '/:notes.example.dev:class.Note',
313
+ class: 'notes.example.dev:class.Note',
262
314
  props: {},
263
315
  },
264
316
  ],
@@ -1,11 +1,13 @@
1
- import { describe, expect, test } from 'bun:test'
1
+ import type { AuthApi, Identity, IssuerId, MintedCredential } from '@astrale-os/sdk/auth'
2
+
3
+ import { describe, expect, mock, test } from 'bun:test'
2
4
 
3
5
  import { AstraleError } from '../../errors'
4
- import { parseTtl } from '../token'
6
+ import { issueToken, parseTtl } from '../token'
5
7
 
6
8
  describe('parseTtl', () => {
7
- test('defaults to 3600 seconds', () => {
8
- expect(parseTtl(undefined)).toBe(3600)
9
+ test('defaults below the five-minute local source-credential lifetime', () => {
10
+ expect(parseTtl(undefined)).toBe(240)
9
11
  })
10
12
 
11
13
  test('rejects non-positive and non-integer values', () => {
@@ -19,3 +21,46 @@ describe('parseTtl', () => {
19
21
  expect(parseTtl('90')).toBe(90)
20
22
  })
21
23
  })
24
+
25
+ describe('issueToken', () => {
26
+ const kernel = 'https://kernel.test' as IssuerId
27
+ const identity = {
28
+ id: 'caller' as Identity['id'],
29
+ issuer: 'https://issuer.test' as Identity['issuer'],
30
+ subject: 'caller' as Identity['subject'],
31
+ frozen: false,
32
+ requiredClaims: [],
33
+ } satisfies Identity
34
+
35
+ test('mints a reusable top-level credential for the Kernel audience', async () => {
36
+ const mint = mock(async () => 'kernel-token' as MintedCredential)
37
+ const delegate = mock(async () => 'delegated-token' as MintedCredential)
38
+ const whoami = mock(async () => identity)
39
+ const auth = { mint, delegate, whoami } satisfies Pick<AuthApi, 'delegate' | 'mint' | 'whoami'>
40
+
41
+ await expect(issueToken(auth, kernel, kernel, 90)).resolves.toBe(
42
+ 'kernel-token' as MintedCredential,
43
+ )
44
+ expect(mint).toHaveBeenCalledWith({ ttlSeconds: 90 })
45
+ expect(whoami).not.toHaveBeenCalled()
46
+ expect(delegate).not.toHaveBeenCalled()
47
+ })
48
+
49
+ test('delegates the selected identity only for an external audience', async () => {
50
+ const mint = mock(async () => 'kernel-token' as MintedCredential)
51
+ const delegate = mock(async () => 'delegated-token' as MintedCredential)
52
+ const whoami = mock(async () => identity)
53
+ const auth = { mint, delegate, whoami } satisfies Pick<AuthApi, 'delegate' | 'mint' | 'whoami'>
54
+
55
+ await expect(issueToken(auth, kernel, 'https://service.test' as IssuerId, 120)).resolves.toBe(
56
+ 'delegated-token' as MintedCredential,
57
+ )
58
+ expect(mint).not.toHaveBeenCalled()
59
+ expect(whoami).toHaveBeenCalledTimes(1)
60
+ expect(delegate).toHaveBeenCalledWith(identity.id, {
61
+ audience: 'https://service.test',
62
+ ttlSeconds: 120,
63
+ attenuation: { kind: 'identity', self: true },
64
+ })
65
+ })
66
+ })
@@ -35,9 +35,11 @@ describe('CLI update staleness', () => {
35
35
  const result = await cliStale(
36
36
  { channel: 'canary' },
37
37
  {
38
- update: async () => {
39
- throw new Error('package managed')
40
- },
38
+ update: async () => ({
39
+ status: 'managed',
40
+ currentVersion: '1.0.0-beta.0',
41
+ executable: '/opt/homebrew/bin/node',
42
+ }),
41
43
  fetchPackageVersion: async ({ channel }) => {
42
44
  expect(channel).toBe('canary')
43
45
  return '1.0.0-canary.0'
@@ -52,6 +54,26 @@ describe('CLI update staleness', () => {
52
54
  })
53
55
  })
54
56
 
57
+ test('does not misclassify a script update failure as package-managed', async () => {
58
+ const fetchPackageVersion = mock(async () => '9.9.9')
59
+ const result = await cliStale(
60
+ {},
61
+ {
62
+ update: async () => {
63
+ throw new Error('release endpoint unavailable')
64
+ },
65
+ fetchPackageVersion,
66
+ },
67
+ )
68
+
69
+ expect(result).toMatchObject({
70
+ stale: false,
71
+ managed: false,
72
+ error: 'release endpoint unavailable',
73
+ })
74
+ expect(fetchPackageVersion).not.toHaveBeenCalled()
75
+ })
76
+
55
77
  test('maps the default to beta and the stable channel to npm latest', async () => {
56
78
  const originalFetch = globalThis.fetch
57
79
  const fetchMock = mock(async (input: RequestInfo | URL) => {
@@ -147,3 +147,52 @@ describe('view capture timing', () => {
147
147
  expect(commands.length).toBeGreaterThan(2)
148
148
  })
149
149
  })
150
+
151
+ describe('view session runtime', () => {
152
+ test('builds one serve config with the admitted operator origin grant', async () => {
153
+ const { createViewServeConfig } = await import('../view')
154
+ const record = {
155
+ id: 'v-proof',
156
+ pid: 0,
157
+ port: 4419,
158
+ nonce: 'proof',
159
+ pageUrl: 'http://127.0.0.1:4419/s/proof/',
160
+ view: { target: Path.parse('/:ai-gateway.astrale.ai').raw, route: resolved[0] },
161
+ createdAt: '2026-08-26T00:00:00.000Z',
162
+ }
163
+
164
+ const config = createViewServeConfig(
165
+ record,
166
+ { allowExternalOrigin: ['https://connect.nango.dev/'] },
167
+ { url: 'https://kernel.test', kernelIssuer: 'https://kernel.test' },
168
+ )
169
+
170
+ expect(config.session).toBe(record)
171
+ expect(config.externalOrigins).toEqual(['https://connect.nango.dev'])
172
+ expect(config.proxy).toEqual({
173
+ kernelUrl: 'https://kernel.test',
174
+ issuer: 'https://kernel.test',
175
+ caFile: undefined,
176
+ direct: true,
177
+ })
178
+ })
179
+
180
+ test('spawns the detached server from a compiled executable without its virtual Bun entry', async () => {
181
+ const { resolveServeRuntime, viewServeInvocation } = await import('../view')
182
+
183
+ const runtime = await resolveServeRuntime({
184
+ executable: '/opt/astrale/bin/astrale',
185
+ entry: '/$bunfs/root/astrale',
186
+ exists: () => true,
187
+ find: async () => '/usr/bin/node',
188
+ })
189
+ expect(runtime).toEqual({
190
+ file: '/opt/astrale/bin/astrale',
191
+ args: [],
192
+ })
193
+ expect(viewServeInvocation(runtime, '/tmp/view.config.json')).toEqual({
194
+ file: '/opt/astrale/bin/astrale',
195
+ args: ['__view-serve', '--config', '/tmp/view.config.json'],
196
+ })
197
+ })
198
+ })
@@ -1,8 +1,9 @@
1
1
  import type { Path } from '@astrale-os/sdk/graph/path'
2
+ import type { ResolvedFunction, ResolvedMethod } from '@astrale-os/sdk/schema'
2
3
 
3
- import { AstraleError } from '../errors'
4
+ import { bundle, schema } from '@astrale-os/sdk/schema'
4
5
 
5
- type JsonRecord = Record<string, unknown>
6
+ import { AstraleError } from '../errors'
6
7
 
7
8
  export interface CallableDescription {
8
9
  readonly path: string
@@ -18,40 +19,40 @@ export interface CallableDescription {
18
19
  readonly candidates?: readonly CallableDescription[]
19
20
  }
20
21
 
21
- /** Resolve one callable's input/output from an installed Domain schema document. */
22
- export function describeCallableFromSchema(
22
+ /** Resolve one callable from an admitted installed Domain bundle. */
23
+ export function describeCallableFromBundle(
23
24
  path: Path,
24
- schema: unknown,
25
+ input: unknown,
25
26
  ): CallableDescription | undefined {
26
27
  if (path.ast.anchor.kind !== 'domain') return undefined
27
28
  const origin = path.ast.anchor.origin
28
29
  const last = path.ast.steps.at(-1)
29
- const document = asRecord(schema)
30
- if (document === undefined || last === undefined) return undefined
30
+ if (last === undefined) return undefined
31
+ const domain = schema.resolve(bundle.accept(input).root)
32
+ if (domain.origin !== origin) return undefined
31
33
 
32
34
  if (last.kind === 'method') {
33
- const owner = path.ast.steps.at(-2)
34
- if (
35
- owner?.kind === 'projection' &&
36
- (owner.projection.kind === 'class' || owner.projection.kind === 'interface')
37
- ) {
38
- const bag = asRecord(
39
- owner.projection.kind === 'class' ? document.classes : document.interfaces,
35
+ const ownerStep = path.ast.steps.at(-2)
36
+ if (ownerStep?.kind === 'projection' && ownerStep.projection.kind === 'class') {
37
+ const owner = domain.classes[ownerStep.projection.name]
38
+ if (owner?.kind !== 'node') return undefined
39
+ const method = namedMethod(
40
+ last.dispatch === 'static' ? owner.static.methods : owner.methods,
41
+ last.name,
42
+ last.dispatch,
40
43
  )
41
- const definition = asRecord(bag?.[owner.projection.name])
42
- const method = asRecord(asRecord(definition?.methods)?.[last.name])
43
- if (method !== undefined) {
44
- return Object.freeze({
45
- path: path.raw,
46
- origin,
47
- class: owner.projection.name,
48
- method: last.name,
49
- dispatch: last.dispatch,
50
- ...callableFields(method),
51
- })
52
- }
44
+ return method === undefined
45
+ ? undefined
46
+ : description(path, origin, owner.ref.name, method, last.dispatch)
53
47
  }
54
- const matches = findNamedMethods(document, origin, path.raw, last.name, last.dispatch)
48
+
49
+ const matches = Object.values(domain.classes).flatMap((owner) => {
50
+ if (owner.kind !== 'node') return []
51
+ const method = namedMethod(owner.methods, last.name, last.dispatch)
52
+ return method === undefined
53
+ ? []
54
+ : [description(path, origin, owner.ref.name, method, last.dispatch)]
55
+ })
55
56
  if (matches.length === 1) return matches[0]
56
57
  if (matches.length > 1) {
57
58
  return Object.freeze({
@@ -66,13 +67,13 @@ export function describeCallableFromSchema(
66
67
  }
67
68
 
68
69
  if (last.kind === 'projection' && last.projection.kind === 'function') {
69
- const fn = asRecord(asRecord(document.functions)?.[last.projection.name])
70
- if (fn === undefined) return undefined
70
+ const callable = domain.functions[last.projection.name]
71
+ if (callable === undefined) return undefined
71
72
  return Object.freeze({
72
73
  path: path.raw,
73
74
  origin,
74
- function: last.projection.name,
75
- ...callableFields(fn),
75
+ function: callable.ref.name,
76
+ ...callableFields(callable),
76
77
  })
77
78
  }
78
79
  return undefined
@@ -82,50 +83,41 @@ export function missingCallableDescription(path: string): AstraleError {
82
83
  return new AstraleError(
83
84
  'CALL_DESCRIBE_UNAVAILABLE',
84
85
  `No callable schema is installed for ${path}.`,
85
- 'Use a Domain-rooted Path such as /:host.astrale.ai:class.Manager:createInstance. Method Paths are not Function nodes.',
86
+ 'Use a Domain-rooted Path such as /:kernel.astrale.ai:class.Identity:whois. Method Paths are not Function nodes.',
86
87
  )
87
88
  }
88
89
 
89
- function findNamedMethods(
90
- schema: JsonRecord,
90
+ function namedMethod(
91
+ methods: Iterable<ResolvedMethod>,
92
+ name: string,
93
+ dispatch: 'static' | 'instance',
94
+ ): ResolvedMethod | undefined {
95
+ const expectedStatic = dispatch === 'static'
96
+ return [...methods].find((method) => method.name === name && method.static === expectedStatic)
97
+ }
98
+
99
+ function description(
100
+ path: Path,
91
101
  origin: string,
92
- path: string,
93
- method: string,
102
+ className: string,
103
+ callable: ResolvedMethod,
94
104
  dispatch: 'static' | 'instance',
95
- ): CallableDescription[] {
96
- const matches: CallableDescription[] = []
97
- for (const bag of [schema.classes, schema.interfaces]) {
98
- const definitions = asRecord(bag)
99
- if (definitions === undefined) continue
100
- for (const [className, definition] of Object.entries(definitions)) {
101
- const methodDef = asRecord(asRecord(asRecord(definition)?.methods)?.[method])
102
- if (methodDef === undefined) continue
103
- matches.push(
104
- Object.freeze({
105
- path,
106
- origin,
107
- class: className,
108
- method,
109
- dispatch,
110
- ...callableFields(methodDef),
111
- }),
112
- )
113
- }
114
- }
115
- return matches
105
+ ): CallableDescription {
106
+ return Object.freeze({
107
+ path: path.raw,
108
+ origin,
109
+ class: className,
110
+ method: callable.name,
111
+ dispatch,
112
+ ...callableFields(callable),
113
+ })
116
114
  }
117
115
 
118
- function callableFields(value: JsonRecord): Partial<CallableDescription> {
116
+ function callableFields(callable: ResolvedFunction | ResolvedMethod): Partial<CallableDescription> {
119
117
  return {
120
- ...(typeof value.description === 'string' ? { description: value.description } : {}),
121
- ...(value.auth === undefined ? {} : { auth: value.auth }),
122
- ...(value.input === undefined ? {} : { input: value.input }),
123
- ...(value.output === undefined ? {} : { output: value.output }),
118
+ ...(callable.description === undefined ? {} : { description: callable.description }),
119
+ auth: callable.auth,
120
+ input: callable.input,
121
+ output: callable.output,
124
122
  }
125
123
  }
126
-
127
- function asRecord(input: unknown): JsonRecord | undefined {
128
- return input !== null && typeof input === 'object' && !Array.isArray(input)
129
- ? (input as JsonRecord)
130
- : undefined
131
- }