@astrale-os/cli 0.8.1-alpha.0 → 0.8.1-alpha.2

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 (310) hide show
  1. package/dist/astrale.js +62959 -41238
  2. package/dist/public/connect-core.js +55030 -0
  3. package/dist/public/keys/index.js +45631 -0
  4. package/dist/public/paths/index.js +43990 -0
  5. package/dist/types/admin/instance/model.d.ts +40 -0
  6. package/dist/types/connect-core.d.ts +22 -0
  7. package/dist/types/connection/auth.d.ts +33 -0
  8. package/dist/types/errors.d.ts +23 -0
  9. package/dist/types/identity/registry.d.ts +25 -0
  10. package/dist/types/keys/algorithm.d.ts +6 -0
  11. package/dist/types/keys/credential.d.ts +21 -0
  12. package/dist/types/keys/index.d.ts +4 -0
  13. package/dist/types/keys/pair.d.ts +34 -0
  14. package/dist/types/lib/admin-target.d.ts +74 -0
  15. package/dist/types/lib/ca-fetch.d.ts +2 -0
  16. package/dist/types/lib/config.d.ts +71 -0
  17. package/dist/types/lib/format.d.ts +1 -0
  18. package/dist/types/lib/idp-session.d.ts +33 -0
  19. package/dist/types/lib/idp.d.ts +397 -0
  20. package/dist/types/lib/instance-target.d.ts +36 -0
  21. package/dist/types/lib/instance.d.ts +186 -0
  22. package/dist/types/lib/log.d.ts +30 -0
  23. package/dist/types/lib/login-flow.d.ts +49 -0
  24. package/dist/types/lib/meta.d.ts +36 -0
  25. package/dist/types/lib/output.d.ts +83 -0
  26. package/dist/types/lib/table.d.ts +18 -0
  27. package/dist/types/lib/validation.d.ts +11 -0
  28. package/dist/types/paths/index.d.ts +2 -0
  29. package/dist/types/state/exchange-credentials.d.ts +25 -0
  30. package/dist/types/state/files.d.ts +9 -0
  31. package/dist/types/state/identities.d.ts +42 -0
  32. package/dist/types/state/index.d.ts +8 -0
  33. package/dist/types/state/paths.d.ts +32 -0
  34. package/package.json +22 -7
  35. package/src/admin/.spec/api.d.ts +3 -0
  36. package/src/admin/.spec/architecture.md +15 -0
  37. package/src/admin/.spec/icon.svg +3 -0
  38. package/src/admin/.spec/layout.ts +6 -0
  39. package/src/admin/catalog/.spec/api.d.ts +65 -0
  40. package/src/admin/catalog/.spec/architecture.md +6 -0
  41. package/src/admin/catalog/.spec/icon.svg +3 -0
  42. package/src/admin/catalog/.spec/layout.ts +6 -0
  43. package/src/admin/catalog/__tests__/client.test.ts +171 -0
  44. package/src/admin/catalog/client.ts +214 -0
  45. package/src/admin/catalog/index.ts +13 -0
  46. package/src/admin/catalog/model.ts +42 -0
  47. package/src/admin/catalog/tsconfig.json +8 -0
  48. package/src/admin/graph/.spec/api.d.ts +28 -0
  49. package/src/admin/graph/.spec/architecture.md +5 -0
  50. package/src/admin/graph/.spec/icon.svg +3 -0
  51. package/src/admin/graph/.spec/layout.ts +3 -0
  52. package/src/admin/graph/index.ts +6 -0
  53. package/src/admin/graph/nodes.ts +56 -0
  54. package/src/admin/index.ts +2 -0
  55. package/src/admin/instance/.spec/api.d.ts +66 -0
  56. package/src/admin/instance/.spec/architecture.md +10 -0
  57. package/src/admin/instance/.spec/icon.svg +3 -0
  58. package/src/admin/instance/.spec/layout.ts +6 -0
  59. package/src/admin/instance/__tests__/client.test.ts +289 -0
  60. package/src/admin/instance/client.ts +298 -0
  61. package/src/admin/instance/index.ts +16 -0
  62. package/src/admin/instance/model.ts +65 -0
  63. package/src/admin/instance/tsconfig.json +8 -0
  64. package/src/commands/__tests__/admin-instance.test.ts +3 -36
  65. package/src/commands/__tests__/call.test.ts +21 -68
  66. package/src/commands/__tests__/domain-install-owned.test.ts +26 -27
  67. package/src/commands/__tests__/domain-list.test.ts +2 -6
  68. package/src/commands/__tests__/logs.test.ts +50 -98
  69. package/src/commands/__tests__/ls.test.ts +21 -17
  70. package/src/commands/__tests__/read-commands.test.ts +171 -101
  71. package/src/commands/__tests__/view.test.ts +100 -51
  72. package/src/commands/admin/status.ts +3 -3
  73. package/src/commands/admin/use.ts +29 -6
  74. package/src/commands/auth/login.ts +1 -1
  75. package/src/commands/auth/logout.ts +4 -2
  76. package/src/commands/auth/status.ts +2 -2
  77. package/src/commands/auth/token.ts +2 -2
  78. package/src/commands/browser.ts +1 -1
  79. package/src/commands/call.ts +55 -91
  80. package/src/commands/describe.ts +52 -147
  81. package/src/commands/domain/install.ts +81 -50
  82. package/src/commands/domain/list.ts +4 -8
  83. package/src/commands/domain/publish.ts +9 -31
  84. package/src/commands/get.ts +32 -65
  85. package/src/commands/identity/__tests__/register.test.ts +93 -0
  86. package/src/commands/identity/create.ts +2 -2
  87. package/src/commands/identity/delete.ts +2 -2
  88. package/src/commands/identity/export.ts +7 -33
  89. package/src/commands/identity/import.ts +21 -70
  90. package/src/commands/identity/list.ts +2 -2
  91. package/src/commands/identity/register.ts +148 -117
  92. package/src/commands/identity/sync.ts +2 -2
  93. package/src/commands/identity/unsync.ts +2 -2
  94. package/src/commands/identity/use.ts +2 -2
  95. package/src/commands/identity/whoami.ts +2 -2
  96. package/src/commands/idp/add.ts +1 -1
  97. package/src/commands/idp/list.ts +1 -1
  98. package/src/commands/idp/refresh.ts +1 -1
  99. package/src/commands/idp/remove.ts +2 -2
  100. package/src/commands/idp/show.ts +1 -1
  101. package/src/commands/instance/active.ts +2 -2
  102. package/src/commands/instance/bookmark.ts +8 -2
  103. package/src/commands/instance/create.ts +1 -1
  104. package/src/commands/instance/delete.ts +4 -10
  105. package/src/commands/instance/forget.ts +1 -1
  106. package/src/commands/instance/list.ts +3 -3
  107. package/src/commands/instance/status.ts +5 -7
  108. package/src/commands/instance/use.ts +3 -3
  109. package/src/commands/logs.ts +152 -279
  110. package/src/commands/ls.ts +78 -204
  111. package/src/commands/mutate.ts +70 -139
  112. package/src/commands/query.ts +100 -278
  113. package/src/commands/session/analyze.ts +1 -1
  114. package/src/commands/session/list.ts +1 -1
  115. package/src/commands/setup.ts +1 -1
  116. package/src/commands/status.ts +1 -1
  117. package/src/commands/studio.ts +1 -1
  118. package/src/commands/token.ts +26 -21
  119. package/src/commands/update.ts +1 -1
  120. package/src/commands/use.ts +2 -3
  121. package/src/commands/view-serve.ts +1 -1
  122. package/src/commands/view.ts +67 -109
  123. package/src/connect-core.test.ts +38 -0
  124. package/src/connect-core.ts +42 -0
  125. package/src/connection/.spec/api.d.ts +111 -0
  126. package/src/connection/.spec/architecture.md +41 -0
  127. package/src/connection/.spec/capabilities/connection.ts +13 -0
  128. package/src/connection/.spec/examples/call.ts +7 -0
  129. package/src/connection/.spec/flows/command.ts +20 -0
  130. package/src/connection/.spec/flows/session.ts +61 -0
  131. package/src/connection/.spec/laws/connection.ts +181 -0
  132. package/src/connection/.spec/layout.ts +19 -0
  133. package/src/{kernel → connection}/__tests__/auth.test.ts +26 -2
  134. package/src/connection/__tests__/ca-fetch.test.ts +53 -0
  135. package/src/connection/__tests__/call.test.ts +17 -0
  136. package/src/connection/__tests__/credential.test.ts +109 -0
  137. package/src/connection/__tests__/errors.test.ts +96 -0
  138. package/src/connection/__tests__/exchange.test.ts +224 -0
  139. package/src/connection/__tests__/fixtures/localhost-cert.pem +19 -0
  140. package/src/connection/__tests__/fixtures/localhost-key.base64 +1 -0
  141. package/src/connection/__tests__/managed.test.ts +39 -0
  142. package/src/connection/__tests__/self.test.ts +51 -0
  143. package/src/connection/__tests__/session.test.ts +140 -0
  144. package/src/connection/__tests__/target.test.ts +93 -0
  145. package/src/{kernel → connection}/auth.ts +8 -8
  146. package/src/connection/call.ts +7 -0
  147. package/src/{kernel/run.ts → connection/command.ts} +22 -13
  148. package/src/connection/credential.ts +114 -0
  149. package/src/{kernel → connection}/errors.ts +32 -9
  150. package/src/connection/exchange.ts +274 -0
  151. package/src/connection/index.ts +8 -0
  152. package/src/connection/self.ts +120 -0
  153. package/src/connection/session.ts +176 -0
  154. package/src/connection/target.ts +111 -0
  155. package/src/connection/tsconfig.json +6 -0
  156. package/src/graph/.spec/api.d.ts +34 -0
  157. package/src/graph/.spec/laws/documents.ts +24 -0
  158. package/src/graph/.spec/laws/projection.ts +13 -0
  159. package/src/graph/.spec/layout.ts +6 -0
  160. package/src/graph/__tests__/mutation.test.ts +43 -0
  161. package/src/graph/__tests__/projection.test.ts +22 -0
  162. package/src/graph/__tests__/query.test.ts +154 -0
  163. package/src/graph/index.ts +3 -0
  164. package/src/graph/mutation.ts +18 -0
  165. package/src/graph/projection.ts +21 -0
  166. package/src/graph/query.ts +108 -0
  167. package/src/graph/tsconfig.json +6 -0
  168. package/src/identity/.history/ard/bootstrap-root-transfer.md +57 -0
  169. package/src/identity/.history/ard/domain-mediated-registration.md +36 -0
  170. package/src/identity/.spec/api.d.ts +124 -0
  171. package/src/identity/.spec/architecture.md +33 -0
  172. package/src/identity/.spec/capabilities/identity.ts +7 -0
  173. package/src/identity/.spec/flows/bootstrap-root.ts +79 -0
  174. package/src/identity/.spec/flows/domain-mediated-registration.ts +51 -0
  175. package/src/identity/.spec/flows/import.ts +29 -0
  176. package/src/identity/.spec/laws/identity.ts +56 -0
  177. package/src/identity/.spec/layout.ts +13 -0
  178. package/src/identity/.spec/schemas/identity-export-v1.schema.json +25 -0
  179. package/src/identity/__tests__/fixtures/registry-journey.ts +53 -0
  180. package/src/identity/__tests__/registration.test.ts +64 -0
  181. package/src/identity/__tests__/registry.test.ts +70 -0
  182. package/src/identity/__tests__/transfer.test.ts +198 -0
  183. package/src/identity/index.ts +23 -0
  184. package/src/identity/registration.ts +72 -0
  185. package/src/identity/registry.ts +172 -0
  186. package/src/identity/transfer.ts +187 -0
  187. package/src/identity/tsconfig.json +6 -0
  188. package/src/index.ts +1 -0
  189. package/src/keys/.spec/api.d.ts +74 -0
  190. package/src/keys/.spec/architecture.md +13 -0
  191. package/src/keys/.spec/laws/keys.ts +43 -0
  192. package/src/keys/.spec/layout.ts +6 -0
  193. package/src/keys/__tests__/algorithm.test.ts +39 -0
  194. package/src/keys/__tests__/keys.test.ts +168 -0
  195. package/src/keys/algorithm.ts +33 -0
  196. package/src/keys/credential.ts +112 -0
  197. package/src/keys/index.ts +14 -0
  198. package/src/keys/pair.ts +219 -0
  199. package/src/keys/tsconfig.json +6 -0
  200. package/src/lib/__tests__/admin-target.test.ts +67 -7
  201. package/src/lib/__tests__/binary.test.ts +2 -3
  202. package/src/lib/__tests__/command-dx.test.ts +2 -2
  203. package/src/lib/__tests__/config.test.ts +3 -2
  204. package/src/lib/__tests__/idp.test.ts +4 -6
  205. package/src/lib/__tests__/instance-target.test.ts +7 -4
  206. package/src/lib/__tests__/local-status.test.ts +1 -1
  207. package/src/lib/__tests__/log.test.ts +31 -0
  208. package/src/lib/__tests__/meta.test.ts +55 -0
  209. package/src/lib/__tests__/sdk-deps.test.ts +1 -1
  210. package/src/lib/__tests__/self.test.ts +8 -230
  211. package/src/lib/__tests__/use-target.test.ts +1 -1
  212. package/src/lib/__tests__/view-open-intent.test.ts +78 -104
  213. package/src/lib/__tests__/view-session.test.ts +99 -0
  214. package/src/lib/admin-domain.ts +39 -25
  215. package/src/lib/admin-instance.ts +38 -43
  216. package/src/lib/admin-target.ts +67 -13
  217. package/src/lib/binary.ts +13 -34
  218. package/src/lib/browser.ts +1 -1
  219. package/src/{kernel → lib}/ca-fetch.ts +35 -27
  220. package/src/lib/config.ts +1 -1
  221. package/src/lib/idp-session.ts +1 -1
  222. package/src/lib/idp.ts +3 -8
  223. package/src/lib/instance-target.ts +12 -8
  224. package/src/lib/instance.ts +34 -2
  225. package/src/lib/local-status.ts +2 -2
  226. package/src/lib/log.ts +3 -1
  227. package/src/lib/login-flow.ts +32 -5
  228. package/src/lib/meta.ts +15 -11
  229. package/src/lib/provision-instance.ts +4 -12
  230. package/src/lib/self.ts +5 -147
  231. package/src/lib/update.ts +1 -1
  232. package/src/lib/use-target.ts +1 -1
  233. package/src/lib/validation.ts +2 -2
  234. package/src/lib/view/open-intent.ts +7 -37
  235. package/src/lib/view/port-allocation.ts +1 -1
  236. package/src/lib/view/resolve.ts +52 -45
  237. package/src/lib/view/server.ts +16 -33
  238. package/src/lib/view/session.ts +51 -22
  239. package/src/paths/.spec/api.d.ts +13 -0
  240. package/src/paths/.spec/architecture.md +5 -0
  241. package/src/paths/.spec/layout.ts +3 -0
  242. package/src/paths/index.ts +13 -0
  243. package/src/program/.spec/api.d.ts +42 -0
  244. package/src/program/.spec/architecture.md +18 -0
  245. package/src/program/.spec/laws/program.ts +29 -0
  246. package/src/program/.spec/layout.ts +14 -0
  247. package/src/program/__tests__/program.test.ts +359 -0
  248. package/src/program/build.ts +190 -0
  249. package/src/program/command.ts +42 -0
  250. package/src/program/index.ts +2 -0
  251. package/src/program/options.ts +40 -0
  252. package/src/{registry.ts → program/registry.ts} +5 -1
  253. package/src/program/tsconfig.json +6 -0
  254. package/src/setup/steps/admin.ts +18 -3
  255. package/src/setup/steps/instance.ts +1 -1
  256. package/src/state/.spec/api.d.ts +133 -0
  257. package/src/state/.spec/architecture.md +31 -0
  258. package/src/state/.spec/capabilities/state.ts +7 -0
  259. package/src/state/.spec/flows/identity-update.ts +22 -0
  260. package/src/state/.spec/laws/state.ts +73 -0
  261. package/src/state/.spec/layout.ts +14 -0
  262. package/src/state/__tests__/exchange-credentials.test.ts +188 -0
  263. package/src/state/__tests__/files.test.ts +113 -0
  264. package/src/state/__tests__/fixtures/identity-transition.ts +37 -0
  265. package/src/state/__tests__/identities.test.ts +142 -0
  266. package/src/state/__tests__/paths.test.ts +27 -0
  267. package/src/state/exchange-credentials.ts +219 -0
  268. package/src/state/files.ts +129 -0
  269. package/src/state/identities.ts +236 -0
  270. package/src/state/index.ts +29 -0
  271. package/src/state/paths.ts +61 -0
  272. package/src/state/tsconfig.json +6 -0
  273. package/src/telemetry/__tests__/redact.test.ts +3 -3
  274. package/src/telemetry/__tests__/trigger.test.ts +1 -1
  275. package/src/telemetry/settings.ts +1 -1
  276. package/src/telemetry/store.ts +1 -1
  277. package/src/test-utils.ts +1 -1
  278. package/studio/client/dist/assets/{elk-api-DVw5WDTH.js → elk-api-D0cBetPW.js} +1 -1
  279. package/studio/client/dist/assets/index-Dspir4w7.js +81 -0
  280. package/studio/client/dist/assets/{index-Dl709Ra1.js → index-bVD2KJgz.js} +4 -4
  281. package/studio/client/dist/index.html +1 -1
  282. package/studio/package.json +10 -1
  283. package/studio/server/introspect/extractor.ts +1 -1
  284. package/studio/server/state/views.test.ts +3 -7
  285. package/studio/server/state/views.ts +23 -30
  286. package/tsconfig.json +1 -1
  287. package/viewer/dist/main.js +65 -39
  288. package/src/command.ts +0 -42
  289. package/src/commands/__tests__/help-contract.test.ts +0 -164
  290. package/src/kernel/__tests__/client-owned-lookup.test.ts +0 -51
  291. package/src/kernel/__tests__/errors.test.ts +0 -43
  292. package/src/kernel/__tests__/expand.test.ts +0 -123
  293. package/src/kernel/client.ts +0 -214
  294. package/src/kernel/expand.ts +0 -192
  295. package/src/kernel/graph.ts +0 -96
  296. package/src/kernel/index.ts +0 -29
  297. package/src/kernel/options.ts +0 -22
  298. package/src/kernel/types.ts +0 -14
  299. package/src/lib/__tests__/domain-identity.test.ts +0 -60
  300. package/src/lib/__tests__/fs-atomic.test.ts +0 -104
  301. package/src/lib/__tests__/identity.test.ts +0 -79
  302. package/src/lib/__tests__/keys.test.ts +0 -129
  303. package/src/lib/domain-identity.ts +0 -49
  304. package/src/lib/env.ts +0 -49
  305. package/src/lib/fs-atomic.ts +0 -126
  306. package/src/lib/identity.ts +0 -256
  307. package/src/lib/keys.ts +0 -294
  308. package/src/lib/paths.ts +0 -11
  309. package/src/program.ts +0 -213
  310. package/studio/client/dist/assets/index-CTbkDp3z.js +0 -81
