@astrale-os/cli 1.0.0-beta.1 → 1.0.0-beta.11
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/README.md +23 -12
- package/dist/astrale.js +48048 -56798
- package/dist/public/connect-core.js +2011 -30765
- package/dist/public/keys/index.js +884 -29483
- package/dist/public/paths/index.js +13101 -41459
- package/dist/types/admin/binding.d.ts +19 -0
- package/dist/types/admin/instance/client.d.ts +22 -0
- package/dist/types/admin/instance/index.d.ts +2 -0
- package/dist/types/admin/instance/model.d.ts +5 -12
- package/dist/types/connection/auth.d.ts +3 -0
- package/dist/types/connection/call.d.ts +3 -0
- package/dist/types/connection/command.d.ts +27 -0
- package/dist/types/connection/credential.d.ts +14 -0
- package/dist/types/connection/errors.d.ts +1 -0
- package/dist/types/connection/exchange.d.ts +9 -0
- package/dist/types/connection/failure/classify.d.ts +2 -0
- package/dist/types/connection/failure/index.d.ts +5 -0
- package/dist/types/connection/failure/model.d.ts +25 -0
- package/dist/types/connection/failure/render.d.ts +3 -0
- package/dist/types/connection/index.d.ts +8 -0
- package/dist/types/connection/reasons.d.ts +37 -0
- package/dist/types/connection/self.d.ts +37 -0
- package/dist/types/connection/session.d.ts +31 -0
- package/dist/types/connection/target.d.ts +38 -0
- package/dist/types/errors.d.ts +1 -1
- package/dist/types/lib/admin-instance.d.ts +17 -0
- package/dist/types/lib/admin-target.d.ts +11 -36
- package/dist/types/lib/config.d.ts +7 -59
- package/dist/types/lib/failure-debug.d.ts +1 -0
- package/dist/types/lib/idp.d.ts +26 -168
- package/dist/types/lib/instance-target.d.ts +1 -1
- package/dist/types/lib/instance.d.ts +30 -88
- package/dist/types/lib/log.d.ts +5 -3
- package/dist/types/lib/meta.d.ts +2 -2
- package/dist/types/lib/proc.d.ts +43 -0
- package/dist/types/lib/self.d.ts +9 -0
- package/dist/types/lib/update.d.ts +91 -0
- package/dist/types/lib/validation.d.ts +4 -1
- package/package.json +16 -19
- package/src/__tests__/fixtures/publication.ts +20 -0
- package/src/admin/__tests__/binding.test.ts +31 -0
- package/src/admin/__tests__/fixture.ts +101 -0
- package/src/admin/binding.ts +98 -0
- package/src/admin/catalog/.spec/api.d.ts +2 -2
- package/src/admin/catalog/__tests__/client.test.ts +20 -49
- package/src/admin/catalog/client.ts +36 -32
- package/src/admin/catalog/model.ts +3 -4
- package/src/admin/graph/.spec/api.d.ts +4 -10
- package/src/admin/graph/nodes.ts +1 -1
- package/src/admin/instance/.spec/api.d.ts +2 -2
- package/src/admin/instance/__tests__/client.test.ts +39 -164
- package/src/admin/instance/client.ts +35 -124
- package/src/admin/instance/index.ts +1 -2
- package/src/admin/instance/model.ts +11 -20
- package/src/commands/__tests__/admin-instance.test.ts +11 -8
- package/src/commands/__tests__/auth-login.test.ts +54 -1
- package/src/commands/__tests__/call-describe.test.ts +46 -33
- package/src/commands/__tests__/call.test.ts +5 -1
- package/src/commands/__tests__/domain-install-operation.test.ts +12 -7
- package/src/commands/__tests__/domain-install-owned.test.ts +6 -2
- package/src/commands/__tests__/domain-list.test.ts +63 -4
- package/src/commands/__tests__/install-identity-override.test.ts +19 -19
- package/src/commands/__tests__/instance-list-rows.test.ts +1 -1
- package/src/commands/__tests__/instance-status.test.ts +139 -0
- package/src/commands/__tests__/read-commands.test.ts +35 -10
- package/src/commands/__tests__/studio-descriptor.test.ts +22 -0
- package/src/commands/__tests__/update.test.ts +48 -19
- package/src/commands/call-describe.ts +60 -68
- package/src/commands/call.ts +43 -56
- package/src/commands/domain/install.ts +13 -17
- package/src/commands/domain/list.ts +18 -13
- package/src/commands/domain/publish.ts +14 -11
- package/src/commands/domain/uninstall.ts +2 -2
- package/src/commands/get.ts +14 -15
- package/src/commands/identity/__tests__/register.test.ts +3 -3
- package/src/commands/identity/register.ts +5 -4
- package/src/commands/instance/create.ts +8 -16
- package/src/commands/instance/delete.ts +12 -9
- package/src/commands/instance/list.ts +2 -2
- package/src/commands/instance/status.ts +66 -20
- package/src/commands/instance/use.ts +16 -12
- package/src/commands/introspect.ts +9 -16
- package/src/commands/logs.ts +24 -25
- package/src/commands/mutate.ts +6 -5
- package/src/commands/query.ts +27 -30
- package/src/commands/studio.ts +20 -0
- package/src/commands/token.ts +8 -7
- package/src/commands/update.ts +38 -14
- package/src/commands/view.ts +7 -6
- 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 +20 -1
- package/src/connection/__tests__/errors.test.ts +43 -32
- package/src/connection/__tests__/exchange.test.ts +36 -6
- 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/__tests__/target.test.ts +1 -0
- package/src/connection/auth.ts +13 -0
- 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 +52 -27
- package/src/connection/failure/classify.ts +110 -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 +19 -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/keys/__tests__/keys.test.ts +4 -0
- package/src/keys/credential.ts +1 -0
- package/src/lib/__tests__/admin-target.test.ts +12 -0
- package/src/lib/__tests__/command-dx.test.ts +1 -1
- package/src/lib/__tests__/domain-publication.test.ts +138 -0
- package/src/lib/__tests__/idp-session.test.ts +22 -0
- package/src/lib/__tests__/idp.test.ts +36 -4
- package/src/lib/__tests__/instance-candidates.test.ts +1 -3
- package/src/lib/__tests__/instance-target.test.ts +7 -39
- package/src/lib/__tests__/instance.test.ts +6 -0
- package/src/lib/__tests__/log-errors.test.ts +64 -0
- package/src/lib/__tests__/studio-server-deps.test.ts +53 -16
- package/src/lib/__tests__/update.test.ts +113 -8
- package/src/lib/__tests__/view-open-intent.test.ts +0 -2
- package/src/lib/__tests__/view-server.test.ts +18 -3
- package/src/lib/__tests__/view-session.test.ts +1 -1
- package/src/lib/admin-instance.ts +14 -8
- package/src/lib/admin-target.ts +21 -2
- package/src/lib/command-dx.ts +9 -9
- package/src/lib/domain-publication.ts +104 -0
- package/src/lib/failure-debug.ts +26 -0
- package/src/lib/idp.ts +39 -4
- package/src/lib/instance-candidates.ts +2 -2
- package/src/lib/instance-target.ts +3 -14
- package/src/lib/instance.ts +22 -12
- package/src/lib/log.ts +16 -9
- package/src/lib/login-flow.ts +13 -3
- package/src/lib/meta.ts +2 -2
- package/src/lib/provision-instance.ts +34 -57
- package/src/lib/update.ts +124 -30
- package/src/lib/validation.ts +2 -2
- package/src/lib/view/resolve.ts +1 -1
- package/src/lib/view/server.ts +17 -3
- package/src/program/__tests__/program.test.ts +20 -4
- package/src/program/build.ts +2 -2
- package/src/setup/__tests__/instance-step.test.ts +27 -10
- package/src/setup/steps/instance.ts +22 -15
- package/studio/client/dist/assets/elk-api-DjmKsHXc.js +1 -0
- package/studio/client/dist/assets/index-7YHdeCxp.js +81 -0
- package/studio/client/dist/assets/index-C0FAnwNw.css +2 -0
- package/studio/client/dist/assets/rolldown-runtime-Dd_uD5pT.js +1 -0
- package/studio/client/dist/assets/schema-studio-BilUNb6Z.css +1 -0
- package/studio/client/dist/assets/schema-studio-CABpTfvH.js +8 -0
- package/studio/client/dist/index.html +3 -2
- package/studio/package.json +10 -14
- 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/bridge/stdio.ts +8 -1
- package/studio/server/agent/conversation.test.ts +10 -5
- package/studio/server/agent/conversation.ts +56 -8
- package/studio/server/agent/harness/claude/adapter.test.ts +15 -5
- package/studio/server/agent/harness/codex/loadout.ts +18 -21
- package/studio/server/agent/harness/gateway/config.ts +20 -18
- package/studio/server/agent/harness/selection.ts +9 -1
- 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 +6 -5
- package/studio/server/agent/prompts/turn.ts +13 -4
- package/studio/server/agent/routes.test.ts +1 -1
- package/studio/server/agent/run/completion.ts +2 -2
- package/studio/server/agent/run/coordinator.test.ts +1 -1
- package/studio/server/agent/run/preparation.ts +6 -5
- package/studio/server/agent/run/transcript.test.ts +35 -1
- package/studio/server/agent/run/transcript.ts +169 -3
- package/studio/server/agent/run/usage.test.ts +24 -0
- package/studio/server/agent/run/usage.ts +26 -1
- package/studio/server/api/canvas.ts +43 -0
- package/studio/server/api/comments.ts +85 -0
- package/studio/server/api/context.ts +61 -0
- package/studio/server/api/deployment.ts +39 -0
- package/studio/server/api/documents.ts +55 -0
- package/studio/server/api/domain.ts +59 -0
- package/studio/server/api/http.ts +45 -0
- package/studio/server/api/project.ts +57 -0
- package/studio/server/api/schema.ts +12 -0
- package/studio/server/api/views.ts +57 -0
- package/studio/server/api/workspace.ts +60 -0
- package/studio/server/api-agent-loadout.test.ts +1 -1
- package/studio/server/api.test.ts +147 -0
- package/studio/server/api.ts +20 -422
- package/studio/server/cache.test.ts +122 -0
- package/studio/server/cache.ts +162 -11
- package/studio/server/cli-consumers.test.ts +494 -0
- package/studio/server/cli.test.ts +88 -0
- package/studio/server/cli.ts +240 -0
- package/studio/server/client-package.ts +25 -1
- package/studio/server/domain.test.ts +50 -40
- package/studio/server/domain.ts +83 -52
- package/studio/server/environment/dotenv-preview.test.ts +39 -0
- package/studio/server/environment/dotenv-preview.ts +27 -0
- package/studio/server/environment/files.test.ts +53 -0
- package/studio/server/{state/env.ts → environment/files.ts} +7 -48
- package/studio/server/{state → handoff}/copy.ts +21 -6
- package/studio/server/handoff/service.test.ts +22 -0
- package/studio/server/{state/handoff.ts → handoff/service.ts} +21 -14
- package/studio/server/index.ts +1 -1
- package/studio/server/instances/active.ts +72 -0
- package/studio/server/instances/deploy-record.test.ts +31 -0
- package/studio/server/instances/deploy-record.ts +37 -0
- package/studio/server/instances/deploy.ts +56 -0
- package/studio/server/{state/instance.test.ts → instances/probe.test.ts} +25 -21
- package/studio/server/instances/probe.ts +67 -0
- package/studio/server/instances/status.test.ts +12 -0
- package/studio/server/instances/status.ts +49 -0
- package/studio/server/introspect/anatomy/client-tree.ts +57 -0
- package/studio/server/introspect/anatomy/env-fields.ts +66 -0
- package/studio/server/introspect/anatomy/schema-definition.ts +53 -0
- package/studio/server/introspect/anatomy/source.ts +181 -0
- package/studio/server/introspect/anatomy/views/routes.ts +114 -0
- package/studio/server/introspect/anatomy/views.ts +53 -0
- package/studio/server/introspect/anatomy-extras.test.ts +106 -35
- package/studio/server/introspect/anatomy-extras.ts +7 -726
- package/studio/server/introspect/anatomy.test.ts +22 -14
- package/studio/server/introspect/anatomy.ts +52 -28
- package/studio/server/introspect/bundle.ts +15 -9
- package/studio/server/introspect/canonical-schema.test.ts +93 -309
- package/studio/server/introspect/canonical-schema.ts +257 -425
- package/studio/server/introspect/config-preview.test.ts +32 -0
- package/studio/server/introspect/config-preview.ts +119 -0
- package/studio/server/introspect/core-extractor.ts +18 -127
- package/studio/server/introspect/core.ts +1 -2
- package/studio/server/introspect/diff.test.ts +18 -18
- package/studio/server/introspect/diff.ts +44 -90
- package/studio/server/introspect/extractor.ts +23 -116
- package/studio/server/introspect/hash.ts +5 -2
- package/studio/server/introspect/overlay-tsmorph.test.ts +119 -240
- package/studio/server/introspect/overlay-tsmorph.ts +7 -1305
- package/studio/server/introspect/overlay.test.ts +26 -54
- package/studio/server/introspect/overlay.ts +10 -37
- package/studio/server/introspect/revision.test.ts +54 -0
- package/studio/server/introspect/revision.ts +49 -0
- package/studio/server/introspect/runtime.test.ts +83 -143
- package/studio/server/introspect/runtime.ts +40 -11
- package/studio/server/introspect/schema-ir-json.test.ts +70 -0
- package/studio/server/introspect/schema-ir-json.ts +151 -0
- 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/annotations.ts +14 -0
- package/studio/server/introspect/source-overlay/handlers.ts +161 -0
- package/studio/server/introspect/source-overlay/kernel-calls.ts +46 -0
- package/studio/server/introspect/source-overlay/project.ts +248 -0
- package/studio/server/introspect/source-overlay/spans.ts +330 -0
- package/studio/server/json.ts +46 -0
- package/studio/server/lifecycle.ts +5 -1
- package/studio/server/sse.test.ts +26 -0
- package/studio/server/sse.ts +4 -1
- package/studio/server/state/baseline.test.ts +223 -7
- package/studio/server/state/baseline.ts +148 -34
- package/studio/server/state/comments.test.ts +66 -1
- package/studio/server/state/comments.ts +173 -10
- package/studio/server/state/context.ts +34 -2
- package/studio/server/state/documents.ts +42 -1
- package/studio/server/state/integrations.ts +27 -1
- package/studio/server/state/layout.test.ts +34 -0
- package/studio/server/state/layout.ts +44 -17
- package/studio/server/state/settings.ts +29 -22
- package/studio/server/state/store.test.ts +122 -0
- package/studio/server/state/store.ts +66 -17
- package/studio/server/state/visibility.ts +22 -11
- package/studio/server/views/model.test.ts +57 -0
- package/studio/server/views/model.ts +66 -0
- package/studio/server/views/runtime.ts +40 -0
- package/studio/server/views/selection-repository.test.ts +31 -0
- package/studio/server/views/selection-repository.ts +60 -0
- package/studio/server/views/session.test.ts +73 -0
- package/studio/server/views/session.ts +166 -0
- package/studio/server/views/target.test.ts +78 -0
- package/studio/server/views/target.ts +222 -0
- package/studio/server/watch.test.ts +11 -11
- package/studio/server/watch.ts +9 -5
- package/studio/server/workspace/catalog.test.ts +17 -0
- package/studio/server/workspace/catalog.ts +43 -0
- package/studio/server/{state → workspace}/create.test.ts +1 -1
- package/studio/server/{state → workspace}/create.ts +1 -1
- package/studio/server/{state → workspace}/git.ts +1 -1
- package/studio/server/workspace/updates.ts +83 -0
- package/studio/server/workspace-state.ts +1 -1
- package/studio/server/workspace-watch.ts +2 -2
- package/studio/shared/contracts/README.md +16 -0
- package/studio/shared/contracts/agent.ts +270 -0
- package/studio/shared/contracts/runtime.ts +50 -0
- package/studio/shared/contracts/schema.ts +393 -0
- package/studio/shared/contracts/surface.test.ts +317 -0
- package/studio/shared/contracts/workspace.ts +265 -0
- package/studio/shared/layout.test.ts +116 -0
- package/studio/shared/schema/identity.test.ts +34 -0
- package/studio/shared/schema/identity.ts +68 -0
- package/studio/shared/types.ts +11 -1099
- package/studio/tsconfig.json +1 -1
- package/viewer/dist/main.js +40 -61
- package/.check-workspace.cjs +0 -40
- package/src/commands/__tests__/instance-create-hosts.test.ts +0 -29
- package/studio/client/dist/assets/elk-api-D2xgMJvi.js +0 -1
- package/studio/client/dist/assets/index-BMdnsIJA.css +0 -1
- package/studio/client/dist/assets/index-ChOr3yP0.css +0 -1
- package/studio/client/dist/assets/index-D-vRV8w7.js +0 -8
- package/studio/client/dist/assets/index-LGSWRrk8.js +0 -81
- package/studio/server/state/catalog.ts +0 -117
- package/studio/server/state/instance.ts +0 -280
- package/studio/server/state/updates.ts +0 -63
- package/studio/server/state/views.test.ts +0 -286
- package/studio/server/state/views.ts +0 -535
|
@@ -1,103 +1,28 @@
|
|
|
1
|
-
import
|
|
2
|
-
import type { Node } from '@astrale-os/sdk/graph/node'
|
|
3
|
-
import type { QueryAST } from '@astrale-os/sdk/query'
|
|
4
|
-
|
|
5
|
-
import { ClassPath } from '@astrale-os/sdk/graph/class'
|
|
6
|
-
import { NodeId } from '@astrale-os/sdk/graph/node'
|
|
7
|
-
import { Path } from '@astrale-os/sdk/graph/path'
|
|
8
|
-
import { describe, expect, mock, test } from 'bun:test'
|
|
9
|
-
|
|
10
|
-
import type { AdminGraphApi } from '../../graph'
|
|
1
|
+
import { describe, expect, test } from 'bun:test'
|
|
11
2
|
|
|
3
|
+
import { adminBinding, adminSession } from '../../__tests__/fixture'
|
|
12
4
|
import { connectAdminInstances } from '../client'
|
|
13
5
|
|
|
14
|
-
const methods: Array<{ owner: string; name: string }> = []
|
|
15
|
-
|
|
16
|
-
function definition(name: string) {
|
|
17
|
-
return {
|
|
18
|
-
name,
|
|
19
|
-
$: {
|
|
20
|
-
method(method: string) {
|
|
21
|
-
const value = { owner: name, name: method }
|
|
22
|
-
methods.push(value)
|
|
23
|
-
return value
|
|
24
|
-
},
|
|
25
|
-
property(property: string) {
|
|
26
|
-
return { key: property }
|
|
27
|
-
},
|
|
28
|
-
},
|
|
29
|
-
}
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
function node(id: string, props: Record<string, unknown>): Node {
|
|
33
|
-
return {
|
|
34
|
-
id: NodeId(id),
|
|
35
|
-
class: ClassPath.from('admin.astrale.ai', 'Instance'),
|
|
36
|
-
props: props as never,
|
|
37
|
-
}
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
function graphResult(nodes: readonly Node[], cursor?: string) {
|
|
41
|
-
return {
|
|
42
|
-
result: {
|
|
43
|
-
kind: 'nodes' as const,
|
|
44
|
-
nodes: nodes.map((value) => ({ kind: 'value' as const, value })),
|
|
45
|
-
},
|
|
46
|
-
page: cursor === undefined ? {} : { next: cursor },
|
|
47
|
-
}
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
function sourceName(ast: QueryAST): string | undefined {
|
|
51
|
-
const term = ast.source.terms[0]
|
|
52
|
-
return term?.kind === 'definition' ? term.definition.name : undefined
|
|
53
|
-
}
|
|
54
|
-
|
|
55
6
|
function fixture(input: {
|
|
56
|
-
instances?: readonly Node[]
|
|
57
|
-
hosts?: readonly Node[]
|
|
58
|
-
reserved?: Node | null
|
|
59
7
|
invoke?: (method: { owner: string; name: string }, receiver: unknown, input: unknown) => unknown
|
|
60
8
|
}) {
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
$: {
|
|
70
|
-
publication: { origin: 'admin.astrale.ai' },
|
|
71
|
-
origin: 'admin.astrale.ai',
|
|
72
|
-
class(name: string) {
|
|
73
|
-
if (name === 'Instance') return Instance
|
|
74
|
-
if (name === 'Host') return Host
|
|
75
|
-
if (name === 'Fleet') return Fleet
|
|
76
|
-
throw new Error(`Unexpected class ${name}`)
|
|
77
|
-
},
|
|
78
|
-
core: { nodes: { fleet: { path: Path.id(NodeId('fleet')) } } },
|
|
79
|
-
invoke,
|
|
80
|
-
},
|
|
81
|
-
} as unknown as DomainBinding
|
|
82
|
-
const query = mock(async (ast: QueryAST) => {
|
|
83
|
-
const name = sourceName(ast)
|
|
84
|
-
if (name === 'Instance') return graphResult(input.instances ?? [])
|
|
85
|
-
if (name === 'Host') return graphResult(input.hosts ?? [])
|
|
86
|
-
throw new Error(`Unexpected graph query ${String(name)}`)
|
|
9
|
+
const calls: Array<{
|
|
10
|
+
method: { owner: string; name: string }
|
|
11
|
+
receiver: string
|
|
12
|
+
value: unknown
|
|
13
|
+
}> = []
|
|
14
|
+
const remote = adminSession((method, receiver, value) => {
|
|
15
|
+
calls.push({ method, receiver: String(receiver), value })
|
|
16
|
+
return input.invoke?.(method, receiver, value)
|
|
87
17
|
})
|
|
88
|
-
const
|
|
89
|
-
if (String(source) === '@fleet') return page(input.reserved ?? null)
|
|
90
|
-
if (edge.name === 'instance_runs_on_host') return page(input.hosts?.[0] ?? null)
|
|
91
|
-
throw new Error(`Unexpected neighbor query ${String(source)} ${String(edge)}`)
|
|
92
|
-
})
|
|
93
|
-
const graph = { query, neighbors } as unknown as AdminGraphApi
|
|
18
|
+
const binding = adminBinding()
|
|
94
19
|
return {
|
|
95
20
|
binding,
|
|
96
|
-
|
|
97
|
-
|
|
21
|
+
invoke: remote.invoke,
|
|
22
|
+
calls,
|
|
98
23
|
connect: () =>
|
|
99
24
|
connectAdminInstances(
|
|
100
|
-
{ session:
|
|
25
|
+
{ session: remote.session },
|
|
101
26
|
{
|
|
102
27
|
bind: async () => binding,
|
|
103
28
|
operationId: (kind) => `cli.instance.${kind}:test`,
|
|
@@ -106,15 +31,6 @@ function fixture(input: {
|
|
|
106
31
|
}
|
|
107
32
|
}
|
|
108
33
|
|
|
109
|
-
function page(item: Node | null) {
|
|
110
|
-
return {
|
|
111
|
-
nodes: item === null ? [] : [item],
|
|
112
|
-
first: item,
|
|
113
|
-
graph: { nodes: item === null ? [] : [item], edges: [] },
|
|
114
|
-
cursor: null,
|
|
115
|
-
}
|
|
116
|
-
}
|
|
117
|
-
|
|
118
34
|
describe('V2 Admin Instance adapter', () => {
|
|
119
35
|
test('lists caller-visible Instances through the Admin-owned Fleet operation', async () => {
|
|
120
36
|
const contract = fixture({
|
|
@@ -128,8 +44,6 @@ describe('V2 Admin Instance adapter', () => {
|
|
|
128
44
|
state: 'ready',
|
|
129
45
|
createdAt: '2026-08-12T00:00:00.000Z',
|
|
130
46
|
updatedAt: '2026-08-12T00:00:00.000Z',
|
|
131
|
-
hostId: '@host-node',
|
|
132
|
-
region: 'fr-par',
|
|
133
47
|
},
|
|
134
48
|
]
|
|
135
49
|
: undefined,
|
|
@@ -142,15 +56,15 @@ describe('V2 Admin Instance adapter', () => {
|
|
|
142
56
|
url: 'https://demo.eu.astrale.ai',
|
|
143
57
|
state: 'ready',
|
|
144
58
|
createdAt: '2026-08-12T00:00:00.000Z',
|
|
145
|
-
hostId: '@host-node',
|
|
146
|
-
region: 'fr-par',
|
|
147
59
|
},
|
|
148
60
|
])
|
|
149
|
-
expect(contract.
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
61
|
+
expect(contract.calls).toEqual([
|
|
62
|
+
{
|
|
63
|
+
method: { owner: 'Fleet', name: 'listInstances' },
|
|
64
|
+
receiver: '/:admin.astrale.ai:core.fleet::listInstances',
|
|
65
|
+
value: {},
|
|
66
|
+
},
|
|
67
|
+
])
|
|
154
68
|
})
|
|
155
69
|
|
|
156
70
|
test('delegates default placement to Fleet without reading Host inventory', async () => {
|
|
@@ -162,10 +76,7 @@ describe('V2 Admin Instance adapter', () => {
|
|
|
162
76
|
createdAt: '2026-08-12T00:00:00.000Z',
|
|
163
77
|
updatedAt: '2026-08-12T00:00:00.000Z',
|
|
164
78
|
}
|
|
165
|
-
const contract = fixture({
|
|
166
|
-
hosts: [node('host-node', { id: 'host-paris', state: 'ready' })],
|
|
167
|
-
invoke: () => created,
|
|
168
|
-
})
|
|
79
|
+
const contract = fixture({ invoke: () => created })
|
|
169
80
|
|
|
170
81
|
await expect((await contract.connect()).create('demo')).resolves.toMatchObject({
|
|
171
82
|
id: created.id,
|
|
@@ -174,47 +85,17 @@ describe('V2 Admin Instance adapter', () => {
|
|
|
174
85
|
state: created.state,
|
|
175
86
|
createdAt: created.createdAt,
|
|
176
87
|
})
|
|
177
|
-
expect(contract.
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
})
|
|
185
|
-
|
|
186
|
-
test('resolves --host-id to an exact Host receiver and rejects the reserved Admin Host', async () => {
|
|
187
|
-
const reserved = node('admin-host', { id: 'admin', state: 'ready' })
|
|
188
|
-
const consumer = node('consumer-host', { id: 'consumer-paris', state: 'ready' })
|
|
189
|
-
const contract = fixture({
|
|
190
|
-
hosts: [reserved, consumer],
|
|
191
|
-
reserved,
|
|
192
|
-
invoke: () => ({
|
|
193
|
-
id: '@instance-node',
|
|
194
|
-
slug: 'demo',
|
|
195
|
-
url: 'https://demo.eu.astrale.ai',
|
|
196
|
-
state: 'ready',
|
|
197
|
-
}),
|
|
198
|
-
})
|
|
199
|
-
const api = await contract.connect()
|
|
200
|
-
|
|
201
|
-
await expect(api.create('demo', 'admin')).rejects.toMatchObject({ name: 'NotFoundError' })
|
|
202
|
-
await expect(api.create('demo', 'consumer-paris')).resolves.toMatchObject({ slug: 'demo' })
|
|
203
|
-
expect(contract.invoke).toHaveBeenCalledWith(
|
|
204
|
-
expect.objectContaining({ owner: 'Host', name: 'createInstance' }),
|
|
205
|
-
Path.id(NodeId('consumer-host')),
|
|
206
|
-
{ operationId: 'cli.instance.create:test', slug: 'demo' },
|
|
207
|
-
)
|
|
88
|
+
expect(contract.calls).toEqual([
|
|
89
|
+
{
|
|
90
|
+
method: { owner: 'Fleet', name: 'createInstance' },
|
|
91
|
+
receiver: '/:admin.astrale.ai:core.fleet::createInstance',
|
|
92
|
+
value: { operationId: 'cli.instance.create:test', slug: 'demo' },
|
|
93
|
+
},
|
|
94
|
+
])
|
|
208
95
|
})
|
|
209
96
|
|
|
210
97
|
test('refreshes and deletes through the resolved Instance receiver', async () => {
|
|
211
|
-
const instance = node('instance-node', {
|
|
212
|
-
slug: 'demo',
|
|
213
|
-
url: 'https://demo.eu.astrale.ai',
|
|
214
|
-
state: 'ready',
|
|
215
|
-
})
|
|
216
98
|
const contract = fixture({
|
|
217
|
-
instances: [instance],
|
|
218
99
|
invoke: (method) =>
|
|
219
100
|
method.name === 'listInstances'
|
|
220
101
|
? [
|
|
@@ -238,22 +119,16 @@ describe('V2 Admin Instance adapter', () => {
|
|
|
238
119
|
|
|
239
120
|
await expect(api.status('demo')).resolves.toMatchObject({ state: 'ready' })
|
|
240
121
|
await expect(api.delete('@instance-node')).resolves.toMatchObject({ state: 'deleted' })
|
|
241
|
-
expect(contract.
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
122
|
+
expect(contract.calls.map(({ method }) => method)).toEqual([
|
|
123
|
+
{ owner: 'Fleet', name: 'listInstances' },
|
|
124
|
+
{ owner: 'Instance', name: 'status' },
|
|
125
|
+
{ owner: 'Fleet', name: 'listInstances' },
|
|
126
|
+
{ owner: 'Instance', name: 'delete' },
|
|
246
127
|
])
|
|
247
128
|
})
|
|
248
129
|
|
|
249
130
|
test('installs a resolved catalog Domain through Instance.installDomain', async () => {
|
|
250
|
-
const instance = node('instance-node', {
|
|
251
|
-
slug: 'demo',
|
|
252
|
-
url: 'https://demo.eu.astrale.ai',
|
|
253
|
-
state: 'ready',
|
|
254
|
-
})
|
|
255
131
|
const contract = fixture({
|
|
256
|
-
instances: [instance],
|
|
257
132
|
invoke: (method) =>
|
|
258
133
|
method.name === 'listInstances'
|
|
259
134
|
? [
|
|
@@ -282,10 +157,10 @@ describe('V2 Admin Instance adapter', () => {
|
|
|
282
157
|
origin: 'crm.acme.dev',
|
|
283
158
|
ok: true,
|
|
284
159
|
})
|
|
285
|
-
expect(contract.
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
{ operationId: 'cli.instance.install-domain:test', domain: '@crm-domain' },
|
|
289
|
-
)
|
|
160
|
+
expect(contract.calls.at(-1)).toEqual({
|
|
161
|
+
method: { owner: 'Instance', name: 'installDomain' },
|
|
162
|
+
receiver: '@instance-node::installDomain',
|
|
163
|
+
value: { operationId: 'cli.instance.install-domain:test', domain: '@crm-domain' },
|
|
164
|
+
})
|
|
290
165
|
})
|
|
291
166
|
})
|
|
@@ -1,14 +1,16 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
import type { ClientSession } from '@astrale-os/kernel-client/session'
|
|
3
|
-
import type { Node } from '@astrale-os/sdk/graph/node'
|
|
1
|
+
import type { ClientSession } from '@astrale-os/sdk/client/session'
|
|
4
2
|
|
|
5
|
-
import { bind } from '@astrale-os/kernel-client/domain'
|
|
6
3
|
import { Path } from '@astrale-os/sdk/graph/path'
|
|
7
|
-
import { Query } from '@astrale-os/sdk/query'
|
|
8
4
|
|
|
9
|
-
import { readAllNodes, type AdminGraphApi } from '../graph'
|
|
10
5
|
import {
|
|
11
|
-
|
|
6
|
+
bindAdmin,
|
|
7
|
+
invokeAdminMethod,
|
|
8
|
+
requireAdminBinding,
|
|
9
|
+
requireAdminClass,
|
|
10
|
+
requireAdminCore,
|
|
11
|
+
type AdminBinding,
|
|
12
|
+
} from '../binding'
|
|
13
|
+
import {
|
|
12
14
|
AdminInstanceNotFoundError,
|
|
13
15
|
findOwnedInstance,
|
|
14
16
|
type DomainInstallReceipt,
|
|
@@ -17,33 +19,20 @@ import {
|
|
|
17
19
|
type OwnedInstanceInfo,
|
|
18
20
|
} from './model'
|
|
19
21
|
|
|
20
|
-
const ADMIN_ORIGIN = 'admin.astrale.ai'
|
|
21
|
-
const PAGE_SIZE = 256
|
|
22
|
-
const MAXIMUM_INSTANCES = 10_000
|
|
23
|
-
const MAXIMUM_PAGES = Math.ceil(MAXIMUM_INSTANCES / PAGE_SIZE) + 1
|
|
24
|
-
|
|
25
|
-
const HostRef = Object.freeze({ origin: ADMIN_ORIGIN, kind: 'class' as const, name: 'Host' })
|
|
26
|
-
const fleetReservesAdminHost = Object.freeze({
|
|
27
|
-
origin: ADMIN_ORIGIN,
|
|
28
|
-
kind: 'class' as const,
|
|
29
|
-
name: 'fleet_reserves_admin_host',
|
|
30
|
-
})
|
|
31
|
-
|
|
32
22
|
export interface AdminInstanceContext {
|
|
33
23
|
readonly session: ClientSession
|
|
34
|
-
readonly graph: AdminGraphApi
|
|
35
24
|
}
|
|
36
25
|
|
|
37
26
|
export interface AdminInstanceApi {
|
|
38
27
|
list(): Promise<OwnedInstanceInfo[]>
|
|
39
|
-
create(slug: string
|
|
28
|
+
create(slug: string): Promise<InstanceInfo>
|
|
40
29
|
status(identifier: string): Promise<InstanceInfo>
|
|
41
30
|
delete(identifier: string): Promise<InstanceInfo>
|
|
42
31
|
installDomain(identifier: string, domain: string): Promise<DomainInstallReceipt>
|
|
43
32
|
}
|
|
44
33
|
|
|
45
34
|
export interface AdminInstanceDependencies {
|
|
46
|
-
readonly bind?: (session: ClientSession) => Promise<
|
|
35
|
+
readonly bind?: (session: ClientSession) => Promise<AdminBinding>
|
|
47
36
|
readonly operationId?: (kind: 'create' | 'status' | 'delete' | 'install-domain') => string
|
|
48
37
|
}
|
|
49
38
|
|
|
@@ -55,24 +44,22 @@ export async function connectAdminInstances(
|
|
|
55
44
|
context: AdminInstanceContext,
|
|
56
45
|
dependencies: AdminInstanceDependencies = {},
|
|
57
46
|
): Promise<AdminInstanceApi> {
|
|
58
|
-
const binding = await (dependencies.bind ??
|
|
59
|
-
if (binding.$.publication?.origin !== ADMIN_ORIGIN || binding.$.origin !== ADMIN_ORIGIN) {
|
|
60
|
-
throw new TypeError('Configured Admin target does not serve the Admin Domain.')
|
|
61
|
-
}
|
|
47
|
+
const binding = requireAdminBinding(await (dependencies.bind ?? bindAdmin)(context.session))
|
|
62
48
|
|
|
63
|
-
const Instance = binding
|
|
64
|
-
const
|
|
65
|
-
const
|
|
66
|
-
const fleet = binding.$.core.nodes.fleet?.path
|
|
67
|
-
if (fleet === undefined) throw new TypeError('Admin Domain has no singleton Fleet receiver.')
|
|
49
|
+
const Instance = requireAdminClass(binding, 'Instance', 'node')
|
|
50
|
+
const Fleet = requireAdminClass(binding, 'Fleet', 'node')
|
|
51
|
+
const fleet = requireAdminCore(binding, 'fleet')
|
|
68
52
|
|
|
69
53
|
const operationId = dependencies.operationId ?? defaultOperationId
|
|
70
54
|
|
|
71
55
|
const list = async (): Promise<OwnedInstanceInfo[]> => {
|
|
72
|
-
const result: unknown = await
|
|
73
|
-
|
|
56
|
+
const result: unknown = await invokeAdminMethod(
|
|
57
|
+
context.session,
|
|
58
|
+
binding,
|
|
59
|
+
Fleet,
|
|
60
|
+
'listInstances',
|
|
74
61
|
fleet,
|
|
75
|
-
{}
|
|
62
|
+
{},
|
|
76
63
|
)
|
|
77
64
|
if (!Array.isArray(result)) throw new TypeError('Admin Instance inventory is invalid.')
|
|
78
65
|
return result.map((value) => instanceFromSummary(value) as OwnedInstanceInfo)
|
|
@@ -89,113 +76,48 @@ export async function connectAdminInstances(
|
|
|
89
76
|
identifier: string,
|
|
90
77
|
): Promise<InstanceInfo> => {
|
|
91
78
|
const instance = await requireInstance(identifier)
|
|
92
|
-
const output = await
|
|
93
|
-
|
|
79
|
+
const output = await invokeAdminMethod(
|
|
80
|
+
context.session,
|
|
81
|
+
binding,
|
|
82
|
+
Instance,
|
|
83
|
+
method,
|
|
94
84
|
Path.parse(instance.id),
|
|
95
|
-
{ operationId: operationId(method) }
|
|
85
|
+
{ operationId: operationId(method) },
|
|
96
86
|
)
|
|
97
87
|
return instanceFromSummary(output)
|
|
98
88
|
}
|
|
99
89
|
|
|
100
90
|
return Object.freeze({
|
|
101
91
|
list,
|
|
102
|
-
async create(slug: string
|
|
92
|
+
async create(slug: string) {
|
|
103
93
|
const input = Object.freeze({
|
|
104
94
|
operationId: operationId('create'),
|
|
105
95
|
slug,
|
|
106
96
|
})
|
|
107
|
-
if (hostId !== undefined) {
|
|
108
|
-
const selected = await hostInventory(context.graph, binding, Host, fleet)
|
|
109
|
-
const host = selected.hosts.find(
|
|
110
|
-
(item) => item.id === hostId && item.state === 'ready' && item.path !== selected.reserved,
|
|
111
|
-
)
|
|
112
|
-
if (host === undefined) throw new AdminHostNotFoundError(hostId)
|
|
113
|
-
return instanceFromSummary(
|
|
114
|
-
await binding.$.invoke(
|
|
115
|
-
Host.$.method('createInstance') as never,
|
|
116
|
-
Path.parse(host.path),
|
|
117
|
-
input as never,
|
|
118
|
-
),
|
|
119
|
-
)
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
// Default placement belongs to Admin. Avoid a redundant Host graph read:
|
|
123
|
-
// it requires broader graph authority than this Fleet operation and can
|
|
124
|
-
// deny an otherwise authorized caller before the server selects a Host.
|
|
125
97
|
return instanceFromSummary(
|
|
126
|
-
await binding
|
|
98
|
+
await invokeAdminMethod(context.session, binding, Fleet, 'createInstance', fleet, input),
|
|
127
99
|
)
|
|
128
100
|
},
|
|
129
101
|
status: (identifier: string) => invokeInstance('status', identifier),
|
|
130
102
|
delete: (identifier: string) => invokeInstance('delete', identifier),
|
|
131
103
|
async installDomain(identifier: string, domain: string): Promise<DomainInstallReceipt> {
|
|
132
104
|
const instance = await requireInstance(identifier)
|
|
133
|
-
const output = await
|
|
134
|
-
|
|
105
|
+
const output = await invokeAdminMethod(
|
|
106
|
+
context.session,
|
|
107
|
+
binding,
|
|
108
|
+
Instance,
|
|
109
|
+
'installDomain',
|
|
135
110
|
Path.parse(instance.id),
|
|
136
111
|
{
|
|
137
112
|
operationId: operationId('install-domain'),
|
|
138
113
|
domain: Path.parse(domain).raw,
|
|
139
|
-
}
|
|
114
|
+
},
|
|
140
115
|
)
|
|
141
116
|
return domainInstallReceipt(output)
|
|
142
117
|
},
|
|
143
118
|
})
|
|
144
119
|
}
|
|
145
120
|
|
|
146
|
-
async function bindInstalledAdmin(session: ClientSession): Promise<DomainBinding> {
|
|
147
|
-
return bind(session, await session.installed(ADMIN_ORIGIN))
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
interface HostInventoryItem {
|
|
151
|
-
readonly id: string
|
|
152
|
-
readonly nodeId: string
|
|
153
|
-
readonly path: string
|
|
154
|
-
readonly state: string
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
async function hostInventory(
|
|
158
|
-
graph: AdminGraphApi,
|
|
159
|
-
binding: DomainBinding,
|
|
160
|
-
Host: ReturnType<DomainBinding['$']['class']>,
|
|
161
|
-
fleet: Path,
|
|
162
|
-
): Promise<{ readonly hosts: readonly HostInventoryItem[]; readonly reserved?: string }> {
|
|
163
|
-
const [nodes, reservedPage] = await Promise.all([
|
|
164
|
-
readAllNodes(
|
|
165
|
-
graph,
|
|
166
|
-
Query.from({ kind: 'node', definitions: [HostRef] }).select({
|
|
167
|
-
kind: 'nodes',
|
|
168
|
-
projection: { kind: 'value' },
|
|
169
|
-
}),
|
|
170
|
-
{
|
|
171
|
-
label: 'Admin Host inventory',
|
|
172
|
-
maximum: MAXIMUM_INSTANCES,
|
|
173
|
-
maximumPages: MAXIMUM_PAGES,
|
|
174
|
-
},
|
|
175
|
-
),
|
|
176
|
-
graph.neighbors(fleet, fleetReservesAdminHost, {
|
|
177
|
-
direction: 'outgoing',
|
|
178
|
-
page: { size: 1 },
|
|
179
|
-
}),
|
|
180
|
-
])
|
|
181
|
-
const reserved = reservedPage.first === null ? undefined : Path.id(reservedPage.first.id).raw
|
|
182
|
-
return Object.freeze({
|
|
183
|
-
hosts: Object.freeze(
|
|
184
|
-
nodes.map((node) =>
|
|
185
|
-
Object.freeze({
|
|
186
|
-
id: requiredStringProperty(Host, node, 'id'),
|
|
187
|
-
nodeId: String(node.id),
|
|
188
|
-
path: Path.id(node.id).raw,
|
|
189
|
-
state: requiredStringProperty(Host, node, 'state'),
|
|
190
|
-
}),
|
|
191
|
-
),
|
|
192
|
-
),
|
|
193
|
-
...(reserved === undefined ? {} : { reserved }),
|
|
194
|
-
})
|
|
195
|
-
}
|
|
196
|
-
|
|
197
|
-
type DynamicDefinition = ReturnType<DomainBinding['$']['class']>
|
|
198
|
-
|
|
199
121
|
function instanceFromSummary(input: unknown): InstanceInfo {
|
|
200
122
|
const value = record(input, 'Admin Instance summary')
|
|
201
123
|
const failure = value.failure === undefined ? undefined : record(value.failure, 'Admin failure')
|
|
@@ -204,10 +126,6 @@ function instanceFromSummary(input: unknown): InstanceInfo {
|
|
|
204
126
|
slug: requiredString(value.slug, 'Admin Instance slug'),
|
|
205
127
|
url: optionalStringValue(value.url) ?? '',
|
|
206
128
|
state: instanceState(value.state),
|
|
207
|
-
...(value.hostId === undefined
|
|
208
|
-
? {}
|
|
209
|
-
: { hostId: requiredString(value.hostId, 'Admin Host id') }),
|
|
210
|
-
...(value.region === undefined ? {} : { region: requiredString(value.region, 'Admin region') }),
|
|
211
129
|
...(value.phase === undefined
|
|
212
130
|
? {}
|
|
213
131
|
: { phase: requiredString(value.phase, 'Admin Instance phase') }),
|
|
@@ -243,13 +161,6 @@ function domainInstallReceipt(input: unknown): DomainInstallReceipt {
|
|
|
243
161
|
})
|
|
244
162
|
}
|
|
245
163
|
|
|
246
|
-
function requiredStringProperty(definition: DynamicDefinition, node: Node, name: string): string {
|
|
247
|
-
return requiredString(
|
|
248
|
-
node.props[definition.$.property(name).key],
|
|
249
|
-
`Admin ${definition.name}.${name}`,
|
|
250
|
-
)
|
|
251
|
-
}
|
|
252
|
-
|
|
253
164
|
function instanceState(input: unknown): InstanceState {
|
|
254
165
|
if (
|
|
255
166
|
input === 'provisioning' ||
|
|
@@ -5,10 +5,9 @@ export {
|
|
|
5
5
|
type AdminInstanceDependencies,
|
|
6
6
|
} from './client'
|
|
7
7
|
export {
|
|
8
|
-
AdminHostNotFoundError,
|
|
9
8
|
AdminInstanceNotFoundError,
|
|
10
9
|
findOwnedInstance,
|
|
11
|
-
|
|
10
|
+
formatInstanceState,
|
|
12
11
|
type DomainInstallReceipt,
|
|
13
12
|
type InstanceInfo,
|
|
14
13
|
type InstanceState,
|
|
@@ -5,9 +5,7 @@ export interface InstanceInfo {
|
|
|
5
5
|
readonly id: string
|
|
6
6
|
readonly slug: string
|
|
7
7
|
readonly url: string
|
|
8
|
-
readonly
|
|
9
|
-
readonly region?: string
|
|
10
|
-
readonly state?: InstanceState
|
|
8
|
+
readonly state: InstanceState
|
|
11
9
|
readonly phase?: string
|
|
12
10
|
readonly error?: string | null
|
|
13
11
|
readonly createdAt?: string
|
|
@@ -28,19 +26,13 @@ export interface DomainInstallReceipt {
|
|
|
28
26
|
readonly error?: string
|
|
29
27
|
}
|
|
30
28
|
|
|
31
|
-
export class AdminInstanceNotFoundError extends
|
|
32
|
-
readonly name = 'NotFoundError'
|
|
33
|
-
|
|
34
|
-
constructor(readonly identifier: string) {
|
|
35
|
-
super(`No owned Admin Instance matches ${JSON.stringify(identifier)}.`)
|
|
36
|
-
}
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
export class AdminHostNotFoundError extends Error {
|
|
40
|
-
readonly name = 'NotFoundError'
|
|
41
|
-
|
|
29
|
+
export class AdminInstanceNotFoundError extends AstraleError {
|
|
42
30
|
constructor(readonly identifier: string) {
|
|
43
|
-
super(
|
|
31
|
+
super(
|
|
32
|
+
'INSTANCE_NOT_FOUND',
|
|
33
|
+
`No owned Admin Instance matches ${JSON.stringify(identifier)}.`,
|
|
34
|
+
'Run `astrale instance list` to see your instances.',
|
|
35
|
+
)
|
|
44
36
|
}
|
|
45
37
|
}
|
|
46
38
|
|
|
@@ -57,9 +49,8 @@ export function findOwnedInstance(
|
|
|
57
49
|
)
|
|
58
50
|
}
|
|
59
51
|
|
|
60
|
-
/** Human-readable
|
|
61
|
-
export function
|
|
62
|
-
return
|
|
63
|
-
.filter(Boolean)
|
|
64
|
-
.join(' · ')
|
|
52
|
+
/** Human-readable non-ready lifecycle state for one Instance. */
|
|
53
|
+
export function formatInstanceState(info: InstanceInfo): string {
|
|
54
|
+
return info.state === 'ready' ? '' : `${info.state}${info.phase ? ` (${info.phase})` : ''}`
|
|
65
55
|
}
|
|
56
|
+
import { AstraleError } from '../../errors'
|
|
@@ -4,7 +4,10 @@ import type { IdentityStore } from '../../identity/index'
|
|
|
4
4
|
|
|
5
5
|
import { AuthError } from '../../errors'
|
|
6
6
|
import { findOwnedInstance, type OwnedInstanceInfo } from '../../lib/admin-instance'
|
|
7
|
-
import {
|
|
7
|
+
import {
|
|
8
|
+
assertInstanceCreateIdentity,
|
|
9
|
+
selectInstanceCreateIdentity,
|
|
10
|
+
} from '../../lib/provision-instance'
|
|
8
11
|
|
|
9
12
|
describe('admin-backed instance commands', () => {
|
|
10
13
|
test('findOwnedInstance matches owner inventory by slug or stable node id', () => {
|
|
@@ -14,8 +17,6 @@ describe('admin-backed instance commands', () => {
|
|
|
14
17
|
url: 'https://demo.eu.astrale.ai',
|
|
15
18
|
state: 'failed',
|
|
16
19
|
organizationId: 'org_123',
|
|
17
|
-
hostId: 'host-1',
|
|
18
|
-
region: 'eu',
|
|
19
20
|
phase: 'installing:default-domains',
|
|
20
21
|
error: 'postInstall failed',
|
|
21
22
|
createdAt: '2026-07-16T00:00:00.000Z',
|
|
@@ -39,8 +40,8 @@ describe('admin-backed instance commands', () => {
|
|
|
39
40
|
},
|
|
40
41
|
}
|
|
41
42
|
|
|
42
|
-
expect(() =>
|
|
43
|
-
expect(() =>
|
|
43
|
+
expect(() => assertInstanceCreateIdentity(store)).toThrow(AuthError)
|
|
44
|
+
expect(() => assertInstanceCreateIdentity(store)).toThrow('WorkOS login required')
|
|
44
45
|
})
|
|
45
46
|
|
|
46
47
|
test('instance create preflight accepts IdP-backed identities', () => {
|
|
@@ -58,7 +59,8 @@ describe('admin-backed instance commands', () => {
|
|
|
58
59
|
},
|
|
59
60
|
}
|
|
60
61
|
|
|
61
|
-
expect(() =>
|
|
62
|
+
expect(() => assertInstanceCreateIdentity(store)).not.toThrow()
|
|
63
|
+
expect(selectInstanceCreateIdentity(store)).toBe('bryan')
|
|
62
64
|
})
|
|
63
65
|
|
|
64
66
|
test('--as selection must also be IdP-backed', () => {
|
|
@@ -82,7 +84,8 @@ describe('admin-backed instance commands', () => {
|
|
|
82
84
|
},
|
|
83
85
|
}
|
|
84
86
|
|
|
85
|
-
expect(() =>
|
|
86
|
-
expect(() =>
|
|
87
|
+
expect(() => assertInstanceCreateIdentity(store, { as: 'local' })).toThrow(AuthError)
|
|
88
|
+
expect(() => assertInstanceCreateIdentity(store, { as: 'workos' })).not.toThrow()
|
|
89
|
+
expect(selectInstanceCreateIdentity(store, { as: 'workos' })).toBe('workos')
|
|
87
90
|
})
|
|
88
91
|
})
|