@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.
- package/dist/astrale.js +55649 -59388
- package/dist/public/connect-core.js +1972 -26831
- package/dist/public/keys/index.js +1754 -40686
- package/dist/public/paths/index.js +859 -39511
- package/dist/types/admin/instance/model.d.ts +3 -4
- package/dist/types/errors.d.ts +1 -1
- package/dist/types/lib/idp.d.ts +1 -0
- package/dist/types/lib/instance-target.d.ts +1 -1
- package/dist/types/lib/log.d.ts +2 -2
- package/dist/types/lib/update.d.ts +30 -0
- package/package.json +4 -6
- package/src/__tests__/fixtures/publication.ts +20 -0
- package/src/admin/__tests__/binding.test.ts +14 -21
- package/src/admin/__tests__/fixture.ts +101 -0
- package/src/admin/binding.ts +86 -6
- package/src/admin/catalog/.spec/api.d.ts +2 -2
- package/src/admin/catalog/__tests__/client.test.ts +20 -49
- package/src/admin/catalog/client.ts +35 -26
- package/src/admin/catalog/model.ts +3 -4
- package/src/admin/graph/.spec/api.d.ts +4 -10
- package/src/admin/graph/nodes.ts +1 -1
- package/src/admin/instance/.spec/api.d.ts +2 -2
- package/src/admin/instance/__tests__/client.test.ts +58 -83
- package/src/admin/instance/client.ts +49 -39
- package/src/admin/instance/model.ts +9 -8
- package/src/commands/__tests__/call-describe.test.ts +46 -33
- package/src/commands/__tests__/domain-install-operation.test.ts +3 -14
- package/src/commands/__tests__/domain-install-owned.test.ts +1 -1
- package/src/commands/__tests__/domain-list.test.ts +5 -25
- package/src/commands/__tests__/install-identity-override.test.ts +3 -14
- package/src/commands/__tests__/read-commands.test.ts +35 -10
- package/src/commands/__tests__/update.test.ts +25 -3
- package/src/commands/call-describe.ts +59 -67
- package/src/commands/call.ts +41 -54
- package/src/commands/domain/install.ts +2 -2
- package/src/commands/domain/uninstall.ts +2 -2
- package/src/commands/get.ts +13 -14
- package/src/commands/identity/__tests__/register.test.ts +3 -3
- package/src/commands/identity/register.ts +5 -4
- package/src/commands/instance/delete.ts +5 -3
- package/src/commands/instance/status.ts +1 -9
- package/src/commands/introspect.ts +4 -11
- package/src/commands/logs.ts +24 -25
- package/src/commands/mutate.ts +6 -5
- package/src/commands/query.ts +27 -30
- package/src/commands/token.ts +8 -7
- package/src/commands/update.ts +31 -11
- package/src/commands/view.ts +4 -3
- package/src/connection/.spec/api.d.ts +7 -8
- package/src/connection/.spec/architecture.md +5 -4
- package/src/connection/.spec/flows/session.ts +2 -2
- package/src/connection/.spec/laws/connection.ts +6 -2
- package/src/connection/.spec/layout.ts +1 -0
- package/src/connection/__tests__/credential.test.ts +1 -1
- package/src/connection/__tests__/errors.test.ts +27 -20
- package/src/connection/__tests__/exchange.test.ts +30 -1
- package/src/connection/__tests__/failure-fixtures.ts +39 -0
- package/src/connection/__tests__/failure-safety.test.ts +75 -0
- package/src/connection/__tests__/self.test.ts +31 -11
- package/src/connection/__tests__/session.test.ts +3 -3
- package/src/connection/call.ts +1 -1
- package/src/connection/credential.ts +2 -2
- package/src/connection/errors.ts +1 -355
- package/src/connection/exchange.ts +47 -23
- package/src/connection/failure/classify.ts +110 -0
- package/src/connection/failure/debug.ts +26 -0
- package/src/connection/failure/index.ts +18 -0
- package/src/connection/failure/model.ts +28 -0
- package/src/connection/failure/render.ts +116 -0
- package/src/connection/reasons.ts +16 -0
- package/src/connection/self.ts +39 -21
- package/src/connection/session.ts +8 -8
- package/src/errors.ts +3 -2
- package/src/graph/.spec/api.d.ts +12 -5
- package/src/graph/.spec/layout.ts +9 -1
- package/src/graph/__tests__/mutation.test.ts +2 -2
- package/src/graph/__tests__/query.test.ts +25 -6
- package/src/graph/class.ts +30 -0
- package/src/graph/index.ts +1 -0
- package/src/graph/query.ts +21 -32
- package/src/identity/.spec/api.d.ts +2 -2
- package/src/identity/__tests__/registration.test.ts +1 -1
- package/src/identity/registration.ts +3 -3
- package/src/lib/__tests__/domain-publication.test.ts +4 -15
- package/src/lib/__tests__/idp.test.ts +25 -0
- package/src/lib/__tests__/instance-target.test.ts +6 -39
- package/src/lib/__tests__/log-errors.test.ts +53 -0
- package/src/lib/__tests__/studio-server-deps.test.ts +1 -1
- package/src/lib/__tests__/update.test.ts +101 -8
- package/src/lib/__tests__/view-open-intent.test.ts +0 -2
- package/src/lib/__tests__/view-server.test.ts +1 -1
- package/src/lib/__tests__/view-session.test.ts +1 -1
- package/src/lib/command-dx.ts +1 -4
- package/src/lib/domain-publication.ts +1 -1
- package/src/lib/idp.ts +29 -1
- package/src/lib/instance-target.ts +3 -14
- package/src/lib/log.ts +11 -6
- package/src/lib/update.ts +120 -26
- package/src/lib/validation.ts +2 -2
- package/src/lib/view/resolve.ts +1 -1
- package/src/program/__tests__/program.test.ts +1 -1
- package/studio/client/dist/assets/{elk-api-lwhFo7vB.js → elk-api-Di4OXGNH.js} +1 -1
- package/studio/client/dist/assets/index-BLm6J11H.js +81 -0
- package/studio/client/dist/assets/index-BiGR8Oo9.css +1 -0
- package/studio/client/dist/assets/index-DTjvkOUX.js +8 -0
- package/studio/client/dist/index.html +2 -2
- package/studio/package.json +2 -9
- package/studio/server/agent/ask.test.ts +1 -1
- package/studio/server/agent/bridge/grant.test.ts +1 -1
- package/studio/server/agent/bridge/routes.test.ts +1 -1
- package/studio/server/agent/harness/claude/adapter.test.ts +15 -5
- package/studio/server/agent/prompts/anchors.test.ts +32 -36
- package/studio/server/agent/prompts/anchors.ts +20 -49
- package/studio/server/agent/prompts/system.test.ts +3 -2
- package/studio/server/agent/prompts/system.ts +3 -3
- package/studio/server/agent/routes.test.ts +1 -1
- package/studio/server/agent/run/coordinator.test.ts +1 -1
- package/studio/server/api/canvas.ts +0 -1
- package/studio/server/api-agent-loadout.test.ts +1 -1
- package/studio/server/api.test.ts +1 -1
- package/studio/server/cache.test.ts +23 -12
- package/studio/server/cache.ts +12 -15
- package/studio/server/domain.test.ts +50 -68
- package/studio/server/domain.ts +82 -63
- package/studio/server/handoff/copy.ts +1 -1
- package/studio/server/index.ts +1 -1
- package/studio/server/instances/probe.test.ts +1 -1
- package/studio/server/introspect/anatomy/views/routes.ts +46 -37
- package/studio/server/introspect/anatomy/views.ts +9 -14
- package/studio/server/introspect/anatomy-extras.test.ts +26 -42
- package/studio/server/introspect/anatomy.test.ts +22 -14
- package/studio/server/introspect/anatomy.ts +6 -9
- package/studio/server/introspect/bundle.ts +0 -3
- package/studio/server/introspect/canonical-schema.test.ts +94 -389
- package/studio/server/introspect/canonical-schema.ts +227 -457
- package/studio/server/introspect/core-extractor.ts +18 -127
- package/studio/server/introspect/core.ts +1 -2
- package/studio/server/introspect/diff.test.ts +11 -7
- package/studio/server/introspect/diff.ts +18 -55
- package/studio/server/introspect/extractor.ts +22 -117
- package/studio/server/introspect/overlay-tsmorph.test.ts +119 -240
- package/studio/server/introspect/overlay.test.ts +26 -54
- package/studio/server/introspect/overlay.ts +10 -37
- package/studio/server/introspect/revision.test.ts +1 -1
- package/studio/server/introspect/revision.ts +1 -1
- package/studio/server/introspect/runtime.test.ts +65 -183
- package/studio/server/introspect/runtime.ts +8 -25
- package/studio/server/introspect/schema-ir-json.test.ts +70 -0
- package/studio/server/introspect/schema-ir-json.ts +38 -68
- package/studio/server/introspect/schema-refs.test.ts +43 -88
- package/studio/server/introspect/schema-refs.ts +14 -49
- package/studio/server/introspect/source-overlay/handlers.ts +116 -636
- package/studio/server/introspect/source-overlay/kernel-calls.ts +0 -3
- package/studio/server/introspect/source-overlay/spans.ts +15 -45
- package/studio/server/state/baseline.test.ts +14 -9
- package/studio/server/state/baseline.ts +3 -5
- package/studio/server/state/visibility.ts +1 -5
- package/studio/server/views/model.ts +3 -3
- package/studio/server/views/target.test.ts +17 -74
- package/studio/server/views/target.ts +24 -50
- package/studio/server/watch.test.ts +11 -11
- package/studio/server/watch.ts +8 -4
- package/studio/server/workspace-watch.ts +2 -2
- package/studio/shared/contracts/schema.ts +38 -107
- package/studio/shared/contracts/surface.test.ts +13 -15
- package/studio/shared/contracts/workspace.ts +2 -11
- package/studio/shared/schema/identity.test.ts +20 -44
- package/studio/shared/schema/identity.ts +9 -23
- package/viewer/dist/main.js +40 -61
- package/studio/client/dist/assets/index-B-c-smo9.js +0 -8
- package/studio/client/dist/assets/index-BckHuAWk.js +0 -81
- package/studio/client/dist/assets/index-C4aNQ6dz.css +0 -1
- package/studio/server/introspect/anatomy/views/legacy.ts +0 -232
package/src/connection/errors.ts
CHANGED
|
@@ -1,355 +1 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
import type { OperationRecovery } from './command'
|
|
4
|
-
|
|
5
|
-
import { AstraleError } from '../errors'
|
|
6
|
-
import { readLocalStatus, type LocalStatus } from '../lib/local-status'
|
|
7
|
-
import { log } from '../lib/log'
|
|
8
|
-
import {
|
|
9
|
-
functionInputIssues,
|
|
10
|
-
queryInputRepair,
|
|
11
|
-
reasonCode,
|
|
12
|
-
schemaUpgradeDetails,
|
|
13
|
-
schemaUpgradeHint,
|
|
14
|
-
type FunctionInputIssue,
|
|
15
|
-
type QueryInputRepair,
|
|
16
|
-
} from './reasons'
|
|
17
|
-
|
|
18
|
-
export { functionInputIssues, schemaUpgradeHint } from './reasons'
|
|
19
|
-
|
|
20
|
-
/**
|
|
21
|
-
* Format and display a kernel client error.
|
|
22
|
-
*
|
|
23
|
-
* Handles CLI-local errors plus the current Kernel Client public error families.
|
|
24
|
-
*
|
|
25
|
-
* When `debug` is true, additional diagnostic information (class name, full
|
|
26
|
-
* error chain, attached url/details) is printed after the user-facing line.
|
|
27
|
-
*/
|
|
28
|
-
export async function formatKernelError(
|
|
29
|
-
error: unknown,
|
|
30
|
-
isRaw: boolean,
|
|
31
|
-
urlArg = '',
|
|
32
|
-
debug = false,
|
|
33
|
-
opts: { recovery?: OperationRecovery } = {},
|
|
34
|
-
): Promise<void> {
|
|
35
|
-
const url =
|
|
36
|
-
urlArg || (error instanceof Error ? ((error as Error & { url?: string }).url ?? '') : '')
|
|
37
|
-
const localContext = await contextForError(error)
|
|
38
|
-
// Handle AstraleError (AuthError, etc.) with structured hints
|
|
39
|
-
if (error instanceof AstraleError) {
|
|
40
|
-
if (isRaw) {
|
|
41
|
-
writeRaw({ error: error.code, message: error.message, hint: error.hint })
|
|
42
|
-
} else {
|
|
43
|
-
log.error(`${chalk.bold(error.code)}: ${error.message}`)
|
|
44
|
-
if (error.hint) log.dim(` ${error.hint}`)
|
|
45
|
-
}
|
|
46
|
-
if (debug) printDebug(error, url)
|
|
47
|
-
return
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
if (!(error instanceof Error)) {
|
|
51
|
-
if (isRaw) writeRaw({ error: 'UNKNOWN', message: String(error) })
|
|
52
|
-
else log.error(String(error))
|
|
53
|
-
return
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
const name = error.name
|
|
57
|
-
|
|
58
|
-
switch (name) {
|
|
59
|
-
case 'TransportError':
|
|
60
|
-
presentTransportError(error, isRaw, url, localContext, opts.recovery)
|
|
61
|
-
break
|
|
62
|
-
|
|
63
|
-
case 'ResponseError': {
|
|
64
|
-
const code = (error as { readonly code?: unknown }).code
|
|
65
|
-
const reason = (error as { readonly reason?: unknown }).reason
|
|
66
|
-
const codeOfReason = reasonCode(reason)
|
|
67
|
-
const inputIssues = functionInputIssues(reason)
|
|
68
|
-
const queryRepair = queryInputRepair(reason)
|
|
69
|
-
const upgrade = schemaUpgradeDetails(reason)
|
|
70
|
-
const removalHint = schemaDataRemovalHint(reason)
|
|
71
|
-
const domainAddressNotPublic = codeOfReason === 'SCHEMA_DOMAIN_ADDRESS_NOT_PUBLIC'
|
|
72
|
-
const displayMessage = domainAddressNotPublic
|
|
73
|
-
? 'Expose the Domain through a public HTTPS URL or public tunnel, then retry.'
|
|
74
|
-
: removalHint !== undefined && !isRaw
|
|
75
|
-
? 'Existing business data still uses schema definitions being removed.'
|
|
76
|
-
: error.message
|
|
77
|
-
const hint =
|
|
78
|
-
codeOfReason === 'FUNCTION_INPUT_INVALID' && (isRaw || inputIssues.length === 0)
|
|
79
|
-
? 'Use `astrale introspect <path>` to see the callable input.'
|
|
80
|
-
: (removalHint ?? (upgrade === undefined ? undefined : schemaUpgradeHint(upgrade)))
|
|
81
|
-
if (isRaw) {
|
|
82
|
-
writeRaw({
|
|
83
|
-
error: 'RESPONSE_ERROR',
|
|
84
|
-
...(code === undefined ? {} : { code }),
|
|
85
|
-
message: displayMessage,
|
|
86
|
-
...(reason === undefined ? {} : { reason }),
|
|
87
|
-
...(hint === undefined ? {} : { hint }),
|
|
88
|
-
})
|
|
89
|
-
} else {
|
|
90
|
-
log.error(
|
|
91
|
-
domainAddressNotPublic
|
|
92
|
-
? `${chalk.bold('SCHEMA_DOMAIN_ADDRESS_NOT_PUBLIC')}: ${displayMessage}`
|
|
93
|
-
: removalHint !== undefined
|
|
94
|
-
? `${chalk.bold('DATA_MIGRATION_REQUIRED')}: ${displayMessage}`
|
|
95
|
-
: `${chalk.bold(code === undefined ? 'RESPONSE_ERROR' : `RESPONSE_ERROR(${String(code)})`)}: ${displayMessage}`,
|
|
96
|
-
)
|
|
97
|
-
if (codeOfReason !== undefined && !domainAddressNotPublic && removalHint === undefined) {
|
|
98
|
-
log.dim(` reason: ${codeOfReason}`)
|
|
99
|
-
}
|
|
100
|
-
presentFunctionInputIssues(inputIssues)
|
|
101
|
-
if (queryRepair !== undefined) presentQueryInputRepair(queryRepair)
|
|
102
|
-
if (upgrade?.issue === 'issuer-changed') {
|
|
103
|
-
log.dim(` installed issuer: ${upgrade.installedIssuer}`)
|
|
104
|
-
log.dim(` replacement issuer: ${upgrade.replacementIssuer}`)
|
|
105
|
-
}
|
|
106
|
-
if (hint !== undefined) log.dim(` ${hint}`)
|
|
107
|
-
}
|
|
108
|
-
break
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
default: {
|
|
112
|
-
const mapped = mapPublicError(error)
|
|
113
|
-
if (isRaw) {
|
|
114
|
-
writeRaw({
|
|
115
|
-
error: mapped.code,
|
|
116
|
-
message: mapped.message,
|
|
117
|
-
...(mapped.hint === undefined ? {} : { hint: mapped.hint }),
|
|
118
|
-
...(mapped.timeoutMs === undefined ? {} : { timeoutMs: mapped.timeoutMs }),
|
|
119
|
-
})
|
|
120
|
-
} else {
|
|
121
|
-
log.error(`${chalk.bold(mapped.code)}: ${mapped.message}`)
|
|
122
|
-
if (mapped.hint) log.dim(` ${mapped.hint}`)
|
|
123
|
-
}
|
|
124
|
-
}
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
if (debug) printDebug(error, url)
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
function schemaDataRemovalHint(reason: unknown): string | undefined {
|
|
131
|
-
if (reason === null || typeof reason !== 'object') return undefined
|
|
132
|
-
const value = reason as { readonly code?: unknown; readonly details?: unknown }
|
|
133
|
-
if (value.code !== 'DATA_MIGRATION_REQUIRED') return undefined
|
|
134
|
-
if (value.details === null || typeof value.details !== 'object') return undefined
|
|
135
|
-
|
|
136
|
-
const requirements = (value.details as { readonly requirements?: unknown }).requirements
|
|
137
|
-
if (
|
|
138
|
-
!Array.isArray(requirements) ||
|
|
139
|
-
requirements.length === 0 ||
|
|
140
|
-
!requirements.every(
|
|
141
|
-
(requirement) =>
|
|
142
|
-
requirement !== null &&
|
|
143
|
-
typeof requirement === 'object' &&
|
|
144
|
-
(requirement as { readonly operation?: unknown }).operation === 'remove-facts' &&
|
|
145
|
-
(requirement as { readonly reason?: unknown }).reason === 'destructive-change',
|
|
146
|
-
)
|
|
147
|
-
) {
|
|
148
|
-
return undefined
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
return 'Delete this data explicitly, then retry. No data was deleted.'
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
function mapPublicError(error: Error): {
|
|
155
|
-
code: string
|
|
156
|
-
message: string
|
|
157
|
-
hint?: string
|
|
158
|
-
timeoutMs?: number
|
|
159
|
-
} {
|
|
160
|
-
const name = error.name
|
|
161
|
-
if (name === 'PathError') {
|
|
162
|
-
return { code: 'PATH_INVALID', message: error.message }
|
|
163
|
-
}
|
|
164
|
-
if (name === 'NodeUnavailableError') {
|
|
165
|
-
return {
|
|
166
|
-
code: 'NODE_UNAVAILABLE',
|
|
167
|
-
message: error.message,
|
|
168
|
-
hint: 'If this is a callable Path, use `astrale call` or `astrale introspect`.',
|
|
169
|
-
}
|
|
170
|
-
}
|
|
171
|
-
if (name === 'AuthValueError') {
|
|
172
|
-
return { code: 'AUTH_VALUE_INVALID', message: error.message }
|
|
173
|
-
}
|
|
174
|
-
if (name === 'ClientError' && /timed out/i.test(error.message)) {
|
|
175
|
-
const timeoutMs = (error as { timeoutMs?: number }).timeoutMs
|
|
176
|
-
return {
|
|
177
|
-
code: 'TIMEOUT',
|
|
178
|
-
message: error.message,
|
|
179
|
-
hint: 'Try increasing with --timeout',
|
|
180
|
-
...(timeoutMs === undefined ? {} : { timeoutMs }),
|
|
181
|
-
}
|
|
182
|
-
}
|
|
183
|
-
if (name === 'ClientError' && /Publication discovery returned HTTP/i.test(error.message)) {
|
|
184
|
-
return {
|
|
185
|
-
code: 'KERNEL_DISCOVERY_FAILED',
|
|
186
|
-
message: error.message,
|
|
187
|
-
hint: 'Pass the Kernel issuer URL (no /invoke suffix), e.g. https://host/kernel/host',
|
|
188
|
-
}
|
|
189
|
-
}
|
|
190
|
-
return { code: name && name !== 'Error' ? name : 'UNKNOWN', message: error.message }
|
|
191
|
-
}
|
|
192
|
-
|
|
193
|
-
function presentTransportError(
|
|
194
|
-
error: Error,
|
|
195
|
-
isRaw: boolean,
|
|
196
|
-
url: string,
|
|
197
|
-
context: LocalStatus | undefined,
|
|
198
|
-
recovery: OperationRecovery | undefined,
|
|
199
|
-
): void {
|
|
200
|
-
const phase = transportPhase(error)
|
|
201
|
-
const delivery = transportDelivery(error)
|
|
202
|
-
const code =
|
|
203
|
-
phase === 'connect'
|
|
204
|
-
? 'CONNECTION_ERROR'
|
|
205
|
-
: phase === 'timeout'
|
|
206
|
-
? 'TIMEOUT'
|
|
207
|
-
: phase === 'closed'
|
|
208
|
-
? 'DISCONNECTED'
|
|
209
|
-
: 'TRANSPORT_ERROR'
|
|
210
|
-
if (isRaw) {
|
|
211
|
-
writeRaw({
|
|
212
|
-
error: code,
|
|
213
|
-
message: error.message,
|
|
214
|
-
...(url === '' ? {} : { url }),
|
|
215
|
-
...(phase === undefined ? {} : { phase }),
|
|
216
|
-
...(delivery === undefined ? {} : { delivery }),
|
|
217
|
-
...(context === undefined ? {} : { context }),
|
|
218
|
-
...(delivery === 'unknown' && recovery !== undefined ? recovery : {}),
|
|
219
|
-
})
|
|
220
|
-
return
|
|
221
|
-
}
|
|
222
|
-
log.error(`${chalk.bold(code)}: ${error.message}`)
|
|
223
|
-
if (url !== '') log.dim(` target: ${url}`)
|
|
224
|
-
if (phase !== undefined) log.dim(` phase: ${phase}`)
|
|
225
|
-
if (phase === 'connect') log.dim(' Check the target and run `astrale status`.')
|
|
226
|
-
else if (phase === 'timeout') log.dim(' Try increasing `--timeout`.')
|
|
227
|
-
else if (delivery === 'unknown') printOperationRecovery(recovery)
|
|
228
|
-
printLocalContext(context)
|
|
229
|
-
}
|
|
230
|
-
|
|
231
|
-
function printOperationRecovery(recovery: OperationRecovery | undefined): void {
|
|
232
|
-
if (recovery === undefined) {
|
|
233
|
-
log.dim(' Delivery is unknown; do not automatically retry a mutating call.')
|
|
234
|
-
return
|
|
235
|
-
}
|
|
236
|
-
log.dim(' Delivery is unknown; retry with the same operation id:')
|
|
237
|
-
log.dim(` operation: ${recovery.operation}`)
|
|
238
|
-
log.dim(` ${recovery.retry}`)
|
|
239
|
-
}
|
|
240
|
-
|
|
241
|
-
function presentFunctionInputIssues(issues: readonly FunctionInputIssue[]): void {
|
|
242
|
-
for (const issue of issues) {
|
|
243
|
-
const location = issue.path === undefined || issue.path === '' ? '<input>' : issue.path
|
|
244
|
-
console.log(chalk.red(` ${location}: ${issue.message} (${chalk.dim(issue.code)})`))
|
|
245
|
-
}
|
|
246
|
-
}
|
|
247
|
-
|
|
248
|
-
function presentQueryInputRepair(repair: QueryInputRepair): void {
|
|
249
|
-
if (repair.phase === 'plan') {
|
|
250
|
-
log.dim(` ${repair.path ?? '/'} ${repair.issue}`)
|
|
251
|
-
return
|
|
252
|
-
}
|
|
253
|
-
if (repair.phase === 'limit') {
|
|
254
|
-
log.dim(` ${repair.path ?? '/'} ${repair.limit} limit ${repair.actual}/${repair.maximum}`)
|
|
255
|
-
return
|
|
256
|
-
}
|
|
257
|
-
log.dim(` ${repair.path} ${repair.phase} input`)
|
|
258
|
-
}
|
|
259
|
-
|
|
260
|
-
function transportPhase(error: Error): string | undefined {
|
|
261
|
-
const phase = (error as Error & { readonly phase?: unknown }).phase
|
|
262
|
-
return phase === 'connect' ||
|
|
263
|
-
phase === 'send' ||
|
|
264
|
-
phase === 'receive' ||
|
|
265
|
-
phase === 'timeout' ||
|
|
266
|
-
phase === 'closed'
|
|
267
|
-
? phase
|
|
268
|
-
: undefined
|
|
269
|
-
}
|
|
270
|
-
|
|
271
|
-
function transportDelivery(error: Error): string | undefined {
|
|
272
|
-
const delivery = (error as Error & { readonly delivery?: unknown }).delivery
|
|
273
|
-
return delivery === 'not-sent' || delivery === 'unknown' ? delivery : undefined
|
|
274
|
-
}
|
|
275
|
-
|
|
276
|
-
function writeRaw(payload: Record<string, unknown>): void {
|
|
277
|
-
process.stderr.write(JSON.stringify(payload) + '\n')
|
|
278
|
-
}
|
|
279
|
-
|
|
280
|
-
async function contextForError(error: unknown): Promise<LocalStatus | undefined> {
|
|
281
|
-
if (!(error instanceof Error)) return undefined
|
|
282
|
-
if (error.name !== 'TransportError') return undefined
|
|
283
|
-
return readLocalStatus().catch(() => undefined)
|
|
284
|
-
}
|
|
285
|
-
|
|
286
|
-
function printLocalContext(context: LocalStatus | undefined): void {
|
|
287
|
-
if (!context) return
|
|
288
|
-
process.stderr.write(chalk.dim('\nContext:\n'))
|
|
289
|
-
if ('error' in context.admin) {
|
|
290
|
-
process.stderr.write(chalk.dim(` admin: invalid (${context.admin.error})\n`))
|
|
291
|
-
} else {
|
|
292
|
-
process.stderr.write(chalk.dim(` admin: ${context.admin.name} -> ${context.admin.url}\n`))
|
|
293
|
-
}
|
|
294
|
-
if (context.instance) {
|
|
295
|
-
process.stderr.write(
|
|
296
|
-
chalk.dim(` instance: ${context.instance.active} -> ${context.instance.url}\n`),
|
|
297
|
-
)
|
|
298
|
-
} else {
|
|
299
|
-
process.stderr.write(chalk.dim(' instance: none\n'))
|
|
300
|
-
}
|
|
301
|
-
if (context.identity) {
|
|
302
|
-
const source =
|
|
303
|
-
context.identity.source === 'idp'
|
|
304
|
-
? `idp:${context.identity.idp ?? 'unknown'}`
|
|
305
|
-
: context.identity.source
|
|
306
|
-
process.stderr.write(chalk.dim(` identity: ${context.identity.name} [${source}]\n`))
|
|
307
|
-
if (context.identity.session?.cached) {
|
|
308
|
-
const state = context.identity.session.requiresLogin ? 'login required' : 'ready'
|
|
309
|
-
process.stderr.write(chalk.dim(` session: ${state}\n`))
|
|
310
|
-
} else if (context.identity.source === 'idp') {
|
|
311
|
-
process.stderr.write(chalk.dim(' session: not cached\n'))
|
|
312
|
-
}
|
|
313
|
-
} else {
|
|
314
|
-
process.stderr.write(chalk.dim(' identity: none\n'))
|
|
315
|
-
}
|
|
316
|
-
}
|
|
317
|
-
|
|
318
|
-
function printDebug(error: unknown, url: string): void {
|
|
319
|
-
process.stderr.write('\n' + chalk.dim('── debug ─────────────────') + '\n')
|
|
320
|
-
if (url) process.stderr.write(chalk.dim(`url: ${url}`) + '\n')
|
|
321
|
-
if (error instanceof Error) {
|
|
322
|
-
process.stderr.write(chalk.dim(`class: ${error.constructor.name}`) + '\n')
|
|
323
|
-
if (error.stack) process.stderr.write(chalk.dim(error.stack) + '\n')
|
|
324
|
-
// Walk cause chain
|
|
325
|
-
let cause = (error as Error & { cause?: unknown }).cause
|
|
326
|
-
while (cause instanceof Error) {
|
|
327
|
-
process.stderr.write(
|
|
328
|
-
chalk.dim(`caused by: ${cause.constructor.name}: ${cause.message}`) + '\n',
|
|
329
|
-
)
|
|
330
|
-
if (cause.stack) process.stderr.write(chalk.dim(cause.stack) + '\n')
|
|
331
|
-
cause = (cause as Error & { cause?: unknown }).cause
|
|
332
|
-
}
|
|
333
|
-
// Any attached fields (code, details, etc.)
|
|
334
|
-
const extras: Record<string, unknown> = {}
|
|
335
|
-
const bag = error as unknown as Record<string, unknown>
|
|
336
|
-
for (const key of [
|
|
337
|
-
'code',
|
|
338
|
-
'type',
|
|
339
|
-
'reason',
|
|
340
|
-
'details',
|
|
341
|
-
'errors',
|
|
342
|
-
'data',
|
|
343
|
-
'timeoutMs',
|
|
344
|
-
'requestId',
|
|
345
|
-
]) {
|
|
346
|
-
const v = bag[key]
|
|
347
|
-
if (v !== undefined) extras[key] = v
|
|
348
|
-
}
|
|
349
|
-
if (Object.keys(extras).length > 0) {
|
|
350
|
-
process.stderr.write(chalk.dim(`extras: ${JSON.stringify(extras, null, 2)}`) + '\n')
|
|
351
|
-
}
|
|
352
|
-
} else {
|
|
353
|
-
process.stderr.write(chalk.dim(String(error)) + '\n')
|
|
354
|
-
}
|
|
355
|
-
}
|
|
1
|
+
export { formatKernelError, functionInputIssues, schemaUpgradeHint } from './failure'
|
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import type { Fetch } from '@astrale-os/kernel-client'
|
|
2
1
|
import type { IssuerId } from '@astrale-os/sdk/auth'
|
|
2
|
+
import type { Fetch } from '@astrale-os/sdk/client'
|
|
3
3
|
|
|
4
|
-
import {
|
|
5
|
-
import { createAuth } from '@astrale-os/kernel-client/auth'
|
|
4
|
+
import { createAuth } from '@astrale-os/sdk/auth'
|
|
6
5
|
import { credential, exchange as exchangeProtocol } from '@astrale-os/sdk/auth'
|
|
6
|
+
import { call, Client } from '@astrale-os/sdk/client'
|
|
7
7
|
|
|
8
8
|
import type { SourceCredentialResolver } from './credential'
|
|
9
9
|
import type { ConnectionTarget } from './target'
|
|
@@ -111,15 +111,20 @@ async function exchange(
|
|
|
111
111
|
exchangeProtocol.paths(domainIssuer).configuration,
|
|
112
112
|
domainIssuer,
|
|
113
113
|
).toString()
|
|
114
|
-
const configurationResponse = await
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
114
|
+
const configurationResponse = await fetchExchange(
|
|
115
|
+
fetch,
|
|
116
|
+
configurationUrl,
|
|
117
|
+
{
|
|
118
|
+
method: 'GET',
|
|
119
|
+
redirect: 'error',
|
|
120
|
+
credentials: 'omit',
|
|
121
|
+
cache: 'no-store',
|
|
122
|
+
referrerPolicy: 'no-referrer',
|
|
123
|
+
headers: { accept: 'application/json' },
|
|
124
|
+
signal,
|
|
125
|
+
},
|
|
126
|
+
'Domain issuer discovery could not be reached.',
|
|
127
|
+
)
|
|
123
128
|
if (!configurationResponse.ok) {
|
|
124
129
|
throw new AstraleError(
|
|
125
130
|
'TOKEN_EXCHANGE_DISCOVERY_FAILED',
|
|
@@ -139,18 +144,23 @@ async function exchange(
|
|
|
139
144
|
)
|
|
140
145
|
}
|
|
141
146
|
|
|
142
|
-
const response = await
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
147
|
+
const response = await fetchExchange(
|
|
148
|
+
fetch,
|
|
149
|
+
endpoint,
|
|
150
|
+
{
|
|
151
|
+
method: 'POST',
|
|
152
|
+
redirect: 'error',
|
|
153
|
+
credentials: 'omit',
|
|
154
|
+
cache: 'no-store',
|
|
155
|
+
referrerPolicy: 'no-referrer',
|
|
156
|
+
headers: {
|
|
157
|
+
authorization: `Bearer ${envelope}`,
|
|
158
|
+
accept: exchangeProtocol.MEDIA_TYPE,
|
|
159
|
+
},
|
|
160
|
+
signal,
|
|
151
161
|
},
|
|
152
|
-
|
|
153
|
-
|
|
162
|
+
'Domain token exchange could not be reached.',
|
|
163
|
+
)
|
|
154
164
|
const body = await boundedJson(response, signal)
|
|
155
165
|
if (!response.ok) {
|
|
156
166
|
let admitted: exchangeProtocol.ErrorResponse
|
|
@@ -193,6 +203,20 @@ async function exchange(
|
|
|
193
203
|
return Object.freeze({ credential: exchanged.token, expiresAt: exchanged.expiresAt })
|
|
194
204
|
}
|
|
195
205
|
|
|
206
|
+
async function fetchExchange(
|
|
207
|
+
fetch: Fetch,
|
|
208
|
+
input: string,
|
|
209
|
+
init: RequestInit,
|
|
210
|
+
message: string,
|
|
211
|
+
): Promise<Response> {
|
|
212
|
+
try {
|
|
213
|
+
return await fetch(input, init)
|
|
214
|
+
} catch (cause) {
|
|
215
|
+
if (init.signal?.aborted) throw cause
|
|
216
|
+
throw new AstraleError('TOKEN_EXCHANGE_UNAVAILABLE', message, undefined, { cause })
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
|
|
196
220
|
function requireExchangeTransport(
|
|
197
221
|
target: ConnectionTarget & { readonly domainIssuer: IssuerId },
|
|
198
222
|
): void {
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import { AuthValueError } from '@astrale-os/sdk/auth'
|
|
2
|
+
import { ClientError, ProtocolError, ResponseError, TransportError } from '@astrale-os/sdk/client'
|
|
3
|
+
import { NodeUnavailableError } from '@astrale-os/sdk/client'
|
|
4
|
+
import { PathError } from '@astrale-os/sdk/graph/path'
|
|
5
|
+
|
|
6
|
+
import type { FailureDiagnostic, TransportDiagnosticContext } from './model'
|
|
7
|
+
|
|
8
|
+
import { AstraleError } from '../../errors'
|
|
9
|
+
|
|
10
|
+
export function classifyFailure(error: unknown): FailureDiagnostic {
|
|
11
|
+
if (error instanceof AstraleError) return simple(error.code, error.message, error.hint)
|
|
12
|
+
if (error instanceof TransportError) {
|
|
13
|
+
const context = transportContext(error)
|
|
14
|
+
if (context === undefined) return simple('TRANSPORT_ERROR', error.message)
|
|
15
|
+
return {
|
|
16
|
+
kind: 'transport',
|
|
17
|
+
code: lifecycleCode(error.phase),
|
|
18
|
+
message: error.message,
|
|
19
|
+
phase: error.phase,
|
|
20
|
+
context,
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
if (error instanceof ResponseError) {
|
|
24
|
+
return {
|
|
25
|
+
kind: 'response',
|
|
26
|
+
code: error.code,
|
|
27
|
+
message: error.message,
|
|
28
|
+
...(error.reason === undefined ? {} : { reason: error.reason }),
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
if (error instanceof ClientError) {
|
|
32
|
+
const failure = sessionFailure(error)
|
|
33
|
+
if (failure !== undefined) return simple(lifecycleCode(failure), error.message)
|
|
34
|
+
}
|
|
35
|
+
if (error instanceof ProtocolError) return simple('PROTOCOL_ERROR', error.message)
|
|
36
|
+
if (error instanceof NodeUnavailableError)
|
|
37
|
+
return simple(
|
|
38
|
+
'NODE_UNAVAILABLE',
|
|
39
|
+
error.message,
|
|
40
|
+
'If this is a callable Path, use `astrale call` or `astrale introspect`.',
|
|
41
|
+
)
|
|
42
|
+
if (error instanceof PathError) return simple('PATH_INVALID', error.message)
|
|
43
|
+
if (error instanceof AuthValueError) return simple('AUTH_VALUE_INVALID', error.message)
|
|
44
|
+
if (error instanceof ClientError) return simple('CLIENT_ERROR', error.message)
|
|
45
|
+
return simple('UNEXPECTED_ERROR', 'The CLI encountered an unexpected internal failure.')
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
type TransportCode = Extract<FailureDiagnostic, { kind: 'transport' }>['code']
|
|
49
|
+
type SessionFailure = 'cancelled' | 'closed' | 'timeout'
|
|
50
|
+
|
|
51
|
+
function lifecycleCode(value: TransportError['phase']): TransportCode
|
|
52
|
+
function lifecycleCode(value: SessionFailure): string
|
|
53
|
+
function lifecycleCode(value: TransportError['phase'] | SessionFailure): string {
|
|
54
|
+
if (value === 'connect') return 'CONNECTION_ERROR'
|
|
55
|
+
if (value === 'timeout') return 'TIMEOUT'
|
|
56
|
+
if (value === 'closed') return 'DISCONNECTED'
|
|
57
|
+
return value === 'cancelled' ? 'CANCELLED' : 'TRANSPORT_ERROR'
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function transportContext(error: TransportError): TransportDiagnosticContext | undefined {
|
|
61
|
+
const evidence = error as TransportError & {
|
|
62
|
+
readonly context?: unknown
|
|
63
|
+
readonly delivery?: unknown
|
|
64
|
+
readonly invocation?: unknown
|
|
65
|
+
}
|
|
66
|
+
if (record(evidence.context)) {
|
|
67
|
+
if (
|
|
68
|
+
evidence.context.kind === 'acquisition' &&
|
|
69
|
+
(evidence.context.resource === 'publication' || evidence.context.resource === 'bundle')
|
|
70
|
+
) {
|
|
71
|
+
return { kind: 'acquisition', resource: evidence.context.resource }
|
|
72
|
+
}
|
|
73
|
+
if (
|
|
74
|
+
evidence.context.kind === 'invocation' &&
|
|
75
|
+
(evidence.context.delivery === 'not-sent' || evidence.context.delivery === 'unknown')
|
|
76
|
+
) {
|
|
77
|
+
return {
|
|
78
|
+
kind: 'invocation',
|
|
79
|
+
delivery: evidence.context.delivery,
|
|
80
|
+
...(evidence.context.invocation === undefined
|
|
81
|
+
? {}
|
|
82
|
+
: { invocation: evidence.context.invocation }),
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
if (evidence.delivery !== 'not-sent' && evidence.delivery !== 'unknown') return undefined
|
|
87
|
+
return {
|
|
88
|
+
kind: 'invocation',
|
|
89
|
+
delivery: evidence.delivery,
|
|
90
|
+
...(evidence.invocation === undefined ? {} : { invocation: evidence.invocation }),
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
function sessionFailure(error: ClientError): SessionFailure | undefined {
|
|
95
|
+
const failure = (error as ClientError & { readonly failure?: unknown }).failure
|
|
96
|
+
return failure === 'cancelled' || failure === 'closed' || failure === 'timeout'
|
|
97
|
+
? failure
|
|
98
|
+
: undefined
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
function record(input: unknown): input is Readonly<Record<string, unknown>> {
|
|
102
|
+
return input !== null && typeof input === 'object' && !Array.isArray(input)
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
const simple = (code: string, message: string, hint?: string): FailureDiagnostic => ({
|
|
106
|
+
kind: 'simple',
|
|
107
|
+
code,
|
|
108
|
+
message,
|
|
109
|
+
...(hint === undefined ? {} : { hint }),
|
|
110
|
+
})
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import chalk from 'chalk'
|
|
2
|
+
|
|
3
|
+
const MAXIMUM_DEBUG_FAILURES = 32
|
|
4
|
+
|
|
5
|
+
export function printFailureDebug(error: unknown, url: string): void {
|
|
6
|
+
process.stderr.write(`\n${chalk.dim('── debug ─────────────────')}\n`)
|
|
7
|
+
if (url) process.stderr.write(`${chalk.dim(`url: ${url}`)}\n`)
|
|
8
|
+
const seen = new Set<unknown>()
|
|
9
|
+
const pending: Array<{ value: unknown; relation: string }> = [{ value: error, relation: 'error' }]
|
|
10
|
+
while (pending.length > 0 && seen.size < MAXIMUM_DEBUG_FAILURES) {
|
|
11
|
+
const { value, relation } = pending.shift()!
|
|
12
|
+
if (seen.has(value)) continue
|
|
13
|
+
seen.add(value)
|
|
14
|
+
if (!(value instanceof Error)) {
|
|
15
|
+
process.stderr.write(`${chalk.dim(`${relation}: ${String(value)}`)}\n`)
|
|
16
|
+
continue
|
|
17
|
+
}
|
|
18
|
+
process.stderr.write(
|
|
19
|
+
`${chalk.dim(`${relation}: ${value.constructor.name}: ${value.message}`)}\n`,
|
|
20
|
+
)
|
|
21
|
+
if (value.stack) process.stderr.write(`${chalk.dim(value.stack)}\n`)
|
|
22
|
+
if (value.cause !== undefined) pending.push({ value: value.cause, relation: 'caused by' })
|
|
23
|
+
if (value instanceof AggregateError)
|
|
24
|
+
for (const child of value.errors) pending.push({ value: child, relation: 'aggregate' })
|
|
25
|
+
}
|
|
26
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { OperationRecovery } from '../command'
|
|
2
|
+
|
|
3
|
+
import { classifyFailure } from './classify'
|
|
4
|
+
import { printFailureDebug } from './debug'
|
|
5
|
+
import { renderFailure } from './render'
|
|
6
|
+
|
|
7
|
+
export { functionInputIssues, schemaUpgradeHint } from '../reasons'
|
|
8
|
+
|
|
9
|
+
export async function formatKernelError(
|
|
10
|
+
error: unknown,
|
|
11
|
+
machine: boolean,
|
|
12
|
+
urlArg = '',
|
|
13
|
+
debug = false,
|
|
14
|
+
options: { recovery?: OperationRecovery } = {},
|
|
15
|
+
): Promise<void> {
|
|
16
|
+
renderFailure(classifyFailure(error), machine, urlArg, options.recovery)
|
|
17
|
+
if (debug) printFailureDebug(error, urlArg)
|
|
18
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
export type TransportDiagnosticContext =
|
|
2
|
+
| { readonly kind: 'acquisition'; readonly resource: 'publication' | 'bundle' }
|
|
3
|
+
| {
|
|
4
|
+
readonly kind: 'invocation'
|
|
5
|
+
readonly delivery: 'not-sent' | 'unknown'
|
|
6
|
+
readonly invocation?: unknown
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export type FailureDiagnostic =
|
|
10
|
+
| {
|
|
11
|
+
readonly kind: 'simple'
|
|
12
|
+
readonly code: string
|
|
13
|
+
readonly message: string
|
|
14
|
+
readonly hint?: string
|
|
15
|
+
}
|
|
16
|
+
| {
|
|
17
|
+
readonly kind: 'response'
|
|
18
|
+
readonly code: number
|
|
19
|
+
readonly message: string
|
|
20
|
+
readonly reason?: unknown
|
|
21
|
+
}
|
|
22
|
+
| {
|
|
23
|
+
readonly kind: 'transport'
|
|
24
|
+
readonly code: 'CONNECTION_ERROR' | 'DISCONNECTED' | 'TIMEOUT' | 'TRANSPORT_ERROR'
|
|
25
|
+
readonly message: string
|
|
26
|
+
readonly phase: string
|
|
27
|
+
readonly context: TransportDiagnosticContext
|
|
28
|
+
}
|