@astrale-os/cli 1.0.0-beta.4 → 1.0.0-beta.6

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 (173) hide show
  1. package/dist/astrale.js +55649 -59388
  2. package/dist/public/connect-core.js +1972 -26831
  3. package/dist/public/keys/index.js +1754 -40686
  4. package/dist/public/paths/index.js +859 -39511
  5. package/dist/types/admin/instance/model.d.ts +3 -4
  6. package/dist/types/errors.d.ts +1 -1
  7. package/dist/types/lib/idp.d.ts +1 -0
  8. package/dist/types/lib/instance-target.d.ts +1 -1
  9. package/dist/types/lib/log.d.ts +2 -2
  10. package/dist/types/lib/update.d.ts +30 -0
  11. package/package.json +4 -6
  12. package/src/__tests__/fixtures/publication.ts +20 -0
  13. package/src/admin/__tests__/binding.test.ts +14 -21
  14. package/src/admin/__tests__/fixture.ts +101 -0
  15. package/src/admin/binding.ts +86 -6
  16. package/src/admin/catalog/.spec/api.d.ts +2 -2
  17. package/src/admin/catalog/__tests__/client.test.ts +20 -49
  18. package/src/admin/catalog/client.ts +35 -26
  19. package/src/admin/catalog/model.ts +3 -4
  20. package/src/admin/graph/.spec/api.d.ts +4 -10
  21. package/src/admin/graph/nodes.ts +1 -1
  22. package/src/admin/instance/.spec/api.d.ts +2 -2
  23. package/src/admin/instance/__tests__/client.test.ts +58 -83
  24. package/src/admin/instance/client.ts +49 -39
  25. package/src/admin/instance/model.ts +9 -8
  26. package/src/commands/__tests__/call-describe.test.ts +46 -33
  27. package/src/commands/__tests__/domain-install-operation.test.ts +3 -14
  28. package/src/commands/__tests__/domain-install-owned.test.ts +1 -1
  29. package/src/commands/__tests__/domain-list.test.ts +5 -25
  30. package/src/commands/__tests__/install-identity-override.test.ts +3 -14
  31. package/src/commands/__tests__/read-commands.test.ts +35 -10
  32. package/src/commands/__tests__/update.test.ts +25 -3
  33. package/src/commands/call-describe.ts +59 -67
  34. package/src/commands/call.ts +41 -54
  35. package/src/commands/domain/install.ts +2 -2
  36. package/src/commands/domain/uninstall.ts +2 -2
  37. package/src/commands/get.ts +13 -14
  38. package/src/commands/identity/__tests__/register.test.ts +3 -3
  39. package/src/commands/identity/register.ts +5 -4
  40. package/src/commands/instance/delete.ts +5 -3
  41. package/src/commands/instance/status.ts +1 -9
  42. package/src/commands/introspect.ts +4 -11
  43. package/src/commands/logs.ts +24 -25
  44. package/src/commands/mutate.ts +6 -5
  45. package/src/commands/query.ts +27 -30
  46. package/src/commands/token.ts +8 -7
  47. package/src/commands/update.ts +31 -11
  48. package/src/commands/view.ts +4 -3
  49. package/src/connection/.spec/api.d.ts +7 -8
  50. package/src/connection/.spec/architecture.md +5 -4
  51. package/src/connection/.spec/flows/session.ts +2 -2
  52. package/src/connection/.spec/laws/connection.ts +6 -2
  53. package/src/connection/.spec/layout.ts +1 -0
  54. package/src/connection/__tests__/credential.test.ts +1 -1
  55. package/src/connection/__tests__/errors.test.ts +27 -20
  56. package/src/connection/__tests__/exchange.test.ts +30 -1
  57. package/src/connection/__tests__/failure-fixtures.ts +39 -0
  58. package/src/connection/__tests__/failure-safety.test.ts +75 -0
  59. package/src/connection/__tests__/self.test.ts +31 -11
  60. package/src/connection/__tests__/session.test.ts +3 -3
  61. package/src/connection/call.ts +1 -1
  62. package/src/connection/credential.ts +2 -2
  63. package/src/connection/errors.ts +1 -355
  64. package/src/connection/exchange.ts +47 -23
  65. package/src/connection/failure/classify.ts +110 -0
  66. package/src/connection/failure/debug.ts +26 -0
  67. package/src/connection/failure/index.ts +18 -0
  68. package/src/connection/failure/model.ts +28 -0
  69. package/src/connection/failure/render.ts +116 -0
  70. package/src/connection/reasons.ts +16 -0
  71. package/src/connection/self.ts +39 -21
  72. package/src/connection/session.ts +8 -8
  73. package/src/errors.ts +3 -2
  74. package/src/graph/.spec/api.d.ts +12 -5
  75. package/src/graph/.spec/layout.ts +9 -1
  76. package/src/graph/__tests__/mutation.test.ts +2 -2
  77. package/src/graph/__tests__/query.test.ts +25 -6
  78. package/src/graph/class.ts +30 -0
  79. package/src/graph/index.ts +1 -0
  80. package/src/graph/query.ts +21 -32
  81. package/src/identity/.spec/api.d.ts +2 -2
  82. package/src/identity/__tests__/registration.test.ts +1 -1
  83. package/src/identity/registration.ts +3 -3
  84. package/src/lib/__tests__/domain-publication.test.ts +4 -15
  85. package/src/lib/__tests__/idp.test.ts +25 -0
  86. package/src/lib/__tests__/instance-target.test.ts +6 -39
  87. package/src/lib/__tests__/log-errors.test.ts +53 -0
  88. package/src/lib/__tests__/studio-server-deps.test.ts +1 -1
  89. package/src/lib/__tests__/update.test.ts +101 -8
  90. package/src/lib/__tests__/view-open-intent.test.ts +0 -2
  91. package/src/lib/__tests__/view-server.test.ts +1 -1
  92. package/src/lib/__tests__/view-session.test.ts +1 -1
  93. package/src/lib/command-dx.ts +1 -4
  94. package/src/lib/domain-publication.ts +1 -1
  95. package/src/lib/idp.ts +29 -1
  96. package/src/lib/instance-target.ts +3 -14
  97. package/src/lib/log.ts +11 -6
  98. package/src/lib/update.ts +120 -26
  99. package/src/lib/validation.ts +2 -2
  100. package/src/lib/view/resolve.ts +1 -1
  101. package/src/program/__tests__/program.test.ts +1 -1
  102. package/studio/client/dist/assets/{elk-api-lwhFo7vB.js → elk-api-Di4OXGNH.js} +1 -1
  103. package/studio/client/dist/assets/index-BLm6J11H.js +81 -0
  104. package/studio/client/dist/assets/index-BiGR8Oo9.css +1 -0
  105. package/studio/client/dist/assets/index-DTjvkOUX.js +8 -0
  106. package/studio/client/dist/index.html +2 -2
  107. package/studio/package.json +2 -9
  108. package/studio/server/agent/ask.test.ts +1 -1
  109. package/studio/server/agent/bridge/grant.test.ts +1 -1
  110. package/studio/server/agent/bridge/routes.test.ts +1 -1
  111. package/studio/server/agent/harness/claude/adapter.test.ts +15 -5
  112. package/studio/server/agent/prompts/anchors.test.ts +32 -36
  113. package/studio/server/agent/prompts/anchors.ts +20 -49
  114. package/studio/server/agent/prompts/system.test.ts +3 -2
  115. package/studio/server/agent/prompts/system.ts +3 -3
  116. package/studio/server/agent/routes.test.ts +1 -1
  117. package/studio/server/agent/run/coordinator.test.ts +1 -1
  118. package/studio/server/api/canvas.ts +0 -1
  119. package/studio/server/api-agent-loadout.test.ts +1 -1
  120. package/studio/server/api.test.ts +1 -1
  121. package/studio/server/cache.test.ts +23 -12
  122. package/studio/server/cache.ts +12 -15
  123. package/studio/server/domain.test.ts +50 -68
  124. package/studio/server/domain.ts +82 -63
  125. package/studio/server/handoff/copy.ts +1 -1
  126. package/studio/server/index.ts +1 -1
  127. package/studio/server/instances/probe.test.ts +1 -1
  128. package/studio/server/introspect/anatomy/views/routes.ts +46 -37
  129. package/studio/server/introspect/anatomy/views.ts +9 -14
  130. package/studio/server/introspect/anatomy-extras.test.ts +26 -42
  131. package/studio/server/introspect/anatomy.test.ts +22 -14
  132. package/studio/server/introspect/anatomy.ts +6 -9
  133. package/studio/server/introspect/bundle.ts +0 -3
  134. package/studio/server/introspect/canonical-schema.test.ts +94 -389
  135. package/studio/server/introspect/canonical-schema.ts +227 -457
  136. package/studio/server/introspect/core-extractor.ts +18 -127
  137. package/studio/server/introspect/core.ts +1 -2
  138. package/studio/server/introspect/diff.test.ts +11 -7
  139. package/studio/server/introspect/diff.ts +18 -55
  140. package/studio/server/introspect/extractor.ts +22 -117
  141. package/studio/server/introspect/overlay-tsmorph.test.ts +119 -240
  142. package/studio/server/introspect/overlay.test.ts +26 -54
  143. package/studio/server/introspect/overlay.ts +10 -37
  144. package/studio/server/introspect/revision.test.ts +1 -1
  145. package/studio/server/introspect/revision.ts +1 -1
  146. package/studio/server/introspect/runtime.test.ts +65 -183
  147. package/studio/server/introspect/runtime.ts +8 -25
  148. package/studio/server/introspect/schema-ir-json.test.ts +70 -0
  149. package/studio/server/introspect/schema-ir-json.ts +38 -68
  150. package/studio/server/introspect/schema-refs.test.ts +43 -88
  151. package/studio/server/introspect/schema-refs.ts +14 -49
  152. package/studio/server/introspect/source-overlay/handlers.ts +116 -636
  153. package/studio/server/introspect/source-overlay/kernel-calls.ts +0 -3
  154. package/studio/server/introspect/source-overlay/spans.ts +15 -45
  155. package/studio/server/state/baseline.test.ts +14 -9
  156. package/studio/server/state/baseline.ts +3 -5
  157. package/studio/server/state/visibility.ts +1 -5
  158. package/studio/server/views/model.ts +3 -3
  159. package/studio/server/views/target.test.ts +17 -74
  160. package/studio/server/views/target.ts +24 -50
  161. package/studio/server/watch.test.ts +11 -11
  162. package/studio/server/watch.ts +8 -4
  163. package/studio/server/workspace-watch.ts +2 -2
  164. package/studio/shared/contracts/schema.ts +38 -107
  165. package/studio/shared/contracts/surface.test.ts +13 -15
  166. package/studio/shared/contracts/workspace.ts +2 -11
  167. package/studio/shared/schema/identity.test.ts +20 -44
  168. package/studio/shared/schema/identity.ts +9 -23
  169. package/viewer/dist/main.js +40 -61
  170. package/studio/client/dist/assets/index-B-c-smo9.js +0 -8
  171. package/studio/client/dist/assets/index-BckHuAWk.js +0 -81
  172. package/studio/client/dist/assets/index-C4aNQ6dz.css +0 -1
  173. package/studio/server/introspect/anatomy/views/legacy.ts +0 -232
