@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,9 +1,9 @@
|
|
|
1
|
-
import type { Fetch } from '@astrale-os/kernel-client'
|
|
2
1
|
import type { IssuerId } from '@astrale-os/sdk/auth'
|
|
2
|
+
import type { Fetch } from '@astrale-os/sdk/client'
|
|
3
3
|
|
|
4
|
-
import {
|
|
5
|
-
import { createAuth } from '@astrale-os/kernel-client/auth'
|
|
4
|
+
import { createAuth } from '@astrale-os/sdk/auth'
|
|
6
5
|
import { credential, exchange as exchangeProtocol } from '@astrale-os/sdk/auth'
|
|
6
|
+
import { call, Client } from '@astrale-os/sdk/client'
|
|
7
7
|
|
|
8
8
|
import type { SourceCredentialResolver } from './credential'
|
|
9
9
|
import type { ConnectionTarget } from './target'
|
|
@@ -11,7 +11,7 @@ import type { ConnectionTarget } from './target'
|
|
|
11
11
|
import { AstraleError } from '../errors'
|
|
12
12
|
import { ExchangeCredentialCache } from '../state/exchange-credentials'
|
|
13
13
|
|
|
14
|
-
const EXCHANGE_TTL_SECONDS =
|
|
14
|
+
const EXCHANGE_TTL_SECONDS = 5 * 60
|
|
15
15
|
const MAXIMUM_RESPONSE_BYTES = 256 * 1024
|
|
16
16
|
|
|
17
17
|
/** Exchange exact authenticated User authority for a Domain bearer bound to this Kernel. */
|
|
@@ -27,53 +27,78 @@ export function createExchangeCredentialResolver(
|
|
|
27
27
|
async resolve(kernelIssuer: IssuerId, signal: AbortSignal): Promise<string> {
|
|
28
28
|
requireLive(signal)
|
|
29
29
|
const sourceToken = await source.resolve(kernelIssuer, signal)
|
|
30
|
-
const
|
|
30
|
+
const sourceIdentity = sourceCacheIdentity(sourceToken)
|
|
31
31
|
requireLive(signal)
|
|
32
32
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
const authenticated = client.as(sourceToken)
|
|
36
|
-
const auth = createAuth(async (path, input, options) => {
|
|
37
|
-
const result = await authenticated.call(call(path, input), {
|
|
38
|
-
...options,
|
|
39
|
-
delegate: { ttlSeconds: delegationTtlSeconds },
|
|
40
|
-
})
|
|
41
|
-
return result.value
|
|
42
|
-
})
|
|
43
|
-
const user = await auth.whoami({ signal })
|
|
44
|
-
const key = Object.freeze({
|
|
33
|
+
return await cache.getOrRefresh(
|
|
34
|
+
Object.freeze({
|
|
45
35
|
kernelIssuer,
|
|
46
36
|
domainIssuer: target.domainIssuer,
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
37
|
+
sourceIssuer: sourceIdentity.issuer,
|
|
38
|
+
sourceSubject: sourceIdentity.subject,
|
|
39
|
+
}),
|
|
40
|
+
async () => {
|
|
41
|
+
const delegationTtlSeconds = delegationLifetime(sourceToken)
|
|
42
|
+
const client = new Client({ url: `${kernelIssuer}/invoke`, fetch, timeoutMs })
|
|
43
|
+
try {
|
|
44
|
+
const authenticated = client.as(sourceToken)
|
|
45
|
+
const auth = createAuth(async (path, input, options) => {
|
|
46
|
+
const result = await authenticated.call(call(path, input), {
|
|
47
|
+
...options,
|
|
48
|
+
delegate: { ttlSeconds: delegationTtlSeconds },
|
|
49
|
+
})
|
|
50
|
+
return result.value
|
|
51
|
+
})
|
|
52
|
+
const user = await auth.whoami({ signal })
|
|
53
|
+
let envelope: string | undefined
|
|
54
|
+
for (let attempt = 0; attempt < 3; attempt += 1) {
|
|
55
|
+
try {
|
|
56
|
+
envelope = await auth.delegate(
|
|
57
|
+
user.id,
|
|
58
|
+
{
|
|
59
|
+
audience: target.domainIssuer,
|
|
60
|
+
ttlSeconds: delegationTtlSeconds,
|
|
61
|
+
attenuation: { kind: 'identity', self: true },
|
|
62
|
+
},
|
|
63
|
+
{ signal },
|
|
64
|
+
)
|
|
65
|
+
break
|
|
66
|
+
} catch (cause) {
|
|
67
|
+
if (attempt === 2 || !unknownFunctionOutcome(cause)) throw cause
|
|
68
|
+
}
|
|
65
69
|
}
|
|
70
|
+
if (envelope === undefined) throw new Error('Token delegation returned no credential.')
|
|
71
|
+
return {
|
|
72
|
+
...(await exchange(target.domainIssuer, kernelIssuer, envelope, fetch, signal)),
|
|
73
|
+
user: user.id,
|
|
74
|
+
sourceIssuer: sourceIdentity.issuer,
|
|
75
|
+
sourceSubject: sourceIdentity.subject,
|
|
76
|
+
}
|
|
77
|
+
} finally {
|
|
78
|
+
client.close()
|
|
66
79
|
}
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
})
|
|
70
|
-
} finally {
|
|
71
|
-
client.close()
|
|
72
|
-
}
|
|
80
|
+
},
|
|
81
|
+
)
|
|
73
82
|
},
|
|
74
83
|
})
|
|
75
84
|
}
|
|
76
85
|
|
|
86
|
+
function sourceCacheIdentity(sourceToken: string): { issuer: string; subject: string } {
|
|
87
|
+
const inspected = credential.inspect(sourceToken)
|
|
88
|
+
if (
|
|
89
|
+
typeof inspected.iss !== 'string' ||
|
|
90
|
+
inspected.iss.length === 0 ||
|
|
91
|
+
typeof inspected.sub !== 'string' ||
|
|
92
|
+
inspected.sub.length === 0
|
|
93
|
+
) {
|
|
94
|
+
throw new AstraleError(
|
|
95
|
+
'TOKEN_EXCHANGE_SOURCE_INVALID',
|
|
96
|
+
'The source identity credential has no stable issuer and subject.',
|
|
97
|
+
)
|
|
98
|
+
}
|
|
99
|
+
return Object.freeze({ issuer: inspected.iss, subject: inspected.sub })
|
|
100
|
+
}
|
|
101
|
+
|
|
77
102
|
function unknownFunctionOutcome(cause: unknown): boolean {
|
|
78
103
|
if (cause === null || typeof cause !== 'object') return false
|
|
79
104
|
const error = cause as { readonly code?: unknown; readonly reason?: unknown }
|
|
@@ -111,15 +136,20 @@ async function exchange(
|
|
|
111
136
|
exchangeProtocol.paths(domainIssuer).configuration,
|
|
112
137
|
domainIssuer,
|
|
113
138
|
).toString()
|
|
114
|
-
const configurationResponse = await
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
139
|
+
const configurationResponse = await fetchExchange(
|
|
140
|
+
fetch,
|
|
141
|
+
configurationUrl,
|
|
142
|
+
{
|
|
143
|
+
method: 'GET',
|
|
144
|
+
redirect: 'error',
|
|
145
|
+
credentials: 'omit',
|
|
146
|
+
cache: 'no-store',
|
|
147
|
+
referrerPolicy: 'no-referrer',
|
|
148
|
+
headers: { accept: 'application/json' },
|
|
149
|
+
signal,
|
|
150
|
+
},
|
|
151
|
+
'Domain issuer discovery could not be reached.',
|
|
152
|
+
)
|
|
123
153
|
if (!configurationResponse.ok) {
|
|
124
154
|
throw new AstraleError(
|
|
125
155
|
'TOKEN_EXCHANGE_DISCOVERY_FAILED',
|
|
@@ -139,18 +169,23 @@ async function exchange(
|
|
|
139
169
|
)
|
|
140
170
|
}
|
|
141
171
|
|
|
142
|
-
const response = await
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
172
|
+
const response = await fetchExchange(
|
|
173
|
+
fetch,
|
|
174
|
+
endpoint,
|
|
175
|
+
{
|
|
176
|
+
method: 'POST',
|
|
177
|
+
redirect: 'error',
|
|
178
|
+
credentials: 'omit',
|
|
179
|
+
cache: 'no-store',
|
|
180
|
+
referrerPolicy: 'no-referrer',
|
|
181
|
+
headers: {
|
|
182
|
+
authorization: `Bearer ${envelope}`,
|
|
183
|
+
accept: exchangeProtocol.MEDIA_TYPE,
|
|
184
|
+
},
|
|
185
|
+
signal,
|
|
151
186
|
},
|
|
152
|
-
|
|
153
|
-
|
|
187
|
+
'Domain token exchange could not be reached.',
|
|
188
|
+
)
|
|
154
189
|
const body = await boundedJson(response, signal)
|
|
155
190
|
if (!response.ok) {
|
|
156
191
|
let admitted: exchangeProtocol.ErrorResponse
|
|
@@ -193,6 +228,20 @@ async function exchange(
|
|
|
193
228
|
return Object.freeze({ credential: exchanged.token, expiresAt: exchanged.expiresAt })
|
|
194
229
|
}
|
|
195
230
|
|
|
231
|
+
async function fetchExchange(
|
|
232
|
+
fetch: Fetch,
|
|
233
|
+
input: string,
|
|
234
|
+
init: RequestInit,
|
|
235
|
+
message: string,
|
|
236
|
+
): Promise<Response> {
|
|
237
|
+
try {
|
|
238
|
+
return await fetch(input, init)
|
|
239
|
+
} catch (cause) {
|
|
240
|
+
if (init.signal?.aborted) throw cause
|
|
241
|
+
throw new AstraleError('TOKEN_EXCHANGE_UNAVAILABLE', message, undefined, { cause })
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
|
|
196
245
|
function requireExchangeTransport(
|
|
197
246
|
target: ConnectionTarget & { readonly domainIssuer: IssuerId },
|
|
198
247
|
): void {
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import { AuthValueError } from '@astrale-os/sdk/auth'
|
|
2
|
+
import { ClientError, ProtocolError, ResponseError, TransportError } from '@astrale-os/sdk/client'
|
|
3
|
+
import { NodeUnavailableError } from '@astrale-os/sdk/client'
|
|
4
|
+
import { PathError } from '@astrale-os/sdk/graph/path'
|
|
5
|
+
|
|
6
|
+
import type { FailureDiagnostic, TransportDiagnosticContext } from './model'
|
|
7
|
+
|
|
8
|
+
import { AstraleError } from '../../errors'
|
|
9
|
+
|
|
10
|
+
export function classifyFailure(error: unknown): FailureDiagnostic {
|
|
11
|
+
if (error instanceof AstraleError) return simple(error.code, error.message, error.hint)
|
|
12
|
+
if (error instanceof TransportError) {
|
|
13
|
+
const context = transportContext(error)
|
|
14
|
+
if (context === undefined) return simple('TRANSPORT_ERROR', error.message)
|
|
15
|
+
return {
|
|
16
|
+
kind: 'transport',
|
|
17
|
+
code: lifecycleCode(error.phase),
|
|
18
|
+
message: error.message,
|
|
19
|
+
phase: error.phase,
|
|
20
|
+
context,
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
if (error instanceof ResponseError) {
|
|
24
|
+
return {
|
|
25
|
+
kind: 'response',
|
|
26
|
+
code: error.code,
|
|
27
|
+
message: error.message,
|
|
28
|
+
...(error.reason === undefined ? {} : { reason: error.reason }),
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
if (error instanceof ClientError) {
|
|
32
|
+
const failure = sessionFailure(error)
|
|
33
|
+
if (failure !== undefined) return simple(lifecycleCode(failure), error.message)
|
|
34
|
+
}
|
|
35
|
+
if (error instanceof ProtocolError) return simple('PROTOCOL_ERROR', error.message)
|
|
36
|
+
if (error instanceof NodeUnavailableError)
|
|
37
|
+
return simple(
|
|
38
|
+
'NODE_UNAVAILABLE',
|
|
39
|
+
error.message,
|
|
40
|
+
'If this is a callable Path, use `astrale call` or `astrale introspect`.',
|
|
41
|
+
)
|
|
42
|
+
if (error instanceof PathError) return simple('PATH_INVALID', error.message)
|
|
43
|
+
if (error instanceof AuthValueError) return simple('AUTH_VALUE_INVALID', error.message)
|
|
44
|
+
if (error instanceof ClientError) return simple('CLIENT_ERROR', error.message)
|
|
45
|
+
return simple('UNEXPECTED_ERROR', 'The CLI encountered an unexpected internal failure.')
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
type TransportCode = Extract<FailureDiagnostic, { kind: 'transport' }>['code']
|
|
49
|
+
type SessionFailure = 'cancelled' | 'closed' | 'timeout'
|
|
50
|
+
|
|
51
|
+
function lifecycleCode(value: TransportError['phase']): TransportCode
|
|
52
|
+
function lifecycleCode(value: SessionFailure): string
|
|
53
|
+
function lifecycleCode(value: TransportError['phase'] | SessionFailure): string {
|
|
54
|
+
if (value === 'connect') return 'CONNECTION_ERROR'
|
|
55
|
+
if (value === 'timeout') return 'TIMEOUT'
|
|
56
|
+
if (value === 'closed') return 'DISCONNECTED'
|
|
57
|
+
return value === 'cancelled' ? 'CANCELLED' : 'TRANSPORT_ERROR'
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function transportContext(error: TransportError): TransportDiagnosticContext | undefined {
|
|
61
|
+
const evidence = error as TransportError & {
|
|
62
|
+
readonly context?: unknown
|
|
63
|
+
readonly delivery?: unknown
|
|
64
|
+
readonly invocation?: unknown
|
|
65
|
+
}
|
|
66
|
+
if (record(evidence.context)) {
|
|
67
|
+
if (
|
|
68
|
+
evidence.context.kind === 'acquisition' &&
|
|
69
|
+
(evidence.context.resource === 'publication' || evidence.context.resource === 'bundle')
|
|
70
|
+
) {
|
|
71
|
+
return { kind: 'acquisition', resource: evidence.context.resource }
|
|
72
|
+
}
|
|
73
|
+
if (
|
|
74
|
+
evidence.context.kind === 'invocation' &&
|
|
75
|
+
(evidence.context.delivery === 'not-sent' || evidence.context.delivery === 'unknown')
|
|
76
|
+
) {
|
|
77
|
+
return {
|
|
78
|
+
kind: 'invocation',
|
|
79
|
+
delivery: evidence.context.delivery,
|
|
80
|
+
...(evidence.context.invocation === undefined
|
|
81
|
+
? {}
|
|
82
|
+
: { invocation: evidence.context.invocation }),
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
if (evidence.delivery !== 'not-sent' && evidence.delivery !== 'unknown') return undefined
|
|
87
|
+
return {
|
|
88
|
+
kind: 'invocation',
|
|
89
|
+
delivery: evidence.delivery,
|
|
90
|
+
...(evidence.invocation === undefined ? {} : { invocation: evidence.invocation }),
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
function sessionFailure(error: ClientError): SessionFailure | undefined {
|
|
95
|
+
const failure = (error as ClientError & { readonly failure?: unknown }).failure
|
|
96
|
+
return failure === 'cancelled' || failure === 'closed' || failure === 'timeout'
|
|
97
|
+
? failure
|
|
98
|
+
: undefined
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
function record(input: unknown): input is Readonly<Record<string, unknown>> {
|
|
102
|
+
return input !== null && typeof input === 'object' && !Array.isArray(input)
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
const simple = (code: string, message: string, hint?: string): FailureDiagnostic => ({
|
|
106
|
+
kind: 'simple',
|
|
107
|
+
code,
|
|
108
|
+
message,
|
|
109
|
+
...(hint === undefined ? {} : { hint }),
|
|
110
|
+
})
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { OperationRecovery } from '../command'
|
|
2
|
+
|
|
3
|
+
import { printFailureDebug } from '../../lib/failure-debug'
|
|
4
|
+
import { classifyFailure } from './classify'
|
|
5
|
+
import { renderFailure } from './render'
|
|
6
|
+
|
|
7
|
+
export { functionInputIssues, schemaUpgradeHint } from '../reasons'
|
|
8
|
+
|
|
9
|
+
export async function formatKernelError(
|
|
10
|
+
error: unknown,
|
|
11
|
+
machine: boolean,
|
|
12
|
+
urlArg = '',
|
|
13
|
+
debug = false,
|
|
14
|
+
options: { recovery?: OperationRecovery } = {},
|
|
15
|
+
): Promise<void> {
|
|
16
|
+
renderFailure(classifyFailure(error), machine, urlArg, options.recovery)
|
|
17
|
+
if (debug) printFailureDebug(error, urlArg)
|
|
18
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
export type TransportDiagnosticContext =
|
|
2
|
+
| { readonly kind: 'acquisition'; readonly resource: 'publication' | 'bundle' }
|
|
3
|
+
| {
|
|
4
|
+
readonly kind: 'invocation'
|
|
5
|
+
readonly delivery: 'not-sent' | 'unknown'
|
|
6
|
+
readonly invocation?: unknown
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export type FailureDiagnostic =
|
|
10
|
+
| {
|
|
11
|
+
readonly kind: 'simple'
|
|
12
|
+
readonly code: string
|
|
13
|
+
readonly message: string
|
|
14
|
+
readonly hint?: string
|
|
15
|
+
}
|
|
16
|
+
| {
|
|
17
|
+
readonly kind: 'response'
|
|
18
|
+
readonly code: number
|
|
19
|
+
readonly message: string
|
|
20
|
+
readonly reason?: unknown
|
|
21
|
+
}
|
|
22
|
+
| {
|
|
23
|
+
readonly kind: 'transport'
|
|
24
|
+
readonly code: 'CONNECTION_ERROR' | 'DISCONNECTED' | 'TIMEOUT' | 'TRANSPORT_ERROR'
|
|
25
|
+
readonly message: string
|
|
26
|
+
readonly phase: string
|
|
27
|
+
readonly context: TransportDiagnosticContext
|
|
28
|
+
}
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
import chalk from 'chalk'
|
|
2
|
+
|
|
3
|
+
import type { OperationRecovery } from '../command'
|
|
4
|
+
import type { FailureDiagnostic } from './model'
|
|
5
|
+
|
|
6
|
+
import { log } from '../../lib/log'
|
|
7
|
+
import {
|
|
8
|
+
functionInputIssues,
|
|
9
|
+
queryInputRepair,
|
|
10
|
+
reasonCode,
|
|
11
|
+
schemaDataRemovalHint,
|
|
12
|
+
schemaUpgradeDetails,
|
|
13
|
+
schemaUpgradeHint,
|
|
14
|
+
} from '../reasons'
|
|
15
|
+
|
|
16
|
+
export function renderFailure(
|
|
17
|
+
failure: FailureDiagnostic,
|
|
18
|
+
machine: boolean,
|
|
19
|
+
url: string,
|
|
20
|
+
recovery: OperationRecovery | undefined,
|
|
21
|
+
): void {
|
|
22
|
+
if (failure.kind === 'transport') return renderTransport(failure, machine, url, recovery)
|
|
23
|
+
if (failure.kind === 'response') return renderResponse(failure, machine)
|
|
24
|
+
if (machine)
|
|
25
|
+
return writeRaw({ error: failure.code, message: failure.message, hint: failure.hint })
|
|
26
|
+
log.error(`${chalk.bold(failure.code)}: ${failure.message}`)
|
|
27
|
+
if (failure.hint) log.dim(` ${failure.hint}`)
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function renderTransport(
|
|
31
|
+
failure: Extract<FailureDiagnostic, { kind: 'transport' }>,
|
|
32
|
+
machine: boolean,
|
|
33
|
+
url: string,
|
|
34
|
+
recovery: OperationRecovery | undefined,
|
|
35
|
+
): void {
|
|
36
|
+
const invocation = failure.context.kind === 'invocation' ? failure.context : undefined
|
|
37
|
+
if (machine) {
|
|
38
|
+
writeRaw({
|
|
39
|
+
error: failure.code,
|
|
40
|
+
message: failure.message,
|
|
41
|
+
phase: failure.phase,
|
|
42
|
+
transport: failure.context,
|
|
43
|
+
...(url === '' ? {} : { url }),
|
|
44
|
+
...(invocation?.delivery === 'unknown' && recovery !== undefined ? recovery : {}),
|
|
45
|
+
})
|
|
46
|
+
return
|
|
47
|
+
}
|
|
48
|
+
log.error(`${chalk.bold(failure.code)}: ${failure.message}`)
|
|
49
|
+
if (url) log.dim(` target: ${url}`)
|
|
50
|
+
log.dim(` phase: ${failure.phase}`)
|
|
51
|
+
if (failure.phase === 'connect') log.dim(' Check the target and run `astrale status`.')
|
|
52
|
+
else if (failure.phase === 'timeout') log.dim(' Try increasing `--timeout`.')
|
|
53
|
+
else if (invocation?.delivery === 'unknown') renderRecovery(recovery)
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function renderResponse(
|
|
57
|
+
failure: Extract<FailureDiagnostic, { kind: 'response' }>,
|
|
58
|
+
machine: boolean,
|
|
59
|
+
): void {
|
|
60
|
+
const code = reasonCode(failure.reason)
|
|
61
|
+
const issues = functionInputIssues(failure.reason)
|
|
62
|
+
const removalHint = schemaDataRemovalHint(failure.reason)
|
|
63
|
+
const addressNotPublic = code === 'SCHEMA_DOMAIN_ADDRESS_NOT_PUBLIC'
|
|
64
|
+
const upgrade = schemaUpgradeDetails(failure.reason)
|
|
65
|
+
const message = addressNotPublic
|
|
66
|
+
? 'Expose the Domain through a public HTTPS URL or public tunnel, then retry.'
|
|
67
|
+
: removalHint && !machine
|
|
68
|
+
? 'Existing business data still uses schema definitions being removed.'
|
|
69
|
+
: failure.message
|
|
70
|
+
const hint =
|
|
71
|
+
code === 'FUNCTION_INPUT_INVALID' && (machine || issues.length === 0)
|
|
72
|
+
? 'Use `astrale introspect <path>` to see the callable input.'
|
|
73
|
+
: (removalHint ?? (upgrade ? schemaUpgradeHint(upgrade) : undefined))
|
|
74
|
+
if (machine) {
|
|
75
|
+
writeRaw({
|
|
76
|
+
error: 'RESPONSE_ERROR',
|
|
77
|
+
code: failure.code,
|
|
78
|
+
message,
|
|
79
|
+
...(failure.reason === undefined ? {} : { reason: failure.reason }),
|
|
80
|
+
...(hint === undefined ? {} : { hint }),
|
|
81
|
+
})
|
|
82
|
+
return
|
|
83
|
+
}
|
|
84
|
+
const label = addressNotPublic
|
|
85
|
+
? 'SCHEMA_DOMAIN_ADDRESS_NOT_PUBLIC'
|
|
86
|
+
: removalHint
|
|
87
|
+
? 'DATA_MIGRATION_REQUIRED'
|
|
88
|
+
: `RESPONSE_ERROR(${failure.code})`
|
|
89
|
+
log.error(`${chalk.bold(label)}: ${message}`)
|
|
90
|
+
if (code && !addressNotPublic && !removalHint) log.dim(` reason: ${code}`)
|
|
91
|
+
for (const issue of issues)
|
|
92
|
+
console.log(
|
|
93
|
+
chalk.red(` ${issue.path || '<input>'}: ${issue.message} (${chalk.dim(issue.code)})`),
|
|
94
|
+
)
|
|
95
|
+
const repair = queryInputRepair(failure.reason)
|
|
96
|
+
if (repair?.phase === 'plan') log.dim(` ${repair.path ?? '/'} ${repair.issue}`)
|
|
97
|
+
else if (repair?.phase === 'limit')
|
|
98
|
+
log.dim(` ${repair.path ?? '/'} ${repair.limit} limit ${repair.actual}/${repair.maximum}`)
|
|
99
|
+
else if (repair) log.dim(` ${repair.path} ${repair.phase} input`)
|
|
100
|
+
if (upgrade?.issue === 'issuer-changed') {
|
|
101
|
+
log.dim(` installed issuer: ${upgrade.installedIssuer}`)
|
|
102
|
+
log.dim(` replacement issuer: ${upgrade.replacementIssuer}`)
|
|
103
|
+
}
|
|
104
|
+
if (hint) log.dim(` ${hint}`)
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
function renderRecovery(recovery: OperationRecovery | undefined): void {
|
|
108
|
+
if (!recovery)
|
|
109
|
+
return log.dim(' Delivery is unknown; do not automatically retry a mutating call.')
|
|
110
|
+
log.dim(' Delivery is unknown; retry with the same operation id:')
|
|
111
|
+
log.dim(` operation: ${recovery.operation}`)
|
|
112
|
+
log.dim(` ${recovery.retry}`)
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
const writeRaw = (payload: Record<string, unknown>): void =>
|
|
116
|
+
void process.stderr.write(`${JSON.stringify(payload)}\n`)
|
|
@@ -150,6 +150,22 @@ export function schemaUpgradeHint(details: SchemaUpgradeDetails): string {
|
|
|
150
150
|
)
|
|
151
151
|
}
|
|
152
152
|
|
|
153
|
+
export function schemaDataRemovalHint(reason: unknown): string | undefined {
|
|
154
|
+
if (!reasonWithCode(reason, 'DATA_MIGRATION_REQUIRED')) return undefined
|
|
155
|
+
const requirements = reason.details.requirements
|
|
156
|
+
if (
|
|
157
|
+
!Array.isArray(requirements) ||
|
|
158
|
+
requirements.length === 0 ||
|
|
159
|
+
!requirements.every(
|
|
160
|
+
(item) =>
|
|
161
|
+
record(item) && item.operation === 'remove-facts' && item.reason === 'destructive-change',
|
|
162
|
+
)
|
|
163
|
+
) {
|
|
164
|
+
return undefined
|
|
165
|
+
}
|
|
166
|
+
return 'Delete this data explicitly, then retry. No data was deleted.'
|
|
167
|
+
}
|
|
168
|
+
|
|
153
169
|
function reasonWithCode(
|
|
154
170
|
input: unknown,
|
|
155
171
|
code: string,
|
package/src/connection/self.ts
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
|
-
import
|
|
1
|
+
import { NodeUnavailableError } from '@astrale-os/sdk/client'
|
|
2
2
|
|
|
3
|
+
import type { ConnectionContext } from './session'
|
|
4
|
+
|
|
5
|
+
import { AstraleError } from '../errors'
|
|
3
6
|
import { containsSelfRef, expandSelfReferences } from '../lib/self'
|
|
4
|
-
import { withClientSession } from './session'
|
|
5
7
|
|
|
6
8
|
/** Metadata attached to errors after an authenticated caller-authored @self expansion. */
|
|
7
9
|
export interface SelfExpansionMeta {
|
|
@@ -12,7 +14,7 @@ export interface SelfExpansionMeta {
|
|
|
12
14
|
}
|
|
13
15
|
|
|
14
16
|
/**
|
|
15
|
-
* Run `action`; if it throws
|
|
17
|
+
* Run `action`; if it throws NodeUnavailableError, retain the authenticated
|
|
16
18
|
* expansion for precise diagnostics. The error is re-thrown either way.
|
|
17
19
|
*/
|
|
18
20
|
export async function withSelfHint<T>(
|
|
@@ -23,7 +25,7 @@ export async function withSelfHint<T>(
|
|
|
23
25
|
try {
|
|
24
26
|
return await action()
|
|
25
27
|
} catch (err) {
|
|
26
|
-
if (err instanceof
|
|
28
|
+
if (err instanceof NodeUnavailableError) {
|
|
27
29
|
;(err as Error & { expandedFromSelf?: SelfExpansionMeta }).expandedFromSelf = meta
|
|
28
30
|
}
|
|
29
31
|
throw err
|
|
@@ -36,40 +38,39 @@ type AuthenticatedSelf = {
|
|
|
36
38
|
}
|
|
37
39
|
|
|
38
40
|
export type ResolveSelfIdDependencies = {
|
|
39
|
-
readonly whoami?: (
|
|
41
|
+
readonly whoami?: (context: ConnectionContext) => Promise<AuthenticatedSelf>
|
|
40
42
|
}
|
|
41
43
|
|
|
42
44
|
/** Resolve @self only from the effective principal authenticated by the selected Kernel. */
|
|
43
45
|
export async function resolveSelfIdAuthenticated(
|
|
44
|
-
|
|
46
|
+
context: ConnectionContext,
|
|
45
47
|
dependencies: ResolveSelfIdDependencies = {},
|
|
46
48
|
): Promise<{ readonly id: string; readonly slug?: string }> {
|
|
47
|
-
const resolved = await (dependencies.whoami ?? whoamiSelfId)(
|
|
49
|
+
const resolved = await (dependencies.whoami ?? whoamiSelfId)(context)
|
|
48
50
|
if (typeof resolved.id !== 'string' || resolved.id.trim().length === 0) {
|
|
49
|
-
|
|
51
|
+
throw new AstraleError(
|
|
52
|
+
'SELF_RESOLUTION_FAILED',
|
|
50
53
|
'`@self` could not be resolved: authenticated Identity.whoami returned no NodeId.',
|
|
51
54
|
)
|
|
52
|
-
error.name = 'SelfResolutionError'
|
|
53
|
-
throw error
|
|
54
55
|
}
|
|
55
56
|
return resolved.slug === undefined
|
|
56
57
|
? { id: resolved.id }
|
|
57
58
|
: { id: resolved.id, slug: resolved.slug }
|
|
58
59
|
}
|
|
59
60
|
|
|
60
|
-
async function whoamiSelfId(
|
|
61
|
-
return
|
|
61
|
+
async function whoamiSelfId({ auth, target }: ConnectionContext): Promise<AuthenticatedSelf> {
|
|
62
|
+
return {
|
|
62
63
|
...(await auth.whoami()),
|
|
63
64
|
...(target.slug === undefined ? {} : { slug: target.slug }),
|
|
64
|
-
}
|
|
65
|
+
}
|
|
65
66
|
}
|
|
66
67
|
|
|
67
68
|
/** Expand @self in path-like commands and return metadata for NotFound diagnostics. */
|
|
68
69
|
export async function expandSelfInPath(
|
|
69
70
|
path: string,
|
|
70
|
-
|
|
71
|
+
context: ConnectionContext,
|
|
71
72
|
): Promise<{ readonly path: string; readonly meta?: SelfExpansionMeta }> {
|
|
72
|
-
const expanded = await expandSelfValues(path, [],
|
|
73
|
+
const expanded = await expandSelfValues(path, [], context)
|
|
73
74
|
return expanded.meta === undefined
|
|
74
75
|
? { path: expanded.path }
|
|
75
76
|
: { path: expanded.path, meta: expanded.meta }
|
|
@@ -78,20 +79,37 @@ export async function expandSelfInPath(
|
|
|
78
79
|
/** Expand @self once across one Call path and its CLI-authored string parameters. */
|
|
79
80
|
export async function expandSelfInCall(
|
|
80
81
|
path: string,
|
|
81
|
-
parameters:
|
|
82
|
-
|
|
82
|
+
parameters: Readonly<Record<string, unknown>>,
|
|
83
|
+
context: ConnectionContext,
|
|
83
84
|
): Promise<{
|
|
84
85
|
readonly path: string
|
|
85
|
-
readonly parameters:
|
|
86
|
+
readonly parameters: Readonly<Record<string, unknown>>
|
|
86
87
|
readonly meta?: SelfExpansionMeta
|
|
87
88
|
}> {
|
|
88
|
-
|
|
89
|
+
const values = Object.values(parameters).filter(
|
|
90
|
+
(value): value is string => typeof value === 'string',
|
|
91
|
+
)
|
|
92
|
+
const expanded = await expandSelfValues(path, values, context)
|
|
93
|
+
if (expanded.parameters === values) {
|
|
94
|
+
return expanded.meta === undefined
|
|
95
|
+
? { path: expanded.path, parameters }
|
|
96
|
+
: { path: expanded.path, parameters, meta: expanded.meta }
|
|
97
|
+
}
|
|
98
|
+
let index = 0
|
|
99
|
+
const entries = Object.entries(parameters).map(([key, value]) => [
|
|
100
|
+
key,
|
|
101
|
+
typeof value === 'string' ? expanded.parameters[index++] : value,
|
|
102
|
+
])
|
|
103
|
+
const resolved = Object.freeze(Object.fromEntries(entries))
|
|
104
|
+
return expanded.meta === undefined
|
|
105
|
+
? { path: expanded.path, parameters: resolved }
|
|
106
|
+
: { path: expanded.path, parameters: resolved, meta: expanded.meta }
|
|
89
107
|
}
|
|
90
108
|
|
|
91
109
|
async function expandSelfValues(
|
|
92
110
|
path: string,
|
|
93
111
|
parameters: readonly string[],
|
|
94
|
-
|
|
112
|
+
context: ConnectionContext,
|
|
95
113
|
): Promise<{
|
|
96
114
|
readonly path: string
|
|
97
115
|
readonly parameters: readonly string[]
|
|
@@ -100,7 +118,7 @@ async function expandSelfValues(
|
|
|
100
118
|
if (!containsSelfRef(path) && !parameters.some(containsSelfRef)) {
|
|
101
119
|
return { path, parameters }
|
|
102
120
|
}
|
|
103
|
-
const self = await resolveSelfIdAuthenticated(
|
|
121
|
+
const self = await resolveSelfIdAuthenticated(context)
|
|
104
122
|
const expandedPath = expandSelfReferences(path, self.id)
|
|
105
123
|
const expandedParameters = parameters.map((parameter) => expandSelfReferences(parameter, self.id))
|
|
106
124
|
const changed =
|