@@ -1,30 +1,15 @@
1
- import { compactDecrypt, importJWK } from 'jose'
2
- import { readFile, writeFile, mkdir } from 'node:fs/promises'
3
- import { dirname } from 'node:path'
1
+ import { readFile } from 'node:fs/promises'
4
2
 
5
- import type { CommandDefinition } from '../../command'
6
- import type { RegistryMode } from '../../lib/validation'
3
+ import type { CommandDefinition } from '../../program/index'
7
4
 
8
- import { createIdentity, upsertKeyIdentity } from '../../lib/identity'
9
- import { keypairPaths } from '../../lib/keys'
5
+ import {
6
+ decodeIdentityExport,
7
+ importIdentity,
8
+ isEncryptedIdentityExport,
9
+ } from '../../identity/index'
10
10
  import { fatal, log } from '../../lib/log'
11
11
  import { readPassphrase } from '../../lib/prompt'
12
12
 
13
- type ExportEnvelope = {
14
- version?: number
15
- subject: string
16
- mode?: RegistryMode
17
- kid?: string
18
- issuer?: string
19
- privateJwk: Record<string, unknown>
20
- publicJwk: Record<string, unknown>
21
- }
22
-
23
- function looksEncrypted(raw: string): boolean {
24
- // JOSE compact JWE is 5 base64 segments separated by `.`.
25
- return raw.trim().split('.').length === 5 && !raw.trim().startsWith('{')
26
- }
27
-
28
13
  export default {
29
14
  name: 'import',
30
15
  description: 'Import an identity keypair envelope (auto-detects JWE)',
@@ -46,54 +31,20 @@ export default {
46
31
  action: async (path: string, opts: { name?: string; issuer?: string; replace?: boolean }) => {
47
32
  try {
48
33
  const raw = await readFile(path, 'utf-8')
49
-
50
- let envelopeJson: string
51
- if (looksEncrypted(raw)) {
52
- const passphrase = await readPassphrase('Passphrase: ')
53
- const { plaintext } = await compactDecrypt(
54
- raw.trim(),
55
- new TextEncoder().encode(passphrase),
56
- {
57
- keyManagementAlgorithms: ['PBES2-HS256+A128KW'],
58
- },
59
- )
60
- envelopeJson = new TextDecoder().decode(plaintext)
61
- } else {
62
- envelopeJson = raw
63
- }
64
-
65
- const env = JSON.parse(envelopeJson) as ExportEnvelope
66
- if (!env?.subject || !env?.privateJwk || !env?.publicJwk) {
67
- fatal(new Error('Invalid envelope: missing subject / privateJwk / publicJwk'))
68
- }
69
-
70
- // Validate the keypair parses before touching the registry.
71
- await importJWK(env.privateJwk as never, 'ES256')
72
-
73
- const name = opts.name ?? env.subject
74
- // Create registry entry (without regenerating keys — we'll write the imported ones).
75
- if (opts.replace) {
76
- await upsertKeyIdentity(name, {
77
- subject: env.subject,
78
- mode: env.mode ?? 'local',
79
- issuer: opts.issuer ?? env.issuer,
80
- kid: env.kid,
81
- })
82
- } else {
83
- await createIdentity(name, {
84
- subject: env.subject,
85
- mode: env.mode ?? 'local',
86
- issuer: opts.issuer ?? env.issuer,
87
- kid: env.kid,
88
- skipKeygen: true,
89
- })
90
- }
91
- const { privatePath, publicPath } = keypairPaths(env.subject)
92
- await mkdir(dirname(privatePath), { recursive: true })
93
- await writeFile(privatePath, JSON.stringify(env.privateJwk, null, 2), { mode: 0o600 })
94
- await writeFile(publicPath, JSON.stringify(env.publicJwk, null, 2), { mode: 0o600 })
95
-
96
- log.success(`Imported identity "${name}" (subject=${env.subject}, kid=${env.kid ?? '?'})`)
34
+ const passphrase = isEncryptedIdentityExport(raw)
35
+ ? await readPassphrase('Passphrase: ')
36
+ : undefined
37
+ const envelope = await decodeIdentityExport(raw, passphrase)
38
+ const name = opts.name ?? envelope.subject
39
+ const identity = await importIdentity(envelope, {
40
+ name,
41
+ issuer: opts.issuer,
42
+ replace: opts.replace,
43
+ })
44
+
45
+ log.success(
46
+ `Imported identity "${name}" (subject=${identity.subject}, kid=${identity.kid ?? '?'})`,
47
+ )
97
48
  } catch (e) {
98
49
  fatal(e)
99
50
  }
@@ -1,9 +1,9 @@
1
1
  import chalk from 'chalk'
2
2
 
3
- import type { CommandDefinition } from '../../command'
4
3
  import type { ListOpts, ListProjection } from '../../lib/output'
4
+ import type { CommandDefinition } from '../../program/index'
5
5
 
6
- import { readIdentities } from '../../lib/identity'
6
+ import { readIdentities } from '../../identity/index'
7
7
  import { log } from '../../lib/log'
8
8
  import { isMachine, presentList, RAW_OUTPUT_OPTIONS } from '../../lib/output'
9
9
 
@@ -1,164 +1,195 @@
1
- import { importJWK, SignJWT, type JWK } from 'jose'
1
+ import type { IssuerId, JsonWebKey, ProvisionRequest } from '@astrale-os/sdk/auth'
2
+ import type { JWK } from 'jose'
3
+
4
+ import { provision, jwk } from '@astrale-os/sdk/auth'
5
+ import { ClassPath } from '@astrale-os/sdk/graph/class'
6
+ import { LocalBinding } from '@astrale-os/sdk/graph/model'
7
+ import { normalizeProperties } from '@astrale-os/sdk/graph/properties'
8
+ import { MutationAST } from '@astrale-os/sdk/mutation'
9
+ import { importJWK, SignJWT } from 'jose'
2
10
  import { readFile } from 'node:fs/promises'
3
11
 
4
- import type { CommandDefinition } from '../../command'
5
- import type { ClientContext, KernelCommandOpts } from '../../kernel'
12
+ import type { KernelCommandOpts } from '../../connection'
13
+ import type { IdentityRegistrationResult } from '../../identity/index'
14
+ import type { CommandDefinition } from '../../program/index'
6
15
 
7
- import { bindGraph, runKernelCommand } from '../../kernel'
8
- import { KERNEL_PASSTHROUGH_OPTIONS } from '../../kernel/options'
9
- import { getIdentity, setRegistration } from '../../lib/identity'
10
- import { getActive } from '../../lib/instance'
11
- import { fileExists, keypairPaths } from '../../lib/keys'
16
+ import { registrationKeyForTarget, runKernelCommand } from '../../connection'
17
+ import { getIdentity, setRegistration, submitIdentityProvision } from '../../identity/index'
18
+ import { fileExists, keypairPaths } from '../../keys/index'
12
19
  import { fatal, log } from '../../lib/log'
13
20
  import { output } from '../../lib/output'
14
21
 
15
- type RegisterIdentityResult = { iss: string; sub: string }
16
-
17
22
  type RegisterOpts = KernelCommandOpts & {
18
23
  class?: string
19
- path?: string
20
24
  props?: string
25
+ via?: string
21
26
  }
22
27
 
23
28
  async function readJwk(path: string): Promise<JWK> {
24
- const raw = await readFile(path, 'utf-8')
25
- return JSON.parse(raw) as JWK
26
- }
27
-
28
- /**
29
- * Discover the identity-bearer class on the target instance: find the
30
- * installed domain that owns a `User` class (workspace on managed instances)
31
- * and address it semantically.
32
- * The bearer class must exist before a non-root identity can be registered;
33
- * failing here names the real problem instead of surfacing as a confusing
34
- * permission error on a hardcoded origin.
35
- */
36
- async function resolveUserClassPath(ctx: ClientContext): Promise<string> {
37
- const graph = bindGraph(ctx)
38
- // Root's direct children are the installed Domain nodes.
39
- const result = await graph.children('/')
40
- const domains = result.nodes
41
- .filter((n) => n.class.className === 'Domain')
42
- .map((n) => n.path.raw.replace(/^\//, ''))
43
- .filter(Boolean)
44
- for (const origin of domains) {
45
- // The class materializes as a `class.User` Folder under the domain mount;
46
- // a resolving node means the domain declares it (null when absent).
47
- if (await graph.get(`/${origin}/class.User`)) {
48
- return `/:${origin}:class.User`
49
- }
50
- }
51
- throw new Error(
52
- 'No installed domain declares a `User` class on this instance — registering a ' +
53
- 'non-root identity needs an identity-bearer class (install workspace or another ' +
54
- 'domain, or pass --class <classPath> explicitly).',
55
- )
56
- }
57
-
58
- async function mintBootstrapJwt(privateJwk: JWK): Promise<string> {
59
- const kid = (privateJwk.kid as string | undefined) ?? 'bootstrap'
60
- const key = await importJWK(privateJwk, 'ES256')
61
- return new SignJWT({})
62
- .setProtectedHeader({ alg: 'ES256', kid })
63
- .setIssuer('self')
64
- .setSubject('bootstrap')
65
- .setAudience('bootstrap')
66
- .setIssuedAt()
67
- .setExpirationTime('5m')
68
- .sign(key)
29
+ return JSON.parse(await readFile(path, 'utf8')) as JWK
69
30
  }
70
31
 
71
32
  export default {
72
33
  name: 'register',
73
- description:
74
- "Register a local identity with the target instance's kernel — publishes the public " +
75
- 'key under a thumbprint-derived issuer and caches the resolved (iss, sub) for future calls',
76
- arguments: [{ name: 'name', description: 'Identity name', required: true }],
34
+ description: 'Atomically provision a local key identity and its V2 graph Node',
35
+ afterHelpText: `
36
+ Behavior:
37
+ Creates one Node through Mutation V3 and designates it as a self-proven
38
+ Identity in the same atomic Auth.provision request. --class is required;
39
+ --props must use fully-qualified Property keys owned by that Class.
40
+
41
+ By default the authenticated caller submits the request directly to Kernel
42
+ Auth.provision. Use --via for an application-owned identity Class: the CLI
43
+ sends the exact self-proven request through that Domain callable, then admits
44
+ its result and stores the same target-bound registration. The callable owns
45
+ authorization; the CLI never receives installed Domain authority.
46
+
47
+ Kernel V2 Nodes have opaque identity and no caller-assigned storage path, so
48
+ the historical --path option no longer exists. The CLI binds the key proof
49
+ to the exact provision fingerprint and target Kernel audience, then caches
50
+ the returned (issuer, subject) for subsequent calls.
51
+
52
+ Example:
53
+ $ astrale identity register alice --class /:accounts.example:class.User \
54
+ --props '{"accounts.example:class.User.property.name":"Alice"}' -i staging
55
+ $ astrale identity register responder --class /:ops.example:class.Operator \
56
+ --via /:ops.example:function.provisionOperator -i staging
57
+ `,
58
+ arguments: [{ name: 'name', description: 'Local identity name', required: true }],
77
59
  options: [
78
60
  {
79
61
  flags: '--class <classPath>',
80
- description:
81
- 'Class path of the identity node to create (default: the installed domain that declares class.User)',
62
+ description: 'Exact Class of the identity-bearing Node (required)',
82
63
  },
83
64
  {
84
- flags: '--path <nodePath>',
85
- description: 'Path of the new identity node (default: /workspace/users/<name>)',
65
+ flags: '--props <json>',
66
+ description: 'Fully-qualified canonical properties for the new Node',
86
67
  },
87
68
  {
88
- flags: '--props <json>',
89
- description:
90
- 'Extra props for the identity node (JSON). A User-class node defaults ' +
91
- 'firstName/lastName to the identity name when omitted.',
69
+ flags: '--via <callablePath>',
70
+ description: 'Submit the self-proven request through an authorizing Domain callable',
92
71
  },
93
- ...KERNEL_PASSTHROUGH_OPTIONS,
94
72
  ],
95
73
  action: async (name: string, opts: RegisterOpts) => {
96
74
  try {
75
+ if (!opts.class) throw new TypeError('Missing required flag: --class <classPath>')
97
76
  const identity = await getIdentity(name)
98
77
  const { privatePath, publicPath } = keypairPaths(identity.subject)
99
78
  if (!(await fileExists(privatePath)) || !(await fileExists(publicPath))) {
100
- fatal(
101
- new Error(
102
- `No keypair on disk for "${name}" (expected ${privatePath}). Recreate via \`astrale identity create ${name}\`.`,
103
- ),
79
+ throw new Error(
80
+ `No keypair on disk for "${name}" (expected ${privatePath}). Recreate it with \`astrale identity create ${name}\`.`,
104
81
  )
105
82
  }
106
- const privateJwk = await readJwk(privatePath)
107
- const publicJwk = await readJwk(publicPath)
108
- const nodePath = opts.path ?? `/workspace/users/${name}`
109
83
 
110
- await runKernelCommand({
84
+ const privateKey = await readJwk(privatePath)
85
+ const publicKey = jwk.acceptPublic(await readJwk(publicPath))
86
+ const classPath = ClassPath.parse(opts.class)
87
+ const properties = normalizeProperties(opts.props ? JSON.parse(opts.props) : {})
88
+
89
+ await runKernelCommand<IdentityRegistrationResult>({
111
90
  opts,
112
91
  label: `Register "${name}"`,
113
- fn: async (ctx) => {
114
- const instanceSlug = opts.instance ?? opts.url ?? (await getActive(ctx.config)).name
115
- const existing = identity.registrations?.[instanceSlug]
92
+ fn: async ({ auth, session, target }) => {
93
+ const registrationKey = registrationKeyForTarget(target)
94
+ const existing = identity.registrations?.[registrationKey]
116
95
  if (existing) {
117
- log.warn(`"${name}" already registered on "${instanceSlug}"`)
96
+ log.warn(`"${name}" is already registered on "${registrationKey}"`)
118
97
  return existing
119
98
  }
120
99
 
121
- const classPath = opts.class ?? (await resolveUserClassPath(ctx))
122
-
123
- // A bare `astrale identity create <name>` carries no profile, but a
124
- // User bearer class may require one. Default both names so a dev
125
- // registration works out of the box; `--props` overrides.
126
- const extraProps = opts.props ? (JSON.parse(opts.props) as Record<string, unknown>) : {}
127
- const userDefaults = classPath.endsWith(':class.User')
128
- ? { firstName: name, lastName: name }
129
- : {}
130
-
131
- // One-arm create through function.mutate; the minted node id comes
132
- // back in createdNodes (keyed by the `at` path).
133
- const nodeId = await bindGraph(ctx).createNode(classPath, nodePath, {
134
- 'Statused.status': 'creating',
135
- ...userDefaults,
136
- ...extraProps,
100
+ const prepared = await prepareIdentityProvision({
101
+ name,
102
+ classPath,
103
+ properties,
104
+ privateKey,
105
+ publicKey,
106
+ kernelIssuer: target.kernelIssuer,
137
107
  })
138
-
139
- const bootstrap = await mintBootstrapJwt(privateJwk)
140
- const result = (await ctx.client.call(`@${nodeId}::registerIdentity`, {
141
- signingKey: {
142
- publicKey: { jwk: publicJwk },
143
- credential: bootstrap,
144
- },
145
- })) as RegisterIdentityResult
146
-
147
- await setRegistration(name, instanceSlug, {
148
- iss: result.iss,
149
- sub: result.sub,
108
+ const registered = await submitIdentityProvision({
109
+ request: prepared.request,
110
+ binding: prepared.binding,
111
+ ...(opts.via === undefined ? {} : { via: opts.via }),
112
+ direct: auth,
113
+ callable: session,
114
+ })
115
+ await setRegistration(name, registrationKey, {
116
+ iss: registered.iss,
117
+ sub: registered.sub,
150
118
  registeredAt: new Date().toISOString(),
151
119
  })
152
- return result
153
- },
154
- format: (result, fmtOpts) => {
155
- output(result, fmtOpts)
156
- log.dim(` iss=${result.iss}`)
157
- log.dim(` sub=${result.sub}`)
120
+ return registered
158
121
  },
122
+ format: formatIdentityRegistration,
159
123
  })
160
- } catch (e) {
161
- fatal(e)
124
+ } catch (error) {
125
+ fatal(error, opts)
162
126
  }
163
127
  },
164
128
  } satisfies CommandDefinition
129
+
130
+ /** Preserve one structured stdout value while retaining useful detail in the human view. */
131
+ export function formatIdentityRegistration(
132
+ result: IdentityRegistrationResult,
133
+ format: KernelCommandOpts,
134
+ machine: boolean,
135
+ ): void {
136
+ output(result, format)
137
+ if (machine) return
138
+ log.dim(` iss=${result.iss}`)
139
+ log.dim(` sub=${result.sub}`)
140
+ }
141
+
142
+ /** Build and self-prove one exact canonical provision request for the target Kernel. */
143
+ export async function prepareIdentityProvision(input: {
144
+ readonly name: string
145
+ readonly classPath: ReturnType<typeof ClassPath.parse>
146
+ readonly properties: ReturnType<typeof normalizeProperties>
147
+ readonly privateKey: JWK
148
+ readonly publicKey: JsonWebKey
149
+ readonly kernelIssuer: IssuerId
150
+ }): Promise<{
151
+ readonly binding: ReturnType<typeof LocalBinding>
152
+ readonly request: ProvisionRequest
153
+ }> {
154
+ const binding = LocalBinding('identity')
155
+ const mutation = MutationAST.build((builder) => {
156
+ builder.createNode({ as: binding, class: input.classPath, props: input.properties })
157
+ return undefined
158
+ })
159
+ const idempotencyKey = `identity-register:${input.name}`
160
+ const unsigned = provision.accept({
161
+ idempotencyKey,
162
+ mutation,
163
+ identities: {
164
+ [binding]: { credentials: { publicKey: input.publicKey, proof: 'pending-proof' } },
165
+ },
166
+ })
167
+ const fingerprint = await provision.fingerprint(unsigned)
168
+ const issuer = await provision.selfIssuer(input.kernelIssuer, input.publicKey)
169
+ const proof = await mintProvisionProof(input.privateKey, issuer, input.kernelIssuer, fingerprint)
170
+ return {
171
+ binding,
172
+ request: provision.accept({
173
+ idempotencyKey,
174
+ mutation,
175
+ identities: { [binding]: { credentials: { publicKey: input.publicKey, proof } } },
176
+ }),
177
+ }
178
+ }
179
+
180
+ async function mintProvisionProof(
181
+ privateKey: JWK,
182
+ issuer: string,
183
+ audience: string,
184
+ fingerprint: string,
185
+ ): Promise<string> {
186
+ const key = await importJWK(privateKey, 'ES256')
187
+ return new SignJWT({ provision: fingerprint })
188
+ .setProtectedHeader({ alg: 'ES256', ...(privateKey.kid ? { kid: privateKey.kid } : {}) })
189
+ .setIssuer(issuer)
190
+ .setSubject('self')
191
+ .setAudience(audience)
192
+ .setIssuedAt()
193
+ .setExpirationTime('5m')
194
+ .sign(key)
195
+ }
@@ -1,6 +1,6 @@
1
- import type { CommandDefinition } from '../../command'
1
+ import type { CommandDefinition } from '../../program/index'
2
2
 
3
- import { getIdentity, setIdentityMode } from '../../lib/identity'
3
+ import { getIdentity, setIdentityMode } from '../../identity/index'
4
4
  import { fatal, fatalNotImplemented, log } from '../../lib/log'
5
5
 
6
6
  export default {
@@ -1,6 +1,6 @@
1
- import type { CommandDefinition } from '../../command'
1
+ import type { CommandDefinition } from '../../program/index'
2
2
 
3
- import { getIdentity, setIdentityMode } from '../../lib/identity'
3
+ import { getIdentity, setIdentityMode } from '../../identity/index'
4
4
  import { fatal, log } from '../../lib/log'
5
5
 
6
6
  /** Metadata-only flip remote → local until cloud sync ships (§2.7). */
@@ -1,6 +1,6 @@
1
- import type { CommandDefinition } from '../../command'
1
+ import type { CommandDefinition } from '../../program/index'
2
2
 
3
- import { setDefault } from '../../lib/identity'
3
+ import { setDefault } from '../../identity/index'
4
4
  import { fatal, log } from '../../lib/log'
5
5
 
6
6
  export default {
@@ -1,8 +1,8 @@
1
1
  import chalk from 'chalk'
2
2
 
3
- import type { CommandDefinition } from '../../command'
3
+ import type { CommandDefinition } from '../../program/index'
4
4
 
5
- import { getDefault } from '../../lib/identity'
5
+ import { getDefault } from '../../identity/index'
6
6
  import { fatal } from '../../lib/log'
7
7
  import { isMachine, output, RAW_OUTPUT_OPTIONS, type RawOutputOpts } from '../../lib/output'
8
8
 
@@ -1,6 +1,6 @@
1
1
  import { readFile } from 'node:fs/promises'
2
2
 
3
- import type { CommandDefinition } from '../../command'
3
+ import type { CommandDefinition } from '../../program/index'
4
4
 
5
5
  import {
6
6
  fetchOidcMetadata,
@@ -1,7 +1,7 @@
1
1
  import chalk from 'chalk'
2
2
 
3
- import type { CommandDefinition } from '../../command'
4
3
  import type { ListOpts, ListProjection } from '../../lib/output'
4
+ import type { CommandDefinition } from '../../program/index'
5
5
 
6
6
  import { listIdpConfigs } from '../../lib/idp'
7
7
  import { log } from '../../lib/log'
@@ -1,4 +1,4 @@
1
- import type { CommandDefinition } from '../../command'
1
+ import type { CommandDefinition } from '../../program/index'
2
2
 
3
3
  import {
4
4
  fetchOidcMetadata,
@@ -1,6 +1,6 @@
1
- import type { CommandDefinition } from '../../command'
1
+ import type { CommandDefinition } from '../../program/index'
2
2
 
3
- import { readIdentities } from '../../lib/identity'
3
+ import { readIdentities } from '../../identity/index'
4
4
  import { removeIdpConfig } from '../../lib/idp'
5
5
  import { log } from '../../lib/log'
6
6
  import { output, RAW_OUTPUT_OPTIONS } from '../../lib/output'
@@ -1,4 +1,4 @@
1
- import type { CommandDefinition } from '../../command'
1
+ import type { CommandDefinition } from '../../program/index'
2
2
 
3
3
  import { readIdpConfigOrBuiltin } from '../../lib/idp'
4
4
  import { output, RAW_OUTPUT_OPTIONS } from '../../lib/output'
@@ -1,8 +1,8 @@
1
1
  import chalk from 'chalk'
2
2
 
3
- import type { CommandDefinition } from '../../command'
3
+ import type { CommandDefinition } from '../../program/index'
4
4
 
5
- import { listOwnedInstances } from '../../kernel/client'
5
+ import { listOwnedInstances } from '../../lib/admin-instance'
6
6
  import { findOwnedInstance } from '../../lib/admin-instance'
7
7
  import { getActive, normalizeInstanceKernelUrl } from '../../lib/instance'
8
8
  import { log } from '../../lib/log'
@@ -1,6 +1,6 @@
1
- import type { CommandDefinition } from '../../command'
1
+ import type { CommandDefinition } from '../../program/index'
2
2
 
3
- import { fetchWithCaFile } from '../../kernel/ca-fetch'
3
+ import { fetchWithCaFile } from '../../lib/ca-fetch'
4
4
  import { normalizeInstanceKernelUrl, setActive, upsertInstance } from '../../lib/instance'
5
5
  import { fatal, log } from '../../lib/log'
6
6
  import { checkIssuerReachability } from '../../lib/meta'
@@ -12,6 +12,10 @@ export default {
12
12
  options: [
13
13
  { flags: '--url <url>', description: 'URL of the remote instance (required)' },
14
14
  { flags: '--issuer <url>', description: 'Kernel issuer/audience if different from URL' },
15
+ {
16
+ flags: '--domain-issuer <url>',
17
+ description: 'Product Domain issuer for standard token exchange',
18
+ },
15
19
  { flags: '--ca <path>', description: 'CA certificate file to trust for this bookmark' },
16
20
  {
17
21
  flags: '--as <identity>',
@@ -25,6 +29,7 @@ export default {
25
29
  opts: {
26
30
  url?: string
27
31
  issuer?: string
32
+ domainIssuer?: string
28
33
  ca?: string
29
34
  as?: string
30
35
  use?: boolean
@@ -53,6 +58,7 @@ export default {
53
58
  const { entry, created } = await upsertInstance(name, {
54
59
  url,
55
60
  issuer: expectedIssuer,
61
+ domainIssuer: opts.domainIssuer,
56
62
  caFile: opts.ca,
57
63
  name,
58
64
  kind: 'bookmark',
@@ -1,4 +1,4 @@
1
- import type { CommandDefinition } from '../../command'
1
+ import type { CommandDefinition } from '../../program/index'
2
2
 
3
3
  import { ADMIN_TARGET_OPTIONS } from '../../lib/admin-target'
4
4
  import { fatal } from '../../lib/log'
@@ -1,8 +1,7 @@
1
- import type { CommandDefinition } from '../../command'
2
- import type { KernelCommandOpts } from '../../kernel'
1
+ import type { KernelCommandOpts } from '../../connection'
2
+ import type { CommandDefinition } from '../../program/index'
3
3
 
4
- import { withAdminKernelClient } from '../../kernel/client'
5
- import { adminInstanceMethod, type InstanceInfo } from '../../lib/admin-instance'
4
+ import { deleteOwnedInstance } from '../../lib/admin-instance'
6
5
  import { ADMIN_TARGET_OPTIONS, type AdminTargetCommandOpts } from '../../lib/admin-target'
7
6
  import { clearActive, readInstances, removeInstance, resolveInstanceKey } from '../../lib/instance'
8
7
  import { fatal, log, withSpinner } from '../../lib/log'
@@ -36,12 +35,7 @@ Behavior:
36
35
  const result = await withSpinner(
37
36
  `Deleting instance ${id}`,
38
37
  !isMachine(opts),
39
- () =>
40
- withAdminKernelClient(
41
- opts,
42
- async (ctx) =>
43
- (await ctx.client.call(adminInstanceMethod('delete'), { id })) as InstanceInfo,
44
- ),
38
+ () => deleteOwnedInstance(opts, id),
45
39
  { success: (deleted) => `Deleted instance: ${deleted.slug}` },
46
40
  )
47
41
 
@@ -1,4 +1,4 @@
1
- import type { CommandDefinition } from '../../command'
1
+ import type { CommandDefinition } from '../../program/index'
2
2
 
3
3
  import { readInstances, removeInstance, resolveInstanceKey } from '../../lib/instance'
4
4
  import { fatal, log } from '../../lib/log'
@@ -1,10 +1,10 @@
1
1
  import chalk from 'chalk'
2
2
 
3
- import type { CommandDefinition } from '../../command'
4
- import type { KernelCommandOpts } from '../../kernel'
3
+ import type { KernelCommandOpts } from '../../connection'
5
4
  import type { Column } from '../../lib/output'
5
+ import type { CommandDefinition } from '../../program/index'
6
6
 
7
- import { listOwnedInstances } from '../../kernel/client'
7
+ import { listOwnedInstances } from '../../lib/admin-instance'
8
8
  import {
9
9
  formatInstanceLocation,
10
10
  type InstanceInfo,
@@ -1,11 +1,10 @@
1
1
  import chalk from 'chalk'
2
2
 
3
- import type { CommandDefinition } from '../../command'
4
- import type { KernelCommandOpts } from '../../kernel'
3
+ import type { KernelCommandOpts } from '../../connection'
4
+ import type { CommandDefinition } from '../../program/index'
5
5
 
6
6
  import { AstraleError } from '../../errors'
7
- import { listOwnedInstances } from '../../kernel/client'
8
- import { findOwnedInstance } from '../../lib/admin-instance'
7
+ import { statusOwnedInstance } from '../../lib/admin-instance'
9
8
  import { ADMIN_TARGET_OPTIONS, type AdminTargetCommandOpts } from '../../lib/admin-target'
10
9
  import { fatal, log, withSpinner } from '../../lib/log'
11
10
  import { isMachine, output, type RawOutputOpts } from '../../lib/output'
@@ -20,9 +19,8 @@ export default {
20
19
  action: async (id: string, opts: StatusOpts) => {
21
20
  try {
22
21
  const result = await withSpinner(`Fetching instance ${id}`, !isMachine(opts), () =>
23
- listOwnedInstances(opts).then((instances) => {
24
- const match = findOwnedInstance(instances, id)
25
- if (match) return match
22
+ statusOwnedInstance(opts, id).catch((error) => {
23
+ if (!(error instanceof Error) || error.name !== 'NotFoundError') throw error
26
24
  throw new AstraleError(
27
25
  'INSTANCE_NOT_FOUND',
28
26
  `No owned instance matches "${id}".`,