@@ -12,7 +12,7 @@ import {
12
12
  type SdkOutdated,
13
13
  } from '../lib/sdk-deps'
14
14
  import { ASTRALE_CLI_SKILL, detectSkill, installSkills, SKILL_INSTALL_HINT } from '../lib/skills'
15
- import { DEFAULT_UPDATE_CHANNEL, updateAstrale } from '../lib/update'
15
+ import { DEFAULT_UPDATE_CHANNEL, packageManagedUpdateError, updateAstrale } from '../lib/update'
16
16
 
17
17
  type UpdateOpts = RawOutputOpts & {
18
18
  check?: boolean
@@ -104,14 +104,22 @@ async function refreshSdkDeps(check: boolean, assumeYes = false): Promise<boolea
104
104
  /**
105
105
  * Read-only staleness report for tooling — `astrale update --check --json`.
106
106
  * domain-studio polls this on load to drive its "update available" badge. It is
107
- * unified and NON-THROWING: the CLI axis catches the package-managed / no-metadata
108
- * case (reported as `managed`, never stale), and the SDK axis is already
109
- * best-effort. Skills are intentionally absent they ride along with `astrale
110
- * update`, so a stale CLI or SDK is the only signal worth surfacing.
107
+ * unified and NON-THROWING: an explicit package-managed result uses npm release
108
+ * identity, while script-install failures remain script failures in `error`
109
+ * instead of being recategorized. The SDK axis is already best-effort. Skills are
110
+ * intentionally absent — they ride along with `astrale update`, so a stale CLI or
111
+ * SDK is the only signal worth surfacing.
111
112
  */
112
113
  export type StaleReport = {
113
114
  stale: boolean
114
- cli: { stale: boolean; managed: boolean; current?: string; latest?: string; channel?: string }
115
+ cli: {
116
+ stale: boolean
117
+ managed: boolean
118
+ current?: string
119
+ latest?: string
120
+ channel?: string
121
+ error?: string
122
+ }
115
123
  sdk: { stale: boolean; inProject: boolean; outdated: SdkOutdated[] }
116
124
  }
117
125
 
@@ -138,6 +146,15 @@ export async function cliStale(
138
146
  version: target.version,
139
147
  currentVersion: running,
140
148
  })
