@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
@@ -3,6 +3,7 @@ import { describe, expect, test } from 'bun:test'
3
3
  import type { AstraleConfig } from '../config'
4
4
  import type { InstanceStore } from '../instance'
5
5
 
6
+ import { AdminInstanceNotFoundError } from '../../admin/instance/model'
6
7
  import { AstraleError } from '../../errors'
7
8
  import { DEFAULT_CONFIG } from '../config'
8
9
  import {
@@ -124,9 +125,6 @@ describe('resolveInstanceTarget', () => {
124
125
  })
125
126
 
126
127
  test('unknown slugs surface the original instance-not-found error', async () => {
127
- const notFound = new Error('Path not found: "/admin/instances/missing"')
128
- notFound.name = 'NotFoundError'
129
-
130
128
  await expect(
131
129
  resolveInstanceTarget(
132
130
  { source: 'name', name: 'missing' },
@@ -134,7 +132,7 @@ describe('resolveInstanceTarget', () => {
134
132
  config: DEFAULT_CONFIG,
135
133
  instances: emptyStore,
136
134
  managed: async () => {
137
- throw notFound
135
+ throw new AdminInstanceNotFoundError('missing')
138
136
  },
139
137
  },
140
138
  ),
@@ -168,14 +166,8 @@ describe('instance target helpers', () => {
168
166
  expect(couldBeConfiguredAdminInstance('bryan', directAdminConfig)).toBe(false)
169
167
  })
170
168
 
171
- test('treats wrapped remote Instance.info misses as managed lookup not-found', () => {
172
- const wrapped = new Error('Path not found: "/admin/instances/admin"') as Error & {
173
- data?: unknown
174
- }
175
- wrapped.name = 'KernelError'
176
- wrapped.data = { type: 'NotFoundError' }
177
-
178
- expect(isManagedInstanceNotFound(wrapped)).toBe(true)
169
+ test('treats the owner-scoped Admin error as managed lookup not-found', () => {
170
+ expect(isManagedInstanceNotFound(new AdminInstanceNotFoundError('admin'))).toBe(true)
179
171
  })
180
172
 
181
173
  test('does not hide non-not-found managed lookup failures', () => {
@@ -194,37 +186,12 @@ describe('instance target helpers', () => {
194
186
  })
195
187
  })
196
188
 
197
- describe('isManagedInstanceNotFound: kernel InternalKernelError wrap', () => {
198
- test('NOT_FOUND-prefixed InternalKernelError is an instance miss', () => {
199
- const e = new Error('NOT_FOUND: no instance node for "knowledge"')
200
- e.name = 'InternalKernelError'
201
- expect(isManagedInstanceNotFound(e)).toBe(true)
202
- })
203
- test('other InternalKernelError messages are not swallowed', () => {
189
+ describe('isManagedInstanceNotFound: unknown errors', () => {
190
+ test('does not infer a miss from an InternalKernelError message', () => {
204
191
  const e = new Error('DISPATCH_FAILED: handler crashed')
205
192
  e.name = 'InternalKernelError'
206
193
  expect(isManagedInstanceNotFound(e)).toBe(false)
207
194
  })
208
- test('resolveNamedInstanceTarget maps the wrap to typed INSTANCE_NOT_FOUND', async () => {
209
- const managed = async () => {
210
- const e = new Error('NOT_FOUND: no instance node for "ghost"')
211
- e.name = 'InternalKernelError'
212
- throw e
213
- }
214
- const opts = {
215
- config: DEFAULT_CONFIG,
216
- instances: { instances: {} },
217
- managed,
218
- } as unknown as Parameters<typeof resolveInstanceTarget>[1]
219
- let caught: unknown
220
- try {
221
- await resolveInstanceTarget({ source: 'name', name: 'ghost' }, opts)
222
- } catch (e) {
223
- caught = e
224
- }
225
- expect(caught).toBeInstanceOf(AstraleError)
226
- expect((caught as AstraleError).code).toBe('INSTANCE_NOT_FOUND')
227
- })
228
195
 
229
196
  test('maps Admin token-exchange failure to INSTANCE_NOT_FOUND', async () => {
230
197
  const managed = async () => {
@@ -0,0 +1,53 @@
1
+ import { afterEach, beforeEach, describe, expect, test } from 'bun:test'
2
+
3
+ import { AstraleError } from '../../errors'
4
+ import { failInput, fatal } from '../log'
5
+
6
+ class ExitError extends Error {}
7
+
8
+ let stderr = ''
9
+ let exit: typeof process.exit
10
+ let write: typeof process.stderr.write
11
+
12
+ beforeEach(() => {
13
+ stderr = ''
14
+ exit = process.exit
15
+ write = process.stderr.write
16
+ process.exit = (() => {
17
+ throw new ExitError()
18
+ }) as typeof process.exit
19
+ process.stderr.write = ((chunk: string | Uint8Array) => {
20
+ stderr += String(chunk)
21
+ return true
22
+ }) as typeof process.stderr.write
23
+ })
24
+
25
+ afterEach(() => {
26
+ process.exit = exit
27
+ process.stderr.write = write
28
+ })
29
+
30
+ describe('terminal error safety', () => {
31
+ test('renders an empty aggregate as an honest non-blank unexpected failure', () => {
32
+ expect(() => fatal(new AggregateError([], ''), { json: true })).toThrow(ExitError)
33
+ expect(JSON.parse(stderr)).toEqual({
34
+ error: 'UNEXPECTED_ERROR',
35
+ message: 'The CLI encountered an unexpected internal failure.',
36
+ })
37
+ })
38
+
39
+ test('rejects blank admitted CLI diagnostics', () => {
40
+ expect(() => new AstraleError('INVALID_INPUT', ' ')).toThrow(TypeError)
41
+ })
42
+
43
+ test('labels only expected admission failures as invalid input', () => {
44
+ expect(() => failInput(new TypeError('Authored value is invalid.'), { json: true })).toThrow(
45
+ ExitError,
46
+ )
47
+ expect(JSON.parse(stderr)).toMatchObject({ error: 'INVALID_INPUT' })
48
+
49
+ stderr = ''
50
+ expect(() => failInput(new AggregateError([], ''), { json: true })).toThrow(ExitError)
51
+ expect(JSON.parse(stderr)).toMatchObject({ error: 'UNEXPECTED_ERROR' })
52
+ })
53
+ })
@@ -103,7 +103,7 @@ describe('studio server runtime deps', () => {
103
103
  describe('studio test gate', () => {
104
104
  test('the root and Studio suites include all client, server, and shared tests', () => {
105
105
  expect(cliPackage.scripts?.test).toBe(
106
- 'bun test src studio/client/src studio/server studio/shared && node --test scripts/*.test.mjs',
106
+ 'node scripts/qualification/source-boundary.mjs --target && bun test src studio/client/src studio/server studio/shared && node --test scripts/*.test.mjs',
107
107
  )
108
108
  expect(cliPackage.scripts?.['test:studio']).toBe('pnpm --dir studio test')
109
109
  expect(studioPackage.scripts?.test).toBe('bun test client/src server shared')
@@ -1,16 +1,20 @@
1
1
  import { describe, expect, test } from 'bun:test'
2
- import { chmod, mkdir, mkdtemp, readFile, writeFile } from 'node:fs/promises'
2
+ import { chmod, mkdir, mkdtemp, readFile, symlink, writeFile } from 'node:fs/promises'
3
3
  import { tmpdir } from 'node:os'
4
4
  import { join } from 'node:path'
5
5
 
6
6
  import {
7
+ admitScriptInstall,
8
+ classifyUpdateExecution,
7
9
  DEFAULT_UPDATE_CHANNEL,
8
10
  InstallMetadataSchema,
11
+ readInstallMetadata,
9
12
  releaseBase,
10
13
  shouldUpdate,
11
14
  updateAstrale,
12
15
  writeInstallMetadata,
13
16
  type InstallMetadata,
17
+ type UpdateExecution,
14
18
  } from '../update'
15
19
 
16
20
  async function makeFakeRelease(
@@ -65,7 +69,7 @@ async function makeFakeRelease(
65
69
  async function makeInstall(
66
70
  root: string,
67
71
  version: string,
68
- ): Promise<{ meta: InstallMetadata; path: string }> {
72
+ ): Promise<{ meta: InstallMetadata; path: string; execution: UpdateExecution }> {
69
73
  const bin = join(root, 'bin', 'astrale')
70
74
  await mkdir(join(root, 'bin'), { recursive: true })
71
75
  await writeFile(
@@ -82,10 +86,33 @@ async function makeInstall(
82
86
  bin,
83
87
  }
84
88
  await writeInstallMetadata(meta, path)
85
- return { meta, path }
89
+ return { meta, path, execution: { kind: 'standalone', executable: bin } }
86
90
  }
87
91
 
88
92
  describe('update helpers', () => {
93
+ test('classifies only a Bun-compiled executable as standalone', () => {
94
+ expect(
95
+ classifyUpdateExecution({
96
+ bunVersion: '1.3.14',
97
+ executable: '/tmp/astrale',
98
+ entry: '/$bunfs/root/astrale',
99
+ }),
100
+ ).toEqual({ kind: 'standalone', executable: '/tmp/astrale' })
101
+ expect(
102
+ classifyUpdateExecution({
103
+ bunVersion: '1.3.14',
104
+ executable: '/opt/homebrew/bin/bun',
105
+ entry: '/tmp/astrale.ts',
106
+ }),
107
+ ).toEqual({ kind: 'package-managed', executable: '/opt/homebrew/bin/bun' })
108
+ expect(
109
+ classifyUpdateExecution({
110
+ executable: '/opt/homebrew/bin/node',
111
+ entry: '/tmp/astrale.js',
112
+ }),
113
+ ).toEqual({ kind: 'package-managed', executable: '/opt/homebrew/bin/node' })
114
+ })
115
+
89
116
  test('defaults missing install metadata to the beta channel', () => {
90
117
  expect(DEFAULT_UPDATE_CHANNEL).toBe('beta')
91
118
  expect(
@@ -118,10 +145,70 @@ describe('update helpers', () => {
118
145
  })
119
146
  })
120
147
 
148
+ describe('script install admission', () => {
149
+ test('rejects malformed JSON with the stable metadata error', async () => {
150
+ const root = await mkdtemp(join(tmpdir(), 'astrale-update-test-'))
151
+ const path = join(root, 'install.json')
152
+ await writeFile(path, '{')
153
+
154
+ await expect(readInstallMetadata(path)).rejects.toMatchObject({
155
+ code: 'UPDATE_BAD_INSTALL_METADATA',
156
+ })
157
+ })
158
+
159
+ test('admits a symlink only when it resolves to the recorded binary', async () => {
160
+ const root = await mkdtemp(join(tmpdir(), 'astrale-update-test-'))
161
+ const { meta } = await makeInstall(root, '1.0.0')
162
+ const alias = join(root, 'astrale-alias')
163
+ await symlink(meta.bin, alias)
164
+
165
+ const admitted = await admitScriptInstall(meta, {
166
+ kind: 'standalone',
167
+ executable: alias,
168
+ })
169
+
170
+ expect(admitted.metadata).toEqual(meta)
171
+ })
172
+
173
+ test('rejects a standalone binary that does not own the recorded target', async () => {
174
+ const root = await mkdtemp(join(tmpdir(), 'astrale-update-test-'))
175
+ const { meta } = await makeInstall(root, '1.0.0')
176
+ const other = join(root, 'other-astrale')
177
+ await writeFile(other, '#!/bin/sh\n')
178
+
179
+ await expect(
180
+ admitScriptInstall(meta, { kind: 'standalone', executable: other }),
181
+ ).rejects.toMatchObject({ code: 'UPDATE_INSTALL_MISMATCH' })
182
+ })
183
+ })
184
+
121
185
  describe('updateAstrale', () => {
186
+ test('a package-managed process never consults or mutates a coexisting script install', async () => {
187
+ const root = await mkdtemp(join(tmpdir(), 'astrale-update-test-'))
188
+ const { path, meta } = await makeInstall(root, '1.0.0')
189
+ const before = await readFile(meta.bin, 'utf8')
190
+ process.env.ASTRALE_UPDATE_BASE = 'file:///definitely-not-a-release'
191
+ try {
192
+ const result = await updateAstrale({
193
+ currentVersion: '2.0.0',
194
+ installPath: path,
195
+ execution: { kind: 'package-managed', executable: '/opt/homebrew/bin/node' },
196
+ })
197
+
198
+ expect(result).toEqual({
199
+ status: 'managed',
200
+ currentVersion: '2.0.0',
201
+ executable: '/opt/homebrew/bin/node',
202
+ })
203
+ expect(await readFile(meta.bin, 'utf8')).toBe(before)
204
+ } finally {
205
+ delete process.env.ASTRALE_UPDATE_BASE
206
+ }
207
+ })
208
+
122
209
  test('check compares the installed release identity from metadata', async () => {
123
210
  const root = await mkdtemp(join(tmpdir(), 'astrale-update-test-'))
124
- const { path } = await makeInstall(root, 'main-abc123')
211
+ const { path, execution } = await makeInstall(root, 'main-abc123')
125
212
  const release = await makeFakeRelease(root, 'main-abc123', { binaryVersion: '1.0.0' })
126
213
  process.env.ASTRALE_UPDATE_BASE = `file://${release}`
127
214
  try {
@@ -130,6 +217,7 @@ describe('updateAstrale', () => {
130
217
  currentVersion: '1.0.0',
131
218
  platform: { os: 'darwin', arch: 'arm64' },
132
219
  installPath: path,
220
+ execution,
133
221
  })
134
222
 
135
223
  expect(result).toMatchObject({
@@ -144,7 +232,7 @@ describe('updateAstrale', () => {
144
232
 
145
233
  test('check reports available update without replacing the binary', async () => {
146
234
  const root = await mkdtemp(join(tmpdir(), 'astrale-update-test-'))
147
- const { path, meta } = await makeInstall(root, '1.0.0')
235
+ const { path, meta, execution } = await makeInstall(root, '1.0.0')
148
236
  const release = await makeFakeRelease(root, '1.1.0')
149
237
  process.env.ASTRALE_UPDATE_BASE = `file://${release}`
150
238
  try {
@@ -153,6 +241,7 @@ describe('updateAstrale', () => {
153
241
  currentVersion: '1.0.0',
154
242
  platform: { os: 'darwin', arch: 'arm64' },
155
243
  installPath: path,
244
+ execution,
156
245
  })
157
246
 
158
247
  expect(result).toMatchObject({
@@ -168,7 +257,7 @@ describe('updateAstrale', () => {
168
257
 
169
258
  test('supports legacy string asset manifests', async () => {
170
259
  const root = await mkdtemp(join(tmpdir(), 'astrale-update-test-'))
171
- const { path, meta } = await makeInstall(root, '1.0.0')
260
+ const { path, meta, execution } = await makeInstall(root, '1.0.0')
172
261
  const release = await makeFakeRelease(root, '1.1.0', { legacyManifest: true })
173
262
  process.env.ASTRALE_UPDATE_BASE = `file://${release}`
174
263
  try {
@@ -177,6 +266,7 @@ describe('updateAstrale', () => {
177
266
  currentVersion: '1.0.0',
178
267
  platform: { os: 'darwin', arch: 'arm64' },
179
268
  installPath: path,
269
+ execution,
180
270
  })
181
271
 
182
272
  expect(check).toMatchObject({
@@ -188,6 +278,7 @@ describe('updateAstrale', () => {
188
278
  currentVersion: '1.0.0',
189
279
  platform: { os: 'darwin', arch: 'arm64' },
190
280
  installPath: path,
281
+ execution,
191
282
  })
192
283
 
193
284
  expect(result).toMatchObject({
@@ -204,7 +295,7 @@ describe('updateAstrale', () => {
204
295
 
205
296
  test('updates the binary and install metadata', async () => {
206
297
  const root = await mkdtemp(join(tmpdir(), 'astrale-update-test-'))
207
- const { path, meta } = await makeInstall(root, '1.0.0')
298
+ const { path, meta, execution } = await makeInstall(root, '1.0.0')
208
299
  const release = await makeFakeRelease(root, '1.1.0')
209
300
  process.env.ASTRALE_UPDATE_BASE = `file://${release}`
210
301
  try {
@@ -212,6 +303,7 @@ describe('updateAstrale', () => {
212
303
  currentVersion: '1.0.0',
213
304
  platform: { os: 'darwin', arch: 'arm64' },
214
305
  installPath: path,
306
+ execution,
215
307
  })
216
308
 
217
309
  expect(result).toMatchObject({
@@ -231,7 +323,7 @@ describe('updateAstrale', () => {
231
323
 
232
324
  test('updates canary-style releases whose binary reports the package version', async () => {
233
325
  const root = await mkdtemp(join(tmpdir(), 'astrale-update-test-'))
234
- const { path, meta } = await makeInstall(root, 'main-old123')
326
+ const { path, meta, execution } = await makeInstall(root, 'main-old123')
235
327
  const release = await makeFakeRelease(root, 'main-new456', { binaryVersion: '1.0.0' })
236
328
  process.env.ASTRALE_UPDATE_BASE = `file://${release}`
237
329
  try {
@@ -239,6 +331,7 @@ describe('updateAstrale', () => {
239
331
  currentVersion: '1.0.0',
240
332
  platform: { os: 'darwin', arch: 'arm64' },
241
333
  installPath: path,
334
+ execution,
242
335
  })
243
336
 
244
337
  expect(result).toMatchObject({
@@ -23,7 +23,6 @@ const profile: ResolvedView = {
23
23
  kind: 'definition',
24
24
  definitions: [{ origin: 'shell.test', kind: 'class', name: 'Person' }],
25
25
  },
26
- auth: 'required',
27
26
  },
28
27
  href: 'https://shell.test/profile',
29
28
  handshake: 'shell',
@@ -41,7 +40,6 @@ const card: ResolvedView = {
41
40
  kind: 'definition',
42
41
  definitions: [{ origin: 'shell.test', kind: 'class', name: 'Person' }],
43
42
  },
44
- auth: 'public',
45
43
  },
46
44
  href: 'https://shell.test/card',
47
45
  handshake: 'none',
@@ -29,7 +29,7 @@ describe('view session server credentials', () => {
29
29
  target: target('/:example.test'),
30
30
  route: {
31
31
  key: 'example.test:view.public',
32
- declaration: { target: { kind: 'domain' }, auth: 'required' },
32
+ declaration: { target: { kind: 'domain' } },
33
33
  href: 'https://example.test/ui/public',
34
34
  handshake: 'none',
35
35
  issuer: issuer('https://example.test'),
@@ -58,7 +58,7 @@ describe('view session private state', () => {
58
58
  issuer: issuer('https://example.test'),
59
59
  etag: `sha256:${'a'.repeat(64)}`,
60
60
  revision: revision('b'),
61
- declaration: { target: { kind: 'domain' }, auth: 'required' },
61
+ declaration: { target: { kind: 'domain' } },
62
62
  },
63
63
  },
64
64
  createdAt: '2026-08-12T00:00:00.000Z',
@@ -41,12 +41,9 @@ export function renderCommanderError(
41
41
 
42
42
  if (error.code === 'commander.missingArgument') {
43
43
  const usage = usageFor(matched.path, matched.command)
44
- const argName = error.message.match(/'([^']+)'/)?.[1]
45
44
  return maybeMachine(
46
45
  [
47
- `Missing required argument${argName ? ` ${chalk.bold(`<${argName}>`)}` : ''} for ${chalk.bold(
48
- `astrale ${matched.path.join(' ')}`,
49
- )}`,
46
+ `Missing required argument for ${chalk.bold(`astrale ${matched.path.join(' ')}`)}`,
50
47
  '',
51
48
  'Usage:',
52
49
  ` astrale ${usage}`,
@@ -1,4 +1,4 @@
1
- import { publication } from '@astrale-os/kernel-protocol'
1
+ import { publication } from '@astrale-os/sdk/publication'
2
2
 
3
3
  const MAXIMUM_PUBLICATION_BYTES = 1024 * 1024
4
4
  type FetchLike = (input: RequestInfo | URL, init?: RequestInit) => Promise<Response>
package/src/lib/idp.ts CHANGED
@@ -215,6 +215,7 @@ export type IdpConfig = {
215
215
  export const BUILTIN_WORKOS_IDP_NAME = 'workos'
216
216
  const DEFAULT_WORKOS_API_HOST = 'https://api.workos.com'
217
217
  const DEFAULT_WORKOS_CLIENT_ID = 'client_01KC29HET5F3QAQ8GNTPZ7F320'
218
+ const LEGACY_WORKOS_CLIENT_ID = 'client_01KC29HEGD7B40TV2C4QZ436BG'
218
219
  const WORKOS_CLIENT_ID_ENV_NAMES = ['WORKOS_CLIENT_ID', 'VITE_WORKOS_CLIENT_ID'] as const
219
220
 
220
221
  export function idpDir(name: string): string {
@@ -281,7 +282,17 @@ export async function readIdpConfigOrBuiltin(
281
282
  validateName(name, 'IdP')
282
283
  const store = await readIdpStore()
283
284
  if (store.idps[name]) {
284
- return await readIdpConfig(name)
285
+ const existing = await readIdpConfig(name)
286
+ const replacement = opts.persist
287
+ ? legacyBuiltinWorkosReplacement(existing, opts.clientId)
288
+ : undefined
289
+ if (!replacement) return existing
290
+ return upsertIdpConfig({
291
+ name: replacement.name,
292
+ metadata: replacement.metadata,
293
+ client: replacement.client,
294
+ builtIn: true,
295
+ })
285
296
  }
286
297
 
287
298
  const builtin = builtinIdpConfig(name, opts.clientId)
@@ -421,6 +432,23 @@ export function workosClientIdFromEnv(env: NodeJS.ProcessEnv = process.env): str
421
432
  )
422
433
  }
423
434
 
435
+ export function legacyBuiltinWorkosReplacement(
436
+ existing: IdpConfig,
437
+ clientIdOverride?: string,
438
+ env: NodeJS.ProcessEnv = process.env,
439
+ ): IdpConfig | undefined {
440
+ if (
441
+ existing.name !== BUILTIN_WORKOS_IDP_NAME ||
442
+ !existing.entry.builtIn ||
443
+ existing.client.client_id !== LEGACY_WORKOS_CLIENT_ID
444
+ ) {
445
+ return undefined
446
+ }
447
+ const replacement = builtinIdpConfig(existing.name, clientIdOverride, env)
448
+ if (!replacement || replacement.client.client_id === LEGACY_WORKOS_CLIENT_ID) return undefined
449
+ return replacement
450
+ }
451
+
424
452
  async function fetchOAuthAuthorizationServerMetadata(
425
453
  issuer: string,
426
454
  ): Promise<Partial<OidcMetadata> | undefined> {
@@ -1,6 +1,6 @@
1
- import type { InstanceInfo } from '../admin/instance/model'
2
1
  import type { AstraleConfig } from './config'
3
2
 
3
+ import { AdminInstanceNotFoundError, type InstanceInfo } from '../admin/instance/model'
4
4
  import { AstraleError } from '../errors'
5
5
  import {
6
6
  DEFAULT_ADMIN_TARGET_NAME,
@@ -190,19 +190,8 @@ export function isAdminDiscoveryFailure(error: unknown): boolean {
190
190
  }
191
191
 
192
192
  export function isManagedInstanceNotFound(error: unknown): boolean {
193
- if (error instanceof AstraleError && error.code === 'INSTANCE_NOT_FOUND') return true
194
- if (!(error instanceof Error)) return false
195
- if (error.name === 'NotFoundError') return true
196
- // The admin kernel reports a missing instance node as InternalKernelError
197
- // with a NOT_FOUND-prefixed message. In this lookup that's an instance
198
- // miss (config problem), not a kernel fault — map it so callers get the
199
- // typed INSTANCE_NOT_FOUND with remediation instead of a raw kernel error.
200
- if (error.name === 'InternalKernelError' && /^NOT_FOUND\b/.test(error.message)) return true
201
- const data = (error as Error & { data?: unknown }).data
202
193
  return (
203
- error.name === 'KernelError' &&
204
- !!data &&
205
- typeof data === 'object' &&
206
- (data as { type?: unknown }).type === 'NotFoundError'
194
+ error instanceof AdminInstanceNotFoundError ||
195
+ (error instanceof AstraleError && error.code === 'INSTANCE_NOT_FOUND')
207
196
  )
208
197
  }
package/src/lib/log.ts CHANGED
@@ -22,24 +22,29 @@ export function fatal(e: unknown, opts?: MachineOpts): never {
22
22
  // Ctrl-C at an interactive (@inquirer/prompts) prompt — exit quietly with the
23
23
  // SIGINT convention, not a red error line.
24
24
  if (e instanceof Error && e.name === 'ExitPromptError') process.exit(130)
25
- const msg = e instanceof Error ? e.message : String(e)
26
- const code = e instanceof AstraleError ? e.code : e instanceof Error ? e.name : 'Error'
25
+ const msg =
26
+ e instanceof AstraleError ? e.message : 'The CLI encountered an unexpected internal failure.'
27
+ const code = e instanceof AstraleError ? e.code : 'UNEXPECTED_ERROR'
27
28
  if (isMachine(opts)) {
28
29
  const payload: Record<string, unknown> = { error: code, message: msg }
29
30
  if (e instanceof AstraleError && e.hint) payload.hint = e.hint
30
31
  process.stderr.write(JSON.stringify(payload) + '\n')
31
32
  process.exit(1)
32
33
  }
33
- log.error(e instanceof AstraleError ? `${code}: ${msg}` : msg)
34
+ log.error(`${code}: ${msg}`)
34
35
  if (e instanceof AstraleError && e.hint) log.dim(` hint: ${e.hint}`)
35
36
  process.exit(1)
36
37
  }
37
38
 
38
- /** Admit expected invalid input and exit through {@link fatal}. */
39
- export function failClosed(error: unknown, opts?: MachineOpts): never {
39
+ /** Project only a caller-authored admission failure into the CLI input family. */
40
+ export function failInput(error: unknown, opts?: MachineOpts): never {
40
41
  if (error instanceof AstraleError) fatal(error, opts)
42
+ if (!(error instanceof TypeError)) fatal(error, opts)
41
43
  fatal(
42
- new AstraleError('INVALID_INPUT', error instanceof Error ? error.message : String(error)),
44
+ new AstraleError(
45
+ 'INVALID_INPUT',
46
+ error.message.trim() ? error.message : 'CLI input is invalid.',
47
+ ),
43
48
  opts,
44
49
  )
45
50
  }