@astrale-os/cli 0.8.0-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 (317) hide show
  1. package/README.md +4 -1
  2. package/dist/astrale.js +62968 -41240
  3. package/dist/public/connect-core.js +55030 -0
  4. package/dist/public/keys/index.js +45631 -0
  5. package/dist/public/paths/index.js +43990 -0
  6. package/dist/types/admin/instance/model.d.ts +40 -0
  7. package/dist/types/connect-core.d.ts +22 -0
  8. package/dist/types/connection/auth.d.ts +33 -0
  9. package/dist/types/errors.d.ts +23 -0
  10. package/dist/types/identity/registry.d.ts +25 -0
  11. package/dist/types/keys/algorithm.d.ts +6 -0
  12. package/dist/types/keys/credential.d.ts +21 -0
  13. package/dist/types/keys/index.d.ts +4 -0
  14. package/dist/types/keys/pair.d.ts +34 -0
  15. package/dist/types/lib/admin-target.d.ts +74 -0
  16. package/dist/types/lib/ca-fetch.d.ts +2 -0
  17. package/dist/types/lib/config.d.ts +71 -0
  18. package/dist/types/lib/format.d.ts +1 -0
  19. package/dist/types/lib/idp-session.d.ts +33 -0
  20. package/dist/types/lib/idp.d.ts +397 -0
  21. package/dist/types/lib/instance-target.d.ts +36 -0
  22. package/dist/types/lib/instance.d.ts +186 -0
  23. package/dist/types/lib/log.d.ts +30 -0
  24. package/dist/types/lib/login-flow.d.ts +49 -0
  25. package/dist/types/lib/meta.d.ts +36 -0
  26. package/dist/types/lib/output.d.ts +83 -0
  27. package/dist/types/lib/table.d.ts +18 -0
  28. package/dist/types/lib/validation.d.ts +11 -0
  29. package/dist/types/paths/index.d.ts +2 -0
  30. package/dist/types/state/exchange-credentials.d.ts +25 -0
  31. package/dist/types/state/files.d.ts +9 -0
  32. package/dist/types/state/identities.d.ts +42 -0
  33. package/dist/types/state/index.d.ts +8 -0
  34. package/dist/types/state/paths.d.ts +32 -0
  35. package/package.json +30 -8
  36. package/src/admin/.spec/api.d.ts +3 -0
  37. package/src/admin/.spec/architecture.md +15 -0
  38. package/src/admin/.spec/icon.svg +3 -0
  39. package/src/admin/.spec/layout.ts +6 -0
  40. package/src/admin/catalog/.spec/api.d.ts +65 -0
  41. package/src/admin/catalog/.spec/architecture.md +6 -0
  42. package/src/admin/catalog/.spec/icon.svg +3 -0
  43. package/src/admin/catalog/.spec/layout.ts +6 -0
  44. package/src/admin/catalog/__tests__/client.test.ts +171 -0
  45. package/src/admin/catalog/client.ts +214 -0
  46. package/src/admin/catalog/index.ts +13 -0
  47. package/src/admin/catalog/model.ts +42 -0
  48. package/src/admin/catalog/tsconfig.json +8 -0
  49. package/src/admin/graph/.spec/api.d.ts +28 -0
  50. package/src/admin/graph/.spec/architecture.md +5 -0
  51. package/src/admin/graph/.spec/icon.svg +3 -0
  52. package/src/admin/graph/.spec/layout.ts +3 -0
  53. package/src/admin/graph/index.ts +6 -0
  54. package/src/admin/graph/nodes.ts +56 -0
  55. package/src/admin/index.ts +2 -0
  56. package/src/admin/instance/.spec/api.d.ts +66 -0
  57. package/src/admin/instance/.spec/architecture.md +10 -0
  58. package/src/admin/instance/.spec/icon.svg +3 -0
  59. package/src/admin/instance/.spec/layout.ts +6 -0
  60. package/src/admin/instance/__tests__/client.test.ts +289 -0
  61. package/src/admin/instance/client.ts +298 -0
  62. package/src/admin/instance/index.ts +16 -0
  63. package/src/admin/instance/model.ts +65 -0
  64. package/src/admin/instance/tsconfig.json +8 -0
  65. package/src/commands/__tests__/admin-instance.test.ts +3 -36
  66. package/src/commands/__tests__/call.test.ts +21 -68
  67. package/src/commands/__tests__/domain-install-owned.test.ts +26 -27
  68. package/src/commands/__tests__/domain-list.test.ts +2 -6
  69. package/src/commands/__tests__/logs.test.ts +50 -98
  70. package/src/commands/__tests__/ls.test.ts +21 -17
  71. package/src/commands/__tests__/read-commands.test.ts +171 -101
  72. package/src/commands/__tests__/view.test.ts +100 -51
  73. package/src/commands/admin/status.ts +3 -3
  74. package/src/commands/admin/use.ts +29 -6
  75. package/src/commands/auth/login.ts +1 -1
  76. package/src/commands/auth/logout.ts +4 -2
  77. package/src/commands/auth/status.ts +2 -2
  78. package/src/commands/auth/token.ts +2 -2
  79. package/src/commands/browser.ts +1 -1
  80. package/src/commands/call.ts +55 -91
  81. package/src/commands/describe.ts +52 -147
  82. package/src/commands/domain/install.ts +81 -50
  83. package/src/commands/domain/list.ts +4 -8
  84. package/src/commands/domain/publish.ts +9 -31
  85. package/src/commands/get.ts +32 -65
  86. package/src/commands/identity/__tests__/register.test.ts +93 -0
  87. package/src/commands/identity/create.ts +2 -2
  88. package/src/commands/identity/delete.ts +2 -2
  89. package/src/commands/identity/export.ts +7 -33
  90. package/src/commands/identity/import.ts +21 -70
  91. package/src/commands/identity/list.ts +2 -2
  92. package/src/commands/identity/register.ts +148 -117
  93. package/src/commands/identity/sync.ts +2 -2
  94. package/src/commands/identity/unsync.ts +2 -2
  95. package/src/commands/identity/use.ts +2 -2
  96. package/src/commands/identity/whoami.ts +2 -2
  97. package/src/commands/idp/add.ts +1 -1
  98. package/src/commands/idp/list.ts +1 -1
  99. package/src/commands/idp/refresh.ts +1 -1
  100. package/src/commands/idp/remove.ts +2 -2
  101. package/src/commands/idp/show.ts +1 -1
  102. package/src/commands/instance/active.ts +2 -2
  103. package/src/commands/instance/bookmark.ts +8 -2
  104. package/src/commands/instance/create.ts +1 -1
  105. package/src/commands/instance/delete.ts +4 -10
  106. package/src/commands/instance/forget.ts +1 -1
  107. package/src/commands/instance/list.ts +3 -3
  108. package/src/commands/instance/status.ts +5 -7
  109. package/src/commands/instance/use.ts +3 -3
  110. package/src/commands/logs.ts +152 -279
  111. package/src/commands/ls.ts +78 -204
  112. package/src/commands/mutate.ts +70 -139
  113. package/src/commands/query.ts +100 -278
  114. package/src/commands/session/analyze.ts +1 -1
  115. package/src/commands/session/list.ts +1 -1
  116. package/src/commands/setup.ts +1 -1
  117. package/src/commands/status.ts +1 -1
  118. package/src/commands/studio.ts +1 -1
  119. package/src/commands/token.ts +26 -21
  120. package/src/commands/update.ts +1 -1
  121. package/src/commands/use.ts +2 -3
  122. package/src/commands/view-serve.ts +1 -1
  123. package/src/commands/view.ts +67 -109
  124. package/src/connect-core.test.ts +38 -0
  125. package/src/connect-core.ts +42 -0
  126. package/src/connection/.spec/api.d.ts +111 -0
  127. package/src/connection/.spec/architecture.md +41 -0
  128. package/src/connection/.spec/capabilities/connection.ts +13 -0
  129. package/src/connection/.spec/examples/call.ts +7 -0
  130. package/src/connection/.spec/flows/command.ts +20 -0
  131. package/src/connection/.spec/flows/session.ts +61 -0
  132. package/src/connection/.spec/laws/connection.ts +181 -0
  133. package/src/connection/.spec/layout.ts +19 -0
  134. package/src/{kernel → connection}/__tests__/auth.test.ts +26 -2
  135. package/src/connection/__tests__/ca-fetch.test.ts +53 -0
  136. package/src/connection/__tests__/call.test.ts +17 -0
  137. package/src/connection/__tests__/credential.test.ts +109 -0
  138. package/src/connection/__tests__/errors.test.ts +96 -0
  139. package/src/connection/__tests__/exchange.test.ts +224 -0
  140. package/src/connection/__tests__/fixtures/localhost-cert.pem +19 -0
  141. package/src/connection/__tests__/fixtures/localhost-key.base64 +1 -0
  142. package/src/connection/__tests__/managed.test.ts +39 -0
  143. package/src/connection/__tests__/self.test.ts +51 -0
  144. package/src/connection/__tests__/session.test.ts +140 -0
  145. package/src/connection/__tests__/target.test.ts +93 -0
  146. package/src/{kernel → connection}/auth.ts +8 -8
  147. package/src/connection/call.ts +7 -0
  148. package/src/{kernel/run.ts → connection/command.ts} +22 -13
  149. package/src/connection/credential.ts +114 -0
  150. package/src/{kernel → connection}/errors.ts +32 -9
  151. package/src/connection/exchange.ts +274 -0
  152. package/src/connection/index.ts +8 -0
  153. package/src/connection/self.ts +120 -0
  154. package/src/connection/session.ts +176 -0
  155. package/src/connection/target.ts +111 -0
  156. package/src/connection/tsconfig.json +6 -0
  157. package/src/graph/.spec/api.d.ts +34 -0
  158. package/src/graph/.spec/laws/documents.ts +24 -0
  159. package/src/graph/.spec/laws/projection.ts +13 -0
  160. package/src/graph/.spec/layout.ts +6 -0
  161. package/src/graph/__tests__/mutation.test.ts +43 -0
  162. package/src/graph/__tests__/projection.test.ts +22 -0
  163. package/src/graph/__tests__/query.test.ts +154 -0
  164. package/src/graph/index.ts +3 -0
  165. package/src/graph/mutation.ts +18 -0
  166. package/src/graph/projection.ts +21 -0
  167. package/src/graph/query.ts +108 -0
  168. package/src/graph/tsconfig.json +6 -0
  169. package/src/identity/.history/ard/bootstrap-root-transfer.md +57 -0
  170. package/src/identity/.history/ard/domain-mediated-registration.md +36 -0
  171. package/src/identity/.spec/api.d.ts +124 -0
  172. package/src/identity/.spec/architecture.md +33 -0
  173. package/src/identity/.spec/capabilities/identity.ts +7 -0
  174. package/src/identity/.spec/flows/bootstrap-root.ts +79 -0
  175. package/src/identity/.spec/flows/domain-mediated-registration.ts +51 -0
  176. package/src/identity/.spec/flows/import.ts +29 -0
  177. package/src/identity/.spec/laws/identity.ts +56 -0
  178. package/src/identity/.spec/layout.ts +13 -0
  179. package/src/identity/.spec/schemas/identity-export-v1.schema.json +25 -0
  180. package/src/identity/__tests__/fixtures/registry-journey.ts +53 -0
  181. package/src/identity/__tests__/registration.test.ts +64 -0
  182. package/src/identity/__tests__/registry.test.ts +70 -0
  183. package/src/identity/__tests__/transfer.test.ts +198 -0
  184. package/src/identity/index.ts +23 -0
  185. package/src/identity/registration.ts +72 -0
  186. package/src/identity/registry.ts +172 -0
  187. package/src/identity/transfer.ts +187 -0
  188. package/src/identity/tsconfig.json +6 -0
  189. package/src/index.ts +1 -0
  190. package/src/keys/.spec/api.d.ts +74 -0
  191. package/src/keys/.spec/architecture.md +13 -0
  192. package/src/keys/.spec/laws/keys.ts +43 -0
  193. package/src/keys/.spec/layout.ts +6 -0
  194. package/src/keys/__tests__/algorithm.test.ts +39 -0
  195. package/src/keys/__tests__/keys.test.ts +168 -0
  196. package/src/keys/algorithm.ts +33 -0
  197. package/src/keys/credential.ts +112 -0
  198. package/src/keys/index.ts +14 -0
  199. package/src/keys/pair.ts +219 -0
  200. package/src/keys/tsconfig.json +6 -0
  201. package/src/lib/__tests__/admin-target.test.ts +67 -7
  202. package/src/lib/__tests__/binary.test.ts +2 -3
  203. package/src/lib/__tests__/command-dx.test.ts +2 -2
  204. package/src/lib/__tests__/config.test.ts +3 -2
  205. package/src/lib/__tests__/idp.test.ts +4 -6
  206. package/src/lib/__tests__/instance-target.test.ts +7 -4
  207. package/src/lib/__tests__/local-status.test.ts +1 -1
  208. package/src/lib/__tests__/log.test.ts +31 -0
  209. package/src/lib/__tests__/meta.test.ts +55 -0
  210. package/src/lib/__tests__/sdk-deps.test.ts +1 -1
  211. package/src/lib/__tests__/self.test.ts +8 -230
  212. package/src/lib/__tests__/use-target.test.ts +1 -1
  213. package/src/lib/__tests__/view-open-intent.test.ts +78 -104
  214. package/src/lib/__tests__/view-session.test.ts +99 -0
  215. package/src/lib/admin-domain.ts +39 -25
  216. package/src/lib/admin-instance.ts +38 -43
  217. package/src/lib/admin-target.ts +67 -13
  218. package/src/lib/binary.ts +13 -34
  219. package/src/lib/browser.ts +1 -1
  220. package/src/{kernel → lib}/ca-fetch.ts +35 -27
  221. package/src/lib/config.ts +1 -1
  222. package/src/lib/idp-session.ts +1 -1
  223. package/src/lib/idp.ts +3 -8
  224. package/src/lib/instance-target.ts +12 -8
  225. package/src/lib/instance.ts +34 -2
  226. package/src/lib/local-status.ts +2 -2
  227. package/src/lib/log.ts +3 -1
  228. package/src/lib/login-flow.ts +32 -5
  229. package/src/lib/meta.ts +15 -11
  230. package/src/lib/provision-instance.ts +4 -12
  231. package/src/lib/self.ts +5 -147
  232. package/src/lib/update.ts +1 -1
  233. package/src/lib/use-target.ts +1 -1
  234. package/src/lib/validation.ts +2 -2
  235. package/src/lib/view/open-intent.ts +7 -37
  236. package/src/lib/view/port-allocation.ts +1 -1
  237. package/src/lib/view/resolve.ts +52 -45
  238. package/src/lib/view/server.ts +16 -33
  239. package/src/lib/view/session.ts +51 -22
  240. package/src/paths/.spec/api.d.ts +13 -0
  241. package/src/paths/.spec/architecture.md +5 -0
  242. package/src/paths/.spec/layout.ts +3 -0
  243. package/src/paths/index.ts +13 -0
  244. package/src/program/.spec/api.d.ts +42 -0
  245. package/src/program/.spec/architecture.md +18 -0
  246. package/src/program/.spec/laws/program.ts +29 -0
  247. package/src/program/.spec/layout.ts +14 -0
  248. package/src/program/__tests__/program.test.ts +359 -0
  249. package/src/program/build.ts +190 -0
  250. package/src/program/command.ts +42 -0
  251. package/src/program/index.ts +2 -0
  252. package/src/program/options.ts +40 -0
  253. package/src/{registry.ts → program/registry.ts} +5 -1
  254. package/src/program/tsconfig.json +6 -0
  255. package/src/setup/steps/admin.ts +18 -3
  256. package/src/setup/steps/instance.ts +1 -1
  257. package/src/state/.spec/api.d.ts +133 -0
  258. package/src/state/.spec/architecture.md +31 -0
  259. package/src/state/.spec/capabilities/state.ts +7 -0
  260. package/src/state/.spec/flows/identity-update.ts +22 -0
  261. package/src/state/.spec/laws/state.ts +73 -0
  262. package/src/state/.spec/layout.ts +14 -0
  263. package/src/state/__tests__/exchange-credentials.test.ts +188 -0
  264. package/src/state/__tests__/files.test.ts +113 -0
  265. package/src/state/__tests__/fixtures/identity-transition.ts +37 -0
  266. package/src/state/__tests__/identities.test.ts +142 -0
  267. package/src/state/__tests__/paths.test.ts +27 -0
  268. package/src/state/exchange-credentials.ts +219 -0
  269. package/src/state/files.ts +129 -0
  270. package/src/state/identities.ts +236 -0
  271. package/src/state/index.ts +29 -0
  272. package/src/state/paths.ts +61 -0
  273. package/src/state/tsconfig.json +6 -0
  274. package/src/telemetry/__tests__/redact.test.ts +3 -3
  275. package/src/telemetry/__tests__/trigger.test.ts +1 -1
  276. package/src/telemetry/settings.ts +1 -1
  277. package/src/telemetry/store.ts +1 -1
  278. package/src/test-utils.ts +1 -1
  279. package/studio/client/dist/assets/elk-api-D0cBetPW.js +1 -0
  280. package/studio/client/dist/assets/elk-worker.min-C9JGDOE-.js +6312 -0
  281. package/studio/client/dist/assets/index-BQnJ5sgd.css +1 -0
  282. package/studio/client/dist/assets/index-ChOr3yP0.css +1 -0
  283. package/studio/client/dist/assets/index-Dspir4w7.js +81 -0
  284. package/studio/client/dist/assets/index-bVD2KJgz.js +8 -0
  285. package/studio/client/dist/index.html +2 -2
  286. package/studio/package.json +10 -1
  287. package/studio/server/introspect/extractor.ts +1 -1
  288. package/studio/server/state/views.test.ts +3 -7
  289. package/studio/server/state/views.ts +23 -30
  290. package/studio/tsconfig.json +23 -0
  291. package/tsconfig.json +1 -1
  292. package/viewer/dist/main.js +65 -39
  293. package/viewer/tsconfig.json +13 -0
  294. package/src/command.ts +0 -42
  295. package/src/commands/__tests__/help-contract.test.ts +0 -164
  296. package/src/kernel/__tests__/client-owned-lookup.test.ts +0 -51
  297. package/src/kernel/__tests__/errors.test.ts +0 -43
  298. package/src/kernel/__tests__/expand.test.ts +0 -123
  299. package/src/kernel/client.ts +0 -214
  300. package/src/kernel/expand.ts +0 -192
  301. package/src/kernel/graph.ts +0 -96
  302. package/src/kernel/index.ts +0 -29
  303. package/src/kernel/options.ts +0 -22
  304. package/src/kernel/types.ts +0 -14
  305. package/src/lib/__tests__/domain-identity.test.ts +0 -60
  306. package/src/lib/__tests__/fs-atomic.test.ts +0 -104
  307. package/src/lib/__tests__/identity.test.ts +0 -79
  308. package/src/lib/__tests__/keys.test.ts +0 -129
  309. package/src/lib/domain-identity.ts +0 -49
  310. package/src/lib/env.ts +0 -49
  311. package/src/lib/fs-atomic.ts +0 -126
  312. package/src/lib/identity.ts +0 -256
  313. package/src/lib/keys.ts +0 -294
  314. package/src/lib/paths.ts +0 -11
  315. package/src/program.ts +0 -213
  316. package/studio/client/dist/assets/index-BaqEuIQJ.js +0 -109
  317. package/studio/client/dist/assets/index-jRdExahh.css +0 -1
