@astrale-os/cli 1.0.0-beta.1 → 1.0.0-beta.10
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 +51471 -56290
- package/dist/public/connect-core.js +2028 -26846
- package/dist/public/keys/index.js +1755 -40694
- package/dist/public/paths/index.js +859 -39518
- 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 +6 -2
- package/dist/types/lib/failure-debug.d.ts +1 -0
- package/dist/types/lib/idp.d.ts +7 -0
- package/dist/types/lib/instance-target.d.ts +1 -1
- package/dist/types/lib/instance.d.ts +10 -2
- 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 +131 -0
- package/package.json +8 -10
- 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 +1 -1
- 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/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-D2xgMJvi.js → elk-api-ByzoDZWH.js} +1 -1
- package/studio/client/dist/assets/index-B0RCgUOT.js +8 -0
- package/studio/client/dist/assets/index-BiGR8Oo9.css +1 -0
- package/studio/client/dist/assets/index-N8J8EKlB.js +81 -0
- package/studio/client/dist/index.html +2 -2
- package/studio/package.json +5 -10
- 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/index-BMdnsIJA.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,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
|
})
|
|
@@ -81,11 +81,56 @@ describe('auth login command', () => {
|
|
|
81
81
|
expect(result.exitCode).toBe(0)
|
|
82
82
|
const session = JSON.parse(await readFile(join(tmp, 'idp-sessions', 'alice.json'), 'utf-8'))
|
|
83
83
|
expect(session.audience).toBe('https://kernel.example.com')
|
|
84
|
+
expect(session.clientId).toBe('client_123')
|
|
84
85
|
expect(session.subject).toBe('user_123')
|
|
85
86
|
} finally {
|
|
86
87
|
await server.stop()
|
|
87
88
|
}
|
|
88
89
|
})
|
|
90
|
+
|
|
91
|
+
test('persists an explicit client id and reuses it for a named login', async () => {
|
|
92
|
+
const server = oauthServer({
|
|
93
|
+
accessToken: unsignedJwt({
|
|
94
|
+
iss: 'http://127.0.0.1',
|
|
95
|
+
sub: 'user_123',
|
|
96
|
+
exp: 1893456000,
|
|
97
|
+
}),
|
|
98
|
+
})
|
|
99
|
+
try {
|
|
100
|
+
await writeIdpConfig(server.url)
|
|
101
|
+
|
|
102
|
+
const explicit = await runAuthLogin(
|
|
103
|
+
'--idp',
|
|
104
|
+
'test',
|
|
105
|
+
'--name',
|
|
106
|
+
'alice',
|
|
107
|
+
'--client-id',
|
|
108
|
+
'client_override',
|
|
109
|
+
'--client-credentials',
|
|
110
|
+
'--client-secret-env',
|
|
111
|
+
'TEST_CLIENT_SECRET',
|
|
112
|
+
'--raw',
|
|
113
|
+
)
|
|
114
|
+
const reused = await runAuthLogin(
|
|
115
|
+
'--idp',
|
|
116
|
+
'test',
|
|
117
|
+
'--name',
|
|
118
|
+
'alice',
|
|
119
|
+
'--client-credentials',
|
|
120
|
+
'--client-secret-env',
|
|
121
|
+
'TEST_CLIENT_SECRET',
|
|
122
|
+
'--raw',
|
|
123
|
+
)
|
|
124
|
+
|
|
125
|
+
expect(explicit.exitCode).toBe(0)
|
|
126
|
+
expect(reused.exitCode).toBe(0)
|
|
127
|
+
expect(server.clientIds()).toEqual(['client_override', 'client_override'])
|
|
128
|
+
const session = JSON.parse(await readFile(join(tmp, 'idp-sessions', 'alice.json'), 'utf-8'))
|
|
129
|
+
expect(session.clientId).toBe('client_override')
|
|
130
|
+
} finally {
|
|
131
|
+
await server.stop()
|
|
132
|
+
}
|
|
133
|
+
})
|
|
89
134
|
})
|
|
90
135
|
|
|
91
136
|
async function writeIdpConfig(baseUrl: string): Promise<void> {
|
|
@@ -131,12 +176,19 @@ async function writeIdpConfig(baseUrl: string): Promise<void> {
|
|
|
131
176
|
)
|
|
132
177
|
}
|
|
133
178
|
|
|
134
|
-
function oauthServer(args: { accessToken: string }): {
|
|
179
|
+
function oauthServer(args: { accessToken: string }): {
|
|
180
|
+
url: string
|
|
181
|
+
clientIds: () => Array<string | null>
|
|
182
|
+
stop: () => Promise<void>
|
|
183
|
+
} {
|
|
184
|
+
const clientIds: Array<string | null> = []
|
|
135
185
|
const server = Bun.serve({
|
|
136
186
|
port: 0,
|
|
137
187
|
fetch: async (request) => {
|
|
138
188
|
const url = new URL(request.url)
|
|
139
189
|
if (url.pathname !== '/token') return new Response('not found', { status: 404 })
|
|
190
|
+
const form = new URLSearchParams(await request.text())
|
|
191
|
+
clientIds.push(form.get('client_id'))
|
|
140
192
|
return Response.json({
|
|
141
193
|
access_token: args.accessToken,
|
|
142
194
|
token_type: 'Bearer',
|
|
@@ -146,6 +198,7 @@ function oauthServer(args: { accessToken: string }): { url: string; stop: () =>
|
|
|
146
198
|
})
|
|
147
199
|
return {
|
|
148
200
|
url: `http://${server.hostname}:${server.port}`,
|
|
201
|
+
clientIds: () => clientIds,
|
|
149
202
|
stop: () => server.stop(true),
|
|
150
203
|
}
|
|
151
204
|
}
|
|
@@ -1,34 +1,47 @@
|
|
|
1
1
|
import { Path } from '@astrale-os/sdk/graph/path'
|
|
2
|
+
import { bundle, defineSchema, func, method, nodeClass } from '@astrale-os/sdk/schema'
|
|
2
3
|
import { describe, expect, test } from 'bun:test'
|
|
3
4
|
|
|
4
|
-
import {
|
|
5
|
+
import { describeCallableFromBundle } from '../call-describe'
|
|
5
6
|
|
|
6
|
-
const
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
output: { mode: 'value' },
|
|
15
|
-
},
|
|
16
|
-
},
|
|
17
|
-
},
|
|
7
|
+
const createInstance = method({
|
|
8
|
+
auth: 'authenticated',
|
|
9
|
+
description: 'Create one child Instance.',
|
|
10
|
+
input: {
|
|
11
|
+
type: 'object',
|
|
12
|
+
properties: { operationId: { type: 'string' }, slug: { type: 'string' } },
|
|
13
|
+
required: ['operationId', 'slug'],
|
|
14
|
+
additionalProperties: false,
|
|
18
15
|
},
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
16
|
+
output: { type: 'object', additionalProperties: true },
|
|
17
|
+
static: true,
|
|
18
|
+
})
|
|
19
|
+
const inspectInstance = method({
|
|
20
|
+
auth: 'authenticated',
|
|
21
|
+
description: 'Inspect one child Instance.',
|
|
22
|
+
input: { type: 'object', properties: {}, additionalProperties: false },
|
|
23
|
+
output: { type: 'object', additionalProperties: true },
|
|
24
|
+
})
|
|
25
|
+
const journal = func({
|
|
26
|
+
auth: 'authenticated',
|
|
27
|
+
description: 'Read the authorized Kernel journal.',
|
|
28
|
+
input: { type: 'object', properties: {}, additionalProperties: false },
|
|
29
|
+
output: { type: 'object', additionalProperties: true },
|
|
30
|
+
})
|
|
31
|
+
const source = defineSchema('host.astrale.ai', {
|
|
32
|
+
classes: {
|
|
33
|
+
Manager: nodeClass({ methods: { createInstance } }),
|
|
34
|
+
Instance: nodeClass({ methods: { inspectInstance } }),
|
|
24
35
|
},
|
|
25
|
-
}
|
|
36
|
+
functions: { journal },
|
|
37
|
+
})
|
|
38
|
+
const installed = bundle.create(source)
|
|
26
39
|
|
|
27
|
-
describe('
|
|
28
|
-
test('reads a static Class method from the Domain
|
|
29
|
-
const described =
|
|
40
|
+
describe('describeCallableFromBundle', () => {
|
|
41
|
+
test('reads a static Class method from the resolved Domain', () => {
|
|
42
|
+
const described = describeCallableFromBundle(
|
|
30
43
|
Path.parse('/:host.astrale.ai:class.Manager:createInstance'),
|
|
31
|
-
|
|
44
|
+
installed,
|
|
32
45
|
)
|
|
33
46
|
expect(described).toMatchObject({
|
|
34
47
|
origin: 'host.astrale.ai',
|
|
@@ -36,30 +49,30 @@ describe('describeCallableFromSchema', () => {
|
|
|
36
49
|
method: 'createInstance',
|
|
37
50
|
dispatch: 'static',
|
|
38
51
|
description: 'Create one child Instance.',
|
|
39
|
-
auth: '
|
|
52
|
+
auth: 'authenticated',
|
|
40
53
|
})
|
|
41
54
|
expect(described?.input).toMatchObject({ required: ['operationId', 'slug'] })
|
|
42
55
|
})
|
|
43
56
|
|
|
44
|
-
test('finds
|
|
57
|
+
test('finds one instance method when the receiver Path is not a Class projection', () => {
|
|
45
58
|
expect(
|
|
46
|
-
|
|
47
|
-
Path.parse('/:host.astrale.ai:core.manager::
|
|
48
|
-
|
|
59
|
+
describeCallableFromBundle(
|
|
60
|
+
Path.parse('/:host.astrale.ai:core.manager::inspectInstance'),
|
|
61
|
+
installed,
|
|
49
62
|
),
|
|
50
63
|
).toMatchObject({
|
|
51
64
|
origin: 'host.astrale.ai',
|
|
52
|
-
class: '
|
|
53
|
-
method: '
|
|
65
|
+
class: 'Instance',
|
|
66
|
+
method: 'inspectInstance',
|
|
54
67
|
dispatch: 'instance',
|
|
55
68
|
})
|
|
56
69
|
})
|
|
57
70
|
|
|
58
|
-
test('reads a standalone Function from the Domain
|
|
71
|
+
test('reads a standalone Function from the resolved Domain', () => {
|
|
59
72
|
expect(
|
|
60
|
-
|
|
73
|
+
describeCallableFromBundle(Path.parse('/:host.astrale.ai:function.journal'), installed),
|
|
61
74
|
).toMatchObject({
|
|
62
|
-
origin: '
|
|
75
|
+
origin: 'host.astrale.ai',
|
|
63
76
|
function: 'journal',
|
|
64
77
|
description: 'Read the authorized Kernel journal.',
|
|
65
78
|
})
|
|
@@ -14,7 +14,11 @@ describe('call command result lifetime', () => {
|
|
|
14
14
|
}
|
|
15
15
|
|
|
16
16
|
const sessionStream = Object.assign(stream(), { async cancel() {} })
|
|
17
|
-
const materialized = await materializeCallResult({
|
|
17
|
+
const materialized = await materializeCallResult({
|
|
18
|
+
kind: 'stream',
|
|
19
|
+
invocation: { source: 'https://kernel.test' as never, id: 'test-stream' },
|
|
20
|
+
stream: sessionStream,
|
|
21
|
+
})
|
|
18
22
|
sessionOpen = false
|
|
19
23
|
|
|
20
24
|
expect(materialized).toEqual({ kind: 'stream', values: ['first', 'second'] })
|
|
@@ -1,5 +1,7 @@
|
|
|
1
|
+
import { defineSchema } from '@astrale-os/sdk/schema'
|
|
1
2
|
import { afterEach, describe, expect, mock, test } from 'bun:test'
|
|
2
3
|
|
|
4
|
+
import { releaseFor } from '../../__tests__/fixtures/publication'
|
|
3
5
|
import { installDirect } from '../domain/install'
|
|
4
6
|
|
|
5
7
|
const GENERATED = '4a4c9a18-50f6-4d84-a7b7-2d83e3e45dc8'
|
|
@@ -7,6 +9,13 @@ const RETRY = '139137b5-af47-47ce-92b2-b64a2b0c63d7'
|
|
|
7
9
|
|
|
8
10
|
const originalFetch = globalThis.fetch
|
|
9
11
|
|
|
12
|
+
const schema = defineSchema('crm.test', {})
|
|
13
|
+
const deployed = releaseFor(schema, 'https://crm.test').publication
|
|
14
|
+
|
|
15
|
+
function publicationResponse(): Response {
|
|
16
|
+
return Response.json(deployed)
|
|
17
|
+
}
|
|
18
|
+
|
|
10
19
|
class ExitError extends Error {}
|
|
11
20
|
|
|
12
21
|
afterEach(() => {
|
|
@@ -15,9 +24,7 @@ afterEach(() => {
|
|
|
15
24
|
|
|
16
25
|
describe('direct domain install operation identity', () => {
|
|
17
26
|
test('generates one operation before transport and exposes it for recovery', async () => {
|
|
18
|
-
globalThis.fetch = mock(async () =>
|
|
19
|
-
Response.json({ domainName: 'crm.test' }),
|
|
20
|
-
) as unknown as typeof fetch
|
|
27
|
+
globalThis.fetch = mock(async () => publicationResponse()) as unknown as typeof fetch
|
|
21
28
|
const create = mock(() => GENERATED)
|
|
22
29
|
const requests: unknown[] = []
|
|
23
30
|
let recovery: unknown
|
|
@@ -54,9 +61,7 @@ describe('direct domain install operation identity', () => {
|
|
|
54
61
|
})
|
|
55
62
|
|
|
56
63
|
test('accepts an explicit operation only as the exact retry identity', async () => {
|
|
57
|
-
globalThis.fetch = mock(async () =>
|
|
58
|
-
Response.json({ domainName: 'crm.test' }),
|
|
59
|
-
) as unknown as typeof fetch
|
|
64
|
+
globalThis.fetch = mock(async () => publicationResponse()) as unknown as typeof fetch
|
|
60
65
|
const create = mock(() => GENERATED)
|
|
61
66
|
const accepted: string[] = []
|
|
62
67
|
const requests: unknown[] = []
|
|
@@ -95,7 +100,7 @@ describe('direct domain install operation identity', () => {
|
|
|
95
100
|
test('rejects a non-UUID operation before metadata or Kernel transport', async () => {
|
|
96
101
|
const originalExit = process.exit
|
|
97
102
|
const originalWrite = process.stderr.write
|
|
98
|
-
const fetch = mock(async () =>
|
|
103
|
+
const fetch = mock(async () => publicationResponse())
|
|
99
104
|
const run = mock(async () => undefined)
|
|
100
105
|
globalThis.fetch = fetch as unknown as typeof globalThis.fetch
|
|
101
106
|
process.exit = (() => {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { ResponseError } from '@astrale-os/
|
|
1
|
+
import { ResponseError } from '@astrale-os/sdk/client'
|
|
2
2
|
import { afterEach, beforeEach, describe, expect, mock, test } from 'bun:test'
|
|
3
3
|
|
|
4
4
|
import type { OwnedInstanceInfo } from '../../lib/admin-instance'
|
|
@@ -11,6 +11,10 @@ class ExitError extends Error {
|
|
|
11
11
|
|
|
12
12
|
const calls: Array<{ path: string; params: unknown }> = []
|
|
13
13
|
let inventory: OwnedInstanceInfo[] = []
|
|
14
|
+
const TEST_INVOCATION = {
|
|
15
|
+
source: 'https://kernel.test',
|
|
16
|
+
id: 'domain-install-owned',
|
|
17
|
+
} as ConstructorParameters<typeof ResponseError>[2]
|
|
14
18
|
|
|
15
19
|
const hostCall = mock(async (call: { target: string; input: unknown }): Promise<unknown> => {
|
|
16
20
|
const path = call.target
|
|
@@ -137,7 +141,7 @@ describe('admin domain install owner boundary', () => {
|
|
|
137
141
|
createdAt: '2026-08-20T00:00:00.000Z',
|
|
138
142
|
updatedAt: '2026-08-20T00:00:00.000Z',
|
|
139
143
|
}
|
|
140
|
-
const failure = new ResponseError(5001, 'Schema operation is not supported.', {
|
|
144
|
+
const failure = new ResponseError(5001, 'Schema operation is not supported.', TEST_INVOCATION, {
|
|
141
145
|
code: 'SCHEMA_UPGRADE_INCOMPATIBLE',
|
|
142
146
|
details: {
|
|
143
147
|
phase: 'upgrade',
|