@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
@@ -0,0 +1,116 @@
1
+ import chalk from 'chalk'
2
+
3
+ import type { OperationRecovery } from '../command'
4
+ import type { FailureDiagnostic } from './model'
5
+
6
+ import { log } from '../../lib/log'
7
+ import {
8
+ functionInputIssues,
9
+ queryInputRepair,
10
+ reasonCode,
11
+ schemaDataRemovalHint,
12
+ schemaUpgradeDetails,
13
+ schemaUpgradeHint,
14
+ } from '../reasons'
15
+
16
+ export function renderFailure(
17
+ failure: FailureDiagnostic,
18
+ machine: boolean,
19
+ url: string,
20
+ recovery: OperationRecovery | undefined,
21
+ ): void {
22
+ if (failure.kind === 'transport') return renderTransport(failure, machine, url, recovery)
23
+ if (failure.kind === 'response') return renderResponse(failure, machine)
24
+ if (machine)
25
+ return writeRaw({ error: failure.code, message: failure.message, hint: failure.hint })
26
+ log.error(`${chalk.bold(failure.code)}: ${failure.message}`)
27
+ if (failure.hint) log.dim(` ${failure.hint}`)
28
+ }
29
+
30
+ function renderTransport(
31
+ failure: Extract<FailureDiagnostic, { kind: 'transport' }>,
32
+ machine: boolean,
33
+ url: string,
34
+ recovery: OperationRecovery | undefined,
35
+ ): void {
36
+ const invocation = failure.context.kind === 'invocation' ? failure.context : undefined
37
+ if (machine) {
38
+ writeRaw({
39
+ error: failure.code,
40
+ message: failure.message,
41
+ phase: failure.phase,
42
+ transport: failure.context,
43
+ ...(url === '' ? {} : { url }),
44
+ ...(invocation?.delivery === 'unknown' && recovery !== undefined ? recovery : {}),
45
+ })
46
+ return
47
+ }
48
+ log.error(`${chalk.bold(failure.code)}: ${failure.message}`)
49
+ if (url) log.dim(` target: ${url}`)
50
+ log.dim(` phase: ${failure.phase}`)
51
+ if (failure.phase === 'connect') log.dim(' Check the target and run `astrale status`.')
52
+ else if (failure.phase === 'timeout') log.dim(' Try increasing `--timeout`.')
53
+ else if (invocation?.delivery === 'unknown') renderRecovery(recovery)
54
+ }
55
+
56
+ function renderResponse(
57
+ failure: Extract<FailureDiagnostic, { kind: 'response' }>,
58
+ machine: boolean,
59
+ ): void {
60
+ const code = reasonCode(failure.reason)
61
+ const issues = functionInputIssues(failure.reason)
62
+ const removalHint = schemaDataRemovalHint(failure.reason)
63
+ const addressNotPublic = code === 'SCHEMA_DOMAIN_ADDRESS_NOT_PUBLIC'
64
+ const upgrade = schemaUpgradeDetails(failure.reason)
65
+ const message = addressNotPublic
66
+ ? 'Expose the Domain through a public HTTPS URL or public tunnel, then retry.'
67
+ : removalHint && !machine
68
+ ? 'Existing business data still uses schema definitions being removed.'
69
+ : failure.message
70
+ const hint =
71
+ code === 'FUNCTION_INPUT_INVALID' && (machine || issues.length === 0)
72
+ ? 'Use `astrale introspect <path>` to see the callable input.'
73
+ : (removalHint ?? (upgrade ? schemaUpgradeHint(upgrade) : undefined))
74
+ if (machine) {
75
+ writeRaw({
76
+ error: 'RESPONSE_ERROR',
77
+ code: failure.code,
78
+ message,
79
+ ...(failure.reason === undefined ? {} : { reason: failure.reason }),
80
+ ...(hint === undefined ? {} : { hint }),
81
+ })
82
+ return
83
+ }
84
+ const label = addressNotPublic
85
+ ? 'SCHEMA_DOMAIN_ADDRESS_NOT_PUBLIC'
86
+ : removalHint
87
+ ? 'DATA_MIGRATION_REQUIRED'
88
+ : `RESPONSE_ERROR(${failure.code})`
89
+ log.error(`${chalk.bold(label)}: ${message}`)
90
+ if (code && !addressNotPublic && !removalHint) log.dim(` reason: ${code}`)
91
+ for (const issue of issues)
92
+ console.log(
93
+ chalk.red(` ${issue.path || '<input>'}: ${issue.message} (${chalk.dim(issue.code)})`),
94
+ )
95
+ const repair = queryInputRepair(failure.reason)
96
+ if (repair?.phase === 'plan') log.dim(` ${repair.path ?? '/'} ${repair.issue}`)
97
+ else if (repair?.phase === 'limit')
98
+ log.dim(` ${repair.path ?? '/'} ${repair.limit} limit ${repair.actual}/${repair.maximum}`)
99
+ else if (repair) log.dim(` ${repair.path} ${repair.phase} input`)
100
+ if (upgrade?.issue === 'issuer-changed') {
101
+ log.dim(` installed issuer: ${upgrade.installedIssuer}`)
102
+ log.dim(` replacement issuer: ${upgrade.replacementIssuer}`)
103
+ }
104
+ if (hint) log.dim(` ${hint}`)
105
+ }
106
+
107
+ function renderRecovery(recovery: OperationRecovery | undefined): void {
108
+ if (!recovery)
109
+ return log.dim(' Delivery is unknown; do not automatically retry a mutating call.')
110
+ log.dim(' Delivery is unknown; retry with the same operation id:')
111
+ log.dim(` operation: ${recovery.operation}`)
112
+ log.dim(` ${recovery.retry}`)
113
+ }
114
+
115
+ const writeRaw = (payload: Record<string, unknown>): void =>
116
+ void process.stderr.write(`${JSON.stringify(payload)}\n`)
@@ -150,6 +150,22 @@ export function schemaUpgradeHint(details: SchemaUpgradeDetails): string {
150
150
  )
