@astrale-os/cli 1.0.0-beta.3 → 1.0.0-beta.30
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 +80 -6
- package/dist/astrale.js +55471 -58674
- package/dist/public/connect-core.js +2158 -30706
- package/dist/public/keys/index.js +1016 -29541
- package/dist/public/paths/index.js +370 -28654
- package/dist/types/admin/contract.d.ts +26 -0
- package/dist/types/admin/instance/client.d.ts +20 -0
- package/dist/types/admin/instance/index.d.ts +2 -0
- package/dist/types/admin/instance/model.d.ts +6 -10
- 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 +13 -59
- package/dist/types/lib/failure-debug.d.ts +1 -0
- package/dist/types/lib/idp.d.ts +25 -173
- 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/output.d.ts +1 -1
- 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 +109 -0
- package/dist/types/lib/validation.d.ts +4 -1
- package/dist/types/state/exchange-credentials.d.ts +6 -2
- package/dist/types/state/files.d.ts +2 -0
- package/dist/types/state/index.d.ts +3 -2
- package/dist/types/state/paths.d.ts +2 -0
- package/dist/types/state/session-routes.d.ts +10 -0
- package/package.json +16 -19
- package/src/__tests__/fixtures/publication.ts +20 -0
- package/src/admin/.spec/architecture.md +12 -5
- package/src/admin/__tests__/fixture.ts +25 -0
- package/src/admin/catalog/.spec/api.d.ts +1 -3
- package/src/admin/catalog/.spec/architecture.md +5 -4
- package/src/admin/catalog/__tests__/client.test.ts +143 -69
- package/src/admin/catalog/client.ts +40 -54
- package/src/admin/catalog/model.ts +3 -4
- package/src/admin/contract.ts +47 -0
- 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 +5 -9
- package/src/admin/instance/.spec/architecture.md +7 -7
- package/src/admin/instance/__tests__/client.test.ts +157 -175
- package/src/admin/instance/client.ts +39 -128
- package/src/admin/instance/index.ts +1 -2
- package/src/admin/instance/model.ts +12 -18
- package/src/commands/__tests__/admin-instance.test.ts +11 -8
- package/src/commands/__tests__/call-describe.test.ts +46 -33
- package/src/commands/__tests__/call.test.ts +34 -0
- package/src/commands/__tests__/domain-install-operation.test.ts +30 -22
- 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-direct.test.ts +115 -8
- package/src/commands/__tests__/install-identity-override.test.ts +6 -17
- 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__/introspect-parse.test.ts +80 -1
- package/src/commands/__tests__/logs.test.ts +250 -2
- package/src/commands/__tests__/read-commands.test.ts +118 -10
- package/src/commands/__tests__/token-ttl.test.ts +49 -4
- package/src/commands/__tests__/update.test.ts +25 -3
- package/src/commands/__tests__/view.test.ts +50 -2
- package/src/commands/auth/logout.ts +2 -1
- package/src/commands/browser.ts +29 -0
- package/src/commands/call-describe.ts +60 -68
- package/src/commands/call.ts +71 -59
- package/src/commands/domain/install.ts +73 -46
- package/src/commands/domain/list.ts +1 -1
- package/src/commands/domain/publish.ts +14 -11
- package/src/commands/domain/uninstall.ts +2 -2
- package/src/commands/get.ts +15 -16
- package/src/commands/identity/__tests__/register.test.ts +3 -3
- package/src/commands/identity/register.ts +8 -8
- 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 +53 -41
- package/src/commands/logs.ts +123 -36
- package/src/commands/mutate.ts +6 -5
- package/src/commands/query.ts +35 -32
- package/src/commands/session/analyze.ts +26 -4
- package/src/commands/token.ts +42 -27
- package/src/commands/ui/__tests__/commands.test.ts +152 -0
- package/src/commands/ui/add.ts +51 -0
- package/src/commands/ui/doctor.ts +21 -0
- package/src/commands/ui/init.ts +38 -0
- package/src/commands/ui/list.ts +26 -0
- package/src/commands/ui/preset-apply.ts +19 -0
- package/src/commands/ui/preset-list.ts +12 -0
- package/src/commands/ui/shared.ts +25 -0
- package/src/commands/update.ts +89 -48
- package/src/commands/view.ts +82 -37
- package/src/connection/.spec/api.d.ts +7 -8
- package/src/connection/.spec/architecture.md +14 -5
- 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__/auth.test.ts +1 -0
- package/src/connection/__tests__/credential.test.ts +21 -1
- package/src/connection/__tests__/errors.test.ts +27 -20
- package/src/connection/__tests__/exchange.test.ts +64 -11
- 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 +8 -4
- package/src/connection/__tests__/target.test.ts +2 -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 +110 -61
- 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 +27 -9
- 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__/fixtures/registry-journey.ts +13 -1
- package/src/identity/__tests__/registration.test.ts +1 -1
- package/src/identity/__tests__/registry.test.ts +4 -0
- package/src/identity/registration.ts +3 -3
- package/src/identity/registry.ts +2 -0
- 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__/binary.test.ts +16 -1
- package/src/lib/__tests__/browser-retention.test.ts +212 -0
- package/src/lib/__tests__/command-dx.test.ts +1 -1
- package/src/lib/__tests__/config.test.ts +27 -0
- package/src/lib/__tests__/domain-publication.test.ts +10 -16
- package/src/lib/__tests__/idp.test.ts +34 -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__/log-errors.test.ts +64 -0
- package/src/lib/__tests__/skills.test.ts +809 -0
- package/src/lib/__tests__/studio-server-deps.test.ts +1 -1
- package/src/lib/__tests__/update.test.ts +264 -11
- package/src/lib/__tests__/view-assets.test.ts +25 -0
- package/src/lib/__tests__/view-external-open-intent.test.ts +150 -0
- package/src/lib/__tests__/view-external-open-origins.test.ts +27 -0
- package/src/lib/__tests__/view-open-intent.test.ts +0 -2
- package/src/lib/__tests__/view-server.test.ts +26 -3
- package/src/lib/__tests__/view-session.test.ts +2 -1
- package/src/lib/admin-instance.ts +14 -8
- package/src/lib/admin-target.ts +21 -2
- package/src/lib/binary.ts +22 -5
- package/src/lib/browser-retention.ts +210 -0
- package/src/lib/command-dx.ts +9 -9
- package/src/lib/config.ts +12 -1
- package/src/lib/domain-publication.ts +9 -7
- package/src/lib/failure-debug.ts +26 -0
- package/src/lib/idp.ts +34 -3
- package/src/lib/instance-candidates.ts +2 -2
- package/src/lib/instance-target.ts +3 -14
- package/src/lib/instance.ts +16 -11
- package/src/lib/log.ts +16 -9
- package/src/lib/output.ts +1 -1
- package/src/lib/proc.ts +7 -2
- package/src/lib/provision-instance.ts +34 -57
- package/src/lib/skills/lock.ts +117 -0
- package/src/lib/skills/sync.ts +814 -0
- package/src/lib/skills.ts +1 -52
- package/src/lib/update.ts +298 -45
- package/src/lib/validation.ts +2 -2
- package/src/lib/view/assets.ts +11 -2
- package/src/lib/view/external-open-intent.ts +41 -0
- package/src/lib/view/external-open-origins.ts +37 -0
- package/src/lib/view/host-capabilities.ts +17 -0
- package/src/lib/view/resolve.ts +1 -1
- package/src/lib/view/server.ts +18 -3
- package/src/lib/view/session.ts +2 -0
- package/src/program/.spec/api.d.ts +1 -0
- package/src/program/__tests__/program.test.ts +163 -6
- package/src/program/argv.ts +14 -0
- package/src/program/build.ts +26 -4
- package/src/program/command.ts +1 -0
- package/src/program/index.ts +1 -0
- package/src/program/registry.ts +2 -1
- package/src/setup/__tests__/instance-step.test.ts +27 -10
- package/src/setup/steps/instance.ts +22 -15
- package/src/setup/steps/skills.ts +40 -38
- package/src/state/.spec/api.d.ts +21 -2
- package/src/state/.spec/architecture.md +18 -4
- package/src/state/.spec/layout.ts +1 -0
- package/src/state/__tests__/exchange-credentials.test.ts +88 -42
- package/src/state/__tests__/files.test.ts +24 -1
- package/src/state/__tests__/fixtures/session-route-process.ts +93 -0
- package/src/state/__tests__/paths.test.ts +1 -0
- package/src/state/__tests__/session-routes.test.ts +138 -0
- package/src/state/exchange-credentials.ts +22 -9
- package/src/state/files.ts +41 -0
- package/src/state/index.ts +3 -1
- package/src/state/paths.ts +3 -0
- package/src/state/session-routes.ts +34 -0
- package/src/telemetry/__tests__/analyze-log.test.ts +38 -0
- package/src/telemetry/__tests__/recorder.test.ts +1 -0
- package/src/telemetry/__tests__/retention.test.ts +267 -0
- package/src/telemetry/__tests__/settings.test.ts +102 -0
- package/src/telemetry/__tests__/store-scan.test.ts +128 -0
- package/src/telemetry/__tests__/trigger.test.ts +33 -4
- package/src/telemetry/analyze.ts +24 -2
- package/src/telemetry/recorder.ts +15 -4
- package/src/telemetry/retention.ts +176 -0
- package/src/telemetry/session.ts +18 -12
- package/src/telemetry/settings.ts +64 -11
- package/src/telemetry/store.ts +92 -9
- package/src/telemetry/trigger.ts +16 -28
- package/src/ui/.spec/api.d.ts +14 -0
- package/src/ui/.spec/architecture.md +14 -0
- package/src/ui/.spec/laws.ts +50 -0
- package/src/ui/.spec/layout.ts +16 -0
- package/src/ui/__tests__/ui.test.ts +1742 -0
- package/src/ui/index.ts +13 -0
- package/src/ui/lock.ts +89 -0
- package/src/ui/model.ts +83 -0
- package/src/ui/operations.ts +1044 -0
- package/src/ui/project.ts +333 -0
- package/src/ui/release.ts +398 -0
- package/src/ui/runner.ts +18 -0
- package/studio/client/dist/assets/elk-api-DjmKsHXc.js +1 -0
- package/studio/client/dist/assets/index-BFVFs_8x.js +81 -0
- package/studio/client/dist/assets/index-DuB9iUdu.css +2 -0
- package/studio/client/dist/assets/rolldown-runtime-Dd_uD5pT.js +1 -0
- package/studio/client/dist/assets/schema-studio--a0kX3QU.css +1 -0
- package/studio/client/dist/assets/schema-studio-DH6oEWME.js +8 -0
- package/studio/client/dist/index.html +4 -3
- package/studio/package.json +8 -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/harness/claude/adapter.test.ts +15 -5
- package/studio/server/agent/harness/gateway/token.test.ts +1 -1
- package/studio/server/agent/harness/gateway/token.ts +3 -3
- package/studio/server/agent/prompts/anchors.test.ts +41 -32
- package/studio/server/agent/prompts/anchors.ts +23 -51
- package/studio/server/agent/prompts/system.test.ts +4 -4
- package/studio/server/agent/prompts/system.ts +5 -5
- package/studio/server/agent/routes.test.ts +1 -1
- package/studio/server/agent/run/coordinator.test.ts +1 -1
- package/studio/server/agent/run/preparation.ts +1 -1
- package/studio/server/api/canvas.ts +0 -1
- package/studio/server/api/context.ts +1 -1
- package/studio/server/api/deployment.ts +1 -1
- package/studio/server/api/views.ts +2 -2
- package/studio/server/api/workspace.ts +1 -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 +24 -18
- package/studio/server/cache.ts +10 -54
- package/studio/server/cli-consumers.test.ts +31 -3
- package/studio/server/domain.test.ts +50 -68
- package/studio/server/domain.ts +82 -63
- package/studio/server/handoff/copy.ts +2 -2
- 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 +10 -16
- package/studio/server/introspect/anatomy-extras.test.ts +29 -56
- 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 +155 -424
- package/studio/server/introspect/canonical-schema.ts +248 -601
- package/studio/server/introspect/core.ts +14 -22
- package/studio/server/introspect/diff.test.ts +21 -9
- package/studio/server/introspect/diff.ts +19 -64
- package/studio/server/introspect/extractor.ts +23 -122
- package/studio/server/introspect/overlay-tsmorph.test.ts +116 -241
- package/studio/server/introspect/overlay-tsmorph.ts +0 -1
- package/studio/server/introspect/overlay.ts +3 -51
- package/studio/server/introspect/revision.test.ts +24 -28
- package/studio/server/introspect/revision.ts +11 -22
- package/studio/server/introspect/runtime.test.ts +77 -195
- package/studio/server/introspect/runtime.ts +5 -67
- package/studio/server/introspect/schema-ir-json.test.ts +81 -0
- package/studio/server/introspect/schema-ir-json.ts +39 -69
- 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/lifecycle.ts +4 -1
- package/studio/server/state/baseline.test.ts +14 -9
- package/studio/server/state/baseline.ts +3 -5
- package/studio/server/state/documents.test.ts +69 -0
- package/studio/server/state/documents.ts +57 -10
- package/studio/server/state/visibility.ts +1 -5
- package/studio/server/views/model.ts +3 -3
- package/studio/server/views/target.test.ts +18 -76
- 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/updates.ts +23 -1
- package/studio/server/workspace-watch.ts +2 -2
- package/studio/shared/contracts/runtime.ts +4 -0
- package/studio/shared/contracts/schema.ts +44 -131
- package/studio/shared/contracts/surface.test.ts +15 -17
- package/studio/shared/contracts/workspace.ts +5 -11
- package/studio/shared/schema/identity.test.ts +20 -44
- package/studio/shared/schema/identity.ts +9 -24
- package/studio/tsconfig.json +2 -1
- package/tsconfig.json +1 -1
- package/viewer/dist/index.html +21 -0
- package/viewer/dist/main.js +41 -62
- package/.check-workspace.cjs +0 -40
- package/src/admin/binding.ts +0 -168
- package/src/commands/__tests__/instance-create-hosts.test.ts +0 -29
- package/studio/client/dist/assets/elk-api-lwhFo7vB.js +0 -1
- package/studio/client/dist/assets/index-B-c-smo9.js +0 -8
- package/studio/client/dist/assets/index-BckHuAWk.js +0 -81
- package/studio/client/dist/assets/index-C4aNQ6dz.css +0 -1
- package/studio/client/dist/assets/index-ChOr3yP0.css +0 -1
- package/studio/server/introspect/anatomy/views/legacy.ts +0 -232
- package/studio/server/introspect/core-extractor.ts +0 -139
- package/studio/server/introspect/overlay.test.ts +0 -72
- package/studio/server/introspect/source-overlay/annotations.ts +0 -14
|
@@ -1,135 +1,46 @@
|
|
|
1
|
-
import
|
|
2
|
-
import type { QueryAST } from '@astrale-os/sdk/query'
|
|
3
|
-
|
|
4
|
-
import { ClassPath } from '@astrale-os/sdk/graph/class'
|
|
5
|
-
import { NodeId } from '@astrale-os/sdk/graph/node'
|
|
6
|
-
import { Path } from '@astrale-os/sdk/graph/path'
|
|
7
|
-
import { describe, expect, mock, test } from 'bun:test'
|
|
8
|
-
|
|
9
|
-
import type { AdminBinding } from '../../binding'
|
|
10
|
-
import type { AdminGraphApi } from '../../graph'
|
|
1
|
+
import { describe, expect, test } from 'bun:test'
|
|
11
2
|
|
|
3
|
+
import { adminSession } from '../../__tests__/fixture'
|
|
12
4
|
import { connectAdminInstances } from '../client'
|
|
13
5
|
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
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
|
-
function fixture(input: {
|
|
56
|
-
instances?: readonly Node[]
|
|
57
|
-
hosts?: readonly Node[]
|
|
58
|
-
reserved?: Node | null
|
|
59
|
-
invoke?: (method: { owner: string; name: string }, receiver: unknown, input: unknown) => unknown
|
|
60
|
-
}) {
|
|
61
|
-
methods.length = 0
|
|
62
|
-
const Instance = definition('Instance')
|
|
63
|
-
const Host = definition('Host')
|
|
64
|
-
const Fleet = definition('Fleet')
|
|
65
|
-
const invoke = mock(async (method: unknown, receiver: unknown, value: unknown) =>
|
|
66
|
-
input.invoke?.(method as { owner: string; name: string }, receiver, value),
|
|
67
|
-
)
|
|
68
|
-
const binding = {
|
|
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 AdminBinding
|
|
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)}`)
|
|
87
|
-
})
|
|
88
|
-
const neighbors = mock(async (source: unknown, edge: { name?: string }) => {
|
|
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)}`)
|
|
6
|
+
function fixture(input: { invoke?: (target: string, input: unknown) => unknown }) {
|
|
7
|
+
const calls: Array<{
|
|
8
|
+
target: string
|
|
9
|
+
value: unknown
|
|
10
|
+
}> = []
|
|
11
|
+
const remote = adminSession((target, value) => {
|
|
12
|
+
calls.push({ target, value })
|
|
13
|
+
return input.invoke?.(target, value)
|
|
92
14
|
})
|
|
93
|
-
const graph = { query, neighbors } as unknown as AdminGraphApi
|
|
94
15
|
return {
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
16
|
+
call: remote.call,
|
|
17
|
+
reflection: remote.reflection,
|
|
18
|
+
calls,
|
|
98
19
|
connect: () =>
|
|
99
20
|
connectAdminInstances(
|
|
100
|
-
{ session:
|
|
21
|
+
{ session: remote.session },
|
|
101
22
|
{
|
|
102
|
-
bind: async () => binding,
|
|
103
23
|
operationId: (kind) => `cli.instance.${kind}:test`,
|
|
104
24
|
},
|
|
105
25
|
),
|
|
106
26
|
}
|
|
107
27
|
}
|
|
108
28
|
|
|
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
29
|
describe('V2 Admin Instance adapter', () => {
|
|
119
30
|
test('lists caller-visible Instances through the Admin-owned Fleet operation', async () => {
|
|
120
31
|
const contract = fixture({
|
|
121
|
-
invoke: (
|
|
122
|
-
|
|
32
|
+
invoke: (target) =>
|
|
33
|
+
target.endsWith('::listInstances')
|
|
123
34
|
? [
|
|
124
35
|
{
|
|
125
36
|
id: '@instance-node',
|
|
126
37
|
slug: 'demo',
|
|
127
38
|
url: 'https://demo.eu.astrale.ai',
|
|
39
|
+
hostId: '@host-node',
|
|
40
|
+
region: 'fr-par',
|
|
128
41
|
state: 'ready',
|
|
129
42
|
createdAt: '2026-08-12T00:00:00.000Z',
|
|
130
43
|
updatedAt: '2026-08-12T00:00:00.000Z',
|
|
131
|
-
hostId: '@host-node',
|
|
132
|
-
region: 'fr-par',
|
|
133
44
|
},
|
|
134
45
|
]
|
|
135
46
|
: undefined,
|
|
@@ -140,17 +51,20 @@ describe('V2 Admin Instance adapter', () => {
|
|
|
140
51
|
id: '@instance-node',
|
|
141
52
|
slug: 'demo',
|
|
142
53
|
url: 'https://demo.eu.astrale.ai',
|
|
143
|
-
state: 'ready',
|
|
144
|
-
createdAt: '2026-08-12T00:00:00.000Z',
|
|
145
54
|
hostId: '@host-node',
|
|
146
55
|
region: 'fr-par',
|
|
56
|
+
state: 'ready',
|
|
57
|
+
createdAt: '2026-08-12T00:00:00.000Z',
|
|
58
|
+
updatedAt: '2026-08-12T00:00:00.000Z',
|
|
147
59
|
},
|
|
148
60
|
])
|
|
149
|
-
expect(contract.
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
61
|
+
expect(contract.calls).toEqual([
|
|
62
|
+
{
|
|
63
|
+
target: '/:admin.astrale.ai:core.fleet::listInstances',
|
|
64
|
+
value: {},
|
|
65
|
+
},
|
|
66
|
+
])
|
|
67
|
+
expect(contract.reflection).not.toHaveBeenCalled()
|
|
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,49 +85,18 @@ 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
|
-
expect(contract.graph.neighbors).not.toHaveBeenCalled()
|
|
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
|
+
target: '/:admin.astrale.ai:core.fleet::createInstance',
|
|
91
|
+
value: { operationId: 'cli.instance.create:test', slug: 'demo' },
|
|
92
|
+
},
|
|
93
|
+
])
|
|
208
94
|
})
|
|
209
95
|
|
|
210
96
|
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
97
|
const contract = fixture({
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
method.name === 'listInstances'
|
|
98
|
+
invoke: (target) =>
|
|
99
|
+
target.endsWith('::listInstances')
|
|
220
100
|
? [
|
|
221
101
|
{
|
|
222
102
|
id: '@instance-node',
|
|
@@ -231,31 +111,34 @@ describe('V2 Admin Instance adapter', () => {
|
|
|
231
111
|
id: '@instance-node',
|
|
232
112
|
slug: 'demo',
|
|
233
113
|
url: 'https://demo.eu.astrale.ai',
|
|
234
|
-
state:
|
|
114
|
+
state: target.endsWith('::delete') ? 'deleted' : 'ready',
|
|
115
|
+
createdAt: '2026-08-12T00:00:00.000Z',
|
|
116
|
+
updatedAt: '2026-08-12T00:00:00.000Z',
|
|
235
117
|
},
|
|
236
118
|
})
|
|
237
119
|
const api = await contract.connect()
|
|
238
120
|
|
|
239
121
|
await expect(api.status('demo')).resolves.toMatchObject({ state: 'ready' })
|
|
240
122
|
await expect(api.delete('@instance-node')).resolves.toMatchObject({ state: 'deleted' })
|
|
241
|
-
expect(contract.
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
123
|
+
expect(contract.calls).toEqual([
|
|
124
|
+
{ target: '/:admin.astrale.ai:core.fleet::listInstances', value: {} },
|
|
125
|
+
{
|
|
126
|
+
target: '@instance-node::status',
|
|
127
|
+
value: { operationId: 'cli.instance.status:test' },
|
|
128
|
+
},
|
|
129
|
+
{ target: '/:admin.astrale.ai:core.fleet::listInstances', value: {} },
|
|
130
|
+
{
|
|
131
|
+
target: '@instance-node::delete',
|
|
132
|
+
value: { operationId: 'cli.instance.delete:test' },
|
|
133
|
+
},
|
|
246
134
|
])
|
|
135
|
+
expect(contract.reflection).not.toHaveBeenCalled()
|
|
247
136
|
})
|
|
248
137
|
|
|
249
138
|
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
139
|
const contract = fixture({
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
method.name === 'listInstances'
|
|
140
|
+
invoke: (target) =>
|
|
141
|
+
target.endsWith('::listInstances')
|
|
259
142
|
? [
|
|
260
143
|
{
|
|
261
144
|
id: '@instance-node',
|
|
@@ -282,10 +165,109 @@ describe('V2 Admin Instance adapter', () => {
|
|
|
282
165
|
origin: 'crm.acme.dev',
|
|
283
166
|
ok: true,
|
|
284
167
|
})
|
|
285
|
-
expect(contract.
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
168
|
+
expect(contract.calls.at(-1)).toEqual({
|
|
169
|
+
target: '@instance-node::installDomain',
|
|
170
|
+
value: { operationId: 'cli.instance.install-domain:test', domain: '@crm-domain' },
|
|
171
|
+
})
|
|
172
|
+
expect(contract.reflection).not.toHaveBeenCalled()
|
|
173
|
+
})
|
|
174
|
+
|
|
175
|
+
test('connects without network I/O and list performs exactly one call', async () => {
|
|
176
|
+
const contract = fixture({ invoke: () => [] })
|
|
177
|
+
|
|
178
|
+
const api = await contract.connect()
|
|
179
|
+
expect(contract.call).not.toHaveBeenCalled()
|
|
180
|
+
expect(contract.reflection).not.toHaveBeenCalled()
|
|
181
|
+
|
|
182
|
+
await expect(api.list()).resolves.toEqual([])
|
|
183
|
+
expect(contract.call).toHaveBeenCalledTimes(1)
|
|
184
|
+
expect(contract.reflection).not.toHaveBeenCalled()
|
|
185
|
+
})
|
|
186
|
+
|
|
187
|
+
test('rejects malformed inventory, lifecycle, Method, and install outputs', async () => {
|
|
188
|
+
await expect((await fixture({ invoke: () => ({}) }).connect()).list()).rejects.toThrow(
|
|
189
|
+
'Admin Instance inventory is invalid.',
|
|
190
|
+
)
|
|
191
|
+
await expect(
|
|
192
|
+
(
|
|
193
|
+
await fixture({
|
|
194
|
+
invoke: () => [{ id: '@instance-node', slug: 'demo', state: 'mystery' }],
|
|
195
|
+
}).connect()
|
|
196
|
+
).list(),
|
|
197
|
+
).rejects.toThrow('Admin Instance state is invalid.')
|
|
198
|
+
await expect(
|
|
199
|
+
(
|
|
200
|
+
await fixture({
|
|
201
|
+
invoke: () => [{ id: 'not-a-path', slug: 'demo', state: 'ready' }],
|
|
202
|
+
}).connect()
|
|
203
|
+
).list(),
|
|
204
|
+
).rejects.toThrow('Admin Instance id is invalid.')
|
|
205
|
+
await expect(
|
|
206
|
+
(
|
|
207
|
+
await fixture({
|
|
208
|
+
invoke: () => [
|
|
209
|
+
{ id: '@instance-node', slug: 'demo', hostId: 'not-a-path', state: 'ready' },
|
|
210
|
+
],
|
|
211
|
+
}).connect()
|
|
212
|
+
).list(),
|
|
213
|
+
).rejects.toThrow('Admin Host id is invalid.')
|
|
214
|
+
|
|
215
|
+
const malformedStatus = fixture({
|
|
216
|
+
invoke: (target) =>
|
|
217
|
+
target.endsWith('::listInstances')
|
|
218
|
+
? [
|
|
219
|
+
{
|
|
220
|
+
id: '@instance-node',
|
|
221
|
+
slug: 'demo',
|
|
222
|
+
state: 'ready',
|
|
223
|
+
createdAt: '2026-08-12T00:00:00.000Z',
|
|
224
|
+
updatedAt: '2026-08-12T00:00:00.000Z',
|
|
225
|
+
},
|
|
226
|
+
]
|
|
227
|
+
: {
|
|
228
|
+
id: '@instance-node',
|
|
229
|
+
slug: 'demo',
|
|
230
|
+
state: 'ready',
|
|
231
|
+
failure: {},
|
|
232
|
+
createdAt: '2026-08-12T00:00:00.000Z',
|
|
233
|
+
updatedAt: '2026-08-12T00:00:00.000Z',
|
|
234
|
+
},
|
|
235
|
+
})
|
|
236
|
+
await expect((await malformedStatus.connect()).status('demo')).rejects.toThrow(
|
|
237
|
+
'Admin failure message is invalid.',
|
|
289
238
|
)
|
|
239
|
+
|
|
240
|
+
const malformedInstall = fixture({
|
|
241
|
+
invoke: (target) =>
|
|
242
|
+
target.endsWith('::listInstances')
|
|
243
|
+
? [
|
|
244
|
+
{
|
|
245
|
+
id: '@instance-node',
|
|
246
|
+
slug: 'demo',
|
|
247
|
+
state: 'ready',
|
|
248
|
+
createdAt: '2026-08-12T00:00:00.000Z',
|
|
249
|
+
updatedAt: '2026-08-12T00:00:00.000Z',
|
|
250
|
+
},
|
|
251
|
+
]
|
|
252
|
+
: {
|
|
253
|
+
domain: '@crm-domain',
|
|
254
|
+
instance: '@instance-node',
|
|
255
|
+
origin: 'crm.acme.dev',
|
|
256
|
+
ok: 'yes',
|
|
257
|
+
},
|
|
258
|
+
})
|
|
259
|
+
await expect(
|
|
260
|
+
(await malformedInstall.connect()).installDomain('demo', '@crm-domain'),
|
|
261
|
+
).rejects.toThrow('Admin Domain install outcome is invalid.')
|
|
262
|
+
|
|
263
|
+
const malformedInstallPath = fixture({
|
|
264
|
+
invoke: (target) =>
|
|
265
|
+
target.endsWith('::listInstances')
|
|
266
|
+
? [{ id: '@instance-node', slug: 'demo', state: 'ready' }]
|
|
267
|
+
: { domain: 'not-a-path', instance: '@instance-node', origin: 'crm.acme.dev', ok: true },
|
|
268
|
+
})
|
|
269
|
+
await expect(
|
|
270
|
+
(await malformedInstallPath.connect()).installDomain('demo', '@crm-domain'),
|
|
271
|
+
).rejects.toThrow('Admin Domain reference is invalid.')
|
|
290
272
|
})
|
|
291
273
|
})
|
|
@@ -1,13 +1,9 @@
|
|
|
1
|
-
import type { ClientSession } from '@astrale-os/
|
|
2
|
-
import type { Node } from '@astrale-os/sdk/graph/node'
|
|
1
|
+
import type { ClientSession } from '@astrale-os/sdk/client/session'
|
|
3
2
|
|
|
4
3
|
import { Path } from '@astrale-os/sdk/graph/path'
|
|
5
|
-
import { Query } from '@astrale-os/sdk/query'
|
|
6
4
|
|
|
7
|
-
import {
|
|
8
|
-
import { readAllNodes, type AdminGraphApi } from '../graph'
|
|
5
|
+
import { AdminContract, callAdminMethod } from '../contract'
|
|
9
6
|
import {
|
|
10
|
-
AdminHostNotFoundError,
|
|
11
7
|
AdminInstanceNotFoundError,
|
|
12
8
|
findOwnedInstance,
|
|
13
9
|
type DomainInstallReceipt,
|
|
@@ -16,62 +12,38 @@ import {
|
|
|
16
12
|
type OwnedInstanceInfo,
|
|
17
13
|
} from './model'
|
|
18
14
|
|
|
19
|
-
const ADMIN_ORIGIN = 'admin.astrale.ai'
|
|
20
|
-
const PAGE_SIZE = 256
|
|
21
|
-
const MAXIMUM_INSTANCES = 10_000
|
|
22
|
-
const MAXIMUM_PAGES = Math.ceil(MAXIMUM_INSTANCES / PAGE_SIZE) + 1
|
|
23
|
-
|
|
24
|
-
const HostRef = Object.freeze({ origin: ADMIN_ORIGIN, kind: 'class' as const, name: 'Host' })
|
|
25
|
-
const fleetReservesAdminHost = Object.freeze({
|
|
26
|
-
origin: ADMIN_ORIGIN,
|
|
27
|
-
kind: 'class' as const,
|
|
28
|
-
name: 'fleet_reserves_admin_host',
|
|
29
|
-
})
|
|
30
|
-
|
|
31
15
|
export interface AdminInstanceContext {
|
|
32
16
|
readonly session: ClientSession
|
|
33
|
-
readonly graph: AdminGraphApi
|
|
34
17
|
}
|
|
35
18
|
|
|
36
19
|
export interface AdminInstanceApi {
|
|
37
20
|
list(): Promise<OwnedInstanceInfo[]>
|
|
38
|
-
create(slug: string
|
|
21
|
+
create(slug: string): Promise<InstanceInfo>
|
|
39
22
|
status(identifier: string): Promise<InstanceInfo>
|
|
40
23
|
delete(identifier: string): Promise<InstanceInfo>
|
|
41
24
|
installDomain(identifier: string, domain: string): Promise<DomainInstallReceipt>
|
|
42
25
|
}
|
|
43
26
|
|
|
44
27
|
export interface AdminInstanceDependencies {
|
|
45
|
-
readonly bind?: (session: ClientSession) => Promise<AdminBinding>
|
|
46
28
|
readonly operationId?: (kind: 'create' | 'status' | 'delete' | 'install-domain') => string
|
|
47
29
|
}
|
|
48
30
|
|
|
49
31
|
/**
|
|
50
|
-
*
|
|
51
|
-
*
|
|
32
|
+
* Connect the public Instance journey through stable Admin call paths. Routine
|
|
33
|
+
* operations perform no schema discovery. No Host lifecycle method is present.
|
|
52
34
|
*/
|
|
53
35
|
export async function connectAdminInstances(
|
|
54
36
|
context: AdminInstanceContext,
|
|
55
37
|
dependencies: AdminInstanceDependencies = {},
|
|
56
38
|
): Promise<AdminInstanceApi> {
|
|
57
|
-
const binding = await (dependencies.bind ?? bindAdmin)(context.session)
|
|
58
|
-
if (binding.$.publication?.origin !== ADMIN_ORIGIN || binding.$.origin !== ADMIN_ORIGIN) {
|
|
59
|
-
throw new TypeError('Configured Admin target does not serve the Admin Domain.')
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
const Instance = binding.$.class('Instance')
|
|
63
|
-
const Host = binding.$.class('Host')
|
|
64
|
-
const Fleet = binding.$.class('Fleet')
|
|
65
|
-
const fleet = binding.$.core.nodes.fleet?.path
|
|
66
|
-
if (fleet === undefined) throw new TypeError('Admin Domain has no singleton Fleet receiver.')
|
|
67
|
-
|
|
68
39
|
const operationId = dependencies.operationId ?? defaultOperationId
|
|
69
40
|
|
|
70
41
|
const list = async (): Promise<OwnedInstanceInfo[]> => {
|
|
71
|
-
const result: unknown = await
|
|
72
|
-
|
|
73
|
-
fleet,
|
|
74
|
-
|
|
42
|
+
const result: unknown = await callAdminMethod(
|
|
43
|
+
context.session,
|
|
44
|
+
AdminContract.fleet,
|
|
45
|
+
'listInstances',
|
|
46
|
+
{},
|
|
75
47
|
)
|
|
76
48
|
if (!Array.isArray(result)) throw new TypeError('Admin Instance inventory is invalid.')
|
|
77
49
|
return result.map((value) => instanceFromSummary(value) as OwnedInstanceInfo)
|
|
@@ -88,121 +60,55 @@ export async function connectAdminInstances(
|
|
|
88
60
|
identifier: string,
|
|
89
61
|
): Promise<InstanceInfo> => {
|
|
90
62
|
const instance = await requireInstance(identifier)
|
|
91
|
-
const output = await
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
{ operationId: operationId(method) } as never,
|
|
95
|
-
)
|
|
63
|
+
const output = await callAdminMethod(context.session, Path.parse(instance.id), method, {
|
|
64
|
+
operationId: operationId(method),
|
|
65
|
+
})
|
|
96
66
|
return instanceFromSummary(output)
|
|
97
67
|
}
|
|
98
68
|
|
|
99
69
|
return Object.freeze({
|
|
100
70
|
list,
|
|
101
|
-
async create(slug: string
|
|
71
|
+
async create(slug: string) {
|
|
102
72
|
const input = Object.freeze({
|
|
103
73
|
operationId: operationId('create'),
|
|
104
74
|
slug,
|
|
105
75
|
})
|
|
106
|
-
if (hostId !== undefined) {
|
|
107
|
-
const selected = await hostInventory(context.graph, binding, Host, fleet)
|
|
108
|
-
const host = selected.hosts.find(
|
|
109
|
-
(item) => item.id === hostId && item.state === 'ready' && item.path !== selected.reserved,
|
|
110
|
-
)
|
|
111
|
-
if (host === undefined) throw new AdminHostNotFoundError(hostId)
|
|
112
|
-
return instanceFromSummary(
|
|
113
|
-
await binding.$.invoke(
|
|
114
|
-
Host.$.method('createInstance') as never,
|
|
115
|
-
Path.parse(host.path),
|
|
116
|
-
input as never,
|
|
117
|
-
),
|
|
118
|
-
)
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
// Default placement belongs to Admin. Avoid a redundant Host graph read:
|
|
122
|
-
// it requires broader graph authority than this Fleet operation and can
|
|
123
|
-
// deny an otherwise authorized caller before the server selects a Host.
|
|
124
76
|
return instanceFromSummary(
|
|
125
|
-
await
|
|
77
|
+
await callAdminMethod(context.session, AdminContract.fleet, 'createInstance', input),
|
|
126
78
|
)
|
|
127
79
|
},
|
|
128
80
|
status: (identifier: string) => invokeInstance('status', identifier),
|
|
129
81
|
delete: (identifier: string) => invokeInstance('delete', identifier),
|
|
130
82
|
async installDomain(identifier: string, domain: string): Promise<DomainInstallReceipt> {
|
|
131
83
|
const instance = await requireInstance(identifier)
|
|
132
|
-
const output = await
|
|
133
|
-
|
|
84
|
+
const output = await callAdminMethod(
|
|
85
|
+
context.session,
|
|
134
86
|
Path.parse(instance.id),
|
|
87
|
+
'installDomain',
|
|
135
88
|
{
|
|
136
89
|
operationId: operationId('install-domain'),
|
|
137
90
|
domain: Path.parse(domain).raw,
|
|
138
|
-
}
|
|
91
|
+
},
|
|
139
92
|
)
|
|
140
93
|
return domainInstallReceipt(output)
|
|
141
94
|
},
|
|
142
95
|
})
|
|
143
96
|
}
|
|
144
97
|
|
|
145
|
-
interface HostInventoryItem {
|
|
146
|
-
readonly id: string
|
|
147
|
-
readonly nodeId: string
|
|
148
|
-
readonly path: string
|
|
149
|
-
readonly state: string
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
async function hostInventory(
|
|
153
|
-
graph: AdminGraphApi,
|
|
154
|
-
binding: AdminBinding,
|
|
155
|
-
Host: ReturnType<AdminBinding['$']['class']>,
|
|
156
|
-
fleet: Path,
|
|
157
|
-
): Promise<{ readonly hosts: readonly HostInventoryItem[]; readonly reserved?: string }> {
|
|
158
|
-
const [nodes, reservedPage] = await Promise.all([
|
|
159
|
-
readAllNodes(
|
|
160
|
-
graph,
|
|
161
|
-
Query.from({ kind: 'node', definitions: [HostRef] }).select({
|
|
162
|
-
kind: 'nodes',
|
|
163
|
-
projection: { kind: 'value' },
|
|
164
|
-
}),
|
|
165
|
-
{
|
|
166
|
-
label: 'Admin Host inventory',
|
|
167
|
-
maximum: MAXIMUM_INSTANCES,
|
|
168
|
-
maximumPages: MAXIMUM_PAGES,
|
|
169
|
-
},
|
|
170
|
-
),
|
|
171
|
-
graph.neighbors(fleet, fleetReservesAdminHost, {
|
|
172
|
-
direction: 'outgoing',
|
|
173
|
-
page: { size: 1 },
|
|
174
|
-
}),
|
|
175
|
-
])
|
|
176
|
-
const reserved = reservedPage.first === null ? undefined : Path.id(reservedPage.first.id).raw
|
|
177
|
-
return Object.freeze({
|
|
178
|
-
hosts: Object.freeze(
|
|
179
|
-
nodes.map((node) =>
|
|
180
|
-
Object.freeze({
|
|
181
|
-
id: requiredStringProperty(Host, node, 'id'),
|
|
182
|
-
nodeId: String(node.id),
|
|
183
|
-
path: Path.id(node.id).raw,
|
|
184
|
-
state: requiredStringProperty(Host, node, 'state'),
|
|
185
|
-
}),
|
|
186
|
-
),
|
|
187
|
-
),
|
|
188
|
-
...(reserved === undefined ? {} : { reserved }),
|
|
189
|
-
})
|
|
190
|
-
}
|
|
191
|
-
|
|
192
|
-
type DynamicDefinition = ReturnType<AdminBinding['$']['class']>
|
|
193
|
-
|
|
194
98
|
function instanceFromSummary(input: unknown): InstanceInfo {
|
|
195
99
|
const value = record(input, 'Admin Instance summary')
|
|
196
100
|
const failure = value.failure === undefined ? undefined : record(value.failure, 'Admin failure')
|
|
197
101
|
return Object.freeze({
|
|
198
|
-
id:
|
|
102
|
+
id: requiredNodePath(value.id, 'Admin Instance id'),
|
|
199
103
|
slug: requiredString(value.slug, 'Admin Instance slug'),
|
|
200
104
|
url: optionalStringValue(value.url) ?? '',
|
|
201
|
-
state: instanceState(value.state),
|
|
202
105
|
...(value.hostId === undefined
|
|
203
106
|
? {}
|
|
204
|
-
: { hostId:
|
|
205
|
-
...(value.region === undefined
|
|
107
|
+
: { hostId: requiredNodePath(value.hostId, 'Admin Host id') }),
|
|
108
|
+
...(value.region === undefined
|
|
109
|
+
? {}
|
|
110
|
+
: { region: requiredString(value.region, 'Admin Host region') }),
|
|
111
|
+
state: instanceState(value.state),
|
|
206
112
|
...(value.phase === undefined
|
|
207
113
|
? {}
|
|
208
114
|
: { phase: requiredString(value.phase, 'Admin Instance phase') }),
|
|
@@ -212,6 +118,9 @@ function instanceFromSummary(input: unknown): InstanceInfo {
|
|
|
212
118
|
...(value.createdAt === undefined
|
|
213
119
|
? {}
|
|
214
120
|
: { createdAt: requiredString(value.createdAt, 'Admin Instance creation time') }),
|
|
121
|
+
...(value.updatedAt === undefined
|
|
122
|
+
? {}
|
|
123
|
+
: { updatedAt: requiredString(value.updatedAt, 'Admin Instance update time') }),
|
|
215
124
|
...(value.organizationId === undefined
|
|
216
125
|
? {}
|
|
217
126
|
: { organizationId: requiredString(value.organizationId, 'Admin organization id') }),
|
|
@@ -223,8 +132,8 @@ function domainInstallReceipt(input: unknown): DomainInstallReceipt {
|
|
|
223
132
|
const failure = value.failure === undefined ? undefined : record(value.failure, 'Admin failure')
|
|
224
133
|
if (typeof value.ok !== 'boolean') throw new TypeError('Admin Domain install outcome is invalid.')
|
|
225
134
|
return Object.freeze({
|
|
226
|
-
domain:
|
|
227
|
-
instance:
|
|
135
|
+
domain: requiredNodePath(value.domain, 'Admin Domain reference'),
|
|
136
|
+
instance: requiredNodePath(value.instance, 'Admin Instance reference'),
|
|
228
137
|
origin: requiredString(value.origin, 'Installed Domain origin'),
|
|
229
138
|
ok: value.ok,
|
|
230
139
|
...(value.installedRevision === undefined
|
|
@@ -238,13 +147,6 @@ function domainInstallReceipt(input: unknown): DomainInstallReceipt {
|
|
|
238
147
|
})
|
|
239
148
|
}
|
|
240
149
|
|
|
241
|
-
function requiredStringProperty(definition: DynamicDefinition, node: Node, name: string): string {
|
|
242
|
-
return requiredString(
|
|
243
|
-
node.props[definition.$.property(name).key],
|
|
244
|
-
`Admin ${definition.name}.${name}`,
|
|
245
|
-
)
|
|
246
|
-
}
|
|
247
|
-
|
|
248
150
|
function instanceState(input: unknown): InstanceState {
|
|
249
151
|
if (
|
|
250
152
|
input === 'provisioning' ||
|
|
@@ -263,6 +165,15 @@ function requiredString(input: unknown, label: string): string {
|
|
|
263
165
|
return input
|
|
264
166
|
}
|
|
265
167
|
|
|
168
|
+
function requiredNodePath(input: unknown, label: string): string {
|
|
169
|
+
const value = requiredString(input, label)
|
|
170
|
+
try {
|
|
171
|
+
return Path.parse(value).raw
|
|
172
|
+
} catch {
|
|
173
|
+
throw new TypeError(`${label} is invalid.`)
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
|
|
266
177
|
function optionalStringValue(input: unknown): string | undefined {
|
|
267
178
|
if (input === undefined) return undefined
|
|
268
179
|
return requiredString(input, 'Admin string value')
|