149
+ if (r.status === 'managed') {
150
+ const latest = await dependencies.fetchPackageVersion(target).catch(() => undefined)
151
+ return {
152
+ stale: latest !== undefined && latest !== running,
153
+ managed: true,
154
+ current: running,
155
+ ...(latest === undefined ? {} : { latest, channel: 'npm' }),
156
+ }
157
+ }
141
158
  if (r.status === 'updated') {
142
159
  return {
143
160
  stale: false,
@@ -154,13 +171,12 @@ export async function cliStale(
154
171
  latest: r.latestVersion,
155
172
  channel: r.channel,
156
173
  }
157
- } catch {
158
- const latest = await dependencies.fetchPackageVersion(target).catch(() => undefined)
174
+ } catch (error) {
159
175
  return {
160
- stale: latest !== undefined && latest !== running,
161
- managed: true,
176
+ stale: false,
177
+ managed: false,
162
178
  current: running,
163
- ...(latest === undefined ? {} : { latest, channel: 'npm' }),
179
+ error: error instanceof Error ? error.message : String(error),
164
180
  }
165
181
  }
166
182
  }
@@ -294,6 +310,10 @@ Examples:
294
310
  log.success(`Updated astrale ${result.previousVersion} -> ${result.currentVersion}`)
295
311
  log.dim(` channel: ${result.channel}`)
