@astrale-os/cli 1.0.0-beta.5 → 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 +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
|
@@ -24,17 +24,16 @@ export interface DomainInstallReceipt {
|
|
|
24
24
|
readonly installedRevision?: string;
|
|
25
25
|
readonly error?: string;
|
|
26
26
|
}
|
|
27
|
-
export declare class AdminInstanceNotFoundError extends
|
|
27
|
+
export declare class AdminInstanceNotFoundError extends AstraleError {
|
|
28
28
|
readonly identifier: string;
|
|
29
|
-
readonly name = "NotFoundError";
|
|
30
29
|
constructor(identifier: string);
|
|
31
30
|
}
|
|
32
|
-
export declare class AdminHostNotFoundError extends
|
|
31
|
+
export declare class AdminHostNotFoundError extends AstraleError {
|
|
33
32
|
readonly identifier: string;
|
|
34
|
-
readonly name = "NotFoundError";
|
|
35
33
|
constructor(identifier: string);
|
|
36
34
|
}
|
|
37
35
|
/** Match an owner-scoped Instance by slug, canonical Node Path, or bare Node id. */
|
|
38
36
|
export declare function findOwnedInstance(instances: readonly OwnedInstanceInfo[], identifier: string): OwnedInstanceInfo | undefined;
|
|
39
37
|
/** Human-readable location of an Instance (state, region, and Host Path). */
|
|
40
38
|
export declare function formatInstanceLocation(info: InstanceInfo): string;
|
|
39
|
+
import { AstraleError } from '../../errors';
|
package/dist/types/errors.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
export declare class AstraleError extends Error {
|
|
2
2
|
code: string;
|
|
3
3
|
hint?: string;
|
|
4
|
-
constructor(code: string, message: string, hint?: string);
|
|
4
|
+
constructor(code: string, message: string, hint?: string, options?: ErrorOptions);
|
|
5
5
|
}
|
|
6
6
|
export declare class AuthError extends AstraleError {
|
|
7
7
|
constructor(message: string, hint?: string);
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import type { InstanceInfo } from '../admin/instance/model';
|
|
2
1
|
import type { AstraleConfig } from './config';
|
|
2
|
+
import { type InstanceInfo } from '../admin/instance/model';
|
|
3
3
|
import { type AdminTargetCommandOpts, type ResolvedAdminTarget } from './admin-target';
|
|
4
4
|
import { type InstanceStore } from './instance';
|
|
5
5
|
export type InstanceTargetRequest = {
|
package/dist/types/lib/log.d.ts
CHANGED
|
@@ -11,8 +11,8 @@ export declare const log: {
|
|
|
11
11
|
/** Report an error with hint (when present) and exit. `--json` / `--ci` / a
|
|
12
12
|
* non-TTY stdout always get one structured JSON line on stderr. */
|
|
13
13
|
export declare function fatal(e: unknown, opts?: MachineOpts): never;
|
|
14
|
-
/**
|
|
15
|
-
export declare function
|
|
14
|
+
/** Project only a caller-authored admission failure into the CLI input family. */
|
|
15
|
+
export declare function failInput(error: unknown, opts?: MachineOpts): never;
|
|
16
16
|
/** Shortcut for stub commands that aren't wired in v1 (§15). */
|
|
17
17
|
export declare function fatalNotImplemented(feature: string, hint?: string): never;
|
|
18
18
|
/**
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@astrale-os/cli",
|
|
3
|
-
"version": "1.0.0-beta.
|
|
3
|
+
"version": "1.0.0-beta.6",
|
|
4
4
|
"description": "Astrale CLI — connect to existing Astrale kernels",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"astrale",
|
|
@@ -62,14 +62,9 @@
|
|
|
62
62
|
"zod": "^3.25.0"
|
|
63
63
|
},
|
|
64
64
|
"devDependencies": {
|
|
65
|
-
"@astrale-os/kernel-client": ">=0.6.0-beta.8 <1.0.0",
|
|
66
|
-
"@astrale-os/kernel-core": ">=0.9.0-beta.7 <1.0.0",
|
|
67
|
-
"@astrale-os/kernel-dsl": ">=0.2.0-beta.5 <1.0.0",
|
|
68
|
-
"@astrale-os/kernel-protocol": ">=0.5.0-beta.7 <1.0.0",
|
|
69
|
-
"@astrale-os/kernel-server": ">=0.5.0-beta.8 <1.0.0",
|
|
70
65
|
"@astrale-os/ox": ">=0.1.3 <1.0.0",
|
|
71
|
-
"@astrale-os/sdk": ">=0.5.0-beta.
|
|
72
|
-
"@astrale-os/shell": ">=0.
|
|
66
|
+
"@astrale-os/sdk": ">=0.5.0-beta.17 <1.0.0",
|
|
67
|
+
"@astrale-os/shell": ">=0.4.0 <1.0.0",
|
|
73
68
|
"@astrale/commitlint-config": "npm:@jsr/astrale__commitlint-config@~2.0.1",
|
|
74
69
|
"@commitlint/cli": "~20.3.1",
|
|
75
70
|
"@commitlint/config-conventional": "~20.3.1",
|
|
@@ -111,7 +106,7 @@
|
|
|
111
106
|
"lint:fix": "oxlint --fix .",
|
|
112
107
|
"format": "pnpm exec oxfmt --write .",
|
|
113
108
|
"format:check": "pnpm exec oxfmt --check .",
|
|
114
|
-
"test": "bun test src studio/client/src studio/server studio/shared && node --test scripts/*.test.mjs",
|
|
109
|
+
"test": "node scripts/qualification/source-boundary.mjs --target && bun test src studio/client/src studio/server studio/shared && node --test scripts/*.test.mjs",
|
|
115
110
|
"test:studio": "pnpm --dir studio test",
|
|
116
111
|
"test:watch": "bun test --watch src"
|
|
117
112
|
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { schema } from '@astrale-os/sdk/schema'
|
|
2
|
+
|
|
3
|
+
import { defineApplication } from '@astrale-os/sdk/application'
|
|
4
|
+
import { compile } from '@astrale-os/sdk/deployment/build'
|
|
5
|
+
import { addressing, assemble } from '@astrale-os/sdk/deployment/release'
|
|
6
|
+
import { defineRuntime } from '@astrale-os/sdk/runtime'
|
|
7
|
+
|
|
8
|
+
/** Build a canonical empty-runtime Release for cross-boundary CLI tests. */
|
|
9
|
+
export function releaseFor<const Schema extends schema.DomainSchema>(
|
|
10
|
+
source: Schema,
|
|
11
|
+
issuer: string,
|
|
12
|
+
) {
|
|
13
|
+
const runtime = defineRuntime<Schema>()({
|
|
14
|
+
integrations: {},
|
|
15
|
+
initialize: () => ({ providers: {} }),
|
|
16
|
+
actions: [],
|
|
17
|
+
workflows: [],
|
|
18
|
+
})
|
|
19
|
+
return assemble(compile(defineApplication({ schema: source, runtime })), addressing(issuer))
|
|
20
|
+
}
|
|
@@ -1,38 +1,31 @@
|
|
|
1
|
-
import type { ClientSession } from '@astrale-os/
|
|
1
|
+
import type { ClientSession } from '@astrale-os/sdk/client/session'
|
|
2
2
|
|
|
3
|
-
import {
|
|
4
|
-
import { issuer } from '@astrale-os/sdk/auth'
|
|
5
|
-
import { createDeployment } from '@astrale-os/sdk/deployment'
|
|
6
|
-
import { defineSchema } from '@astrale-os/sdk/schema/v1'
|
|
3
|
+
import { bundle, defineSchema, schema as schemaApi } from '@astrale-os/sdk/schema'
|
|
7
4
|
import { expect, mock, test } from 'bun:test'
|
|
8
5
|
|
|
6
|
+
import { releaseFor } from '../../__tests__/fixtures/publication'
|
|
9
7
|
import { bindAdmin } from '../binding'
|
|
10
8
|
|
|
11
9
|
const schema = defineSchema('admin.astrale.ai', {})
|
|
12
|
-
const
|
|
13
|
-
definition: defineDomain({
|
|
14
|
-
schema,
|
|
15
|
-
handlers: { functions: {}, classes: {}, interfaces: {} },
|
|
16
|
-
}),
|
|
17
|
-
issuer: issuer.accept('https://admin.beta.astrale.ai'),
|
|
18
|
-
bundleHref: 'https://admin.beta.astrale.ai/domain.bundle.json',
|
|
19
|
-
bindings: { callables: [] },
|
|
20
|
-
})
|
|
10
|
+
const release = releaseFor(schema, 'https://admin.beta.astrale.ai')
|
|
21
11
|
|
|
22
12
|
test('binds the installed Admin Domain instead of the source Kernel publication', async () => {
|
|
23
13
|
const installation = mock(async () => ({
|
|
24
14
|
state: 'ready' as const,
|
|
25
15
|
target: 'sha256:admin-target' as const,
|
|
26
|
-
source: { kind: 'remote' as const, publication:
|
|
27
|
-
bundle:
|
|
16
|
+
source: { kind: 'remote' as const, publication: release.publication },
|
|
17
|
+
bundle: bundle.create(schema),
|
|
28
18
|
readiness: 'sha256:admin-readiness' as const,
|
|
29
19
|
capabilities: { requested: [], materialized: [] },
|
|
30
20
|
}))
|
|
31
|
-
const session = {
|
|
21
|
+
const session = {
|
|
22
|
+
installation,
|
|
23
|
+
bind: (domain: unknown) => ({ domain, graph: {} }),
|
|
24
|
+
} as unknown as ClientSession
|
|
32
25
|
|
|
33
26
|
const binding = await bindAdmin(session)
|
|
34
27
|
|
|
35
28
|
expect(installation).toHaveBeenCalledWith('admin.astrale.ai')
|
|
36
|
-
expect(binding
|
|
37
|
-
expect(
|
|
29
|
+
expect(binding.domain.origin).toBe('admin.astrale.ai')
|
|
30
|
+
expect(binding.domain.revision).toBe(schemaApi.revision(schema))
|
|
38
31
|
})
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import type { ClientSession } from '@astrale-os/sdk/client/session'
|
|
2
|
+
import type { CallableReference } from '@astrale-os/sdk/client/session'
|
|
3
|
+
|
|
4
|
+
import {
|
|
5
|
+
callableAddress,
|
|
6
|
+
core,
|
|
7
|
+
defineSchema,
|
|
8
|
+
edgeClass,
|
|
9
|
+
method,
|
|
10
|
+
nodeClass,
|
|
11
|
+
property,
|
|
12
|
+
schema,
|
|
13
|
+
} from '@astrale-os/sdk/schema'
|
|
14
|
+
import { mock } from 'bun:test'
|
|
15
|
+
|
|
16
|
+
import type { AdminBinding } from '../binding'
|
|
17
|
+
|
|
18
|
+
const operation = () =>
|
|
19
|
+
method({
|
|
20
|
+
auth: 'authenticated',
|
|
21
|
+
input: { type: 'object', additionalProperties: true },
|
|
22
|
+
output: { type: 'object', additionalProperties: true },
|
|
23
|
+
})
|
|
24
|
+
|
|
25
|
+
const Domain = nodeClass({
|
|
26
|
+
properties: {
|
|
27
|
+
origin: property({ type: 'string' }, { required: true }),
|
|
28
|
+
name: property({ type: 'string' }, { required: true }),
|
|
29
|
+
discoveryUrl: property({ type: 'string' }, { required: true }),
|
|
30
|
+
description: property({ type: 'string' }),
|
|
31
|
+
createdAt: property({ type: 'string' }, { required: true }),
|
|
32
|
+
updatedAt: property({ type: 'string' }, { required: true }),
|
|
33
|
+
},
|
|
34
|
+
methods: { configureDefault: operation() },
|
|
35
|
+
})
|
|
36
|
+
const Fleet = nodeClass({
|
|
37
|
+
methods: {
|
|
38
|
+
publishDomain: operation(),
|
|
39
|
+
listInstances: operation(),
|
|
40
|
+
createInstance: operation(),
|
|
41
|
+
},
|
|
42
|
+
})
|
|
43
|
+
const Host = nodeClass({
|
|
44
|
+
properties: {
|
|
45
|
+
id: property({ type: 'string' }, { required: true }),
|
|
46
|
+
state: property({ type: 'string' }, { required: true }),
|
|
47
|
+
},
|
|
48
|
+
methods: { createInstance: operation() },
|
|
49
|
+
})
|
|
50
|
+
const Instance = nodeClass({
|
|
51
|
+
methods: {
|
|
52
|
+
status: operation(),
|
|
53
|
+
delete: operation(),
|
|
54
|
+
installDomain: operation(),
|
|
55
|
+
},
|
|
56
|
+
})
|
|
57
|
+
const fleet_installs_domain_by_default = edgeClass.directed({
|
|
58
|
+
source: { as: 'fleet', accepts: [Fleet], outgoing: '0..*' },
|
|
59
|
+
target: { as: 'domain', accepts: [Domain], incoming: '0..*' },
|
|
60
|
+
})
|
|
61
|
+
const fleet_reserves_admin_host = edgeClass.directed({
|
|
62
|
+
source: { as: 'fleet', accepts: [Fleet], outgoing: '0..1' },
|
|
63
|
+
target: { as: 'host', accepts: [Host], incoming: '0..1' },
|
|
64
|
+
})
|
|
65
|
+
|
|
66
|
+
export const AdminTestSchema = defineSchema('admin.astrale.ai', {
|
|
67
|
+
classes: {
|
|
68
|
+
Domain,
|
|
69
|
+
Fleet,
|
|
70
|
+
Host,
|
|
71
|
+
Instance,
|
|
72
|
+
fleet_installs_domain_by_default,
|
|
73
|
+
fleet_reserves_admin_host,
|
|
74
|
+
},
|
|
75
|
+
core: { nodes: { fleet: core.node(() => Fleet, {}) }, edges: [] },
|
|
76
|
+
})
|
|
77
|
+
|
|
78
|
+
export const AdminTestDomain = schema.resolve(AdminTestSchema)
|
|
79
|
+
|
|
80
|
+
export function adminBinding(): AdminBinding {
|
|
81
|
+
return { domain: AdminTestDomain, graph: {} } as unknown as AdminBinding
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
export function adminSession(
|
|
85
|
+
implementation?: (
|
|
86
|
+
method: { readonly owner: string; readonly name: string },
|
|
87
|
+
receiver: unknown,
|
|
88
|
+
input: unknown,
|
|
89
|
+
) => unknown,
|
|
90
|
+
) {
|
|
91
|
+
const invoke = mock(async (reference: CallableReference, input: unknown) => {
|
|
92
|
+
const address = callableAddress(reference.callable)
|
|
93
|
+
if (address.kind !== 'method') throw new Error('Expected an Admin Method.')
|
|
94
|
+
return implementation?.(
|
|
95
|
+
{ owner: address.owner.name, name: address.name },
|
|
96
|
+
reference.target,
|
|
97
|
+
input,
|
|
98
|
+
)
|
|
99
|
+
})
|
|
100
|
+
return { invoke, session: { invoke } as unknown as ClientSession }
|
|
101
|
+
}
|
package/src/admin/binding.ts
CHANGED
|
@@ -1,6 +1,15 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { Input } from '@astrale-os/sdk/client'
|
|
2
|
+
import type { ClientSession } from '@astrale-os/sdk/client/session'
|
|
3
|
+
import type {
|
|
4
|
+
ResolvedClass,
|
|
5
|
+
ResolvedCoreDefinition,
|
|
6
|
+
ResolvedMethod,
|
|
7
|
+
ResolvedProperty,
|
|
8
|
+
} from '@astrale-os/sdk/schema'
|
|
2
9
|
import type { DomainBinding } from '@astrale-os/shell'
|
|
3
10
|
|
|
11
|
+
import { reference } from '@astrale-os/sdk/client/session'
|
|
12
|
+
import { Path } from '@astrale-os/sdk/graph/path'
|
|
4
13
|
import { bindDomain } from '@astrale-os/shell'
|
|
5
14
|
|
|
6
15
|
const ADMIN_ORIGIN = 'admin.astrale.ai'
|
|
@@ -10,9 +19,80 @@ export type AdminBinding = DomainBinding
|
|
|
10
19
|
/** Bind the exact Admin revision installed on this source Kernel. */
|
|
11
20
|
export async function bindAdmin(session: ClientSession): Promise<AdminBinding> {
|
|
12
21
|
const installed = await session.installation(ADMIN_ORIGIN)
|
|
13
|
-
|
|
14
|
-
|
|
22
|
+
return requireAdminBinding(await bindDomain(session, installed.bundle.root))
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/** Admit an injected dynamic binding as the Admin Domain. */
|
|
26
|
+
export function requireAdminBinding(binding: AdminBinding): AdminBinding {
|
|
27
|
+
if (binding.domain.origin !== ADMIN_ORIGIN) {
|
|
15
28
|
throw new TypeError('Configured Admin target does not serve the Admin Domain.')
|
|
16
29
|
}
|
|
17
30
|
return binding
|
|
18
31
|
}
|
|
32
|
+
|
|
33
|
+
/** Resolve one required Admin Class without rebuilding its schema contract. */
|
|
34
|
+
export function requireAdminClass(
|
|
35
|
+
binding: AdminBinding,
|
|
36
|
+
name: string,
|
|
37
|
+
kind: 'node',
|
|
38
|
+
): ResolvedClass<'node'>
|
|
39
|
+
export function requireAdminClass(
|
|
40
|
+
binding: AdminBinding,
|
|
41
|
+
name: string,
|
|
42
|
+
kind: 'edge',
|
|
43
|
+
): ResolvedClass<'edge'>
|
|
44
|
+
export function requireAdminClass(
|
|
45
|
+
binding: AdminBinding,
|
|
46
|
+
name: string,
|
|
47
|
+
kind: 'node' | 'edge',
|
|
48
|
+
): ResolvedClass<'node'> | ResolvedClass<'edge'> {
|
|
49
|
+
const selected = binding.domain.classes[name]
|
|
50
|
+
if (selected?.kind !== kind) throw new TypeError(`Admin ${name} is not a ${kind} Class.`)
|
|
51
|
+
return selected
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/** Resolve one required Admin Core Node as its canonical projection Path. */
|
|
55
|
+
export function requireAdminCore(binding: AdminBinding, name: string): Path {
|
|
56
|
+
const selected = binding.domain.core.nodes[name] as ResolvedCoreDefinition | undefined
|
|
57
|
+
if (selected === undefined) throw new TypeError(`Admin has no Core Node ${name}.`)
|
|
58
|
+
return Path.project(selected.ref)
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/** Resolve one effective Property by unambiguous member name. */
|
|
62
|
+
export function requireAdminProperty(
|
|
63
|
+
owner: ResolvedClass,
|
|
64
|
+
name: string,
|
|
65
|
+
): ResolvedProperty<unknown> {
|
|
66
|
+
const selected = [...owner.properties].find((property) => property.name === name)
|
|
67
|
+
if (selected === undefined) {
|
|
68
|
+
throw new TypeError(`Admin ${owner.ref.name}.${name} Property is absent.`)
|
|
69
|
+
}
|
|
70
|
+
return selected
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/** Invoke one resolved executable instance Method through Kernel Client. */
|
|
74
|
+
export async function invokeAdminMethod(
|
|
75
|
+
session: ClientSession,
|
|
76
|
+
binding: AdminBinding,
|
|
77
|
+
owner: ResolvedClass<'node'>,
|
|
78
|
+
name: string,
|
|
79
|
+
receiver: Path,
|
|
80
|
+
input: Input,
|
|
81
|
+
): Promise<unknown> {
|
|
82
|
+
const method = [...owner.methods].find((candidate) => candidate.name === name)
|
|
83
|
+
if (method === undefined || !isExecutableInstanceMethod(method)) {
|
|
84
|
+
throw new TypeError(`Admin ${owner.ref.name}.${name} is not an executable instance Method.`)
|
|
85
|
+
}
|
|
86
|
+
return session.invoke(reference(binding.domain, method)(receiver), input)
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
type ExecutableInstanceMethod = ResolvedMethod<
|
|
90
|
+
unknown,
|
|
91
|
+
unknown,
|
|
92
|
+
false,
|
|
93
|
+
Exclude<ResolvedMethod['inheritance'], 'abstract'>
|
|
94
|
+
>
|
|
95
|
+
|
|
96
|
+
function isExecutableInstanceMethod(method: ResolvedMethod): method is ExecutableInstanceMethod {
|
|
97
|
+
return method.static === false && method.inheritance !== 'abstract'
|
|
98
|
+
}
|
|
@@ -1,41 +1,25 @@
|
|
|
1
1
|
import type { Node } from '@astrale-os/sdk/graph/node'
|
|
2
2
|
import type { QueryAST } from '@astrale-os/sdk/query'
|
|
3
3
|
|
|
4
|
-
import { ClassPath } from '@astrale-os/sdk/graph/class'
|
|
5
4
|
import { NodeId } from '@astrale-os/sdk/graph/node'
|
|
6
|
-
import { Path } from '@astrale-os/sdk/graph/path'
|
|
7
5
|
import { describe, expect, mock, test } from 'bun:test'
|
|
8
6
|
|
|
9
|
-
import type { AdminBinding } from '../../binding'
|
|
10
7
|
import type { AdminGraphApi } from '../../graph'
|
|
11
8
|
|
|
9
|
+
import { AdminTestDomain, adminBinding, adminSession } from '../../__tests__/fixture'
|
|
12
10
|
import { connectAdminCatalog } from '../client'
|
|
13
11
|
|
|
14
|
-
function definition(name: string) {
|
|
15
|
-
return {
|
|
16
|
-
name,
|
|
17
|
-
$: {
|
|
18
|
-
method(method: string) {
|
|
19
|
-
return { owner: name, name: method }
|
|
20
|
-
},
|
|
21
|
-
property(property: string) {
|
|
22
|
-
return { key: property }
|
|
23
|
-
},
|
|
24
|
-
},
|
|
25
|
-
}
|
|
26
|
-
}
|
|
27
|
-
|
|
28
12
|
function domainNode(id: string, origin: string): Node {
|
|
29
13
|
return {
|
|
30
14
|
id: NodeId(id),
|
|
31
|
-
class:
|
|
32
|
-
props: {
|
|
15
|
+
class: AdminTestDomain.classes.Domain.key,
|
|
16
|
+
props: AdminTestDomain.classes.Domain.properties.from({
|
|
33
17
|
origin,
|
|
34
18
|
name: origin.split('.')[0]!,
|
|
35
19
|
discoveryUrl: `https://${origin}`,
|
|
36
20
|
createdAt: '2026-08-12T00:00:00.000Z',
|
|
37
21
|
updatedAt: '2026-08-12T00:00:00.000Z',
|
|
38
|
-
}
|
|
22
|
+
}),
|
|
39
23
|
}
|
|
40
24
|
}
|
|
41
25
|
|
|
@@ -44,24 +28,16 @@ function fixture(input: {
|
|
|
44
28
|
defaults?: readonly Node[]
|
|
45
29
|
invoke?: (method: { owner: string; name: string }, receiver: unknown, value: unknown) => unknown
|
|
46
30
|
}) {
|
|
47
|
-
const
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
const
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
if (name === 'Domain') return Domain
|
|
58
|
-
if (name === 'Fleet') return Fleet
|
|
59
|
-
throw new Error(`Unexpected class ${name}`)
|
|
60
|
-
},
|
|
61
|
-
core: { nodes: { fleet: { path: Path.id(NodeId('fleet')) } } },
|
|
62
|
-
invoke,
|
|
63
|
-
},
|
|
64
|
-
} as unknown as AdminBinding
|
|
31
|
+
const calls: Array<{
|
|
32
|
+
method: { owner: string; name: string }
|
|
33
|
+
receiver: string
|
|
34
|
+
value: unknown
|
|
35
|
+
}> = []
|
|
36
|
+
const remote = adminSession((method, receiver, value) => {
|
|
37
|
+
calls.push({ method, receiver: String(receiver), value })
|
|
38
|
+
return input.invoke?.(method, receiver, value)
|
|
39
|
+
})
|
|
40
|
+
const binding = adminBinding()
|
|
65
41
|
const query = mock(async (_ast: QueryAST) => ({
|
|
66
42
|
result: {
|
|
67
43
|
kind: 'nodes' as const,
|
|
@@ -79,10 +55,11 @@ function fixture(input: {
|
|
|
79
55
|
}))
|
|
80
56
|
const graph = { query, neighbors } as unknown as AdminGraphApi
|
|
81
57
|
return {
|
|
82
|
-
invoke,
|
|
58
|
+
invoke: remote.invoke,
|
|
59
|
+
calls,
|
|
83
60
|
connect: () =>
|
|
84
61
|
connectAdminCatalog(
|
|
85
|
-
{ session:
|
|
62
|
+
{ session: remote.session, graph },
|
|
86
63
|
{
|
|
87
64
|
bind: async () => binding,
|
|
88
65
|
operationId: (kind) => `cli.domain.${kind}:test`,
|
|
@@ -130,16 +107,10 @@ describe('V2 Admin Domain catalog adapter', () => {
|
|
|
130
107
|
installByDefault: true,
|
|
131
108
|
}),
|
|
132
109
|
).resolves.toMatchObject({ changed: true, isNew: true, entry: { installByDefault: true } })
|
|
133
|
-
expect(
|
|
134
|
-
contract.invoke.mock.calls.map(([method, receiver, value]) => ({
|
|
135
|
-
method,
|
|
136
|
-
receiver: String(receiver),
|
|
137
|
-
value,
|
|
138
|
-
})),
|
|
139
|
-
).toEqual([
|
|
110
|
+
expect(contract.calls).toEqual([
|
|
140
111
|
{
|
|
141
112
|
method: { owner: 'Fleet', name: 'publishDomain' },
|
|
142
|
-
receiver: '
|
|
113
|
+
receiver: '/:admin.astrale.ai:core.fleet::publishDomain',
|
|
143
114
|
value: {
|
|
144
115
|
operationId: 'cli.domain.publish:test',
|
|
145
116
|
origin: 'crm.acme.dev',
|
|
@@ -149,7 +120,7 @@ describe('V2 Admin Domain catalog adapter', () => {
|
|
|
149
120
|
},
|
|
150
121
|
{
|
|
151
122
|
method: { owner: 'Domain', name: 'configureDefault' },
|
|
152
|
-
receiver: '@crm-domain',
|
|
123
|
+
receiver: '@crm-domain::configureDefault',
|
|
153
124
|
value: {
|
|
154
125
|
operationId: 'cli.domain.configure-default:test',
|
|
155
126
|
enabled: true,
|
|
@@ -1,10 +1,19 @@
|
|
|
1
|
-
import type { ClientSession } from '@astrale-os/
|
|
1
|
+
import type { ClientSession } from '@astrale-os/sdk/client/session'
|
|
2
2
|
import type { Node } from '@astrale-os/sdk/graph/node'
|
|
3
|
+
import type { ResolvedClass } from '@astrale-os/sdk/schema'
|
|
3
4
|
|
|
4
5
|
import { Path } from '@astrale-os/sdk/graph/path'
|
|
5
6
|
import { Query } from '@astrale-os/sdk/query'
|
|
6
7
|
|
|
7
|
-
import {
|
|
8
|
+
import {
|
|
9
|
+
bindAdmin,
|
|
10
|
+
invokeAdminMethod,
|
|
11
|
+
requireAdminBinding,
|
|
12
|
+
requireAdminClass,
|
|
13
|
+
requireAdminCore,
|
|
14
|
+
requireAdminProperty,
|
|
15
|
+
type AdminBinding,
|
|
16
|
+
} from '../binding'
|
|
8
17
|
import { readAllNodes, type AdminGraphApi } from '../graph'
|
|
9
18
|
import {
|
|
10
19
|
AdminDomainNotFoundError,
|
|
@@ -13,16 +22,9 @@ import {
|
|
|
13
22
|
type PublishDomainResult,
|
|
14
23
|
} from './model'
|
|
15
24
|
|
|
16
|
-
const ADMIN_ORIGIN = 'admin.astrale.ai'
|
|
17
25
|
const PAGE_SIZE = 256
|
|
18
26
|
const MAXIMUM_DOMAINS = 10_000
|
|
19
27
|
const MAXIMUM_PAGES = Math.ceil(MAXIMUM_DOMAINS / PAGE_SIZE) + 1
|
|
20
|
-
const DomainRef = Object.freeze({ origin: ADMIN_ORIGIN, kind: 'class' as const, name: 'Domain' })
|
|
21
|
-
const fleetInstallsByDefault = Object.freeze({
|
|
22
|
-
origin: ADMIN_ORIGIN,
|
|
23
|
-
kind: 'class' as const,
|
|
24
|
-
name: 'fleet_installs_domain_by_default',
|
|
25
|
-
})
|
|
26
28
|
|
|
27
29
|
export interface AdminCatalogContext {
|
|
28
30
|
readonly session: ClientSession
|
|
@@ -45,21 +47,22 @@ export async function connectAdminCatalog(
|
|
|
45
47
|
context: AdminCatalogContext,
|
|
46
48
|
dependencies: AdminCatalogDependencies = {},
|
|
47
49
|
): Promise<AdminCatalogApi> {
|
|
48
|
-
const binding = await (dependencies.bind ?? bindAdmin)(context.session)
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
50
|
+
const binding = requireAdminBinding(await (dependencies.bind ?? bindAdmin)(context.session))
|
|
51
|
+
const Domain = requireAdminClass(binding, 'Domain', 'node')
|
|
52
|
+
const Fleet = requireAdminClass(binding, 'Fleet', 'node')
|
|
53
|
+
const fleetInstallsByDefault = requireAdminClass(
|
|
54
|
+
binding,
|
|
55
|
+
'fleet_installs_domain_by_default',
|
|
56
|
+
'edge',
|
|
57
|
+
)
|
|
58
|
+
const fleet = requireAdminCore(binding, 'fleet')
|
|
56
59
|
const operationId = dependencies.operationId ?? defaultOperationId
|
|
57
60
|
|
|
58
61
|
const list = async (): Promise<DomainInfo[]> => {
|
|
59
62
|
const [nodes, defaultsPage] = await Promise.all([
|
|
60
63
|
readAllNodes(
|
|
61
64
|
context.graph,
|
|
62
|
-
Query.from({
|
|
65
|
+
Query.from({ nodes: [Domain] }).select({
|
|
63
66
|
kind: 'nodes',
|
|
64
67
|
projection: { kind: 'value' },
|
|
65
68
|
}),
|
|
@@ -107,13 +110,13 @@ export async function connectAdminCatalog(
|
|
|
107
110
|
let entry = existing
|
|
108
111
|
if (registryChanged) {
|
|
109
112
|
entry = domainFromSummary(
|
|
110
|
-
await binding
|
|
113
|
+
await invokeAdminMethod(context.session, binding, Fleet, 'publishDomain', fleet, {
|
|
111
114
|
operationId: operationId('publish'),
|
|
112
115
|
origin: input.origin,
|
|
113
116
|
name: input.name,
|
|
114
117
|
discoveryUrl: input.url,
|
|
115
118
|
...(description === undefined ? {} : { description }),
|
|
116
|
-
}
|
|
119
|
+
}),
|
|
117
120
|
existing?.installByDefault === true,
|
|
118
121
|
)
|
|
119
122
|
}
|
|
@@ -124,13 +127,16 @@ export async function connectAdminCatalog(
|
|
|
124
127
|
(entry.installByDefault ?? false) !== input.installByDefault
|
|
125
128
|
if (defaultChanged) {
|
|
126
129
|
entry = domainFromSummary(
|
|
127
|
-
await
|
|
128
|
-
|
|
130
|
+
await invokeAdminMethod(
|
|
131
|
+
context.session,
|
|
132
|
+
binding,
|
|
133
|
+
Domain,
|
|
134
|
+
'configureDefault',
|
|
129
135
|
Path.parse(entry.id),
|
|
130
136
|
{
|
|
131
137
|
operationId: operationId('configure-default'),
|
|
132
138
|
enabled: input.installByDefault,
|
|
133
|
-
}
|
|
139
|
+
},
|
|
134
140
|
),
|
|
135
141
|
input.installByDefault === true,
|
|
136
142
|
)
|
|
@@ -144,7 +150,7 @@ export async function connectAdminCatalog(
|
|
|
144
150
|
})
|
|
145
151
|
}
|
|
146
152
|
|
|
147
|
-
type DynamicDefinition =
|
|
153
|
+
type DynamicDefinition = ResolvedClass<'node'>
|
|
148
154
|
|
|
149
155
|
function domainFromNode(
|
|
150
156
|
definition: DynamicDefinition,
|
|
@@ -180,7 +186,10 @@ function domainFromSummary(input: unknown, installByDefault: boolean): DomainInf
|
|
|
180
186
|
}
|
|
181
187
|
|
|
182
188
|
function requiredProperty(definition: DynamicDefinition, node: Node, name: string): string {
|
|
183
|
-
return requiredString(
|
|
189
|
+
return requiredString(
|
|
190
|
+
node.props[requireAdminProperty(definition, name).key],
|
|
191
|
+
`Admin Domain.${name}`,
|
|
192
|
+
)
|
|
184
193
|
}
|
|
185
194
|
|
|
186
195
|
function optionalProperty(
|
|
@@ -188,7 +197,7 @@ function optionalProperty(
|
|
|
188
197
|
node: Node,
|
|
189
198
|
name: string,
|
|
190
199
|
): Readonly<Record<string, string>> {
|
|
191
|
-
const value = node.props[definition
|
|
200
|
+
const value = node.props[requireAdminProperty(definition, name).key]
|
|
192
201
|
return value === undefined ? {} : { [name]: requiredString(value, `Admin Domain.${name}`) }
|
|
193
202
|
}
|
|
194
203
|
|
|
@@ -33,10 +33,9 @@ export interface InstallDomainResult {
|
|
|
33
33
|
readonly error?: string | null
|
|
34
34
|
}
|
|
35
35
|
|
|
36
|
-
export class AdminDomainNotFoundError extends
|
|
37
|
-
readonly name = 'NotFoundError'
|
|
38
|
-
|
|
36
|
+
export class AdminDomainNotFoundError extends AstraleError {
|
|
39
37
|
constructor(readonly identifier: string) {
|
|
40
|
-
super(`No visible Admin Domain matches ${JSON.stringify(identifier)}.`)
|
|
38
|
+
super('DOMAIN_NOT_FOUND', `No visible Admin Domain matches ${JSON.stringify(identifier)}.`)
|
|
41
39
|
}
|
|
42
40
|
}
|
|
41
|
+
import { AstraleError } from '../../errors'
|
|
@@ -1,18 +1,12 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
import type { ClassPath } from '@astrale-os/sdk/graph/class'
|
|
1
|
+
import type { GraphApi } from '@astrale-os/sdk/client'
|
|
3
2
|
import type { Node } from '@astrale-os/sdk/graph/node'
|
|
4
|
-
import type {
|
|
5
|
-
import type { QueryAST, QueryResult } from '@astrale-os/sdk/query'
|
|
3
|
+
import type { QueryAST } from '@astrale-os/sdk/query'
|
|
6
4
|
|
|
7
5
|
/** Smallest Kernel graph capability needed by bounded Admin graph reads. */
|
|
8
|
-
export
|
|
9
|
-
query(ast: QueryAST, options?: GraphQueryOptions): Promise<QueryResult>
|
|
10
|
-
}
|
|
6
|
+
export type AdminGraphQueryApi = Pick<GraphApi, 'query'>
|
|
11
7
|
|
|
12
8
|
/** Caller-scoped graph reads shared by the Admin catalog and Instance journeys. */
|
|
13
|
-
export
|
|
14
|
-
neighbors(source: PathLike, edge: ClassPath, options: NeighborsOptions): Promise<NodePage>
|
|
15
|
-
}
|
|
9
|
+
export type AdminGraphApi = Pick<GraphApi, 'query' | 'neighbors'>
|
|
16
10
|
|
|
17
11
|
export interface ReadAllNodesOptions {
|
|
18
12
|
readonly label: string
|