@astrale-os/cli 1.0.0-beta.5 → 1.0.0-beta.7
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 +66254 -64461
- package/dist/public/connect-core.js +14895 -33867
- package/dist/public/keys/index.js +1246 -34250
- package/dist/public/paths/index.js +866 -33833
- package/dist/types/admin/instance/model.d.ts +3 -4
- package/dist/types/errors.d.ts +1 -1
- package/dist/types/lib/instance-target.d.ts +1 -1
- package/dist/types/lib/log.d.ts +2 -2
- package/package.json +4 -9
- package/src/__tests__/fixtures/publication.ts +20 -0
- package/src/admin/__tests__/binding.test.ts +12 -19
- package/src/admin/__tests__/fixture.ts +101 -0
- package/src/admin/binding.ts +83 -3
- package/src/admin/catalog/.spec/api.d.ts +1 -1
- 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 +1 -1
- 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/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/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__/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/command-dx.ts +1 -4
- package/src/lib/domain-publication.ts +1 -1
- package/src/lib/instance-target.ts +3 -14
- package/src/lib/log.ts +11 -6
- 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-YEDvaNU-.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 -7
- 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 +12 -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 +41 -38
- package/studio/client/dist/assets/index-C-PvAXV4.js +0 -81
- package/studio/client/dist/assets/index-C4aNQ6dz.css +0 -1
- package/studio/client/dist/assets/index-CzpmJD0j.js +0 -8
- package/studio/server/introspect/anatomy/views/legacy.ts +0 -232
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import type { IssuerId, JsonWebKey, ProvisionRequest } from '@astrale-os/sdk/auth'
|
|
2
|
+
import type { ClassKey as ClassKeyValue } from '@astrale-os/sdk/graph/class'
|
|
2
3
|
import type { JWK } from 'jose'
|
|
3
4
|
|
|
4
5
|
import { provision, jwk } from '@astrale-os/sdk/auth'
|
|
5
|
-
import {
|
|
6
|
-
import { LocalBinding } from '@astrale-os/sdk/graph/model'
|
|
6
|
+
import { LocalBinding } from '@astrale-os/sdk/graph'
|
|
7
7
|
import { normalizeProperties } from '@astrale-os/sdk/graph/properties'
|
|
8
8
|
import { MutationAST } from '@astrale-os/sdk/mutation'
|
|
9
9
|
import { importJWK, SignJWT } from 'jose'
|
|
@@ -14,6 +14,7 @@ import type { IdentityRegistrationResult } from '../../identity/index'
|
|
|
14
14
|
import type { CommandDefinition } from '../../program/index'
|
|
15
15
|
|
|
16
16
|
import { registrationKeyForTarget, runKernelCommand } from '../../connection'
|
|
17
|
+
import { classKey } from '../../graph'
|
|
17
18
|
import { getIdentity, setRegistration, submitIdentityProvision } from '../../identity/index'
|
|
18
19
|
import { fileExists, keypairPaths } from '../../keys/index'
|
|
19
20
|
import { fatal, log } from '../../lib/log'
|
|
@@ -83,7 +84,7 @@ Example:
|
|
|
83
84
|
|
|
84
85
|
const privateKey = await readJwk(privatePath)
|
|
85
86
|
const publicKey = jwk.acceptPublic(await readJwk(publicPath))
|
|
86
|
-
const classPath =
|
|
87
|
+
const classPath = classKey(opts.class, '--class')
|
|
87
88
|
const properties = normalizeProperties(opts.props ? JSON.parse(opts.props) : {})
|
|
88
89
|
|
|
89
90
|
await runKernelCommand<IdentityRegistrationResult>({
|
|
@@ -142,7 +143,7 @@ export function formatIdentityRegistration(
|
|
|
142
143
|
/** Build and self-prove one exact canonical provision request for the target Kernel. */
|
|
143
144
|
export async function prepareIdentityProvision(input: {
|
|
144
145
|
readonly name: string
|
|
145
|
-
readonly classPath:
|
|
146
|
+
readonly classPath: ClassKeyValue
|
|
146
147
|
readonly properties: ReturnType<typeof normalizeProperties>
|
|
147
148
|
readonly privateKey: JWK
|
|
148
149
|
readonly publicKey: JsonWebKey
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { KernelCommandOpts } from '../../connection'
|
|
2
2
|
import type { CommandDefinition } from '../../program/index'
|
|
3
3
|
|
|
4
|
+
import { AdminInstanceNotFoundError } from '../../admin/instance'
|
|
4
5
|
import { deleteOwnedInstance } from '../../lib/admin-instance'
|
|
5
6
|
import { ADMIN_TARGET_OPTIONS, type AdminTargetCommandOpts } from '../../lib/admin-target'
|
|
6
7
|
import { clearActive, readInstances, removeInstance, resolveInstanceKey } from '../../lib/instance'
|
|
@@ -53,9 +54,10 @@ Behavior:
|
|
|
53
54
|
} catch (e) {
|
|
54
55
|
// Bookmark-only id → no admin Instance → kernel NOT_FOUND. Point to
|
|
55
56
|
// `forget` (local drop) instead of surfacing the raw path error.
|
|
56
|
-
|
|
57
|
-
e instanceof
|
|
58
|
-
|
|
57
|
+
if (
|
|
58
|
+
e instanceof AdminInstanceNotFoundError &&
|
|
59
|
+
resolveInstanceKey(await readInstances(), id)
|
|
60
|
+
) {
|
|
59
61
|
log.error(`No admin-managed instance "${id}".`)
|
|
60
62
|
log.dim(` hint: it's a local bookmark — drop it with: astrale instance forget ${id}`)
|
|
61
63
|
process.exit(1)
|
|
@@ -3,7 +3,6 @@ import chalk from 'chalk'
|
|
|
3
3
|
import type { KernelCommandOpts } from '../../connection'
|
|
4
4
|
import type { CommandDefinition } from '../../program/index'
|
|
5
5
|
|
|
6
|
-
import { AstraleError } from '../../errors'
|
|
7
6
|
import { statusOwnedInstance } from '../../lib/admin-instance'
|
|
8
7
|
import { ADMIN_TARGET_OPTIONS, type AdminTargetCommandOpts } from '../../lib/admin-target'
|
|
9
8
|
import { fatal, log, withSpinner } from '../../lib/log'
|
|
@@ -19,14 +18,7 @@ export default {
|
|
|
19
18
|
action: async (id: string, opts: StatusOpts) => {
|
|
20
19
|
try {
|
|
21
20
|
const result = await withSpinner(`Fetching instance ${id}`, !isMachine(opts), () =>
|
|
22
|
-
statusOwnedInstance(opts, id)
|
|
23
|
-
if (!(error instanceof Error) || error.name !== 'NotFoundError') throw error
|
|
24
|
-
throw new AstraleError(
|
|
25
|
-
'INSTANCE_NOT_FOUND',
|
|
26
|
-
`No owned instance matches "${id}".`,
|
|
27
|
-
'Run `astrale instance list` to see your instances.',
|
|
28
|
-
)
|
|
29
|
-
}),
|
|
21
|
+
statusOwnedInstance(opts, id),
|
|
30
22
|
)
|
|
31
23
|
if (isMachine(opts)) {
|
|
32
24
|
output(result, opts)
|
|
@@ -5,9 +5,9 @@ import type { CommandDefinition } from '../program/index'
|
|
|
5
5
|
|
|
6
6
|
import { runKernelCommand } from '../connection'
|
|
7
7
|
import { AstraleError } from '../errors'
|
|
8
|
-
import {
|
|
8
|
+
import { failInput } from '../lib/log'
|
|
9
9
|
import { output } from '../lib/output'
|
|
10
|
-
import {
|
|
10
|
+
import { describeCallableFromBundle, missingCallableDescription } from './call-describe'
|
|
11
11
|
|
|
12
12
|
type IntrospectOpts = KernelCommandOpts & { bundle?: boolean }
|
|
13
13
|
|
|
@@ -17,7 +17,7 @@ export async function introspectCommand(target: string, opts: IntrospectOpts): P
|
|
|
17
17
|
try {
|
|
18
18
|
;({ origin, path } = parseIntrospectTarget(target))
|
|
19
19
|
} catch (error) {
|
|
20
|
-
|
|
20
|
+
failInput(error, opts)
|
|
21
21
|
}
|
|
22
22
|
const wantsCallable = isCallablePath(path)
|
|
23
23
|
|
|
@@ -44,7 +44,7 @@ export async function introspectCommand(target: string, opts: IntrospectOpts): P
|
|
|
44
44
|
)
|
|
45
45
|
}
|
|
46
46
|
if (wantsCallable) {
|
|
47
|
-
const described =
|
|
47
|
+
const described = describeCallableFromBundle(path, result.bundle)
|
|
48
48
|
if (described === undefined) throw missingCallableDescription(path.raw)
|
|
49
49
|
return described
|
|
50
50
|
}
|
|
@@ -85,13 +85,6 @@ function isCallablePath(path: Path): boolean {
|
|
|
85
85
|
return last.kind === 'projection' && last.projection.kind === 'function'
|
|
86
86
|
}
|
|
87
87
|
|
|
88
|
-
function bundleRoot(bundle: unknown): unknown {
|
|
89
|
-
if (bundle !== null && typeof bundle === 'object' && 'root' in bundle) {
|
|
90
|
-
return (bundle as { root: unknown }).root
|
|
91
|
-
}
|
|
92
|
-
return bundle
|
|
93
|
-
}
|
|
94
|
-
|
|
95
88
|
export default {
|
|
96
89
|
name: 'introspect',
|
|
97
90
|
description: 'Read installed Domain schema from the Kernel Schema syscall',
|
package/src/commands/logs.ts
CHANGED
|
@@ -1,21 +1,16 @@
|
|
|
1
1
|
import { Path } from '@astrale-os/sdk/graph/path'
|
|
2
|
-
import {
|
|
2
|
+
import { K } from '@astrale-os/sdk/schema'
|
|
3
3
|
import chalk from 'chalk'
|
|
4
4
|
|
|
5
5
|
import type { ConnectionContext, KernelCommandOpts } from '../connection'
|
|
6
6
|
import type { Column, ListProjection } from '../lib/output'
|
|
7
7
|
import type { CommandDefinition } from '../program/index'
|
|
8
8
|
|
|
9
|
-
import {
|
|
10
|
-
|
|
11
|
-
expandSelfInPath,
|
|
12
|
-
runKernelCommand,
|
|
13
|
-
withClientSession,
|
|
14
|
-
} from '../connection'
|
|
15
|
-
import { failClosed } from '../lib/log'
|
|
9
|
+
import { createPathCall, expandSelfInPath, runKernelCommand } from '../connection'
|
|
10
|
+
import { failInput } from '../lib/log'
|
|
16
11
|
import { isMachine, output, presentList } from '../lib/output'
|
|
17
12
|
|
|
18
|
-
const JOURNAL_PATH = Path.project(
|
|
13
|
+
const JOURNAL_PATH = Path.project(K.functions.journal.ref).raw
|
|
19
14
|
const DEFAULT_LIMIT = 200
|
|
20
15
|
const FOLLOW_INTERVAL_MS = 2_000
|
|
21
16
|
|
|
@@ -127,7 +122,7 @@ async function runOnce(opts: LogsOpts): Promise<void> {
|
|
|
127
122
|
await runKernelCommand({
|
|
128
123
|
opts,
|
|
129
124
|
label: 'Kernel journal',
|
|
130
|
-
fn: (context) => fetchPage(context, opts),
|
|
125
|
+
fn: async (context) => fetchPage(context, await resolveLogsOpts(opts, context)),
|
|
131
126
|
format: (page, format) => {
|
|
132
127
|
if (isMachine(format) || format.format !== undefined) output(page, format)
|
|
133
128
|
else {
|
|
@@ -139,14 +134,19 @@ async function runOnce(opts: LogsOpts): Promise<void> {
|
|
|
139
134
|
}
|
|
140
135
|
|
|
141
136
|
async function follow(opts: LogsOpts): Promise<void> {
|
|
142
|
-
await
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
cursor =
|
|
148
|
-
|
|
149
|
-
|
|
137
|
+
await runKernelCommand({
|
|
138
|
+
opts,
|
|
139
|
+
label: 'Kernel journal',
|
|
140
|
+
fn: async (context): Promise<never> => {
|
|
141
|
+
const resolved = await resolveLogsOpts(opts, context)
|
|
142
|
+
let cursor = resolved.cursor
|
|
143
|
+
for (;;) {
|
|
144
|
+
const page = await fetchPage(context, { ...resolved, cursor })
|
|
145
|
+
for (const record of page.records) printRecord(record)
|
|
146
|
+
cursor = page.cursor ?? cursor
|
|
147
|
+
await new Promise((resolve) => setTimeout(resolve, FOLLOW_INTERVAL_MS))
|
|
148
|
+
}
|
|
149
|
+
},
|
|
150
150
|
})
|
|
151
151
|
}
|
|
152
152
|
|
|
@@ -227,11 +227,10 @@ function positiveInteger(flag: string, raw: string): number {
|
|
|
227
227
|
return value
|
|
228
228
|
}
|
|
229
229
|
|
|
230
|
-
async function
|
|
231
|
-
buildJournalInput(opts)
|
|
230
|
+
async function resolveLogsOpts(opts: LogsOpts, context: ConnectionContext): Promise<LogsOpts> {
|
|
232
231
|
const principal = nonEmpty(opts.principal)
|
|
233
232
|
if (principal !== '@self') return opts
|
|
234
|
-
const { path } = await expandSelfInPath('@self',
|
|
233
|
+
const { path } = await expandSelfInPath('@self', context)
|
|
235
234
|
return { ...opts, principal: path.startsWith('@') ? path.slice(1) : path }
|
|
236
235
|
}
|
|
237
236
|
|
|
@@ -274,11 +273,11 @@ Examples:
|
|
|
274
273
|
],
|
|
275
274
|
action: async (opts: LogsOpts) => {
|
|
276
275
|
try {
|
|
277
|
-
|
|
278
|
-
if (prepared.follow) await follow(prepared)
|
|
279
|
-
else await runOnce(prepared)
|
|
276
|
+
buildJournalInput(opts)
|
|
280
277
|
} catch (error) {
|
|
281
|
-
|
|
278
|
+
failInput(error, opts)
|
|
282
279
|
}
|
|
280
|
+
if (opts.follow) await follow(opts)
|
|
281
|
+
else await runOnce(opts)
|
|
283
282
|
},
|
|
284
283
|
} satisfies CommandDefinition
|
package/src/commands/mutate.ts
CHANGED
|
@@ -7,8 +7,9 @@ import type { KernelCommandOpts } from '../connection'
|
|
|
7
7
|
import type { CommandDefinition } from '../program/index'
|
|
8
8
|
|
|
9
9
|
import { runKernelCommand } from '../connection'
|
|
10
|
+
import { AstraleError } from '../errors'
|
|
10
11
|
import { prepareMutation } from '../graph/index'
|
|
11
|
-
import {
|
|
12
|
+
import { failInput } from '../lib/log'
|
|
12
13
|
import { output } from '../lib/output'
|
|
13
14
|
import { renderTable } from '../lib/table'
|
|
14
15
|
|
|
@@ -23,7 +24,7 @@ export async function mutateCommand(opts: MutateOpts): Promise<void> {
|
|
|
23
24
|
try {
|
|
24
25
|
mutation = prepareMutation(await readDocument(opts))
|
|
25
26
|
} catch (error) {
|
|
26
|
-
|
|
27
|
+
failInput(error, opts)
|
|
27
28
|
}
|
|
28
29
|
|
|
29
30
|
if (opts.dry) {
|
|
@@ -51,9 +52,9 @@ async function readDocument(opts: MutateOpts): Promise<unknown> {
|
|
|
51
52
|
try {
|
|
52
53
|
raw = await readFile(opts.file, 'utf8')
|
|
53
54
|
} catch (error) {
|
|
54
|
-
throw new
|
|
55
|
-
|
|
56
|
-
)
|
|
55
|
+
throw new AstraleError('FILE_READ_FAILED', `Cannot read --file ${opts.file}.`, undefined, {
|
|
56
|
+
cause: error,
|
|
57
|
+
})
|
|
57
58
|
}
|
|
58
59
|
return parseJson(raw, opts.file)
|
|
59
60
|
}
|
package/src/commands/query.ts
CHANGED
|
@@ -6,8 +6,9 @@ import type { KernelCommandOpts, SelfExpansionMeta } from '../connection'
|
|
|
6
6
|
import type { CommandDefinition } from '../program/index'
|
|
7
7
|
|
|
8
8
|
import { expandSelfInPath, runKernelCommand, withSelfHint } from '../connection'
|
|
9
|
+
import { AstraleError } from '../errors'
|
|
9
10
|
import { prepareQuery, type QueryCommandInput } from '../graph/index'
|
|
10
|
-
import {
|
|
11
|
+
import { failInput } from '../lib/log'
|
|
11
12
|
import { isMachine, output } from '../lib/output'
|
|
12
13
|
|
|
13
14
|
type QueryOpts = KernelCommandOpts & {
|
|
@@ -22,46 +23,42 @@ type QueryOpts = KernelCommandOpts & {
|
|
|
22
23
|
|
|
23
24
|
export async function queryCommand(sources: string[], opts: QueryOpts): Promise<void> {
|
|
24
25
|
let input: QueryCommandInput
|
|
25
|
-
let meta: SelfExpansionMeta | undefined
|
|
26
26
|
try {
|
|
27
27
|
const ast = await readAst(opts)
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
cursor: opts.cursor,
|
|
37
|
-
}
|
|
38
|
-
meta = expanded.find((entry) => entry.meta !== undefined)?.meta
|
|
39
|
-
} else {
|
|
40
|
-
input = {
|
|
41
|
-
sources,
|
|
42
|
-
ast,
|
|
43
|
-
definition: opts.definition,
|
|
44
|
-
edge: opts.edge,
|
|
45
|
-
direction: opts.direction,
|
|
46
|
-
limit: opts.limit,
|
|
47
|
-
cursor: opts.cursor,
|
|
48
|
-
}
|
|
28
|
+
input = {
|
|
29
|
+
sources,
|
|
30
|
+
...(ast === undefined ? {} : { ast }),
|
|
31
|
+
definition: opts.definition,
|
|
32
|
+
edge: opts.edge,
|
|
33
|
+
direction: opts.direction,
|
|
34
|
+
limit: opts.limit,
|
|
35
|
+
cursor: opts.cursor,
|
|
49
36
|
}
|
|
50
37
|
} catch (error) {
|
|
51
|
-
|
|
38
|
+
failInput(error, opts)
|
|
52
39
|
}
|
|
53
40
|
|
|
54
41
|
let prepared
|
|
55
42
|
try {
|
|
56
43
|
prepared = prepareQuery(input)
|
|
57
44
|
} catch (error) {
|
|
58
|
-
|
|
45
|
+
failInput(error, opts)
|
|
59
46
|
}
|
|
60
47
|
|
|
61
48
|
await runKernelCommand({
|
|
62
49
|
opts,
|
|
63
50
|
label: 'Query',
|
|
64
|
-
fn:
|
|
51
|
+
fn: async (context) => {
|
|
52
|
+
const expanded = await Promise.all(sources.map((source) => expandSelfInPath(source, context)))
|
|
53
|
+
const meta: SelfExpansionMeta | undefined = expanded.find(
|
|
54
|
+
(entry) => entry.meta !== undefined,
|
|
55
|
+
)?.meta
|
|
56
|
+
const resolved =
|
|
57
|
+
meta === undefined
|
|
58
|
+
? prepared
|
|
59
|
+
: prepareQuery({ ...input, sources: expanded.map(({ path }) => path) })
|
|
60
|
+
return withSelfHint(() => context.graph.query(resolved.ast, { page: resolved.page }), meta)
|
|
61
|
+
},
|
|
65
62
|
format: (response, format) => {
|
|
66
63
|
output(response.result, format)
|
|
67
64
|
if (response.page.next && !isMachine(format)) {
|
|
@@ -81,9 +78,9 @@ async function readAst(opts: QueryOpts): Promise<unknown | undefined> {
|
|
|
81
78
|
try {
|
|
82
79
|
raw = await readFile(opts.file, 'utf8')
|
|
83
80
|
} catch (error) {
|
|
84
|
-
throw new
|
|
85
|
-
|
|
86
|
-
)
|
|
81
|
+
throw new AstraleError('FILE_READ_FAILED', `Cannot read --file ${opts.file}.`, undefined, {
|
|
82
|
+
cause: error,
|
|
83
|
+
})
|
|
87
84
|
}
|
|
88
85
|
return parseJson(raw, opts.file)
|
|
89
86
|
}
|
|
@@ -122,7 +119,7 @@ Examples:
|
|
|
122
119
|
{ flags: '-f, --file <path>', description: 'Read a canonical Query V6 document from a file' },
|
|
123
120
|
{
|
|
124
121
|
flags: '--definition <path>',
|
|
125
|
-
description: 'Select Nodes
|
|
122
|
+
description: 'Select Nodes satisfying one exact Class',
|
|
126
123
|
},
|
|
127
124
|
{ flags: '--edge <class>', description: 'Expand one exact Edge Class' },
|
|
128
125
|
{
|
package/src/commands/token.ts
CHANGED
|
@@ -6,7 +6,7 @@ import type { CommandDefinition } from '../program/index'
|
|
|
6
6
|
import { runKernelCommand } from '../connection'
|
|
7
7
|
import { AstraleError } from '../errors'
|
|
8
8
|
import { decodeJwtExpiration } from '../lib/local-status'
|
|
9
|
-
import {
|
|
9
|
+
import { failInput, log } from '../lib/log'
|
|
10
10
|
import { output } from '../lib/output'
|
|
11
11
|
|
|
12
12
|
/**
|
|
@@ -23,7 +23,12 @@ export type TokenOpts = KernelCommandOpts & {
|
|
|
23
23
|
|
|
24
24
|
export async function tokenCommand(opts: TokenOpts): Promise<void> {
|
|
25
25
|
const commandOpts: TokenOpts = opts.for && !opts.as ? { ...opts, as: opts.for } : opts
|
|
26
|
-
|
|
26
|
+
let ttl: number
|
|
27
|
+
try {
|
|
28
|
+
ttl = parseTtl(commandOpts.ttl)
|
|
29
|
+
} catch (error) {
|
|
30
|
+
failInput(error, opts)
|
|
31
|
+
}
|
|
27
32
|
await runKernelCommand<string>({
|
|
28
33
|
opts: commandOpts,
|
|
29
34
|
label: 'Minting delegation token',
|
|
@@ -97,10 +102,6 @@ Examples:
|
|
|
97
102
|
{ flags: '--for <identity>', description: 'Mint the token for this identity (alias of --as)' },
|
|
98
103
|
],
|
|
99
104
|
action: async (opts) => {
|
|
100
|
-
|
|
101
|
-
await tokenCommand(opts as Parameters<typeof tokenCommand>[0])
|
|
102
|
-
} catch (error) {
|
|
103
|
-
failClosed(error, opts as TokenOpts)
|
|
104
|
-
}
|
|
105
|
+
await tokenCommand(opts as Parameters<typeof tokenCommand>[0])
|
|
105
106
|
},
|
|
106
107
|
} satisfies CommandDefinition
|
package/src/commands/view.ts
CHANGED
|
@@ -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 {
|
|
93
|
-
|
|
94
|
-
|
|
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
|
-
|
|
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:
|
|
88
|
-
|
|
86
|
+
parameters: Readonly<Record<string, unknown>>,
|
|
87
|
+
context: ConnectionContext,
|
|
89
88
|
): Promise<{
|
|
90
89
|
readonly path: string
|
|
91
|
-
readonly parameters:
|
|
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
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
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 {
|
|
2
|
-
import type {
|
|
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
|
|
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
|
|
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
|
})
|
|
@@ -1,7 +1,9 @@
|
|
|
1
|
-
import { ResponseError
|
|
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 =
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
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: '
|
|
41
|
+
error: 'TRANSPORT_ERROR',
|
|
36
42
|
message: 'Publication discovery request failed.',
|
|
37
43
|
url: 'https://localhost:8443/kernel/host',
|
|
38
|
-
phase: '
|
|
39
|
-
|
|
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
|
-
|
|
54
|
-
|
|
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
|
|
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
|
-
|
|
241
|
-
|
|
242
|
-
|
|
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:
|
|
255
|
+
message: expect.any(String),
|
|
249
256
|
})
|
|
250
257
|
})
|
|
251
258
|
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { Fetch } from '@astrale-os/
|
|
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(
|