@@ -0,0 +1,108 @@
1
+ import type {
2
+ QueryAST as QueryASTValue,
3
+ QueryDefinitionRef,
4
+ QueryDirection,
5
+ } from '@astrale-os/sdk/query'
6
+
7
+ import { Path } from '@astrale-os/sdk/graph/path'
8
+ import { Query, QueryAST } from '@astrale-os/sdk/query'
9
+
10
+ export interface QueryCommandInput {
11
+ readonly sources: readonly string[]
12
+ readonly definition?: string
13
+ readonly ast?: unknown
14
+ readonly edge?: string
15
+ readonly direction?: QueryDirection
16
+ readonly limit?: string
17
+ readonly cursor?: string
18
+ }
19
+
20
+ export interface PreparedQuery {
21
+ readonly ast: QueryASTValue
22
+ readonly page: Readonly<{ readonly size: number; readonly after?: string }>
23
+ }
24
+
25
+ const DEFAULT_LIMIT = 100
26
+
27
+ /** Admit canonical Query V6 or author the intentionally small Path/Definition/one-edge subset. */
28
+ export function prepareQuery(input: QueryCommandInput): PreparedQuery {
29
+ const limit = positiveInteger(input.limit, '--limit', DEFAULT_LIMIT)
30
+ if (input.ast !== undefined) {
31
+ if (
32
+ input.sources.length > 0 ||
33
+ input.definition !== undefined ||
34
+ input.edge !== undefined ||
35
+ input.direction !== undefined
36
+ ) {
37
+ throw new TypeError(
38
+ '--ast/--file cannot be combined with sources, --definition, --edge, or --direction',
39
+ )
40
+ }
41
+ return withPage(QueryAST.decode(input.ast), limit, input.cursor)
42
+ }
43
+
44
+ if (input.sources.length === 0 && input.definition === undefined) {
45
+ throw new TypeError(
46
+ 'query requires a Path source, --definition, or a canonical Query V6 document',
47
+ )
48
+ }
49
+ if (input.direction !== undefined && input.edge === undefined) {
50
+ throw new TypeError('--direction requires --edge')
51
+ }
52
+
53
+ 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!] })
63
+ const ast =
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' })
72
+ return withPage(ast, limit, input.cursor)
73
+ }
74
+
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,
90
+ })
91
+ }
92
+
93
+ function positiveInteger(raw: string | undefined, name: string, fallback: number): number {
94
+ if (raw === undefined) return fallback
95
+ if (!/^\d+$/.test(raw)) throw new TypeError(`${name} must be a positive integer`)
96
+ const value = Number.parseInt(raw, 10)
97
+ if (!Number.isSafeInteger(value) || value <= 0) {
98
+ throw new TypeError(`${name} must be a positive integer`)
99
+ }
100
+ return value
101
+ }
102
+
103
+ function withPage(ast: QueryASTValue, size: number, cursor: string | undefined): PreparedQuery {
104
+ return Object.freeze({
105
+ ast,
106
+ page: Object.freeze({ size, ...(cursor === undefined ? {} : { after: cursor }) }),
107
+ })
108
+ }
@@ -0,0 +1,6 @@
1
+ {
2
+ "extends": "../../tsconfig.json",
3
+ "compilerOptions": { "types": ["@types/node"] },
4
+ "include": ["*.ts"],
5
+ "exclude": ["__tests__"]
6
+ }
@@ -0,0 +1,57 @@
1
+ # Bootstrap root transfer and child authority
2
+
3
+ Status: resolved
4
+ Impact: high
5
+ Selection: identity-export-v1-and-authenticated-management-delegation
6
+
7
+ ## Context
8
+
9
+ A production Host creates the manager Kernel's signing material before the CLI has any local state.
10
+ Qualification may mount that generated secret into the operator environment, but it must not
11
+ manufacture the CLI registry or key files. The Host producer and CLI importer previously agreed only
12
+ through coincident TypeScript object shapes. The root-to-child journey was also distributed across
13
+ Host, Runtime, Client, and CLI behavior, leaving room to confuse a manager-signed Management carrier
14
+ with the effective child principal.
15
+
16
+ ## Decision
17
+
18
+ https://schemas.astrale.ai/cli/identity-export/1 is the portable JSON Schema for the plaintext
19
+ IdentityExport V1 envelope. CLI Identity owns and publishes that schema. Structural schema admission
20
+ does not prove a keypair: identity import additionally proves that the private and public JWKs form
21
+ one pair before publishing either keys or registry state.
22
+
23
+ The Host secret generator emits one schema-conforming identity.json with mode 0600. The platform
24
+ runner imports it through:
25
+
26
+ astrale identity import <identity.json> --name platform-root
27
+
28
+ The imported root is the manager Kernel principal. It is never provisioned or registered as an
29
+ application identity. The manager bookmark uses the non-reserved alias platform-manager, pins the
30
+ manager issuer, and selects platform-root as its default identity. Manager identity is proved by the
31
+ authenticated public Identity.whoami syscall, not local CLI status.
32
+
33
+ Because this credential is issued by the target Kernel itself, CLI signing carries the imported
34
+ subject as an already-resolved identity Grant. The external-primary `{ identity: self }` form is not
35
+ valid for a self-issued Kernel bearer and cannot be substituted at this boundary.
36
+
37
+ Child creation is the public receiver call
38
+ /:host.astrale.ai:core.manager::createInstance. Once its Operation and Instance status report a
39
+ ready route and issuer, the canonical child credential is minted at the manager:
40
+
41
+ astrale token -i platform-manager --for platform-root --audience <child-issuer> --raw
42
+
43
+ That token is a manager-to-child Management carrier. Its outer JWT subject names the manager
44
+ principal; child Runtime maps it to the child Kernel principal. Child calls therefore keep the token
45
+ as --creds, and @self resolves through the child's authenticated Identity.whoami. The child bookmark
46
+ pins the child issuer and carries no --as platform-root default.
47
+
48
+ ## Consequences
49
+
50
+ - Bootstrap crosses one explicit file contract and one supported CLI command; no CLI state file is
51
+ fabricated.
52
+ - Direct manager signing and child Management delegation remain distinct.
53
+ - No consumer may infer the effective principal from an unverified JWT subject or cached
54
+ registration.
55
+ - The portable schema is structural and versioned; cryptographic pair proof remains CLI admission.
56
+ - A future encrypted Host-to-CLI bootstrap format requires a new explicit deployment decision. CLI's
57
+ interactive compact-JWE export remains an operator transfer representation, not Host output.
@@ -0,0 +1,36 @@
1
+ # Domain-mediated Identity registration
2
+
3
+ Status: resolved
4
+ Impact: high
5
+ Selection: explicit-authority-owner-callable
6
+
7
+ ## Context
8
+
9
+ `identity register` prepares an atomic graph Node and self-proven Identity designation. Submitting
10
+ that request directly to Kernel Auth is correct only when the authenticated caller already holds
11
+ creation authority for the chosen Class. A child Kernel management principal does not thereby own
12
+ an installed application Domain's `Operator` Class, and production correctly denied that attempted
13
+ authority substitution.
14
+
15
+ The CLI must still own local key proof and target-bound registration state. Manufacturing CLI state,
16
+ handing application credentials to the operator, or weakening mutation authorization would cross
17
+ the wrong boundary.
18
+
19
+ ## Decision
20
+
21
+ Registration retains direct Auth submission as its default. `--via <callablePath>` selects a second,
22
+ explicit submission boundary: after constructing the exact same Provision request and fingerprint-
23
+ bound self proof, CLI invokes the named Domain callable through the normal Host route. That callable
24
+ owns admission and may exercise its Domain authority to submit the request to Kernel Auth.
25
+
26
+ CLI treats the callable response as untrusted. It admits the prepared binding's issuer, subject, and
27
+ optional created Node ID, then stores only the target-keyed issuer and subject. It never accepts or
28
+ persists application authority, and it never exposes the self proof in argv or output.
29
+
30
+ ## Consequences
31
+
32
+ - Kernel mutation non-amplification remains unchanged.
33
+ - A Domain that offers registration must declare and authorize a Provision-shaped callable.
34
+ - The option is explicit because choosing the authority owner is security-significant.
35
+ - Direct registration remains useful for Classes the selected caller genuinely owns.
36
+ - A failed callable or malformed response leaves CLI registration state unchanged.
@@ -0,0 +1,124 @@
1
+ import type { Call } from '@astrale-os/kernel-client'
2
+ import type { ProvisionRequest } from '@astrale-os/sdk/auth'
3
+ import type { LocalBinding } from '@astrale-os/sdk/graph/model'
4
+ import type { JWK } from 'jose'
5
+
6
+ import type {
7
+ Identity,
8
+ IdentityMode,
9
+ IdentityStore,
10
+ IdentityStoreOptions,
11
+ Registration,
12
+ } from '../../state/.spec/api.js'
13
+
14
+ export type { Identity, IdentityStore, Registration } from '../../state/.spec/api.js'
15
+
16
+ /** Filesystem coordinates used only by explicit identity transfer operations. */
17
+ export interface IdentityFileOptions {
18
+ readonly state?: IdentityStoreOptions
19
+ readonly keysDir?: string
20
+ }
21
+
22
+ /** Canonical V1 key-identity transfer envelope. */
23
+ export interface IdentityExport {
24
+ readonly version: 1
25
+ readonly subject: string
26
+ readonly mode: IdentityMode
27
+ readonly kid?: string
28
+ readonly issuer?: string
29
+ readonly privateJwk: JWK
30
+ readonly publicJwk: JWK
31
+ }
32
+
33
+ export interface IdentityImportOptions extends IdentityFileOptions {
34
+ readonly name?: string
35
+ readonly issuer?: string
36
+ readonly replace?: boolean
37
+ }
38
+
39
+ export interface IdentityRegistrationResult {
40
+ readonly iss: string
41
+ readonly sub: string
42
+ readonly nodeId?: string
43
+ }
44
+
45
+ export interface IdentityProvisionSubmission {
46
+ readonly request: ProvisionRequest
47
+ readonly binding: LocalBinding
48
+ readonly via?: string
49
+ readonly direct: {
50
+ provision(request: ProvisionRequest): Promise<unknown>
51
+ }
52
+ readonly callable: {
53
+ call(call: Call): Promise<unknown>
54
+ }
55
+ }
56
+
57
+ /** Submit one prepared request either directly or through its explicit Domain authority owner. */
58
+ export function submitIdentityProvision(
59
+ input: IdentityProvisionSubmission,
60
+ ): Promise<IdentityRegistrationResult>
61
+
62
+ /** Admit only the exact binding the CLI prepared; remote callables remain untrusted input. */
63
+ export function acceptProvisionedIdentity(
64
+ value: unknown,
65
+ binding: LocalBinding,
66
+ ): IdentityRegistrationResult
67
+
68
+ export function readIdentities(): Promise<IdentityStore>
69
+
70
+ export function createIdentity(
71
+ name: string,
72
+ options?: {
73
+ readonly subject?: string
74
+ readonly mode?: IdentityMode
75
+ readonly issuer?: string
76
+ readonly kid?: string
77
+ },
78
+ ): Promise<Identity>
79
+
80
+ export function deleteIdentity(name: string): Promise<void>
81
+ export function setDefault(name: string): Promise<void>
82
+ export function getDefault(): Promise<Identity & { readonly name: string }>
83
+ export function getIdentity(name: string): Promise<Identity>
84
+
85
+ export function upsertIdpIdentity(
86
+ name: string,
87
+ options: {
88
+ readonly subject: string
89
+ readonly idp: string
90
+ readonly issuer: string
91
+ readonly audience?: string
92
+ readonly claims?: Readonly<Record<string, unknown>>
93
+ readonly use?: boolean
94
+ },
95
+ ): Promise<Identity>
96
+
97
+ export function setRegistration(
98
+ name: string,
99
+ instanceSlug: string,
100
+ registration: Registration,
101
+ ): Promise<void>
102
+
103
+ export function setIdentityMode(name: string, mode: IdentityMode): Promise<void>
104
+
105
+ /** Detect the retained compact-JWE representation without decoding it. */
106
+ export function isEncryptedIdentityExport(raw: string): boolean
107
+
108
+ /** Decode legacy plaintext, V1 plaintext, or compact-JWE content into one admitted V1 envelope. */
109
+ export function decodeIdentityExport(raw: string, passphrase?: string): Promise<IdentityExport>
110
+
111
+ /** Encode one admitted envelope as plaintext JSON or compact JWE. */
112
+ export function encodeIdentityExport(envelope: IdentityExport, passphrase?: string): Promise<string>
113
+
114
+ /** Read and prove one key-backed identity's transferable envelope. */
115
+ export function exportIdentity(name: string, options?: IdentityFileOptions): Promise<IdentityExport>
116
+
117
+ /** Import one admitted envelope after checking registry conflicts but before publishing registry state. */
118
+ export function importIdentity(
119
+ envelope: IdentityExport,
120
+ options?: IdentityImportOptions,
121
+ ): Promise<Identity>
122
+
123
+ /** Atomically publish one explicit private export file with mode 0600. */
124
+ export function writeIdentityExport(path: string, content: string): Promise<void>
@@ -0,0 +1,33 @@
1
+ # CLI identity ownership
2
+
3
+ Identity owns local identity selection and the coordination between the State registry, per-subject
4
+ Keys, and IdP sessions. State owns durable registry decoding and locking; Keys owns JWK admission,
5
+ pair proof, and private files. Commands own prompts and presentation, not persistence mechanics.
6
+
7
+ Identity transfer accepts the retained unversioned plaintext envelope and the current V1 plaintext
8
+ or compact-JWE representation. Admission completes before mutation. Import checks the registry under
9
+ its file lock, persists the admitted keypair, and only then publishes the identity entry. Export
10
+ proves the stored pair and atomically writes one mode-`0600` user-selected file.
11
+
12
+ The canonical plaintext V1 structure is independently published as
13
+ `https://schemas.astrale.ai/cli/identity-export/1`. A production Host may emit that envelope as its
14
+ bootstrap root secret, but CLI Identity remains the format owner and cryptographically proves the
15
+ pair on import. The governing bootstrap flow imports the root as `platform-root`, authenticates it
16
+ directly to the `platform-manager` Kernel, and reaches a child only through a manager-minted
17
+ child-audience Management carrier. The carrier's outer subject is not the child's effective
18
+ principal; authenticated child `Identity.whoami` owns that projection.
19
+
20
+ Identity registration always prepares one self-proven atomic Provision request. Direct registration
21
+ submits it to Kernel Auth only when the selected caller already owns the target Class authority. An
22
+ explicit `--via <callable>` instead sends those exact bytes through the Domain that owns an
23
+ application Identity Class. The Domain callable owns admission and effect authority; CLI admits only
24
+ the prepared binding from the response before persisting its target-bound `(issuer, subject)`.
25
+
26
+ ```mermaid
27
+ flowchart LR
28
+ C[identity command] --> D[decode or encode transfer]
29
+ D --> I[Identity orchestration]
30
+ I --> S[State registry lock]
31
+ I --> K[Keys pair proof and files]
32
+ S --> F[identities.json]
33
+ ```
@@ -0,0 +1,7 @@
1
+ import { defineCapability } from '@astrale-os/spec/authoring'
2
+
3
+ export const CLI_IDENTITY = defineCapability({
4
+ id: 'CLI-IDENTITY',
5
+ statement:
6
+ 'Preserves local identity selection, isolated key material, and explicit import/export journeys.',
7
+ })
@@ -0,0 +1,79 @@
1
+ import type { IdentityExport } from '../api.js'
2
+
3
+ interface Target {
4
+ readonly name: string
5
+ readonly url: string
6
+ readonly issuer: string
7
+ }
8
+
9
+ interface ReadyChild {
10
+ readonly target: Target
11
+ readonly operation: string
12
+ readonly instance: string
13
+ }
14
+
15
+ interface BootstrapAuthority {
16
+ /** Supported identity-import boundary; never a direct State or Keys write. */
17
+ importIdentity(input: {
18
+ readonly name: 'platform-root'
19
+ readonly envelope: IdentityExport
20
+ }): Promise<void>
21
+ bookmark(input: {
22
+ readonly target: Target
23
+ readonly defaultIdentity?: 'platform-root'
24
+ }): Promise<void>
25
+ /** Authenticated public Identity.whoami result on the selected target. */
26
+ whoami(input: {
27
+ readonly target: Target
28
+ readonly as?: 'platform-root'
29
+ readonly credential?: string
30
+ }): Promise<{ readonly id: string }>
31
+ /** Public manager createInstance call plus Operation and Instance status polling. */
32
+ callCreateChild(input: {
33
+ readonly manager: Target
34
+ readonly as: 'platform-root'
35
+ readonly operationId: string
36
+ readonly slug: string
37
+ }): Promise<ReadyChild>
38
+ /** Public Auth delegation invoked on the manager after authenticated whoami. */
39
+ delegateSelf(input: {
40
+ readonly manager: Target
41
+ readonly as: 'platform-root'
42
+ readonly principal: string
43
+ readonly audience: string
44
+ }): Promise<string>
45
+ }
46
+
47
+ /**
48
+ * Bootstrap uses the imported root directly on the manager, then an unchanged
49
+ * manager-to-child Management carrier whose effective child principal is
50
+ * established only by the child's authenticated whoami result.
51
+ */
52
+ export async function bootstrapRootAndChild(
53
+ authority: BootstrapAuthority,
54
+ input: {
55
+ readonly envelope: IdentityExport
56
+ readonly manager: Target & { readonly name: 'platform-manager' }
57
+ readonly childSlug: string
58
+ readonly operationId: string
59
+ },
60
+ ): Promise<{ readonly managerId: string; readonly childId: string; readonly child: ReadyChild }> {
61
+ await authority.importIdentity({ name: 'platform-root', envelope: input.envelope })
62
+ await authority.bookmark({ target: input.manager, defaultIdentity: 'platform-root' })
63
+ const manager = await authority.whoami({ target: input.manager, as: 'platform-root' })
64
+ const child = await authority.callCreateChild({
65
+ manager: input.manager,
66
+ as: 'platform-root',
67
+ operationId: input.operationId,
68
+ slug: input.childSlug,
69
+ })
70
+ const credential = await authority.delegateSelf({
71
+ manager: input.manager,
72
+ as: 'platform-root',
73
+ principal: manager.id,
74
+ audience: child.target.issuer,
75
+ })
76
+ await authority.bookmark({ target: child.target })
77
+ const effectiveChild = await authority.whoami({ target: child.target, credential })
78
+ return { managerId: manager.id, childId: effectiveChild.id, child }
79
+ }
@@ -0,0 +1,51 @@
1
+ import type { ProvisionRequest, ProvisionResult } from '@astrale-os/sdk/auth'
2
+ import type { LocalBinding } from '@astrale-os/sdk/mutation'
3
+
4
+ interface RegistrationAuthority {
5
+ prepareSelfProven(input: {
6
+ readonly identity: string
7
+ readonly classPath: string
8
+ readonly properties: Readonly<Record<string, unknown>>
9
+ readonly kernelIssuer: string
10
+ }): Promise<{ readonly binding: LocalBinding; readonly request: ProvisionRequest }>
11
+ call(input: {
12
+ readonly target: string
13
+ readonly request: ProvisionRequest
14
+ }): Promise<ProvisionResult>
15
+ persist(input: {
16
+ readonly identity: string
17
+ readonly targetKey: string
18
+ readonly issuer: string
19
+ readonly subject: string
20
+ }): Promise<void>
21
+ }
22
+
23
+ /** Application Identity Classes are provisioned only through their explicit authority owner. */
24
+ export async function registerThroughDomain(
25
+ authority: RegistrationAuthority,
26
+ input: {
27
+ readonly identity: string
28
+ readonly classPath: string
29
+ readonly properties: Readonly<Record<string, unknown>>
30
+ readonly kernelIssuer: string
31
+ readonly targetKey: string
32
+ readonly callable: string
33
+ },
34
+ ): Promise<{ readonly issuer: string; readonly subject: string; readonly nodeId?: string }> {
35
+ const prepared = await authority.prepareSelfProven(input)
36
+ const result = await authority.call({ target: input.callable, request: prepared.request })
37
+ const identity = result.identities[prepared.binding]
38
+ if (identity === undefined) throw new Error('Provision result omitted the prepared binding.')
39
+ await authority.persist({
40
+ identity: input.identity,
41
+ targetKey: input.targetKey,
42
+ issuer: identity.issuer,
43
+ subject: identity.subject,
44
+ })
45
+ const nodeId = result.createdNodes[prepared.binding]
46
+ return {
47
+ issuer: identity.issuer,
48
+ subject: identity.subject,
49
+ ...(nodeId === undefined ? {} : { nodeId }),
50
+ }
51
+ }
@@ -0,0 +1,29 @@
1
+ import type { Identity, IdentityExport, IdentityImportOptions, IdentityStore } from '../api.js'
2
+
3
+ declare const withIdentityLock: <Value>(transition: () => Promise<Value>) => Promise<Value>
4
+ declare const readLatest: () => Promise<IdentityStore>
5
+ declare const checkImportConflict: (
6
+ store: IdentityStore,
7
+ envelope: IdentityExport,
8
+ options: IdentityImportOptions,
9
+ ) => string
10
+ declare const persistAdmittedKeypair: (envelope: IdentityExport) => Promise<void>
11
+ declare const publishIdentity: (
12
+ store: IdentityStore,
13
+ name: string,
14
+ envelope: IdentityExport,
15
+ options: IdentityImportOptions,
16
+ ) => Promise<Identity>
17
+
18
+ /** Import performs all fallible admission before entering this ordered persistence flow. */
19
+ export function importIdentity(
20
+ envelope: IdentityExport,
21
+ options: IdentityImportOptions,
22
+ ): Promise<Identity> {
23
+ return withIdentityLock(async () => {
24
+ const store = await readLatest()
25
+ const name = checkImportConflict(store, envelope, options)
26
+ await persistAdmittedKeypair(envelope)
27
+ return publishIdentity(store, name, envelope, options)
28
+ })
29
+ }
@@ -0,0 +1,56 @@
1
+ import { defineLaw } from '@astrale-os/spec/authoring'
2
+
3
+ export const CLI_IDENTITY_REGISTRY_TRANSITIONS = defineLaw({
4
+ id: 'CLI-IDENTITY-REGISTRY-TRANSITIONS',
5
+ statement:
6
+ 'Create, selection, registration, mode change, and non-default deletion preserve one locked registry and isolated subject keypair; deleting the selected identity is rejected unchanged.',
7
+ tests: [{ file: '__tests__/registry.test.ts', id: 'TEST-CLI-IDENTITY-REGISTRY-JOURNEY' }],
8
+ })
9
+
10
+ export const CLI_IDENTITY_TRANSFER_ADMITTED = defineLaw({
11
+ id: 'CLI-IDENTITY-TRANSFER-ADMITTED',
12
+ statement:
13
+ 'Malformed, unsupported-version, or cryptographically mismatched transfer content is rejected before either key files or registry state changes.',
14
+ tests: [{ file: '__tests__/transfer.test.ts', id: 'TEST-CLI-IDENTITY-TRANSFER-REJECTS' }],
15
+ })
16
+
17
+ export const CLI_IDENTITY_TRANSFER_ROUNDTRIP = defineLaw({
18
+ id: 'CLI-IDENTITY-TRANSFER-ROUNDTRIP',
19
+ statement:
20
+ 'Legacy plaintext and V1 plaintext or compact-JWE content converge on one V1 envelope, and explicit exports are published atomically with mode 0600.',
21
+ tests: [
22
+ { file: '__tests__/transfer.test.ts', id: 'TEST-CLI-IDENTITY-TRANSFER-ROUNDTRIP' },
23
+ { file: '__tests__/transfer.test.ts', id: 'TEST-CLI-IDENTITY-EXPORT-PRIVATE' },
24
+ ],
25
+ })
26
+
27
+ export const CLI_IDENTITY_TRANSFER_SCHEMA = defineLaw({
28
+ id: 'CLI-IDENTITY-TRANSFER-SCHEMA',
29
+ statement:
30
+ 'Plaintext IdentityExport V1 has one independently versioned portable JSON Schema; decoding additionally proves the JWK pair before any durable mutation.',
31
+ tests: [
32
+ {
33
+ file: '__tests__/transfer.test.ts',
34
+ id: 'TEST-CLI-IDENTITY-TRANSFER-SCHEMA',
35
+ },
36
+ ],
37
+ })
38
+
39
+ export const CLI_IDENTITY_IMPORT_ORDERED = defineLaw({
40
+ id: 'CLI-IDENTITY-IMPORT-ORDERED',
41
+ statement:
42
+ 'Import checks name and identity-source conflicts under the registry lock before replacing keys, then publishes the registry only after the admitted pair is durable.',
43
+ tests: [{ file: '__tests__/transfer.test.ts', id: 'TEST-CLI-IDENTITY-IMPORT-ORDERED' }],
44
+ })
45
+
46
+ export const CLI_IDENTITY_REGISTRATION_AUTHORITY = defineLaw({
47
+ id: 'CLI-IDENTITY-REGISTRATION-AUTHORITY',
48
+ statement:
49
+ 'Identity registration binds one self-proven Provision request to the selected Kernel; direct submission uses caller authority, while explicit Domain-mediated submission sends the same request to the named callable and persists only its admitted prepared binding.',
50
+ tests: [
51
+ {
52
+ file: '__tests__/registration.test.ts',
53
+ id: 'TEST-CLI-IDENTITY-REGISTER-DOMAIN-MEDIATED',
54
+ },
55
+ ],
56
+ })
@@ -0,0 +1,13 @@
1
+ import { defineLayout } from '@astrale-os/spec/authoring'
2
+
3
+ export default defineLayout({
4
+ entries: [
5
+ '__tests__/',
6
+ 'index.ts',
7
+ 'registration.ts',
8
+ 'registry.ts',
9
+ 'transfer.ts',
10
+ 'tsconfig.json',
11
+ ],
12
+ exact: true,
13
+ })
@@ -0,0 +1,25 @@
1
+ {
2
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
3
+ "$id": "https://schemas.astrale.ai/cli/identity-export/1",
4
+ "title": "Astrale Identity Export V1",
5
+ "description": "Portable plaintext key-identity transfer envelope consumed by the Astrale CLI.",
6
+ "type": "object",
7
+ "additionalProperties": false,
8
+ "required": ["version", "subject", "privateJwk", "publicJwk"],
9
+ "properties": {
10
+ "version": { "const": 1 },
11
+ "subject": { "type": "string", "minLength": 1 },
12
+ "mode": { "enum": ["local", "remote"] },
13
+ "kid": { "type": "string", "minLength": 1 },
14
+ "issuer": { "type": "string", "format": "uri" },
15
+ "privateJwk": { "$ref": "#/$defs/jwk" },
16
+ "publicJwk": { "$ref": "#/$defs/jwk" }
17
+ },
18
+ "$defs": {
19
+ "jwk": {
20
+ "type": "object",
21
+ "minProperties": 1,
22
+ "additionalProperties": true
23
+ }
24
+ }
25
+ }