@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,11 +1,9 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { issuer } from '@astrale-os/sdk/auth'
|
|
3
|
-
import { createDeployment } from '@astrale-os/sdk/deployment'
|
|
4
|
-
import { defineSchema } from '@astrale-os/sdk/schema/v1'
|
|
1
|
+
import { defineSchema, schema as schemaApi } from '@astrale-os/sdk/schema'
|
|
5
2
|
import { afterAll, describe, expect, test } from 'bun:test'
|
|
6
3
|
|
|
7
4
|
import type { DomainInfo } from '../../lib/admin-domain'
|
|
8
5
|
|
|
6
|
+
import { releaseFor } from '../../__tests__/fixtures/publication'
|
|
9
7
|
import { byDefaultThenName, domainProjection, probe, type DomainRow } from '../domain/list'
|
|
10
8
|
|
|
11
9
|
const strip = (s: string): string => s.replace(/\[[0-9;]*m/g, '')
|
|
@@ -85,16 +83,7 @@ describe('domain list — canonical Publication check', () => {
|
|
|
85
83
|
|
|
86
84
|
test('reports the admitted schema revision in machine data', async () => {
|
|
87
85
|
const schema = defineSchema('catalog-check.example.dev', {})
|
|
88
|
-
const
|
|
89
|
-
schema,
|
|
90
|
-
handlers: { functions: {}, classes: {}, interfaces: {} },
|
|
91
|
-
})
|
|
92
|
-
const deployed = createDeployment({
|
|
93
|
-
definition,
|
|
94
|
-
issuer: issuer.accept('https://catalog-check.example.dev'),
|
|
95
|
-
bundleHref: 'https://catalog-check.example.dev/domain.bundle.json',
|
|
96
|
-
bindings: { callables: [] },
|
|
97
|
-
}).publication
|
|
86
|
+
const deployed = releaseFor(schema, 'https://catalog-check.example.dev').publication
|
|
98
87
|
const server = Bun.serve({
|
|
99
88
|
port: 0,
|
|
100
89
|
fetch(request) {
|
|
@@ -114,23 +103,14 @@ describe('domain list — canonical Publication check', () => {
|
|
|
114
103
|
),
|
|
115
104
|
).resolves.toMatchObject({
|
|
116
105
|
reachable: true,
|
|
117
|
-
schemaRevision:
|
|
106
|
+
schemaRevision: schemaApi.revision(schema),
|
|
118
107
|
checkError: null,
|
|
119
108
|
})
|
|
120
109
|
})
|
|
121
110
|
|
|
122
111
|
test('rejects a Publication whose origin differs from the catalog entry', async () => {
|
|
123
112
|
const schema = defineSchema('deployed.example.dev', {})
|
|
124
|
-
const
|
|
125
|
-
schema,
|
|
126
|
-
handlers: { functions: {}, classes: {}, interfaces: {} },
|
|
127
|
-
})
|
|
128
|
-
const deployed = createDeployment({
|
|
129
|
-
definition,
|
|
130
|
-
issuer: issuer.accept('https://deployed.example.dev'),
|
|
131
|
-
bundleHref: 'https://deployed.example.dev/domain.bundle.json',
|
|
132
|
-
bindings: { callables: [] },
|
|
133
|
-
}).publication
|
|
113
|
+
const deployed = releaseFor(schema, 'https://deployed.example.dev').publication
|
|
134
114
|
const server = Bun.serve({
|
|
135
115
|
port: 0,
|
|
136
116
|
fetch: () => Response.json(deployed),
|
|
@@ -1,9 +1,7 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { issuer } from '@astrale-os/sdk/auth'
|
|
3
|
-
import { createDeployment } from '@astrale-os/sdk/deployment'
|
|
4
|
-
import { defineSchema } from '@astrale-os/sdk/schema/v1'
|
|
1
|
+
import { defineSchema } from '@astrale-os/sdk/schema'
|
|
5
2
|
import { afterAll, describe, expect, test } from 'bun:test'
|
|
6
3
|
|
|
4
|
+
import { releaseFor } from '../../__tests__/fixtures/publication'
|
|
7
5
|
import { domainRefFromTarget, isIdentityOverride, probeDeclaredOrigin } from '../domain/install'
|
|
8
6
|
|
|
9
7
|
describe('admin-path target classification', () => {
|
|
@@ -47,16 +45,7 @@ describe('declared-origin Publication probe', () => {
|
|
|
47
45
|
|
|
48
46
|
test('reads origin from a well-formed canonical Publication', async () => {
|
|
49
47
|
const schema = defineSchema('crm.acme.dev', {})
|
|
50
|
-
const
|
|
51
|
-
schema,
|
|
52
|
-
handlers: { functions: {}, classes: {}, interfaces: {} },
|
|
53
|
-
})
|
|
54
|
-
const deployed = createDeployment({
|
|
55
|
-
definition,
|
|
56
|
-
issuer: issuer.accept('https://crm.acme.dev'),
|
|
57
|
-
bundleHref: 'https://crm.acme.dev/domain.bundle.json',
|
|
58
|
-
bindings: { callables: [] },
|
|
59
|
-
}).publication
|
|
48
|
+
const deployed = releaseFor(schema, 'https://crm.acme.dev').publication
|
|
60
49
|
const url = servePublication((req) =>
|
|
61
50
|
new URL(req.url).pathname === '/.well-known/astrale/domain.json'
|
|
62
51
|
? Response.json(deployed)
|
|
@@ -36,6 +36,8 @@ let mutations: unknown[] = []
|
|
|
36
36
|
let queryResult: unknown
|
|
37
37
|
let getResult: unknown
|
|
38
38
|
let mutationResult: unknown
|
|
39
|
+
let selfFailure: unknown
|
|
40
|
+
let selfContexts: unknown[] = []
|
|
39
41
|
|
|
40
42
|
const queryMock = mock(async (ast: unknown, options?: unknown) => {
|
|
41
43
|
queryCalls.push({ ast, options })
|
|
@@ -49,7 +51,9 @@ const mutateMock = mock(async (mutation: unknown) => {
|
|
|
49
51
|
mutations.push(mutation)
|
|
50
52
|
return mutationResult
|
|
51
53
|
})
|
|
52
|
-
const expandSelfInPathMock = mock(async (path: string) => {
|
|
54
|
+
const expandSelfInPathMock = mock(async (path: string, context: unknown) => {
|
|
55
|
+
selfContexts.push(context)
|
|
56
|
+
if (selfFailure !== undefined) throw selfFailure
|
|
53
57
|
if (path === '@self') {
|
|
54
58
|
return {
|
|
55
59
|
path: '@expanded-self',
|
|
@@ -59,10 +63,11 @@ const expandSelfInPathMock = mock(async (path: string) => {
|
|
|
59
63
|
return { path, meta: undefined }
|
|
60
64
|
})
|
|
61
65
|
const withSelfHintMock = mock(async (action: () => Promise<unknown>) => action())
|
|
66
|
+
const commandContext = {
|
|
67
|
+
graph: { query: queryMock, getOrThrow: getOrThrowMock, mutate: mutateMock },
|
|
68
|
+
}
|
|
62
69
|
const runKernelCommandMock = mock(async (run: RunOpts) => {
|
|
63
|
-
const result = await run.fn(
|
|
64
|
-
graph: { query: queryMock, getOrThrow: getOrThrowMock, mutate: mutateMock },
|
|
65
|
-
})
|
|
70
|
+
const result = await run.fn(commandContext)
|
|
66
71
|
await run.format?.(result, run.opts, true)
|
|
67
72
|
})
|
|
68
73
|
|
|
@@ -88,6 +93,8 @@ beforeEach(() => {
|
|
|
88
93
|
}
|
|
89
94
|
getResult = undefined
|
|
90
95
|
mutationResult = { createdNodes: {} }
|
|
96
|
+
selfFailure = undefined
|
|
97
|
+
selfContexts = []
|
|
91
98
|
queryMock.mockClear()
|
|
92
99
|
getOrThrowMock.mockClear()
|
|
93
100
|
mutateMock.mockClear()
|
|
@@ -161,7 +168,7 @@ describe('query command', () => {
|
|
|
161
168
|
])
|
|
162
169
|
})
|
|
163
170
|
|
|
164
|
-
test('authors a
|
|
171
|
+
test('authors a Class source through the same canonical Query V6 call', async () => {
|
|
165
172
|
const { queryCommand } = await import('../query')
|
|
166
173
|
|
|
167
174
|
await queryCommand([], {
|
|
@@ -179,8 +186,8 @@ describe('query command', () => {
|
|
|
179
186
|
kind: 'node',
|
|
180
187
|
terms: [
|
|
181
188
|
{
|
|
182
|
-
kind: '
|
|
183
|
-
|
|
189
|
+
kind: 'class',
|
|
190
|
+
class: { origin: 'issues.astrale.ai', kind: 'class', name: 'Issue' },
|
|
184
191
|
},
|
|
185
192
|
],
|
|
186
193
|
binding: 'n0',
|
|
@@ -193,6 +200,17 @@ describe('query command', () => {
|
|
|
193
200
|
])
|
|
194
201
|
})
|
|
195
202
|
|
|
203
|
+
test('resolves @self inside the command session and never relabels transport failure as input', async () => {
|
|
204
|
+
const { queryCommand } = await import('../query')
|
|
205
|
+
await queryCommand(['@self'], { json: true, limit: '2' })
|
|
206
|
+
expect(selfContexts).toEqual([commandContext])
|
|
207
|
+
|
|
208
|
+
const aggregate = new AggregateError([], '')
|
|
209
|
+
selfFailure = aggregate
|
|
210
|
+
await expect(queryCommand(['@self'], { json: true, limit: '2' })).rejects.toBe(aggregate)
|
|
211
|
+
expect(errors.join('\n')).not.toContain('INVALID_INPUT')
|
|
212
|
+
})
|
|
213
|
+
|
|
196
214
|
/** @evidence TEST-CLI-QUERY-REJECTS-V1-BEFORE-CONNECTION */
|
|
197
215
|
test('rejects a legacy AST before opening the command connection', async () => {
|
|
198
216
|
const { queryCommand } = await import('../query')
|
|
@@ -215,7 +233,7 @@ describe('get command', () => {
|
|
|
215
233
|
const { getCommand } = await import('../get')
|
|
216
234
|
getResult = {
|
|
217
235
|
id: 'note-1',
|
|
218
|
-
class: '
|
|
236
|
+
class: 'notes.example.dev:class.Note',
|
|
219
237
|
props: { 'notes.example.dev:class.Note.property.title': 'Hello' },
|
|
220
238
|
}
|
|
221
239
|
|
|
@@ -226,6 +244,13 @@ describe('get command', () => {
|
|
|
226
244
|
expect(stdout).not.toContain('__labels')
|
|
227
245
|
expect(stdout).not.toContain('classId')
|
|
228
246
|
})
|
|
247
|
+
|
|
248
|
+
test('resolves @self through the same point-read command context', async () => {
|
|
249
|
+
const { getCommand } = await import('../get')
|
|
250
|
+
await getCommand('@self', { json: true })
|
|
251
|
+
expect(selfContexts).toEqual([commandContext])
|
|
252
|
+
expect(getTargets).toEqual(['@expanded-self'])
|
|
253
|
+
})
|
|
229
254
|
})
|
|
230
255
|
|
|
231
256
|
describe('mutate command', () => {
|
|
@@ -242,7 +267,7 @@ describe('mutate command', () => {
|
|
|
242
267
|
{
|
|
243
268
|
op: 'node.create',
|
|
244
269
|
as: 'note',
|
|
245
|
-
class: '
|
|
270
|
+
class: 'notes.example.dev:class.Note',
|
|
246
271
|
props: {},
|
|
247
272
|
},
|
|
248
273
|
],
|
|
@@ -258,7 +283,7 @@ describe('mutate command', () => {
|
|
|
258
283
|
{
|
|
259
284
|
op: 'node.create',
|
|
260
285
|
as: 'note',
|
|
261
|
-
class: '
|
|
286
|
+
class: 'notes.example.dev:class.Note',
|
|
262
287
|
props: {},
|
|
263
288
|
},
|
|
264
289
|
],
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import type { Path } from '@astrale-os/sdk/graph/path'
|
|
2
|
+
import type { ResolvedFunction, ResolvedMethod } from '@astrale-os/sdk/schema'
|
|
2
3
|
|
|
3
|
-
import {
|
|
4
|
+
import { bundle, schema } from '@astrale-os/sdk/schema'
|
|
4
5
|
|
|
5
|
-
|
|
6
|
+
import { AstraleError } from '../errors'
|
|
6
7
|
|
|
7
8
|
export interface CallableDescription {
|
|
8
9
|
readonly path: string
|
|
@@ -18,40 +19,40 @@ export interface CallableDescription {
|
|
|
18
19
|
readonly candidates?: readonly CallableDescription[]
|
|
19
20
|
}
|
|
20
21
|
|
|
21
|
-
/** Resolve one callable
|
|
22
|
-
export function
|
|
22
|
+
/** Resolve one callable from an admitted installed Domain bundle. */
|
|
23
|
+
export function describeCallableFromBundle(
|
|
23
24
|
path: Path,
|
|
24
|
-
|
|
25
|
+
input: unknown,
|
|
25
26
|
): CallableDescription | undefined {
|
|
26
27
|
if (path.ast.anchor.kind !== 'domain') return undefined
|
|
27
28
|
const origin = path.ast.anchor.origin
|
|
28
29
|
const last = path.ast.steps.at(-1)
|
|
29
|
-
|
|
30
|
-
|
|
30
|
+
if (last === undefined) return undefined
|
|
31
|
+
const domain = schema.resolve(bundle.accept(input).root)
|
|
32
|
+
if (domain.origin !== origin) return undefined
|
|
31
33
|
|
|
32
34
|
if (last.kind === 'method') {
|
|
33
|
-
const
|
|
34
|
-
if (
|
|
35
|
-
owner
|
|
36
|
-
(owner
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
35
|
+
const ownerStep = path.ast.steps.at(-2)
|
|
36
|
+
if (ownerStep?.kind === 'projection' && ownerStep.projection.kind === 'class') {
|
|
37
|
+
const owner = domain.classes[ownerStep.projection.name]
|
|
38
|
+
if (owner?.kind !== 'node') return undefined
|
|
39
|
+
const method = namedMethod(
|
|
40
|
+
last.dispatch === 'static' ? owner.static.methods : owner.methods,
|
|
41
|
+
last.name,
|
|
42
|
+
last.dispatch,
|
|
40
43
|
)
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
return Object.freeze({
|
|
45
|
-
path: path.raw,
|
|
46
|
-
origin,
|
|
47
|
-
class: owner.projection.name,
|
|
48
|
-
method: last.name,
|
|
49
|
-
dispatch: last.dispatch,
|
|
50
|
-
...callableFields(method),
|
|
51
|
-
})
|
|
52
|
-
}
|
|
44
|
+
return method === undefined
|
|
45
|
+
? undefined
|
|
46
|
+
: description(path, origin, owner.ref.name, method, last.dispatch)
|
|
53
47
|
}
|
|
54
|
-
|
|
48
|
+
|
|
49
|
+
const matches = Object.values(domain.classes).flatMap((owner) => {
|
|
50
|
+
if (owner.kind !== 'node') return []
|
|
51
|
+
const method = namedMethod(owner.methods, last.name, last.dispatch)
|
|
52
|
+
return method === undefined
|
|
53
|
+
? []
|
|
54
|
+
: [description(path, origin, owner.ref.name, method, last.dispatch)]
|
|
55
|
+
})
|
|
55
56
|
if (matches.length === 1) return matches[0]
|
|
56
57
|
if (matches.length > 1) {
|
|
57
58
|
return Object.freeze({
|
|
@@ -66,13 +67,13 @@ export function describeCallableFromSchema(
|
|
|
66
67
|
}
|
|
67
68
|
|
|
68
69
|
if (last.kind === 'projection' && last.projection.kind === 'function') {
|
|
69
|
-
const
|
|
70
|
-
if (
|
|
70
|
+
const callable = domain.functions[last.projection.name]
|
|
71
|
+
if (callable === undefined) return undefined
|
|
71
72
|
return Object.freeze({
|
|
72
73
|
path: path.raw,
|
|
73
74
|
origin,
|
|
74
|
-
function:
|
|
75
|
-
...callableFields(
|
|
75
|
+
function: callable.ref.name,
|
|
76
|
+
...callableFields(callable),
|
|
76
77
|
})
|
|
77
78
|
}
|
|
78
79
|
return undefined
|
|
@@ -86,46 +87,37 @@ export function missingCallableDescription(path: string): AstraleError {
|
|
|
86
87
|
)
|
|
87
88
|
}
|
|
88
89
|
|
|
89
|
-
function
|
|
90
|
-
|
|
90
|
+
function namedMethod(
|
|
91
|
+
methods: Iterable<ResolvedMethod>,
|
|
92
|
+
name: string,
|
|
93
|
+
dispatch: 'static' | 'instance',
|
|
94
|
+
): ResolvedMethod | undefined {
|
|
95
|
+
const expectedStatic = dispatch === 'static'
|
|
96
|
+
return [...methods].find((method) => method.name === name && method.static === expectedStatic)
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
function description(
|
|
100
|
+
path: Path,
|
|
91
101
|
origin: string,
|
|
92
|
-
|
|
93
|
-
|
|
102
|
+
className: string,
|
|
103
|
+
callable: ResolvedMethod,
|
|
94
104
|
dispatch: 'static' | 'instance',
|
|
95
|
-
): CallableDescription
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
Object.freeze({
|
|
105
|
-
path,
|
|
106
|
-
origin,
|
|
107
|
-
class: className,
|
|
108
|
-
method,
|
|
109
|
-
dispatch,
|
|
110
|
-
...callableFields(methodDef),
|
|
111
|
-
}),
|
|
112
|
-
)
|
|
113
|
-
}
|
|
114
|
-
}
|
|
115
|
-
return matches
|
|
105
|
+
): CallableDescription {
|
|
106
|
+
return Object.freeze({
|
|
107
|
+
path: path.raw,
|
|
108
|
+
origin,
|
|
109
|
+
class: className,
|
|
110
|
+
method: callable.name,
|
|
111
|
+
dispatch,
|
|
112
|
+
...callableFields(callable),
|
|
113
|
+
})
|
|
116
114
|
}
|
|
117
115
|
|
|
118
|
-
function callableFields(
|
|
116
|
+
function callableFields(callable: ResolvedFunction | ResolvedMethod): Partial<CallableDescription> {
|
|
119
117
|
return {
|
|
120
|
-
...(
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
118
|
+
...(callable.description === undefined ? {} : { description: callable.description }),
|
|
119
|
+
auth: callable.auth,
|
|
120
|
+
input: callable.input,
|
|
121
|
+
output: callable.output,
|
|
124
122
|
}
|
|
125
123
|
}
|
|
126
|
-
|
|
127
|
-
function asRecord(input: unknown): JsonRecord | undefined {
|
|
128
|
-
return input !== null && typeof input === 'object' && !Array.isArray(input)
|
|
129
|
-
? (input as JsonRecord)
|
|
130
|
-
: undefined
|
|
131
|
-
}
|
package/src/commands/call.ts
CHANGED
|
@@ -5,7 +5,7 @@ import type { CommandDefinition } from '../program/index'
|
|
|
5
5
|
|
|
6
6
|
import { createPathCall, expandSelfInCall, runKernelCommand, withSelfHint } from '../connection'
|
|
7
7
|
import { presentBinary } from '../lib/binary'
|
|
8
|
-
import {
|
|
8
|
+
import { failInput, log } from '../lib/log'
|
|
9
9
|
import { output, present } from '../lib/output'
|
|
10
10
|
|
|
11
11
|
type CallOpts = KernelCommandOpts & {
|
|
@@ -24,60 +24,47 @@ export async function callCommand(
|
|
|
24
24
|
rawParams: string[],
|
|
25
25
|
opts: CallOpts,
|
|
26
26
|
): Promise<void> {
|
|
27
|
-
// Expand @self before describe/execute through authenticated Identity.whoami.
|
|
28
|
-
let expanded: Awaited<ReturnType<typeof expandSelfInCall>>
|
|
29
|
-
try {
|
|
30
|
-
expanded = await expandSelfInCall(path, rawParams, opts)
|
|
31
|
-
} catch (error) {
|
|
32
|
-
failClosed(error, opts)
|
|
33
|
-
}
|
|
34
|
-
const expandedPath = expanded.path
|
|
35
|
-
try {
|
|
36
|
-
Path.parse(expandedPath)
|
|
37
|
-
} catch (error) {
|
|
38
|
-
failClosed(error, opts)
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
// ── Parse params ────────────────────────────────────────
|
|
42
27
|
let params: Record<string, unknown>
|
|
43
28
|
try {
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
// ── Dry-run: show what would be sent ─────────────────────
|
|
50
|
-
if (opts.dryRun) {
|
|
51
|
-
output({ method: expandedPath, params }, opts)
|
|
52
|
-
return
|
|
29
|
+
Path.parse(path)
|
|
30
|
+
params = await parseParams(rawParams, opts.data)
|
|
31
|
+
} catch (error) {
|
|
32
|
+
failInput(error, opts)
|
|
53
33
|
}
|
|
54
34
|
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
35
|
+
await runKernelCommand<MaterializedCallResult | { readonly kind: 'dry'; readonly call: unknown }>(
|
|
36
|
+
{
|
|
37
|
+
opts,
|
|
38
|
+
label: path,
|
|
39
|
+
fn: async (ctx) => {
|
|
40
|
+
const expanded = await expandSelfInCall(path, params, ctx)
|
|
41
|
+
const request = createPathCall(expanded.path, expanded.parameters)
|
|
42
|
+
if (opts.dryRun) return { kind: 'dry', call: request }
|
|
43
|
+
return withSelfHint(
|
|
44
|
+
async () => materializeCallResult(await ctx.session.dispatch(request)),
|
|
45
|
+
expanded.meta,
|
|
46
|
+
)
|
|
47
|
+
},
|
|
48
|
+
format: async (result, fmtOpts) => {
|
|
49
|
+
switch (result.kind) {
|
|
50
|
+
case 'dry':
|
|
51
|
+
output(result.call, fmtOpts)
|
|
52
|
+
return
|
|
53
|
+
case 'value':
|
|
54
|
+
present(result.value, fmtOpts)
|
|
55
|
+
return
|
|
56
|
+
case 'binary':
|
|
57
|
+
await presentBinary(result.value, fmtOpts, { outFile: opts.output })
|
|
58
|
+
return
|
|
59
|
+
case 'stream':
|
|
60
|
+
output(result.values, fmtOpts)
|
|
61
|
+
return
|
|
62
|
+
case 'redirect':
|
|
63
|
+
throw new Error('Client Session returned an unresolved redirect.')
|
|
64
|
+
}
|
|
65
|
+
},
|
|
79
66
|
},
|
|
80
|
-
|
|
67
|
+
)
|
|
81
68
|
}
|
|
82
69
|
|
|
83
70
|
/** Drain a session-backed stream before the command-scoped Client Session closes. */
|
|
@@ -101,7 +88,7 @@ export async function parseParams(
|
|
|
101
88
|
try {
|
|
102
89
|
return JSON.parse(dataFlag)
|
|
103
90
|
} catch {
|
|
104
|
-
throw new
|
|
91
|
+
throw new TypeError(`Invalid JSON in --data: ${dataFlag}`)
|
|
105
92
|
}
|
|
106
93
|
}
|
|
107
94
|
|
|
@@ -114,7 +101,7 @@ export async function parseParams(
|
|
|
114
101
|
try {
|
|
115
102
|
return JSON.parse(stdin)
|
|
116
103
|
} catch {
|
|
117
|
-
throw new
|
|
104
|
+
throw new TypeError('Invalid JSON from stdin')
|
|
118
105
|
}
|
|
119
106
|
}
|
|
120
107
|
|
|
@@ -142,7 +129,7 @@ export function parseKeyValue(pairs: string[]): Record<string, unknown> {
|
|
|
142
129
|
for (const pair of pairs) {
|
|
143
130
|
const eqIdx = pair.indexOf('=')
|
|
144
131
|
if (eqIdx === -1) {
|
|
145
|
-
throw new
|
|
132
|
+
throw new TypeError(`Invalid param "${pair}" — expected key=value format`)
|
|
146
133
|
}
|
|
147
134
|
const key = pair.slice(0, eqIdx)
|
|
148
135
|
const raw = pair.slice(eqIdx + 1)
|
|
@@ -150,7 +137,7 @@ export function parseKeyValue(pairs: string[]): Record<string, unknown> {
|
|
|
150
137
|
const hint = key.endsWith(':')
|
|
151
138
|
? ` (looks like httpie's "key:=value" syntax — Astrale CLI doesn't support it; use --data '{"${key.slice(0, -1)}":<value>}' for nested values)`
|
|
152
139
|
: ` (keys must be identifier-shaped: letters, digits, underscore, hyphen)`
|
|
153
|
-
throw new
|
|
140
|
+
throw new TypeError(`Invalid param key "${key}" in "${pair}"${hint}`)
|
|
154
141
|
}
|
|
155
142
|
result[key] = coerceValue(raw)
|
|
156
143
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
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 { KernelCommandOpts } from '../../connection'
|
|
@@ -414,7 +414,7 @@ export async function installDirect(
|
|
|
414
414
|
fn: async ({ session }) =>
|
|
415
415
|
(await session.call(
|
|
416
416
|
createPathCall(
|
|
417
|
-
Path.project(
|
|
417
|
+
Path.project(K.functions.install.ref).raw,
|
|
418
418
|
directInstallCallInput(url, operation, opts.token),
|
|
419
419
|
),
|
|
420
420
|
)) as DirectInstallResult,
|
|
@@ -1,5 +1,5 @@
|
|
|
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 { KernelCommandOpts } from '../../connection'
|
|
@@ -77,7 +77,7 @@ Examples:
|
|
|
77
77
|
label: `Uninstalling domain ${origin}`,
|
|
78
78
|
fn: async ({ session }) =>
|
|
79
79
|
(await session.call(
|
|
80
|
-
createPathCall(Path.project(
|
|
80
|
+
createPathCall(Path.project(K.functions.uninstall.ref).raw, uninstallCallInput(origin)),
|
|
81
81
|
)) as UninstallResult,
|
|
82
82
|
format: (result, formatOpts, machine) => {
|
|
83
83
|
if (machine) {
|
package/src/commands/get.ts
CHANGED
|
@@ -4,40 +4,39 @@ import type { KernelCommandOpts } from '../connection'
|
|
|
4
4
|
import type { CommandDefinition } from '../program/index'
|
|
5
5
|
|
|
6
6
|
import { expandSelfInPath, runKernelCommand, withSelfHint } from '../connection'
|
|
7
|
-
import { formatKernelError } from '../connection/errors'
|
|
8
7
|
import { AstraleError } from '../errors'
|
|
9
|
-
import {
|
|
8
|
+
import { failInput, fatal } from '../lib/log'
|
|
9
|
+
import { denoise, output } from '../lib/output'
|
|
10
10
|
|
|
11
11
|
type GetOpts = KernelCommandOpts & { schema?: boolean }
|
|
12
12
|
|
|
13
13
|
/** Read one exact canonical Node. The caller's Path is not fabricated into the Node value. */
|
|
14
14
|
export async function getCommand(target: string, opts: GetOpts): Promise<void> {
|
|
15
|
-
let path: string
|
|
16
|
-
let meta
|
|
17
15
|
let parsed
|
|
18
16
|
try {
|
|
19
|
-
|
|
20
|
-
parsed = Path.parse(path)
|
|
17
|
+
parsed = Path.parse(target)
|
|
21
18
|
} catch (error) {
|
|
22
|
-
|
|
23
|
-
process.exit(1)
|
|
19
|
+
failInput(error, opts)
|
|
24
20
|
}
|
|
25
21
|
|
|
26
22
|
const last = parsed.ast.steps.at(-1)
|
|
27
23
|
if (last?.kind === 'method') {
|
|
28
24
|
const error = new AstraleError(
|
|
29
25
|
'NOT_A_NODE',
|
|
30
|
-
`${
|
|
31
|
-
`Use \`astrale call ${
|
|
26
|
+
`${target} is a callable Path, not a Node.`,
|
|
27
|
+
`Use \`astrale call ${target}\` or \`astrale introspect ${target}\`.`,
|
|
32
28
|
)
|
|
33
|
-
|
|
34
|
-
process.exit(1)
|
|
29
|
+
fatal(error, opts)
|
|
35
30
|
}
|
|
36
31
|
|
|
37
32
|
await runKernelCommand({
|
|
38
33
|
opts,
|
|
39
|
-
label: `Node ${
|
|
40
|
-
fn:
|
|
34
|
+
label: `Node ${target}`,
|
|
35
|
+
fn: async (context) => {
|
|
36
|
+
const { path, meta } = await expandSelfInPath(target, context)
|
|
37
|
+
const resolved = meta === undefined ? parsed : Path.parse(path)
|
|
38
|
+
return withSelfHint(() => context.graph.getOrThrow(resolved), meta)
|
|
39
|
+
},
|
|
41
40
|
format: (node, format) => output(opts.schema === true ? node : denoise(node), format),
|
|
42
41
|
})
|
|
43
42
|
}
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { issuer, jwk, provision } from '@astrale-os/sdk/auth'
|
|
2
|
-
import { ClassPath } from '@astrale-os/sdk/graph/class'
|
|
3
2
|
import { normalizeProperties } from '@astrale-os/sdk/graph/properties'
|
|
4
3
|
import { expect, mock, test } from 'bun:test'
|
|
5
4
|
import { exportJWK, generateKeyPair, jwtVerify } from 'jose'
|
|
6
5
|
|
|
6
|
+
import { classKey } from '../../../graph'
|
|
7
7
|
import { formatIdentityRegistration, prepareIdentityProvision } from '../register'
|
|
8
8
|
|
|
9
9
|
/** @evidence TEST-CLI-IDENTITY-REGISTER-JSON-EXACT */
|
|
@@ -46,7 +46,7 @@ test('builds one exact Mutation V3 identity birth bound to a self proof', async
|
|
|
46
46
|
kid: 'alice-key',
|
|
47
47
|
})
|
|
48
48
|
const kernelIssuer = issuer.accept('https://kernel.example')
|
|
49
|
-
const classPath =
|
|
49
|
+
const classPath = classKey('/:accounts.example:class.User', '--class')
|
|
50
50
|
const properties = normalizeProperties({
|
|
51
51
|
'accounts.example:class.User.property.name': 'Alice',
|
|
52
52
|
})
|
|
@@ -70,7 +70,7 @@ test('builds one exact Mutation V3 identity birth bound to a self proof', async
|
|
|
70
70
|
{
|
|
71
71
|
op: 'node.create',
|
|
72
72
|
as: 'identity',
|
|
73
|
-
class: '
|
|
73
|
+
class: 'accounts.example:class.User',
|
|
74
74
|
props: { 'accounts.example:class.User.property.name': 'Alice' },
|
|
75
75
|
},
|
|
76
76
|
],
|