296
312
  log.dim(` binary: ${result.bin}`)
313
+ } else if (result?.status === 'managed') {
314
+ const error = packageManagedUpdateError(result.executable)
315
+ if (!opts.yes) throw error
316
+ log.warn(`CLI self-update skipped: ${error.message}`)
297
317
  }
298
318
 
299
319
  // Axis B — agent skills. Keep an existing install current with the CLI.
@@ -89,9 +89,10 @@ export async function resolveSession(
89
89
  }
90
90
  const targetInput =
91
91
  parsed.kind === 'target' ? parsed.path : (opts.target ?? viewOwnerTarget(parsed.path))
92
- const { path: target } = await expandSelfInPath(targetInput, opts)
93
-
94
- const candidates = await withClientSession(opts, (ctx) => resolveViewCandidates(ctx, target))
92
+ const { target, candidates } = await withClientSession(opts, async (context) => {
93
+ const { path: target } = await expandSelfInPath(targetInput, context)
94
+ return { target, candidates: await resolveViewCandidates(context, target) }
95
+ })
95
96
 
96
97
  if (opts.list) {
97
98
  return { candidates }
@@ -1,8 +1,7 @@
1
- import type { Call } from '@astrale-os/kernel-client'
2
- import type { AuthApi } from '@astrale-os/kernel-client/auth'
3
- import type { GraphApi } from '@astrale-os/kernel-client/graph'
4
- import type { ClientSession } from '@astrale-os/kernel-client/session'
5
1
  import type { IssuerId } from '@astrale-os/sdk/auth'
2
+ import type { AuthApi } from '@astrale-os/sdk/auth'
3
+ import type { Call, GraphApi } from '@astrale-os/sdk/client'
4
+ import type { ClientSession } from '@astrale-os/sdk/client/session'
6
5
 
7
6
  /** Existing CLI connection flags accepted by Kernel-touching commands. */
8
7
  export interface ConnectionOptions {
@@ -78,17 +77,17 @@ export function withAdminClientSession<Value>(
78
77
  /** Expand @self through the effective principal returned by authenticated Identity.whoami. */
79
78
  export function expandSelfInPath(
80
79
  path: string,
81
- options: KernelCommandOpts,
80
+ context: ConnectionContext,
82
81
  ): Promise<{ readonly path: string; readonly meta?: SelfExpansionMeta }>
83
82
 
84
83
  /** Expand @self once across one Call path and its CLI-authored string parameters. */
85
84
  export function expandSelfInCall(
86
85
  path: string,
87
- parameters: readonly string[],
88
- options: KernelCommandOpts,
86
+ parameters: Readonly<Record<string, unknown>>,
87
+ context: ConnectionContext,
89
88
  ): Promise<{
90
89
  readonly path: string
91
- readonly parameters: readonly string[]
90
+ readonly parameters: Readonly<Record<string, unknown>>
92
91
  readonly meta?: SelfExpansionMeta
93
92
  }>
94
93
 
@@ -40,7 +40,8 @@ customizes only the Fetch capability passed to Client. `withClientSession` and
40
40
  `withAdminClientSession` are terminal lifecycle boundaries: success, failure, and cancellation all
41
41
  close both the Client Session and its direct source-Auth client.
42
42
 
43
- The command boundary maps typed Client transport phase and delivery evidence without inspecting a
44
- private cause message. It preserves every admitted Kernel reason in machine output; human repair
45
- details are rendered only after the connection owner admits bounded public Function issues or one
46
- exact Query reason variant.
43
+ The command boundary projects typed Client failure identity, transport context, phase, and
44
+ invocation-only delivery evidence without inspecting a private cause message. Unknown native
45
+ failures become one honest unexpected diagnostic; their bounded cause graph is visible only under
46
+ explicit debug output. Every admitted Kernel reason remains available in machine output, while
47
+ human repair details require a bounded public Function issue or exact Query reason variant.
@@ -1,5 +1,5 @@
1
- import type { SessionAuth } from '@astrale-os/kernel-client/session'
2
- import type { Call } from '@astrale-os/kernel-protocol/invocation'
1
+ import type { Call } from '@astrale-os/sdk/client'
2
+ import type { SessionAuth } from '@astrale-os/sdk/client/session'
3
3
 
4
4
  import type { ConnectionContext, ConnectionOptions, ConnectionTarget } from '../api.js'
5
5
 
@@ -111,7 +111,7 @@ export const CLI_AUTH_REGISTRATION_TARGET = defineLaw({
111
111
  export const CLI_SELF_AUTHENTICATED_PRINCIPAL = defineLaw({
112
112
  id: 'CLI-SELF-AUTHENTICATED-PRINCIPAL',
113
113
  statement:
114
- '@self resolves only from authenticated Identity.whoami on the selected target, so delegated carriers use their effective principal and never an unverified JWT subject or stale local registration.',
114
+ '@self resolves inside the command-owned Client Session from authenticated Identity.whoami on the selected target, so resolution and dispatch share one lifecycle and delegated carriers never use an unverified JWT subject or stale local registration.',
115
115
  tests: [
116
116
  {
117
117
  file: '__tests__/self.test.ts',
@@ -183,7 +183,7 @@ export const CLI_CONNECTION_PUBLIC_SEMANTIC_REASON = defineLaw({
183
183
  export const CLI_CONNECTION_TYPED_ERROR_PRESENTATION = defineLaw({
184
184
  id: 'CLI-CONNECTION-TYPED-ERROR-PRESENTATION',
185
185
  statement:
186
- 'The command boundary maps typed Client transport phase and delivery evidence without parsing a private cause, preserves the admitted Kernel reason in machine output, and renders only bounded public Function issues or one exact Query repair variant for humans.',
186
+ 'The command boundary maps typed Client failure identity, transport context, phase, and invocation-only delivery evidence without parsing a private cause; acquisition never receives retry advice, unknown native failures have one non-blank unexpected diagnostic, and raw causes appear only under explicit debug output.',
187
187
  tests: [
188
188
  {
189
189
  file: '__tests__/errors.test.ts',
@@ -197,5 +197,9 @@ export const CLI_CONNECTION_TYPED_ERROR_PRESENTATION = defineLaw({
197
197
  file: '__tests__/reasons.test.ts',
198
198
  id: 'TEST-CLI-CONNECTION-ADMITS-BOUNDED-REASONS',
199
199
  },
200
+ {
201
+ file: '__tests__/failure-safety.test.ts',
202
+ id: 'TEST-CLI-CONNECTION-FAILURE-SAFETY',
203
+ },
200
204
  ],
201
205
  })
@@ -8,6 +8,7 @@ export default defineLayout({
8
8
  'command.ts',
9
9
  'credential.ts',
10
10
  'errors.ts',
11
+ 'failure/',
11
12
  'exchange.ts',
12
13
  'index.ts',
13
14
  'reasons.ts',
@@ -1,4 +1,4 @@
1
- import type { SessionAuth } from '@astrale-os/kernel-client/session'
1
+ import type { SessionAuth } from '@astrale-os/sdk/client/session'
2
2
 
3
3
  import { issuer, type IssuerId } from '@astrale-os/sdk/auth'
4
4
  import { Path } from '@astrale-os/sdk/graph/path'
@@ -1,7 +1,9 @@
1
- import { ResponseError, TransportError } from '@astrale-os/kernel-client'
1
+ import { ResponseError } from '@astrale-os/sdk/client'
2
+ import { Path } from '@astrale-os/sdk/graph/path'
2
3
  import { describe, expect, test } from 'bun:test'
3
4
 
4
5
  import { formatKernelError, functionInputIssues, schemaUpgradeHint } from '../errors'
6
+ import { transportFailure } from './failure-fixtures'
5
7
 
6
8
  const CONFLICT = 4001
7
9
  const VALIDATION = 1003
@@ -19,24 +21,28 @@ describe('formatKernelError', () => {
19
21
  writes.push(typeof chunk === 'string' ? chunk : new TextDecoder().decode(chunk))
20
22
  return true
21
23
  }) as typeof process.stderr.write
22
- const error = new TransportError('Publication discovery request failed.', {
23
- cause: Object.assign(new Error('connect ECONNREFUSED'), { code: 'ECONNREFUSED' }),
24
- phase: 'connect',
25
- delivery: 'unknown',
26
- }) as TransportError & { url?: string }
27
- error.url = 'https://localhost:8443/kernel/host'
24
+ const error = Object.assign(
25
+ transportFailure('Publication discovery request failed.', 'unknown', {
26
+ kind: 'acquisition',
27
+ resource: 'publication',
28
+ }),
29
+ {
30
+ url: 'https://localhost:8443/kernel/host',
31
+ cause: Object.assign(new Error('connect ECONNREFUSED'), { code: 'ECONNREFUSED' }),
32
+ },
33
+ )
28
34
  try {
29
- await formatKernelError(error, true)
35
+ await formatKernelError(error, true, 'https://localhost:8443/kernel/host')
30
36
  } finally {
31
37
  process.stderr.write = original
32
38
  }
33
39
 
34
40
  expect(JSON.parse(writes[0]!)).toMatchObject({
35
- error: 'CONNECTION_ERROR',
41
+ error: 'TRANSPORT_ERROR',
36
42
  message: 'Publication discovery request failed.',
37
43
  url: 'https://localhost:8443/kernel/host',
38
- phase: 'connect',
39
- delivery: 'unknown',
44
+ phase: 'unknown',
45
+ transport: { kind: 'acquisition', resource: 'publication' },
40
46
  })
41
47
  expect(writes[0]).not.toContain('ECONNREFUSED')
42
48
  })
@@ -50,9 +56,8 @@ describe('formatKernelError', () => {
50
56
  }) as typeof process.stderr.write
51
57
  try {
52
58
  await formatKernelError(
53
- new TransportError('Request timed out.', {
54
- cause: new Error('timeout'),
55
- phase: 'timeout',
59
+ transportFailure('Request timed out.', 'timeout', {
60
+ kind: 'invocation',
56
61
  delivery: 'unknown',
57
62
  }),
58
63
  true,
@@ -74,7 +79,7 @@ describe('formatKernelError', () => {
74
79
  expect(JSON.parse(writes[0]!)).toMatchObject({
75
80
  error: 'TIMEOUT',
76
81
  phase: 'timeout',
77
- delivery: 'unknown',
82
+ transport: { kind: 'invocation', delivery: 'unknown' },
78
83
  operation: '4a4c9a18-50f6-4d84-a7b7-2d83e3e45dc8',
79
84
  retry:
80
85
  'astrale domain install https://crm.test --direct --operation 4a4c9a18-50f6-4d84-a7b7-2d83e3e45dc8',
@@ -229,7 +234,7 @@ describe('formatKernelError', () => {
229
234
  )
230
235
  })
231
236
 
232
- test('maps SDK class names to stable CLI error codes', async () => {
237
+ test('maps SDK error identities to stable CLI error codes', async () => {
233
238
  const writes: string[] = []
234
239
  const original = process.stderr.write
235
240
  process.stderr.write = ((chunk: string | Uint8Array) => {
@@ -237,15 +242,17 @@ describe('formatKernelError', () => {
237
242
  return true
238
243
  }) as typeof process.stderr.write
239
244
  try {
240
- const pathError = new Error('Path must have an Id or Domain anchor.')
241
- pathError.name = 'PathError'
242
- await formatKernelError(pathError, true)
245
+ try {
246
+ Path.parse('not-a-path')
247
+ } catch (error) {
248
+ await formatKernelError(error, true)
249
+ }
243
250
  } finally {
244
251
  process.stderr.write = original
245
252
  }
246
253
  expect(JSON.parse(writes[0]!)).toEqual({
247
254
  error: 'PATH_INVALID',
248
- message: 'Path must have an Id or Domain anchor.',
255
+ message: expect.any(String),
249
256
  })
250
257
  })
251
258
 
@@ -1,4 +1,4 @@
1
- import type { Fetch } from '@astrale-os/kernel-client'
1
+ import type { Fetch } from '@astrale-os/sdk/client'
2
2
 
3
3
  import { issuer } from '@astrale-os/sdk/auth'
4
4
  import { afterEach, beforeEach, describe, expect, test } from 'bun:test'
@@ -190,6 +190,35 @@ describe('Domain token exchange', () => {
190
190
  message: 'Token exchange returned an invalid success response.',
191
191
  })
192
192
  })
193
+
194
+ test('normalizes native issuer Fetch failures without exposing their message', async () => {
195
+ const native = new AggregateError([new Error('private DNS state')], '')
196
+ const resolver = createExchangeCredentialResolver(
197
+ TARGET,
198
+ { resolve: async () => SOURCE_TOKEN },
199
+ async (input, init) => {
200
+ if (String(input) === INVOCATION) {
201
+ const body = JSON.parse(await new Response(init?.body).text()) as Record<string, any>
202
+ return invocationResponse(
203
+ body.requestId,
204
+ body.call.input && Object.keys(body.call.input).length === 0
205
+ ? { id: 'user-1' }
206
+ : 'kernel-destination-envelope',
207
+ new Headers(init?.headers).get('accept')!,
208
+ )
209
+ }
210
+ throw native
211
+ },
212
+ 5_000,
213
+ new ExchangeCredentialCache(join(directory, 'native-failure.json')),
214
+ )
215
+
216
+ await expect(resolver.resolve(KERNEL, new AbortController().signal)).rejects.toMatchObject({
217
+ code: 'TOKEN_EXCHANGE_UNAVAILABLE',
218
+ message: 'Domain issuer discovery could not be reached.',
219
+ cause: native,
220
+ })
221
+ })
193
222
  })
194
223
 
195
224
  function exchangeFetch(
@@ -0,0 +1,39 @@
1
+ import { ClientError, TransportError } from '@astrale-os/sdk/client'
2
+
3
+ import type { TransportDiagnosticContext } from '../failure/model'
4
+
5
+ type CompatibleTransportPhase = TransportError['phase'] | 'unknown'
6
+
7
+ export function transportFailure(
8
+ message: string,
9
+ phase: CompatibleTransportPhase,
10
+ context: TransportDiagnosticContext,
11
+ ): TransportError {
12
+ const error = new Error(message) as TransportError & {
13
+ context: TransportDiagnosticContext
14
+ }
15
+ Object.setPrototypeOf(error, TransportError.prototype)
16
+ Object.assign(error, { name: 'TransportError', phase, context })
17
+ return error
18
+ }
19
+
20
+ export function legacyTransportFailure(
21
+ message: string,
22
+ phase: CompatibleTransportPhase,
23
+ delivery: 'not-sent' | 'unknown',
24
+ ): TransportError {
25
+ const error = new Error(message) as TransportError & { delivery: typeof delivery }
26
+ Object.setPrototypeOf(error, TransportError.prototype)
27
+ Object.assign(error, { name: 'TransportError', phase, delivery })
28
+ return error
29
+ }
30
+
31
+ export function sessionFailure(
32
+ message: string,
33
+ failure: 'cancelled' | 'closed' | 'timeout',
34
+ ): ClientError {
35
+ const error = new Error(message) as ClientError & { failure: typeof failure }
36
+ Object.setPrototypeOf(error, ClientError.prototype)
37
+ Object.assign(error, { name: 'SessionError', failure })
38
+ return error
39
+ }
@@ -0,0 +1,75 @@
1
+ import { expect, test } from 'bun:test'
2
+
3
+ import { formatKernelError } from '../errors'
4
+ import { legacyTransportFailure, sessionFailure, transportFailure } from './failure-fixtures'
5
+
6
+ /** @evidence TEST-CLI-CONNECTION-FAILURE-SAFETY */
7
+ test('unknown and empty native failures never become ordinary diagnostics', async () => {
8
+ for (const failure of [new Error('private provider detail'), new Error(''), { secret: true }]) {
9
+ const output = await capture(() => formatKernelError(failure, true))
10
+ expect(JSON.parse(output)).toEqual({
11
+ error: 'UNEXPECTED_ERROR',
12
+ message: 'The CLI encountered an unexpected internal failure.',
13
+ })
14
+ expect(output).not.toContain('private provider detail')
15
+ }
16
+ })
17
+
18
+ test('Aggregate children are available only under explicit debug output', async () => {
19
+ const failure = new AggregateError([new Error('first private'), new Error('second private')], '')
20
+ const ordinary = await capture(() => formatKernelError(failure, true))
21
+ expect(ordinary).not.toContain('first private')
22
+ expect(ordinary).not.toContain('second private')
23
+
24
+ const debug = await capture(() => formatKernelError(failure, true, '', true))
25
+ expect(debug).toContain('aggregate: Error: first private')
26
+ expect(debug).toContain('aggregate: Error: second private')
27
+ })
28
+
29
+ test('Session and acquisition failures remain typed without unsafe recovery advice', async () => {
30
+ const session = await capture(() =>
31
+ formatKernelError(sessionFailure('Session operation timed out.', 'timeout'), true),
32
+ )
33
+ expect(JSON.parse(session)).toEqual({ error: 'TIMEOUT', message: 'Session operation timed out.' })
34
+
35
+ const acquisition = await capture(() =>
36
+ formatKernelError(
37
+ transportFailure('Publication failed.', 'unknown', {
38
+ kind: 'acquisition',
39
+ resource: 'publication',
40
+ }),
41
+ true,
42
+ '',
43
+ false,
44
+ { recovery: { operation: 'must-not-leak', retry: 'must-not-leak' } },
45
+ ),
46
+ )
47
+ expect(JSON.parse(acquisition)).toMatchObject({
48
+ error: 'TRANSPORT_ERROR',
49
+ transport: { kind: 'acquisition', resource: 'publication' },
50
+ })
51
+ expect(acquisition).not.toContain('must-not-leak')
52
+
53
+ const legacy = await capture(() =>
54
+ formatKernelError(legacyTransportFailure('Legacy send failed.', 'send', 'not-sent'), true),
55
+ )
56
+ expect(JSON.parse(legacy)).toMatchObject({
57
+ error: 'TRANSPORT_ERROR',
58
+ transport: { kind: 'invocation', delivery: 'not-sent' },
59
+ })
60
+ })
61
+
62
+ async function capture(action: () => Promise<void>): Promise<string> {
63
+ const writes: string[] = []
64
+ const original = process.stderr.write
65
+ process.stderr.write = ((chunk: string | Uint8Array) => {
66
+ writes.push(typeof chunk === 'string' ? chunk : new TextDecoder().decode(chunk))
67
+ return true
68
+ }) as typeof process.stderr.write
69
+ try {
70
+ await action()
71
+ return writes.join('')
72
+ } finally {
73
+ process.stderr.write = original
74
+ }
75
+ }
@@ -2,7 +2,11 @@ import { describe, expect, test } from 'bun:test'
2
2
  import { SignJWT } from 'jose'
3
3
  import { webcrypto } from 'node:crypto'
4
4
 
5
- import { resolveSelfIdAuthenticated } from '../self'
5
+ import type { ConnectionContext } from '../session'
6
+
7
+ import { expandSelfInCall, resolveSelfIdAuthenticated } from '../self'
8
+
9
+ const context = Object.freeze({}) as ConnectionContext
6
10
 
7
11
  describe('resolveSelfIdAuthenticated', () => {
8
12
  /** @evidence TEST-CLI-SELF-USES-AUTHENTICATED-EFFECTIVE-PRINCIPAL */
@@ -14,11 +18,9 @@ describe('resolveSelfIdAuthenticated', () => {
14
18
  .setSubject('manager-principal')
15
19
  .setAudience('https://child.example')
16
20
  .sign(key)
17
- const opts = { creds: credential, url: 'https://manager.example/children/child/invoke' }
18
-
19
- const resolved = await resolveSelfIdAuthenticated(opts, {
21
+ const resolved = await resolveSelfIdAuthenticated(context, {
20
22
  whoami: async (received) => {
21
- expect(received).toBe(opts)
23
+ expect(received).toBe(context)
22
24
  return { id: 'child-kernel-principal', slug: 'child' }
23
25
  },
24
26
  })
@@ -28,17 +30,35 @@ describe('resolveSelfIdAuthenticated', () => {
28
30
 
29
31
  test('resolves an imported root without requiring a local registration', async () => {
30
32
  await expect(
31
- resolveSelfIdAuthenticated(
32
- { as: 'platform-root', url: 'https://manager.example/invoke' },
33
- { whoami: async () => ({ id: 'manager-kernel-principal' }) },
34
- ),
33
+ resolveSelfIdAuthenticated(context, {
34
+ whoami: async () => ({ id: 'manager-kernel-principal' }),
35
+ }),
35
36
  ).resolves.toEqual({ id: 'manager-kernel-principal' })
36
37
  })
37
38
 
38
39
  test('fails closed when authenticated whoami returns no NodeId', async () => {
39
40
  await expect(
40
- resolveSelfIdAuthenticated({}, { whoami: async () => ({ id: ' ' }) }),
41
- ).rejects.toMatchObject({ name: 'SelfResolutionError' })
41
+ resolveSelfIdAuthenticated(context, { whoami: async () => ({ id: ' ' }) }),
42
+ ).rejects.toMatchObject({ code: 'SELF_RESOLUTION_FAILED' })
43
+ })
44
+ })
45
+
46
+ describe('expandSelfInCall', () => {
47
+ test('expands only top-level string values after local parameter admission', async () => {
48
+ const callContext = {
49
+ auth: { whoami: async () => ({ id: 'caller-id' }) },
50
+ target: {},
51
+ } as ConnectionContext
52
+ const expanded = await expandSelfInCall(
53
+ '/:people.example.dev:class.Person:get',
54
+ { owner: '@self', count: 2, nested: { owner: '@self' } },
55
+ callContext,
56
+ )
57
+ expect(expanded.parameters).toEqual({
58
+ owner: '@caller-id',
59
+ count: 2,
60
+ nested: { owner: '@self' },
61
+ })
42
62
  })
43
63
  })
44
64
 
@@ -1,6 +1,6 @@
1
- import type { AuthApi } from '@astrale-os/kernel-client/auth'
2
- import type { GraphApi } from '@astrale-os/kernel-client/graph'
3
- import type { ClientSession } 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 { ClientSession } from '@astrale-os/sdk/client/session'
4
4
 
5
5
  import { issuer } from '@astrale-os/sdk/auth'
6
6
  import { describe, expect, test } from 'bun:test'
@@ -1,4 +1,4 @@
1
- import { call, type Call } from '@astrale-os/kernel-client'
1
+ import { call, type Call } from '@astrale-os/sdk/client'
2
2
  import { Path } from '@astrale-os/sdk/graph/path'
3
3
 
4
4
  /** Convert the CLI's untrusted text/JSON boundary into the one public Call representation. */
@@ -1,5 +1,5 @@
1
- import type { Fetch } from '@astrale-os/kernel-client'
2
- import type { SessionAuth } from '@astrale-os/kernel-client/session'
1
+ import type { Fetch } from '@astrale-os/sdk/client'
2
+ import type { SessionAuth } from '@astrale-os/sdk/client/session'
3
3
 
4
4
  import { credential, type IssuerId } from '@astrale-os/sdk/auth'
5
5