151
151
  }
152
152
 
153
+ export function schemaDataRemovalHint(reason: unknown): string | undefined {
154
+ if (!reasonWithCode(reason, 'DATA_MIGRATION_REQUIRED')) return undefined
155
+ const requirements = reason.details.requirements
156
+ if (
157
+ !Array.isArray(requirements) ||
158
+ requirements.length === 0 ||
159
+ !requirements.every(
160
+ (item) =>
161
+ record(item) && item.operation === 'remove-facts' && item.reason === 'destructive-change',
162
+ )
163
+ ) {
164
+ return undefined
165
+ }
166
+ return 'Delete this data explicitly, then retry. No data was deleted.'
167
+ }
168
+
153
169
  function reasonWithCode(
154
170
  input: unknown,
155
171
  code: string,
@@ -1,7 +1,9 @@
1
- import type { KernelCommandOpts } from './command'
1
+ import { NodeUnavailableError } from '@astrale-os/sdk/client'
2
2
 
3
+ import type { ConnectionContext } from './session'
4
+
5
+ import { AstraleError } from '../errors'
3
6
  import { containsSelfRef, expandSelfReferences } from '../lib/self'
4
- import { withClientSession } from './session'
5
7
 
6
8
  /** Metadata attached to errors after an authenticated caller-authored @self expansion. */
7
9
  export interface SelfExpansionMeta {
@@ -12,7 +14,7 @@ export interface SelfExpansionMeta {
12
14
  }
13
15
 
14
16
  /**
15
- * Run `action`; if it throws a node NotFoundError, retain the authenticated
17
+ * Run `action`; if it throws NodeUnavailableError, retain the authenticated
16
18
  * expansion for precise diagnostics. The error is re-thrown either way.
17
19
  */
18
20
  export async function withSelfHint<T>(
@@ -23,7 +25,7 @@ export async function withSelfHint<T>(
23
25
  try {
24
26
  return await action()
25
27
  } catch (err) {
26
- if (err instanceof Error && err.name === 'NotFoundError') {
28
+ if (err instanceof NodeUnavailableError) {
27
29
  ;(err as Error & { expandedFromSelf?: SelfExpansionMeta }).expandedFromSelf = meta
28
30
  }
29
31
  throw err
@@ -36,40 +38,39 @@ type AuthenticatedSelf = {
36
38
  }
37
39
 
38
40
  export type ResolveSelfIdDependencies = {
39
- readonly whoami?: (opts: KernelCommandOpts) => Promise<AuthenticatedSelf>
41
+ readonly whoami?: (context: ConnectionContext) => Promise<AuthenticatedSelf>
40
42
  }
41
43
 
42
44
  /** Resolve @self only from the effective principal authenticated by the selected Kernel. */
43
45
  export async function resolveSelfIdAuthenticated(
44
- opts: KernelCommandOpts,
46
+ context: ConnectionContext,
45
47
  dependencies: ResolveSelfIdDependencies = {},
46
48
  ): Promise<{ readonly id: string; readonly slug?: string }> {
47
- const resolved = await (dependencies.whoami ?? whoamiSelfId)(opts)
49
+ const resolved = await (dependencies.whoami ?? whoamiSelfId)(context)
48
50
  if (typeof resolved.id !== 'string' || resolved.id.trim().length === 0) {
49
- const error = new Error(
51
+ throw new AstraleError(
52
+ 'SELF_RESOLUTION_FAILED',
50
53
  '`@self` could not be resolved: authenticated Identity.whoami returned no NodeId.',
51
54
  )
52
- error.name = 'SelfResolutionError'
53
- throw error
54
55
  }
55
56
  return resolved.slug === undefined
56
57
  ? { id: resolved.id }
57
58
  : { id: resolved.id, slug: resolved.slug }
58
59
  }
59
60
 
60
- async function whoamiSelfId(opts: KernelCommandOpts): Promise<AuthenticatedSelf> {
61
- return withClientSession(opts, async ({ auth, target }) => ({
61
+ async function whoamiSelfId({ auth, target }: ConnectionContext): Promise<AuthenticatedSelf> {
62
+ return {
62
63
  ...(await auth.whoami()),
63
64
  ...(target.slug === undefined ? {} : { slug: target.slug }),
64
- }))
65
+ }
65
66
  }
66
67
 
67
68
  /** Expand @self in path-like commands and return metadata for NotFound diagnostics. */
68
69
  export async function expandSelfInPath(
69
70
  path: string,
70
- options: KernelCommandOpts,
71
+ context: ConnectionContext,
71
72
  ): Promise<{ readonly path: string; readonly meta?: SelfExpansionMeta }> {
72
- const expanded = await expandSelfValues(path, [], options)
73
+ const expanded = await expandSelfValues(path, [], context)
73
74
  return expanded.meta === undefined
74
75
  ? { path: expanded.path }
75
76
  : { path: expanded.path, meta: expanded.meta }
@@ -78,20 +79,37 @@ export async function expandSelfInPath(
78
79
  /** Expand @self once across one Call path and its CLI-authored string parameters. */
79
80
  export async function expandSelfInCall(
80
81
  path: string,
81
- parameters: readonly string[],
82
- options: KernelCommandOpts,
82
+ parameters: Readonly<Record<string, unknown>>,
83
+ context: ConnectionContext,
83
84
  ): Promise<{
84
85
  readonly path: string
85
- readonly parameters: readonly string[]
86
+ readonly parameters: Readonly<Record<string, unknown>>
86
87
  readonly meta?: SelfExpansionMeta
87
88
  }> {
88
- return expandSelfValues(path, parameters, options)
89
+ const values = Object.values(parameters).filter(
90
+ (value): value is string => typeof value === 'string',
91
+ )
92
+ const expanded = await expandSelfValues(path, values, context)
93
+ if (expanded.parameters === values) {
94
+ return expanded.meta === undefined
95
+ ? { path: expanded.path, parameters }
96
+ : { path: expanded.path, parameters, meta: expanded.meta }
97
+ }
98
+ let index = 0
99
+ const entries = Object.entries(parameters).map(([key, value]) => [
100
+ key,
101
+ typeof value === 'string' ? expanded.parameters[index++] : value,
102
+ ])
103
+ const resolved = Object.freeze(Object.fromEntries(entries))
104
+ return expanded.meta === undefined
105
+ ? { path: expanded.path, parameters: resolved }
106
+ : { path: expanded.path, parameters: resolved, meta: expanded.meta }
89
107
  }
90
108
 
91
109
  async function expandSelfValues(
92
110
  path: string,
93
111
  parameters: readonly string[],
94
- options: KernelCommandOpts,
112
+ context: ConnectionContext,
95
113
  ): Promise<{
96
114
  readonly path: string
97
115
  readonly parameters: readonly string[]
@@ -100,7 +118,7 @@ async function expandSelfValues(
100
118
  if (!containsSelfRef(path) && !parameters.some(containsSelfRef)) {
101
119
  return { path, parameters }
102
120
  }
103
- const self = await resolveSelfIdAuthenticated(options)
121
+ const self = await resolveSelfIdAuthenticated(context)
104
122
  const expandedPath = expandSelfReferences(path, self.id)
105
123
  const expandedParameters = parameters.map((parameter) => expandSelfReferences(parameter, self.id))
106
124
  const changed =
@@ -1,11 +1,11 @@
1
- import type { AuthApi } from '@astrale-os/kernel-client/auth'
2
- import type { GraphApi } from '@astrale-os/kernel-client/graph'
3
- import type { ClientSessionOptions, SessionAuth } from '@astrale-os/kernel-client/session'
1
+ import type { AuthApi } from '@astrale-os/sdk/auth'
2
+ import type { GraphApi } from '@astrale-os/sdk/client'
3
+ import type { ClientSessionOptions, SessionAuth } from '@astrale-os/sdk/client/session'
4
4
 
5
- import { call } from '@astrale-os/kernel-client'
6
- import { createAuth } from '@astrale-os/kernel-client/auth'
7
- import { createGraph } from '@astrale-os/kernel-client/graph'
8
- import { ClientSession } from '@astrale-os/kernel-client/session'
5
+ import { createAuth } from '@astrale-os/sdk/auth'
6
+ import { call } from '@astrale-os/sdk/client'
7
+ import { createGraph } from '@astrale-os/sdk/client'
8
+ import { ClientSession } from '@astrale-os/sdk/client/session'
9
9
 
10
10
  import type { AstraleConfig } from '../lib/config'
11
11
  import type { AdminConnectionOptions, ConnectionOptions, ConnectionTarget } from './target'
@@ -21,6 +21,7 @@ import { fetchWithCaFile } from '../lib/ca-fetch'
21
21
  import { readConfig } from '../lib/config'
22
22
  import { log } from '../lib/log'
23
23
  import { isMachine } from '../lib/output'
24
+ import { bindCredentialIdentity } from './auth'
24
25
  import { createCliCredential, validateCredentialSelection } from './credential'
25
26
  import { resolveAdminConnectionTarget, resolveConnectionTarget } from './target'
26
27
 
@@ -38,6 +39,8 @@ export interface ConnectionContext {
38
39
  readonly graph: GraphApi
39
40
  readonly auth: AuthApi
40
41
  readonly target: ConnectionTarget
42
+ /** Local identity label selected for this session; absent for raw credentials or anonymous use. */
43
+ readonly identity?: string
41
44
  }
42
45
 
43
46
  interface OwnedConnection {
@@ -63,6 +66,7 @@ export async function withClientSession<Value>(
63
66
  const target = await resolveConnectionTarget(options, config, {
64
67
  managed: (slug) => lookupManagedInstance(slug, options),
65
68
  })
69
+ options = await bindCredentialIdentity(options, target)
66
70
  warnMissingExplicitTarget(options, target)
67
71
  return runResolvedClientSession(target, timeoutMs, options, config, action, openConnection)
68
72
  }
@@ -76,6 +80,7 @@ export async function withAdminClientSession<Value>(
76
80
  const timeoutMs = resolveTimeoutMs(options.timeout)
77
81
  const config = await readConfig()
78
82
  const target = await resolveAdminConnectionTarget(options, config)
83
+ options = await bindCredentialIdentity(options, target)
79
84
  return runResolvedClientSession(target, timeoutMs, options, config, action, openConnection)
80
85
  }
81
86
 
@@ -141,7 +146,13 @@ function openConnection(
141
146
  const graph = createGraph((call, request) => session.call(call, request))
142
147
  const authApi = createAuth((path, input, request) => session.call(call(path, input), request))
143
148
  return {
144
- context: Object.freeze({ session, graph, auth: authApi, target }),
149
+ context: Object.freeze({
150
+ session,
151
+ graph,
152
+ auth: authApi,
153
+ target,
154
+ ...(options.as === undefined ? {} : { identity: options.as }),
155
+ }),
145
156
  close() {
146
157
  session.close()
147
158
  },
package/src/errors.ts CHANGED
@@ -2,8 +2,9 @@ export class AstraleError extends Error {
2
2
  code: string
3
3
  hint?: string
4
4
 
5
- constructor(code: string, message: string, hint?: string) {
6
- super(message)
5
+ constructor(code: string, message: string, hint?: string, options?: ErrorOptions) {
6
+ if (message.trim() === '') throw new TypeError('CLI error message must be non-blank.')
7
+ super(message, options)
7
8
  this.name = 'AstraleError'
8
9
  this.code = code
9
10
  this.hint = hint
@@ -1,7 +1,14 @@
1
+ import type { ClassKey, ClassRef } from '@astrale-os/sdk/graph/class'
1
2
  import type { MutationAST } from '@astrale-os/sdk/mutation'
2
3
  import type { QueryAST, QueryDirection } from '@astrale-os/sdk/query'
3
4
 
4
- /** Untrusted CLI fields used to author or admit one exact Query V5 request. */
5
+ /** Admit one canonical Domain-rooted Class Path into its DSL coordinate. */
6
+ export function classReference(input: string, label: string): ClassRef
7
+
8
+ /** Admit one canonical Domain-rooted Class Path into its exact string identity. */
9
+ export function classKey(input: string, label: string): ClassKey
10
+
11
+ /** Untrusted CLI fields used to author or admit one exact Query V6 request. */
5
12
  export interface QueryCommandInput {
6
13
  readonly sources: readonly string[]
7
14
  readonly definition?: string
@@ -12,16 +19,16 @@ export interface QueryCommandInput {
12
19
  readonly cursor?: string
13
20
  }
14
21
 
15
- /** Canonical query plus the caller-bound continuation token kept outside the AST. */
22
+ /** Canonical query plus caller-bound pagination kept outside the AST. */
16
23
  export interface PreparedQuery {
17
24
  readonly ast: QueryAST
18
- readonly cursor?: string
25
+ readonly page: Readonly<{ readonly size: number; readonly after?: string }>
19
26
  }
20
27
 
21
- /** Admit canonical Query V5 or author its supported Path/Definition/one-edge CLI subset. */
28
+ /** Admit canonical Query V6 or author its supported Path/Class/one-edge CLI subset. */
22
29
  export function prepareQuery(input: QueryCommandInput): PreparedQuery
23
30
 
24
- /** Admit canonical Mutation V2 or its exact authoring input; legacy PatchData is rejected. */
31
+ /** Admit canonical Mutation V3 or its exact authoring input; legacy PatchData is rejected. */
25
32
  export function prepareMutation(input: unknown): MutationAST
26
33
 
27
34
  /** Return the user-facing leaf of one exact qualified Property key. */
@@ -1,6 +1,14 @@
1
1
  import { defineLayout } from '@astrale-os/spec/authoring'
2
2
 
3
3
  export default defineLayout({
4
- entries: ['__tests__/', 'index.ts', 'mutation.ts', 'projection.ts', 'query.ts', 'tsconfig.json'],
4
+ entries: [
5
+ '__tests__/',
6
+ 'class.ts',
7
+ 'index.ts',
8
+ 'mutation.ts',
9
+ 'projection.ts',
10
+ 'query.ts',
11
+ 'tsconfig.json',
12
+ ],
5
13
  exact: true,
6
14
  })
@@ -14,7 +14,7 @@ describe('prepareMutation', () => {
14
14
  {
15
15
  op: 'node.create',
16
16
  as: 'note',
17
- class: '/:notes.example.dev:class.Note',
17
+ class: 'notes.example.dev:class.Note',
18
18
  props: {},
19
19
  },
20
20
  ],
@@ -29,7 +29,7 @@ describe('prepareMutation', () => {
29
29
  {
30
30
  op: 'node.create',
31
31
  as: 'note',
32
- class: '/:notes.example.dev:class.Note',
32
+ class: 'notes.example.dev:class.Note',
33
33
  props: {},
34
34
  },
35
35
  ],
@@ -38,7 +38,7 @@ describe('prepareQuery', () => {
38
38
  })
39
39
 
40
40
  /** @evidence TEST-CLI-GRAPH-AUTHORS-DEFINITION-QUERY */
41
- test('authors an exact Definition source without backend query text', () => {
41
+ test('authors an exact Class source without backend query text', () => {
42
42
  const prepared = prepareQuery({
43
43
  sources: [],
44
44
  definition: '/:issues.astrale.ai:class.Issue',
@@ -52,8 +52,8 @@ describe('prepareQuery', () => {
52
52
  kind: 'node',
53
53
  terms: [
54
54
  {
55
- kind: 'definition',
56
- definition: { origin: 'issues.astrale.ai', kind: 'class', name: 'Issue' },
55
+ kind: 'class',
56
+ class: { origin: 'issues.astrale.ai', kind: 'class', name: 'Issue' },
57
57
  },
58
58
  ],
59
59
  binding: 'n0',
@@ -64,6 +64,25 @@ describe('prepareQuery', () => {
64
64
  expect(prepared.page).toEqual({ size: 201 })
65
65
  })
66
66
 
67
+ test('unions positional Paths and one Class source in authored order', () => {
68
+ const prepared = prepareQuery({
69
+ sources: ['@note'],
70
+ definition: '/:issues.astrale.ai:class.Issue',
71
+ })
72
+
73
+ expect(JSON.parse(JSON.stringify(prepared.ast.source))).toEqual({
74
+ kind: 'node',
75
+ terms: [
76
+ { kind: 'path', path: '@note' },
77
+ {
78
+ kind: 'class',
79
+ class: { origin: 'issues.astrale.ai', kind: 'class', name: 'Issue' },
80
+ },
81
+ ],
82
+ binding: 'n0',
83
+ })
84
+ })
85
+
67
86
  /** @evidence TEST-CLI-GRAPH-ADMITS-QUERY-V6-ORDERING */
68
87
  test('admits one exact Property-ordered Query V6 document', () => {
69
88
  const ast = {
@@ -73,8 +92,8 @@ describe('prepareQuery', () => {
73
92
  kind: 'node',
74
93
  terms: [
75
94
  {
76
- kind: 'definition',
77
- definition: { origin: 'notes.example.dev', kind: 'class', name: 'Note' },
95
+ kind: 'class',
96
+ class: { origin: 'notes.example.dev', kind: 'class', name: 'Note' },
78
97
  },
79
98
  ],
80
99
  binding: 'n0',
@@ -149,6 +168,6 @@ describe('prepareQuery', () => {
149
168
  ).toThrow('--limit must be a positive integer')
150
169
  expect(() =>
151
170
  prepareQuery({ sources: [], definition: '/:notes.example.dev:view.note' }),
152
- ).toThrow('--definition must be one canonical Class or Interface Path')
171
+ ).toThrow('--definition must be one canonical Class Path')
153
172
  })
154
173
  })
@@ -0,0 +1,30 @@
1
+ import type { ClassKey as ClassKeyValue, ClassRef } from '@astrale-os/sdk/graph/class'
2
+
3
+ import { ClassKey } from '@astrale-os/sdk/graph/class'
4
+ import { Path } from '@astrale-os/sdk/graph/path'
5
+
6
+ /** Admit one canonical Domain-rooted Class Path into its DSL coordinate. */
7
+ export function classReference(input: string, label: string): ClassRef {
8
+ const path = Path.parse(input)
9
+ const step = path.ast.steps[0]
10
+ if (
11
+ path.ast.anchor.kind !== 'domain' ||
12
+ path.ast.steps.length !== 1 ||
13
+ step?.kind !== 'projection' ||
14
+ step.projection.kind !== 'class'
15
+ ) {
16
+ throw new TypeError(`${label} must be one canonical Class Path`)
17
+ }
18
+ return ClassKey.ref(
19
+ ClassKey.of({
20
+ origin: path.ast.anchor.origin,
21
+ kind: 'class',
22
+ name: step.projection.name,
23
+ }),
24
+ )
25
+ }
26
+
27
+ /** Admit one canonical Domain-rooted Class Path into its exact string identity. */
28
+ export function classKey(input: string, label: string): ClassKeyValue {
29
+ return ClassKey.of(classReference(input, label))
30
+ }
@@ -1,3 +1,4 @@
1
+ export { classKey, classReference } from './class'
1
2
  export { prepareMutation } from './mutation'
2
3
  export { nodeProperty, unqualifyProperty } from './projection'
3
4
  export { prepareQuery, type PreparedQuery, type QueryCommandInput } from './query'
@@ -1,12 +1,15 @@
1
1
  import type {
2
+ NodeQueryBuilder,
2
3
  QueryAST as QueryASTValue,
3
- QueryDefinitionRef,
4
4
  QueryDirection,
5
+ QueryNodeInput,
5
6
  } from '@astrale-os/sdk/query'
6
7
 
7
8
  import { Path } from '@astrale-os/sdk/graph/path'
8
9
  import { Query, QueryAST } from '@astrale-os/sdk/query'
9
10
 
11
+ import { classReference } from './class'
12
+
10
13
  export interface QueryCommandInput {
11
14
  readonly sources: readonly string[]
12
15
  readonly definition?: string
@@ -51,43 +54,29 @@ export function prepareQuery(input: QueryCommandInput): PreparedQuery {
51
54
  }
52
55
 
53
56
  const paths = input.sources.map((source) => Path.parse(source))
54
- const definition = input.definition === undefined ? undefined : definitionRef(input.definition)
55
- const query =
56
- paths.length > 0
57
- ? Query.from({
58
- kind: 'node',
59
- paths: paths as [Path, ...Path[]],
60
- ...(definition === undefined ? {} : { definitions: [definition] }),
61
- })
62
- : Query.from({ kind: 'node', definitions: [definition!] })
57
+ const definition =
58
+ input.definition === undefined ? undefined : classReference(input.definition, '--definition')
59
+ const nodes = definition === undefined ? paths : [...paths, definition]
60
+ const query: NodeQueryBuilder<unknown> = Query.from({
61
+ nodes: nodes as [QueryNodeInput, ...QueryNodeInput[]],
62
+ })
63
63
  const ast =
64
64
  input.edge === undefined
65
- ? query.select({ kind: 'graph' })
66
- : query
67
- .expand({
68
- via: [definitionRef(input.edge)],
69
- direction: input.direction ?? 'outgoing',
70
- })
71
- .select({ kind: 'graph' })
65
+ ? query.select({ kind: 'graph', binding: query.node })
66
+ : expanded(query, input.edge, input.direction)
72
67
  return withPage(ast, limit, input.cursor)
73
68
  }
74
69
 
75
- function definitionRef(input: string): QueryDefinitionRef {
76
- const path = Path.parse(input)
77
- const step = path.ast.steps[0]
78
- if (
79
- path.ast.anchor.kind !== 'domain' ||
80
- path.ast.steps.length !== 1 ||
81
- step?.kind !== 'projection' ||
82
- (step.projection.kind !== 'class' && step.projection.kind !== 'interface')
83
- ) {
84
- throw new TypeError('--definition must be one canonical Class or Interface Path')
85
- }
86
- return Object.freeze({
87
- origin: path.ast.anchor.origin,
88
- kind: step.projection.kind,
89
- name: step.projection.name,
70
+ function expanded(
71
+ query: NodeQueryBuilder<unknown>,
72
+ edge: string,
73
+ direction: QueryDirection | undefined,
74
+ ): QueryASTValue {
75
+ const result = query.expand({
76
+ via: [classReference(edge, '--edge')],
77
+ direction: direction ?? 'outgoing',
90
78
  })
79
+ return result.select({ kind: 'graph', binding: result.node })
91
80
  }
92
81
 
93
82
  function positiveInteger(raw: string | undefined, name: string, fallback: number): number {
@@ -1,6 +1,6 @@
1
- import type { Call } from '@astrale-os/kernel-client'
2
1
  import type { ProvisionRequest } from '@astrale-os/sdk/auth'
3
- import type { LocalBinding } from '@astrale-os/sdk/graph/model'
2
+ import type { Call } from '@astrale-os/sdk/client'
3
+ import type { LocalBinding } from '@astrale-os/sdk/graph'
4
4
  import type { JWK } from 'jose'
5
5
 
6
6
  import type {
@@ -1,6 +1,6 @@
1
1
  import type { ProvisionRequest } from '@astrale-os/sdk/auth'
2
2
 
3
- import { LocalBinding } from '@astrale-os/sdk/graph/model'
3
+ import { LocalBinding } from '@astrale-os/sdk/graph'
4
4
  import { expect, test } from 'bun:test'
5
5
 
6
6
  import { submitIdentityProvision } from '../registration'
@@ -1,9 +1,9 @@
1
- import type { Call } from '@astrale-os/kernel-client'
2
1
  import type { ProvisionRequest } from '@astrale-os/sdk/auth'
3
- import type { LocalBinding } from '@astrale-os/sdk/graph/model'
2
+ import type { Call } from '@astrale-os/sdk/client'
3
+ import type { LocalBinding } from '@astrale-os/sdk/graph'
4
4
 
5
- import { call } from '@astrale-os/kernel-client'
6
5
  import { issuer } from '@astrale-os/sdk/auth'
6
+ import { call } from '@astrale-os/sdk/client'
7
7
  import { NodeId } from '@astrale-os/sdk/graph/node'
8
8
  import { Path } from '@astrale-os/sdk/graph/path'
9
9
 
@@ -62,6 +62,10 @@ describe('DESIGN — per-identity keys', () => {
62
62
  await persistKeypair('alice', { keysDir: tmp })
63
63
  const jwt = await signAs('alice', tmp)
64
64
  expect(jwt.split('.').length).toBe(3)
65
+ const claims = decodeJwt(jwt)
66
+ expect(claims.iat).toBeUndefined()
67
+ expect(claims.exp).toBeNumber()
68
+ expect((claims.exp as number) * 1_000).toBeGreaterThan(Date.now() + 299_000)
65
69
  })
66
70
 
67
71
  /** @evidence TEST-CLI-KEYS-DISTINGUISHES-KERNEL-ROOT-GRANT */
@@ -47,6 +47,7 @@ async function signIdentityCredential(options: {
47
47
  .setIssuer(options.issuer)
48
48
  .setSubject(options.subject)
49
49
  .setAudience(options.audience)
50
+ .setExpirationTime('5m')
50
51
  .sign(privateKey)
51
52
  }
52
53