@astrale-os/cli 1.0.0-beta.2 → 1.0.0-beta.20
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 +16 -1
- package/dist/astrale.js +51105 -58057
- package/dist/public/connect-core.js +2085 -30702
- package/dist/public/keys/index.js +884 -29476
- package/dist/public/paths/index.js +13101 -41452
- 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 +7 -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/package.json +15 -20
- 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 +46 -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 +7 -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 +3 -14
- 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 +62 -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 +49 -0
- package/src/commands/call-describe.ts +60 -68
- package/src/commands/call.ts +71 -59
- package/src/commands/domain/install.ts +54 -35
- 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 +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 +48 -35
- package/src/commands/logs.ts +123 -33
- package/src/commands/mutate.ts +6 -5
- package/src/commands/query.ts +35 -32
- 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 +13 -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 +31 -11
- package/src/commands/view.ts +82 -37
- 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 +27 -20
- package/src/connection/__tests__/exchange.test.ts +30 -1
- package/src/connection/__tests__/failure-fixtures.ts +39 -0
- package/src/connection/__tests__/failure-safety.test.ts +75 -0
- package/src/connection/__tests__/self.test.ts +31 -11
- package/src/connection/__tests__/session.test.ts +3 -3
- package/src/connection/__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 +47 -23
- 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__/binary.test.ts +16 -1
- package/src/lib/__tests__/command-dx.test.ts +1 -1
- 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__/studio-server-deps.test.ts +1 -1
- package/src/lib/__tests__/update.test.ts +264 -11
- package/src/lib/__tests__/view-assets.test.ts +14 -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/command-dx.ts +9 -9
- 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/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/telemetry/__tests__/recorder.test.ts +1 -0
- package/src/telemetry/recorder.ts +9 -1
- package/src/ui/.spec/api.d.ts +14 -0
- package/src/ui/.spec/architecture.md +10 -0
- package/src/ui/.spec/laws.ts +36 -0
- package/src/ui/.spec/layout.ts +16 -0
- package/src/ui/__tests__/ui.test.ts +981 -0
- package/src/ui/index.ts +13 -0
- package/src/ui/lock.ts +87 -0
- package/src/ui/model.ts +83 -0
- package/src/ui/operations.ts +710 -0
- package/src/ui/project.ts +219 -0
- package/src/ui/release.ts +321 -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-7YHdeCxp.js +81 -0
- package/studio/client/dist/assets/index-C0FAnwNw.css +2 -0
- package/studio/client/dist/assets/rolldown-runtime-Dd_uD5pT.js +1 -0
- package/studio/client/dist/assets/schema-studio-BilUNb6Z.css +1 -0
- package/studio/client/dist/assets/schema-studio-CABpTfvH.js +8 -0
- package/studio/client/dist/index.html +3 -2
- package/studio/package.json +7 -13
- 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 +32 -36
- package/studio/server/agent/prompts/anchors.ts +20 -49
- package/studio/server/agent/prompts/system.test.ts +3 -2
- package/studio/server/agent/prompts/system.ts +3 -3
- package/studio/server/agent/routes.test.ts +1 -1
- package/studio/server/agent/run/coordinator.test.ts +1 -1
- package/studio/server/api/canvas.ts +0 -1
- package/studio/server/api-agent-loadout.test.ts +1 -1
- package/studio/server/api.test.ts +1 -1
- package/studio/server/cache.test.ts +23 -12
- package/studio/server/cache.ts +12 -15
- package/studio/server/domain.test.ts +50 -68
- package/studio/server/domain.ts +82 -63
- package/studio/server/handoff/copy.ts +1 -1
- package/studio/server/index.ts +1 -1
- package/studio/server/instances/probe.test.ts +1 -1
- package/studio/server/introspect/anatomy/views/routes.ts +46 -37
- package/studio/server/introspect/anatomy/views.ts +9 -14
- package/studio/server/introspect/anatomy-extras.test.ts +26 -42
- package/studio/server/introspect/anatomy.test.ts +22 -14
- package/studio/server/introspect/anatomy.ts +6 -9
- package/studio/server/introspect/bundle.ts +0 -3
- package/studio/server/introspect/canonical-schema.test.ts +94 -389
- package/studio/server/introspect/canonical-schema.ts +227 -457
- package/studio/server/introspect/core-extractor.ts +18 -127
- package/studio/server/introspect/core.ts +1 -2
- package/studio/server/introspect/diff.test.ts +11 -7
- package/studio/server/introspect/diff.ts +18 -55
- package/studio/server/introspect/extractor.ts +22 -117
- package/studio/server/introspect/overlay-tsmorph.test.ts +119 -240
- package/studio/server/introspect/overlay.test.ts +26 -54
- package/studio/server/introspect/overlay.ts +10 -37
- package/studio/server/introspect/revision.test.ts +1 -1
- package/studio/server/introspect/revision.ts +1 -1
- package/studio/server/introspect/runtime.test.ts +65 -183
- package/studio/server/introspect/runtime.ts +8 -25
- package/studio/server/introspect/schema-ir-json.test.ts +70 -0
- package/studio/server/introspect/schema-ir-json.ts +38 -68
- package/studio/server/introspect/schema-refs.test.ts +43 -88
- package/studio/server/introspect/schema-refs.ts +14 -49
- package/studio/server/introspect/source-overlay/handlers.ts +116 -636
- package/studio/server/introspect/source-overlay/kernel-calls.ts +0 -3
- package/studio/server/introspect/source-overlay/spans.ts +15 -45
- package/studio/server/state/baseline.test.ts +14 -9
- package/studio/server/state/baseline.ts +3 -5
- package/studio/server/state/visibility.ts +1 -5
- package/studio/server/views/model.ts +3 -3
- package/studio/server/views/target.test.ts +17 -74
- package/studio/server/views/target.ts +24 -50
- package/studio/server/watch.test.ts +11 -11
- package/studio/server/watch.ts +8 -4
- package/studio/server/workspace-watch.ts +2 -2
- package/studio/shared/contracts/schema.ts +38 -107
- package/studio/shared/contracts/surface.test.ts +13 -15
- package/studio/shared/contracts/workspace.ts +2 -11
- package/studio/shared/schema/identity.test.ts +20 -44
- package/studio/shared/schema/identity.ts +9 -23
- package/viewer/dist/main.js +30 -51
- 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/src/commands/view.ts
CHANGED
|
@@ -19,6 +19,7 @@ import { fatal, log } from '../lib/log'
|
|
|
19
19
|
import { isMachine, output, type RawOutputOpts } from '../lib/output'
|
|
20
20
|
import { findFreePort } from '../lib/port'
|
|
21
21
|
import { run, spawnHandle } from '../lib/proc'
|
|
22
|
+
import { admitExternalOpenOrigins } from '../lib/view/external-open-origins'
|
|
22
23
|
import { withViewPortAllocationLock } from '../lib/view/port-allocation'
|
|
23
24
|
import {
|
|
24
25
|
candidateSlug,
|
|
@@ -42,7 +43,7 @@ import {
|
|
|
42
43
|
import { snapshotText, waitForSettledSnapshot } from '../lib/view/snapshot'
|
|
43
44
|
|
|
44
45
|
/**
|
|
45
|
-
* `astrale view` — open ONE view in
|
|
46
|
+
* `astrale view` — open ONE view in a local browser shell, authenticated as
|
|
46
47
|
* the CLI identity, driveable by agent-browser (default) or a real browser.
|
|
47
48
|
* Design + protocol details: VIEW_CLI_SPEC.md at the workspace root.
|
|
48
49
|
*/
|
|
@@ -62,6 +63,7 @@ type ViewOpts = KernelCommandOpts &
|
|
|
62
63
|
sessions?: boolean
|
|
63
64
|
close?: string | boolean
|
|
64
65
|
all?: boolean
|
|
66
|
+
allowExternalOrigin?: string[]
|
|
65
67
|
}
|
|
66
68
|
|
|
67
69
|
const VIEW_PORT_BASE = 4419
|
|
@@ -89,9 +91,10 @@ export async function resolveSession(
|
|
|
89
91
|
}
|
|
90
92
|
const targetInput =
|
|
91
93
|
parsed.kind === 'target' ? parsed.path : (opts.target ?? viewOwnerTarget(parsed.path))
|
|
92
|
-
const {
|
|
93
|
-
|
|
94
|
-
|
|
94
|
+
const { target, candidates } = await withClientSession(opts, async (context) => {
|
|
95
|
+
const { path: target } = await expandSelfInPath(targetInput, context)
|
|
96
|
+
return { target, candidates: await resolveViewCandidates(context, target) }
|
|
97
|
+
})
|
|
95
98
|
|
|
96
99
|
if (opts.list) {
|
|
97
100
|
return { candidates }
|
|
@@ -150,16 +153,47 @@ async function chooseCandidate(
|
|
|
150
153
|
* die once the CLI exits. The published CLI entry is node-runnable; a dev
|
|
151
154
|
* checkout builds `dist/astrale.js` on demand (Bun is present there).
|
|
152
155
|
*/
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
156
|
+
interface ServeRuntimeEnvironment {
|
|
157
|
+
readonly entry: string | undefined
|
|
158
|
+
readonly executable: string
|
|
159
|
+
readonly exists: typeof existsSync
|
|
160
|
+
readonly find: typeof findOnPath
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
export async function resolveServeRuntime(
|
|
164
|
+
environment: Partial<ServeRuntimeEnvironment> = {},
|
|
165
|
+
): Promise<{ file: string; args: string[] }> {
|
|
166
|
+
const entry = environment.entry ?? process.argv[1]
|
|
167
|
+
const executable = environment.executable ?? process.execPath
|
|
168
|
+
const exists = environment.exists ?? existsSync
|
|
169
|
+
const find = environment.find ?? findOnPath
|
|
170
|
+
const node = await find('node')
|
|
171
|
+
if (node && entry?.endsWith('.js') && exists(entry)) return { file: node, args: [entry] }
|
|
157
172
|
if (node && entry?.endsWith('.ts')) {
|
|
158
173
|
const dist = join(dirname(entry), '..', 'dist', 'astrale.js')
|
|
159
174
|
await ensureDevDist(entry, dist)
|
|
160
|
-
if (
|
|
175
|
+
if (exists(dist)) return { file: node, args: [dist] }
|
|
176
|
+
}
|
|
177
|
+
return directServeRuntime(executable, entry, entry !== undefined && exists(entry))
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
/** Reinvoke a compiled executable without its virtual Bun filesystem entry. */
|
|
181
|
+
export function directServeRuntime(
|
|
182
|
+
executable: string,
|
|
183
|
+
entry: string | undefined,
|
|
184
|
+
entryExists = entry !== undefined && existsSync(entry),
|
|
185
|
+
): { file: string; args: string[] } {
|
|
186
|
+
return {
|
|
187
|
+
file: executable,
|
|
188
|
+
args: entry && !entry.startsWith('/$bunfs/') && entryExists ? [entry] : [],
|
|
161
189
|
}
|
|
162
|
-
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
export function viewServeInvocation(
|
|
193
|
+
runtime: { file: string; args: string[] },
|
|
194
|
+
config: string,
|
|
195
|
+
): { file: string; args: string[] } {
|
|
196
|
+
return { file: runtime.file, args: [...runtime.args, '__view-serve', '--config', config] }
|
|
163
197
|
}
|
|
164
198
|
|
|
165
199
|
async function findOnPath(name: string): Promise<string | null> {
|
|
@@ -228,6 +262,31 @@ async function startSession(view: ResolvedView, opts: ViewOpts): Promise<ViewSes
|
|
|
228
262
|
)
|
|
229
263
|
}
|
|
230
264
|
|
|
265
|
+
export function createViewServeConfig(
|
|
266
|
+
record: ViewSessionRecord,
|
|
267
|
+
opts: Pick<ViewOpts, 'allowExternalOrigin' | 'as' | 'creds' | 'instance' | 'timeout' | 'url'>,
|
|
268
|
+
kernelTarget: { url: string; kernelIssuer: string; caFile?: string },
|
|
269
|
+
): ViewServeConfig {
|
|
270
|
+
return {
|
|
271
|
+
session: record,
|
|
272
|
+
kernel: {
|
|
273
|
+
url: opts.url,
|
|
274
|
+
instance: opts.instance,
|
|
275
|
+
as: opts.as,
|
|
276
|
+
creds: opts.creds,
|
|
277
|
+
timeout: opts.timeout,
|
|
278
|
+
},
|
|
279
|
+
proxy: {
|
|
280
|
+
kernelUrl: kernelTarget.url,
|
|
281
|
+
issuer: kernelTarget.kernelIssuer,
|
|
282
|
+
caFile: kernelTarget.caFile,
|
|
283
|
+
direct: isPublicHttps(kernelTarget.url) && !kernelTarget.caFile,
|
|
284
|
+
},
|
|
285
|
+
externalOrigins: admitExternalOpenOrigins(opts.allowExternalOrigin),
|
|
286
|
+
idleMs: IDLE_MS,
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
|
|
231
290
|
/**
|
|
232
291
|
* Called under the cross-process port-allocation lock. Keep the lock until the
|
|
233
292
|
* detached child answers its readiness probe: only then is the selected port
|
|
@@ -261,34 +320,15 @@ async function startSessionLocked(
|
|
|
261
320
|
identity: opts.creds ? '(pre-signed creds)' : (opts.as ?? defaultIdentity),
|
|
262
321
|
createdAt: new Date().toISOString(),
|
|
263
322
|
}
|
|
264
|
-
const serveConfig
|
|
265
|
-
session: record,
|
|
266
|
-
kernel: {
|
|
267
|
-
url: opts.url,
|
|
268
|
-
instance: opts.instance,
|
|
269
|
-
as: opts.as,
|
|
270
|
-
creds: opts.creds,
|
|
271
|
-
timeout: opts.timeout,
|
|
272
|
-
},
|
|
273
|
-
proxy: {
|
|
274
|
-
kernelUrl: kernelTarget.url,
|
|
275
|
-
issuer: kernelTarget.kernelIssuer,
|
|
276
|
-
caFile: kernelTarget.caFile,
|
|
277
|
-
direct: isPublicHttps(kernelTarget.url) && !kernelTarget.caFile,
|
|
278
|
-
},
|
|
279
|
-
idleMs: IDLE_MS,
|
|
280
|
-
}
|
|
323
|
+
const serveConfig = createViewServeConfig(record, opts, kernelTarget)
|
|
281
324
|
|
|
282
325
|
await saveServeConfig(serveConfig)
|
|
283
326
|
const logFd = await openSessionLog(id)
|
|
284
|
-
const
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
stdio: ['ignore', logFd, logFd],
|
|
290
|
-
},
|
|
291
|
-
)
|
|
327
|
+
const invocation = viewServeInvocation(runtime, configPath(id))
|
|
328
|
+
const child = spawnHandle(invocation.file, invocation.args, {
|
|
329
|
+
detached: true,
|
|
330
|
+
stdio: ['ignore', logFd, logFd],
|
|
331
|
+
})
|
|
292
332
|
child.unref()
|
|
293
333
|
closeSync(logFd)
|
|
294
334
|
if (!child.pid) throw new Error('Failed to spawn the view session server')
|
|
@@ -484,7 +524,7 @@ async function sessionsCommand(opts: ViewOpts): Promise<void> {
|
|
|
484
524
|
|
|
485
525
|
export default {
|
|
486
526
|
name: 'view',
|
|
487
|
-
description: 'Open one view in
|
|
527
|
+
description: 'Open one view in a local browser shell your agent can drive',
|
|
488
528
|
arguments: [
|
|
489
529
|
{
|
|
490
530
|
name: 'spec',
|
|
@@ -520,11 +560,15 @@ export default {
|
|
|
520
560
|
description: 'Close a view session (bare: the only open one; with --all: every session)',
|
|
521
561
|
},
|
|
522
562
|
{ flags: '--all', description: 'With --close: close every session' },
|
|
563
|
+
{
|
|
564
|
+
flags: '--allow-external-origin <origin...>',
|
|
565
|
+
description: 'Grant this View exact HTTPS origins it may open in a new browser context',
|
|
566
|
+
},
|
|
523
567
|
],
|
|
524
568
|
afterHelpText: `
|
|
525
569
|
What it does:
|
|
526
570
|
Renders ONE view — no GUI, no cookies, no WorkOS. It resolves the view on the
|
|
527
|
-
kernel, starts a loopback session server that
|
|
571
|
+
kernel, starts a loopback session server that supplies the shell handshake (real
|
|
528
572
|
handshake via @astrale-os/shell, token minted from YOUR CLI identity, kernel
|
|
529
573
|
calls proxied), and opens the page headless in agent-browser. Driving stays
|
|
530
574
|
agent-browser's job; auth follows --as/--creds/-i like any kernel command.
|
|
@@ -538,6 +582,7 @@ Examples:
|
|
|
538
582
|
$ astrale view /:crm.example.dev:view.dashboard
|
|
539
583
|
$ astrale view /:agents.astrale.ai:view.agent --target @f00d1234 --as alice
|
|
540
584
|
$ astrale view @customer --snapshot
|
|
585
|
+
$ astrale view /:integrations.astrale.ai:view.application --allow-external-origin https://connect.nango.dev https://connect.composio.dev
|
|
541
586
|
$ astrale view --list
|
|
542
587
|
$ astrale view --sessions ; astrale view --close --all
|
|
543
588
|
`,
|
|
@@ -1,8 +1,7 @@
|
|
|
1
|
-
import type { Call } from '@astrale-os/kernel-client'
|
|
2
|
-
import type { AuthApi } from '@astrale-os/kernel-client/auth'
|
|
3
|
-
import type { GraphApi } from '@astrale-os/kernel-client/graph'
|
|
4
|
-
import type { ClientSession } from '@astrale-os/kernel-client/session'
|
|
5
1
|
import type { IssuerId } from '@astrale-os/sdk/auth'
|
|
2
|
+
import type { AuthApi } from '@astrale-os/sdk/auth'
|
|
3
|
+
import type { Call, GraphApi } from '@astrale-os/sdk/client'
|
|
4
|
+
import type { ClientSession } from '@astrale-os/sdk/client/session'
|
|
6
5
|
|
|
7
6
|
/** Existing CLI connection flags accepted by Kernel-touching commands. */
|
|
8
7
|
export interface ConnectionOptions {
|
|
@@ -78,17 +77,17 @@ export function withAdminClientSession<Value>(
|
|
|
78
77
|
/** Expand @self through the effective principal returned by authenticated Identity.whoami. */
|
|
79
78
|
export function expandSelfInPath(
|
|
80
79
|
path: string,
|
|
81
|
-
|
|
80
|
+
context: ConnectionContext,
|
|
82
81
|
): Promise<{ readonly path: string; readonly meta?: SelfExpansionMeta }>
|
|
83
82
|
|
|
84
83
|
/** Expand @self once across one Call path and its CLI-authored string parameters. */
|
|
85
84
|
export function expandSelfInCall(
|
|
86
85
|
path: string,
|
|
87
|
-
parameters:
|
|
88
|
-
|
|
86
|
+
parameters: Readonly<Record<string, unknown>>,
|
|
87
|
+
context: ConnectionContext,
|
|
89
88
|
): Promise<{
|
|
90
89
|
readonly path: string
|
|
91
|
-
readonly parameters:
|
|
90
|
+
readonly parameters: Readonly<Record<string, unknown>>
|
|
92
91
|
readonly meta?: SelfExpansionMeta
|
|
93
92
|
}>
|
|
94
93
|
|
|
@@ -40,7 +40,8 @@ customizes only the Fetch capability passed to Client. `withClientSession` and
|
|
|
40
40
|
`withAdminClientSession` are terminal lifecycle boundaries: success, failure, and cancellation all
|
|
41
41
|
close both the Client Session and its direct source-Auth client.
|
|
42
42
|
|
|
43
|
-
The command boundary
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
43
|
+
The command boundary projects typed Client failure identity, transport context, phase, and
|
|
44
|
+
invocation-only delivery evidence without inspecting a private cause message. Unknown native
|
|
45
|
+
failures become one honest unexpected diagnostic; their bounded cause graph is visible only under
|
|
46
|
+
explicit debug output. Every admitted Kernel reason remains available in machine output, while
|
|
47
|
+
human repair details require a bounded public Function issue or exact Query reason variant.
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
import type {
|
|
1
|
+
import type { Call } from '@astrale-os/sdk/client'
|
|
2
|
+
import type { SessionAuth } from '@astrale-os/sdk/client/session'
|
|
3
3
|
|
|
4
4
|
import type { ConnectionContext, ConnectionOptions, ConnectionTarget } from '../api.js'
|
|
5
5
|
|
|
@@ -111,7 +111,7 @@ export const CLI_AUTH_REGISTRATION_TARGET = defineLaw({
|
|
|
111
111
|
export const CLI_SELF_AUTHENTICATED_PRINCIPAL = defineLaw({
|
|
112
112
|
id: 'CLI-SELF-AUTHENTICATED-PRINCIPAL',
|
|
113
113
|
statement:
|
|
114
|
-
'@self resolves
|
|
114
|
+
'@self resolves inside the command-owned Client Session from authenticated Identity.whoami on the selected target, so resolution and dispatch share one lifecycle and delegated carriers never use an unverified JWT subject or stale local registration.',
|
|
115
115
|
tests: [
|
|
116
116
|
{
|
|
117
117
|
file: '__tests__/self.test.ts',
|
|
@@ -183,7 +183,7 @@ export const CLI_CONNECTION_PUBLIC_SEMANTIC_REASON = defineLaw({
|
|
|
183
183
|
export const CLI_CONNECTION_TYPED_ERROR_PRESENTATION = defineLaw({
|
|
184
184
|
id: 'CLI-CONNECTION-TYPED-ERROR-PRESENTATION',
|
|
185
185
|
statement:
|
|
186
|
-
'The command boundary maps typed Client transport phase and delivery evidence without parsing a private cause
|
|
186
|
+
'The command boundary maps typed Client failure identity, transport context, phase, and invocation-only delivery evidence without parsing a private cause; acquisition never receives retry advice, unknown native failures have one non-blank unexpected diagnostic, and raw causes appear only under explicit debug output.',
|
|
187
187
|
tests: [
|
|
188
188
|
{
|
|
189
189
|
file: '__tests__/errors.test.ts',
|
|
@@ -197,5 +197,9 @@ export const CLI_CONNECTION_TYPED_ERROR_PRESENTATION = defineLaw({
|
|
|
197
197
|
file: '__tests__/reasons.test.ts',
|
|
198
198
|
id: 'TEST-CLI-CONNECTION-ADMITS-BOUNDED-REASONS',
|
|
199
199
|
},
|
|
200
|
+
{
|
|
201
|
+
file: '__tests__/failure-safety.test.ts',
|
|
202
|
+
id: 'TEST-CLI-CONNECTION-FAILURE-SAFETY',
|
|
203
|
+
},
|
|
200
204
|
],
|
|
201
205
|
})
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { SessionAuth } from '@astrale-os/
|
|
1
|
+
import type { SessionAuth } from '@astrale-os/sdk/client/session'
|
|
2
2
|
|
|
3
3
|
import { issuer, type IssuerId } from '@astrale-os/sdk/auth'
|
|
4
4
|
import { Path } from '@astrale-os/sdk/graph/path'
|
|
@@ -6,6 +6,7 @@ import { describe, expect, test } from 'bun:test'
|
|
|
6
6
|
|
|
7
7
|
import type { AstraleConfig } from '../../lib/config'
|
|
8
8
|
|
|
9
|
+
import { bindCredentialIdentity } from '../auth'
|
|
9
10
|
import { createCliCredential, createConnectionCredential } from '../credential'
|
|
10
11
|
|
|
11
12
|
const SOURCE = issuer.accept('https://kernel.example')
|
|
@@ -20,6 +21,24 @@ const config: AstraleConfig = {
|
|
|
20
21
|
}
|
|
21
22
|
|
|
22
23
|
describe('connection credential', () => {
|
|
24
|
+
test('pins the bookmark identity while leaving explicit credentials unnamed', async () => {
|
|
25
|
+
const target = {
|
|
26
|
+
url: `${SOURCE}/invoke`,
|
|
27
|
+
kernelIssuer: SOURCE,
|
|
28
|
+
defaultIdentity: 'bookmark-owner',
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
await expect(bindCredentialIdentity({}, target)).resolves.toMatchObject({
|
|
32
|
+
as: 'bookmark-owner',
|
|
33
|
+
})
|
|
34
|
+
await expect(bindCredentialIdentity({ as: 'explicit-owner' }, target)).resolves.toEqual({
|
|
35
|
+
as: 'explicit-owner',
|
|
36
|
+
})
|
|
37
|
+
await expect(bindCredentialIdentity({ creds: 'opaque' }, target)).resolves.toEqual({
|
|
38
|
+
creds: 'opaque',
|
|
39
|
+
})
|
|
40
|
+
})
|
|
41
|
+
|
|
23
42
|
/** @evidence TEST-CLI-CONNECTION-RESOLVES-SOURCE-AUTH-BEFORE-ROUTING */
|
|
24
43
|
test('resolves source authority without destination knowledge', async () => {
|
|
25
44
|
const audiences: IssuerId[] = []
|
|
@@ -1,7 +1,9 @@
|
|
|
1
|
-
import { ResponseError
|
|
1
|
+
import { ResponseError } from '@astrale-os/sdk/client'
|
|
2
|
+
import { Path } from '@astrale-os/sdk/graph/path'
|
|
2
3
|
import { describe, expect, test } from 'bun:test'
|
|
3
4
|
|
|
4
5
|
import { formatKernelError, functionInputIssues, schemaUpgradeHint } from '../errors'
|
|
6
|
+
import { transportFailure } from './failure-fixtures'
|
|
5
7
|
|
|
6
8
|
const CONFLICT = 4001
|
|
7
9
|
const VALIDATION = 1003
|
|
@@ -19,24 +21,28 @@ describe('formatKernelError', () => {
|
|
|
19
21
|
writes.push(typeof chunk === 'string' ? chunk : new TextDecoder().decode(chunk))
|
|
20
22
|
return true
|
|
21
23
|
}) as typeof process.stderr.write
|
|
22
|
-
const error =
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
24
|
+
const error = Object.assign(
|
|
25
|
+
transportFailure('Publication discovery request failed.', 'unknown', {
|
|
26
|
+
kind: 'acquisition',
|
|
27
|
+
resource: 'publication',
|
|
28
|
+
}),
|
|
29
|
+
{
|
|
30
|
+
url: 'https://localhost:8443/kernel/host',
|
|
31
|
+
cause: Object.assign(new Error('connect ECONNREFUSED'), { code: 'ECONNREFUSED' }),
|
|
32
|
+
},
|
|
33
|
+
)
|
|
28
34
|
try {
|
|
29
|
-
await formatKernelError(error, true)
|
|
35
|
+
await formatKernelError(error, true, 'https://localhost:8443/kernel/host')
|
|
30
36
|
} finally {
|
|
31
37
|
process.stderr.write = original
|
|
32
38
|
}
|
|
33
39
|
|
|
34
40
|
expect(JSON.parse(writes[0]!)).toMatchObject({
|
|
35
|
-
error: '
|
|
41
|
+
error: 'TRANSPORT_ERROR',
|
|
36
42
|
message: 'Publication discovery request failed.',
|
|
37
43
|
url: 'https://localhost:8443/kernel/host',
|
|
38
|
-
phase: '
|
|
39
|
-
|
|
44
|
+
phase: 'unknown',
|
|
45
|
+
transport: { kind: 'acquisition', resource: 'publication' },
|
|
40
46
|
})
|
|
41
47
|
expect(writes[0]).not.toContain('ECONNREFUSED')
|
|
42
48
|
})
|
|
@@ -50,9 +56,8 @@ describe('formatKernelError', () => {
|
|
|
50
56
|
}) as typeof process.stderr.write
|
|
51
57
|
try {
|
|
52
58
|
await formatKernelError(
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
phase: 'timeout',
|
|
59
|
+
transportFailure('Request timed out.', 'timeout', {
|
|
60
|
+
kind: 'invocation',
|
|
56
61
|
delivery: 'unknown',
|
|
57
62
|
}),
|
|
58
63
|
true,
|
|
@@ -74,7 +79,7 @@ describe('formatKernelError', () => {
|
|
|
74
79
|
expect(JSON.parse(writes[0]!)).toMatchObject({
|
|
75
80
|
error: 'TIMEOUT',
|
|
76
81
|
phase: 'timeout',
|
|
77
|
-
delivery: 'unknown',
|
|
82
|
+
transport: { kind: 'invocation', delivery: 'unknown' },
|
|
78
83
|
operation: '4a4c9a18-50f6-4d84-a7b7-2d83e3e45dc8',
|
|
79
84
|
retry:
|
|
80
85
|
'astrale domain install https://crm.test --direct --operation 4a4c9a18-50f6-4d84-a7b7-2d83e3e45dc8',
|
|
@@ -229,7 +234,7 @@ describe('formatKernelError', () => {
|
|
|
229
234
|
)
|
|
230
235
|
})
|
|
231
236
|
|
|
232
|
-
test('maps SDK
|
|
237
|
+
test('maps SDK error identities to stable CLI error codes', async () => {
|
|
233
238
|
const writes: string[] = []
|
|
234
239
|
const original = process.stderr.write
|
|
235
240
|
process.stderr.write = ((chunk: string | Uint8Array) => {
|
|
@@ -237,15 +242,17 @@ describe('formatKernelError', () => {
|
|
|
237
242
|
return true
|
|
238
243
|
}) as typeof process.stderr.write
|
|
239
244
|
try {
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
245
|
+
try {
|
|
246
|
+
Path.parse('not-a-path')
|
|
247
|
+
} catch (error) {
|
|
248
|
+
await formatKernelError(error, true)
|
|
249
|
+
}
|
|
243
250
|
} finally {
|
|
244
251
|
process.stderr.write = original
|
|
245
252
|
}
|
|
246
253
|
expect(JSON.parse(writes[0]!)).toEqual({
|
|
247
254
|
error: 'PATH_INVALID',
|
|
248
|
-
message:
|
|
255
|
+
message: expect.any(String),
|
|
249
256
|
})
|
|
250
257
|
})
|
|
251
258
|
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { Fetch } from '@astrale-os/
|
|
1
|
+
import type { Fetch } from '@astrale-os/sdk/client'
|
|
2
2
|
|
|
3
3
|
import { issuer } from '@astrale-os/sdk/auth'
|
|
4
4
|
import { afterEach, beforeEach, describe, expect, test } from 'bun:test'
|
|
@@ -190,6 +190,35 @@ describe('Domain token exchange', () => {
|
|
|
190
190
|
message: 'Token exchange returned an invalid success response.',
|
|
191
191
|
})
|
|
192
192
|
})
|
|
193
|
+
|
|
194
|
+
test('normalizes native issuer Fetch failures without exposing their message', async () => {
|
|
195
|
+
const native = new AggregateError([new Error('private DNS state')], '')
|
|
196
|
+
const resolver = createExchangeCredentialResolver(
|
|
197
|
+
TARGET,
|
|
198
|
+
{ resolve: async () => SOURCE_TOKEN },
|
|
199
|
+
async (input, init) => {
|
|
200
|
+
if (String(input) === INVOCATION) {
|
|
201
|
+
const body = JSON.parse(await new Response(init?.body).text()) as Record<string, any>
|
|
202
|
+
return invocationResponse(
|
|
203
|
+
body.requestId,
|
|
204
|
+
body.call.input && Object.keys(body.call.input).length === 0
|
|
205
|
+
? { id: 'user-1' }
|
|
206
|
+
: 'kernel-destination-envelope',
|
|
207
|
+
new Headers(init?.headers).get('accept')!,
|
|
208
|
+
)
|
|
209
|
+
}
|
|
210
|
+
throw native
|
|
211
|
+
},
|
|
212
|
+
5_000,
|
|
213
|
+
new ExchangeCredentialCache(join(directory, 'native-failure.json')),
|
|
214
|
+
)
|
|
215
|
+
|
|
216
|
+
await expect(resolver.resolve(KERNEL, new AbortController().signal)).rejects.toMatchObject({
|
|
217
|
+
code: 'TOKEN_EXCHANGE_UNAVAILABLE',
|
|
218
|
+
message: 'Domain issuer discovery could not be reached.',
|
|
219
|
+
cause: native,
|
|
220
|
+
})
|
|
221
|
+
})
|
|
193
222
|
})
|
|
194
223
|
|
|
195
224
|
function exchangeFetch(
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { ClientError, TransportError } from '@astrale-os/sdk/client'
|
|
2
|
+
|
|
3
|
+
import type { TransportDiagnosticContext } from '../failure/model'
|
|
4
|
+
|
|
5
|
+
type CompatibleTransportPhase = TransportError['phase'] | 'unknown'
|
|
6
|
+
|
|
7
|
+
export function transportFailure(
|
|
8
|
+
message: string,
|
|
9
|
+
phase: CompatibleTransportPhase,
|
|
10
|
+
context: TransportDiagnosticContext,
|
|
11
|
+
): TransportError {
|
|
12
|
+
const error = new Error(message) as TransportError & {
|
|
13
|
+
context: TransportDiagnosticContext
|
|
14
|
+
}
|
|
15
|
+
Object.setPrototypeOf(error, TransportError.prototype)
|
|
16
|
+
Object.assign(error, { name: 'TransportError', phase, context })
|
|
17
|
+
return error
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export function legacyTransportFailure(
|
|
21
|
+
message: string,
|
|
22
|
+
phase: CompatibleTransportPhase,
|
|
23
|
+
delivery: 'not-sent' | 'unknown',
|
|
24
|
+
): TransportError {
|
|
25
|
+
const error = new Error(message) as TransportError & { delivery: typeof delivery }
|
|
26
|
+
Object.setPrototypeOf(error, TransportError.prototype)
|
|
27
|
+
Object.assign(error, { name: 'TransportError', phase, delivery })
|
|
28
|
+
return error
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export function sessionFailure(
|
|
32
|
+
message: string,
|
|
33
|
+
failure: 'cancelled' | 'closed' | 'timeout',
|
|
34
|
+
): ClientError {
|
|
35
|
+
const error = new Error(message) as ClientError & { failure: typeof failure }
|
|
36
|
+
Object.setPrototypeOf(error, ClientError.prototype)
|
|
37
|
+
Object.assign(error, { name: 'SessionError', failure })
|
|
38
|
+
return error
|
|
39
|
+
}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import { expect, test } from 'bun:test'
|
|
2
|
+
|
|
3
|
+
import { formatKernelError } from '../errors'
|
|
4
|
+
import { legacyTransportFailure, sessionFailure, transportFailure } from './failure-fixtures'
|
|
5
|
+
|
|
6
|
+
/** @evidence TEST-CLI-CONNECTION-FAILURE-SAFETY */
|
|
7
|
+
test('unknown and empty native failures never become ordinary diagnostics', async () => {
|
|
8
|
+
for (const failure of [new Error('private provider detail'), new Error(''), { secret: true }]) {
|
|
9
|
+
const output = await capture(() => formatKernelError(failure, true))
|
|
10
|
+
expect(JSON.parse(output)).toEqual({
|
|
11
|
+
error: 'UNEXPECTED_ERROR',
|
|
12
|
+
message: 'The CLI encountered an unexpected internal failure.',
|
|
13
|
+
})
|
|
14
|
+
expect(output).not.toContain('private provider detail')
|
|
15
|
+
}
|
|
16
|
+
})
|
|
17
|
+
|
|
18
|
+
test('Aggregate children are available only under explicit debug output', async () => {
|
|
19
|
+
const failure = new AggregateError([new Error('first private'), new Error('second private')], '')
|
|
20
|
+
const ordinary = await capture(() => formatKernelError(failure, true))
|
|
21
|
+
expect(ordinary).not.toContain('first private')
|
|
22
|
+
expect(ordinary).not.toContain('second private')
|
|
23
|
+
|
|
24
|
+
const debug = await capture(() => formatKernelError(failure, true, '', true))
|
|
25
|
+
expect(debug).toContain('aggregate: Error: first private')
|
|
26
|
+
expect(debug).toContain('aggregate: Error: second private')
|
|
27
|
+
})
|
|
28
|
+
|
|
29
|
+
test('Session and acquisition failures remain typed without unsafe recovery advice', async () => {
|
|
30
|
+
const session = await capture(() =>
|
|
31
|
+
formatKernelError(sessionFailure('Session operation timed out.', 'timeout'), true),
|
|
32
|
+
)
|
|
33
|
+
expect(JSON.parse(session)).toEqual({ error: 'TIMEOUT', message: 'Session operation timed out.' })
|
|
34
|
+
|
|
35
|
+
const acquisition = await capture(() =>
|
|
36
|
+
formatKernelError(
|
|
37
|
+
transportFailure('Publication failed.', 'unknown', {
|
|
38
|
+
kind: 'acquisition',
|
|
39
|
+
resource: 'publication',
|
|
40
|
+
}),
|
|
41
|
+
true,
|
|
42
|
+
'',
|
|
43
|
+
false,
|
|
44
|
+
{ recovery: { operation: 'must-not-leak', retry: 'must-not-leak' } },
|
|
45
|
+
),
|
|
46
|
+
)
|
|
47
|
+
expect(JSON.parse(acquisition)).toMatchObject({
|
|
48
|
+
error: 'TRANSPORT_ERROR',
|
|
49
|
+
transport: { kind: 'acquisition', resource: 'publication' },
|
|
50
|
+
})
|
|
51
|
+
expect(acquisition).not.toContain('must-not-leak')
|
|
52
|
+
|
|
53
|
+
const legacy = await capture(() =>
|
|
54
|
+
formatKernelError(legacyTransportFailure('Legacy send failed.', 'send', 'not-sent'), true),
|
|
55
|
+
)
|
|
56
|
+
expect(JSON.parse(legacy)).toMatchObject({
|
|
57
|
+
error: 'TRANSPORT_ERROR',
|
|
58
|
+
transport: { kind: 'invocation', delivery: 'not-sent' },
|
|
59
|
+
})
|
|
60
|
+
})
|
|
61
|
+
|
|
62
|
+
async function capture(action: () => Promise<void>): Promise<string> {
|
|
63
|
+
const writes: string[] = []
|
|
64
|
+
const original = process.stderr.write
|
|
65
|
+
process.stderr.write = ((chunk: string | Uint8Array) => {
|
|
66
|
+
writes.push(typeof chunk === 'string' ? chunk : new TextDecoder().decode(chunk))
|
|
67
|
+
return true
|
|
68
|
+
}) as typeof process.stderr.write
|
|
69
|
+
try {
|
|
70
|
+
await action()
|
|
71
|
+
return writes.join('')
|
|
72
|
+
} finally {
|
|
73
|
+
process.stderr.write = original
|
|
74
|
+
}
|
|
75
|
+
}
|
|
@@ -2,7 +2,11 @@ import { describe, expect, test } from 'bun:test'
|
|
|
2
2
|
import { SignJWT } from 'jose'
|
|
3
3
|
import { webcrypto } from 'node:crypto'
|
|
4
4
|
|
|
5
|
-
import {
|
|
5
|
+
import type { ConnectionContext } from '../session'
|
|
6
|
+
|
|
7
|
+
import { expandSelfInCall, resolveSelfIdAuthenticated } from '../self'
|
|
8
|
+
|
|
9
|
+
const context = Object.freeze({}) as ConnectionContext
|
|
6
10
|
|
|
7
11
|
describe('resolveSelfIdAuthenticated', () => {
|
|
8
12
|
/** @evidence TEST-CLI-SELF-USES-AUTHENTICATED-EFFECTIVE-PRINCIPAL */
|
|
@@ -14,11 +18,9 @@ describe('resolveSelfIdAuthenticated', () => {
|
|
|
14
18
|
.setSubject('manager-principal')
|
|
15
19
|
.setAudience('https://child.example')
|
|
16
20
|
.sign(key)
|
|
17
|
-
const
|
|
18
|
-
|
|
19
|
-
const resolved = await resolveSelfIdAuthenticated(opts, {
|
|
21
|
+
const resolved = await resolveSelfIdAuthenticated(context, {
|
|
20
22
|
whoami: async (received) => {
|
|
21
|
-
expect(received).toBe(
|
|
23
|
+
expect(received).toBe(context)
|
|
22
24
|
return { id: 'child-kernel-principal', slug: 'child' }
|
|
23
25
|
},
|
|
24
26
|
})
|
|
@@ -28,17 +30,35 @@ describe('resolveSelfIdAuthenticated', () => {
|
|
|
28
30
|
|
|
29
31
|
test('resolves an imported root without requiring a local registration', async () => {
|
|
30
32
|
await expect(
|
|
31
|
-
resolveSelfIdAuthenticated(
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
),
|
|
33
|
+
resolveSelfIdAuthenticated(context, {
|
|
34
|
+
whoami: async () => ({ id: 'manager-kernel-principal' }),
|
|
35
|
+
}),
|
|
35
36
|
).resolves.toEqual({ id: 'manager-kernel-principal' })
|
|
36
37
|
})
|
|
37
38
|
|
|
38
39
|
test('fails closed when authenticated whoami returns no NodeId', async () => {
|
|
39
40
|
await expect(
|
|
40
|
-
resolveSelfIdAuthenticated(
|
|
41
|
-
).rejects.toMatchObject({
|
|
41
|
+
resolveSelfIdAuthenticated(context, { whoami: async () => ({ id: ' ' }) }),
|
|
42
|
+
).rejects.toMatchObject({ code: 'SELF_RESOLUTION_FAILED' })
|
|
43
|
+
})
|
|
44
|
+
})
|
|
45
|
+
|
|
46
|
+
describe('expandSelfInCall', () => {
|
|
47
|
+
test('expands only top-level string values after local parameter admission', async () => {
|
|
48
|
+
const callContext = {
|
|
49
|
+
auth: { whoami: async () => ({ id: 'caller-id' }) },
|
|
50
|
+
target: {},
|
|
51
|
+
} as ConnectionContext
|
|
52
|
+
const expanded = await expandSelfInCall(
|
|
53
|
+
'/:people.example.dev:class.Person:get',
|
|
54
|
+
{ owner: '@self', count: 2, nested: { owner: '@self' } },
|
|
55
|
+
callContext,
|
|
56
|
+
)
|
|
57
|
+
expect(expanded.parameters).toEqual({
|
|
58
|
+
owner: '@caller-id',
|
|
59
|
+
count: 2,
|
|
60
|
+
nested: { owner: '@self' },
|
|
61
|
+
})
|
|
42
62
|
})
|
|
43
63
|
})
|
|
44
64
|
|