@astrale-os/sdk 0.5.0-beta.84 → 0.5.0-beta.86
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/CHANGELOG.md +14 -0
- package/dist/deployment/adapter/development/context.d.ts +2 -0
- package/dist/execution/identity/exchange.d.ts +10 -1
- package/dist/execution/identity/exchange.js +33 -6
- package/dist/execution/identity/identity.js +5 -2
- package/dist/execution/identity/index.d.ts +1 -1
- package/dist/execution/identity/index.js +1 -1
- package/dist/execution/index.d.ts +1 -1
- package/dist/execution/index.js +1 -1
- package/dist/tooling/cli/arguments.d.ts +1 -0
- package/dist/tooling/cli/arguments.js +18 -4
- package/dist/tooling/cli/help.js +5 -0
- package/dist/tooling/cli/orchestrate.d.ts +6 -0
- package/dist/tooling/cli/orchestrate.js +16 -10
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,19 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [0.5.0-beta.86](https://github.com/astrale-os/sdk/compare/sdk-v0.5.0-beta.85...sdk-v0.5.0-beta.86) (2026-08-30)
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
### Features
|
|
7
|
+
|
|
8
|
+
* support trusted Domain token-exchange authority ([#352](https://github.com/astrale-os/sdk/issues/352)) ([8bec391](https://github.com/astrale-os/sdk/commit/8bec3915afad8aef6fab66d6e8906ea89ca8ad39))
|
|
9
|
+
|
|
10
|
+
## [0.5.0-beta.85](https://github.com/astrale-os/sdk/compare/sdk-v0.5.0-beta.84...sdk-v0.5.0-beta.85) (2026-08-30)
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
### Features
|
|
14
|
+
|
|
15
|
+
* **dev:** accept file-backed operator credentials ([#350](https://github.com/astrale-os/sdk/issues/350)) ([8575644](https://github.com/astrale-os/sdk/commit/8575644124ae21014aafcbafd98a8465da980e83))
|
|
16
|
+
|
|
3
17
|
## [0.5.0-beta.84](https://github.com/astrale-os/sdk/compare/sdk-v0.5.0-beta.83...sdk-v0.5.0-beta.84) (2026-08-30)
|
|
4
18
|
|
|
5
19
|
|
|
@@ -3,6 +3,8 @@ import type { DevelopmentPlacement } from './placement.js';
|
|
|
3
3
|
export interface DevelopmentOverrides {
|
|
4
4
|
readonly publicUrl?: string;
|
|
5
5
|
readonly localPort?: number;
|
|
6
|
+
/** Private operator credential used by installation-owning adapters only. */
|
|
7
|
+
readonly credentialFile?: string;
|
|
6
8
|
}
|
|
7
9
|
/** Provider-neutral inputs retained for one complete development session. */
|
|
8
10
|
export interface DevelopmentContext extends DevelopmentInput {
|
|
@@ -4,6 +4,15 @@ import type { IdentityNetworkPolicy, VerificationBasisResolver } from './jwks.js
|
|
|
4
4
|
import type { SigningMaterial } from './signing.js';
|
|
5
5
|
export interface ExchangeOptions {
|
|
6
6
|
readonly maximumTtlSeconds?: number;
|
|
7
|
+
/** Authority carried by the exchanged Domain credential. Defaults to the authenticated caller. */
|
|
8
|
+
readonly grantMode?: ExchangeGrantMode;
|
|
9
|
+
}
|
|
10
|
+
export type ExchangeGrantMode = 'caller' | 'union';
|
|
11
|
+
interface AcceptedExchangeOptions {
|
|
12
|
+
readonly maximumTtlSeconds: number;
|
|
13
|
+
readonly grantMode: ExchangeGrantMode;
|
|
7
14
|
}
|
|
8
15
|
export declare function maximumExchangeTtl(input: ExchangeOptions | undefined): number;
|
|
9
|
-
export declare function tokenExchange(issuer: IssuerId, subject: string, signing: SigningMaterial, policy: IdentityNetworkPolicy, verification: VerificationBasisResolver, maximumTtlSeconds: number): exchange.Provider;
|
|
16
|
+
export declare function tokenExchange(issuer: IssuerId, subject: string, signing: SigningMaterial, policy: IdentityNetworkPolicy, verification: VerificationBasisResolver, maximumTtlSeconds: number, grantMode?: ExchangeGrantMode): exchange.Provider;
|
|
17
|
+
export declare function exchangeOptions(input: ExchangeOptions | undefined): AcceptedExchangeOptions;
|
|
18
|
+
export {};
|
|
@@ -8,21 +8,27 @@ import { CredentialRejected, IssuerUnavailable } from './errors.js';
|
|
|
8
8
|
import { verifyCredential, verifyCredentialAgainst } from './jwks.js';
|
|
9
9
|
const DEFAULT_MAXIMUM_TTL_SECONDS = 300;
|
|
10
10
|
export function maximumExchangeTtl(input) {
|
|
11
|
-
|
|
12
|
-
|
|
11
|
+
return acceptExchangeOptions(input).maximumTtlSeconds;
|
|
12
|
+
}
|
|
13
|
+
function acceptExchangeOptions(input) {
|
|
14
|
+
if (input === undefined) {
|
|
15
|
+
return Object.freeze({ maximumTtlSeconds: DEFAULT_MAXIMUM_TTL_SECONDS, grantMode: 'caller' });
|
|
16
|
+
}
|
|
13
17
|
if (input === null ||
|
|
14
18
|
typeof input !== 'object' ||
|
|
15
19
|
Array.isArray(input) ||
|
|
16
|
-
Reflect.ownKeys(input).some((key) => key !== 'maximumTtlSeconds')) {
|
|
20
|
+
Reflect.ownKeys(input).some((key) => key !== 'maximumTtlSeconds' && key !== 'grantMode')) {
|
|
17
21
|
throw new TypeError('Execution identity tokenExchange options are invalid.');
|
|
18
22
|
}
|
|
19
23
|
const value = input.maximumTtlSeconds ?? DEFAULT_MAXIMUM_TTL_SECONDS;
|
|
20
24
|
if (!Number.isSafeInteger(value) || value < 1) {
|
|
21
25
|
throw new TypeError('tokenExchange.maximumTtlSeconds must be a positive safe integer.');
|
|
22
26
|
}
|
|
23
|
-
|
|
27
|
+
const grantMode = input.grantMode ?? 'caller';
|
|
28
|
+
return Object.freeze({ maximumTtlSeconds: value, grantMode: acceptExchangeGrantMode(grantMode) });
|
|
24
29
|
}
|
|
25
|
-
export function tokenExchange(issuer, subject, signing, policy, verification, maximumTtlSeconds) {
|
|
30
|
+
export function tokenExchange(issuer, subject, signing, policy, verification, maximumTtlSeconds, grantMode = 'caller') {
|
|
31
|
+
const acceptedGrantMode = acceptExchangeGrantMode(grantMode);
|
|
26
32
|
return Object.freeze({
|
|
27
33
|
async exchange(request, context) {
|
|
28
34
|
try {
|
|
@@ -45,7 +51,7 @@ export function tokenExchange(issuer, subject, signing, policy, verification, ma
|
|
|
45
51
|
const token = await new SignJWT({
|
|
46
52
|
grant: {
|
|
47
53
|
v: 1,
|
|
48
|
-
expr:
|
|
54
|
+
expr: exchangeGrant(acceptedGrantMode, carried.credential),
|
|
49
55
|
},
|
|
50
56
|
})
|
|
51
57
|
.setProtectedHeader({
|
|
@@ -81,6 +87,27 @@ export function tokenExchange(issuer, subject, signing, policy, verification, ma
|
|
|
81
87
|
},
|
|
82
88
|
});
|
|
83
89
|
}
|
|
90
|
+
export function exchangeOptions(input) {
|
|
91
|
+
return acceptExchangeOptions(input);
|
|
92
|
+
}
|
|
93
|
+
function exchangeGrant(mode, credential) {
|
|
94
|
+
const caller = Object.freeze({ kind: 'identity', credential });
|
|
95
|
+
if (mode === 'caller')
|
|
96
|
+
return caller;
|
|
97
|
+
return Object.freeze({
|
|
98
|
+
kind: 'union',
|
|
99
|
+
operands: Object.freeze([
|
|
100
|
+
Object.freeze({ kind: 'identity', self: true }),
|
|
101
|
+
caller,
|
|
102
|
+
]),
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
function acceptExchangeGrantMode(input) {
|
|
106
|
+
if (input !== 'caller' && input !== 'union') {
|
|
107
|
+
throw new TypeError('tokenExchange.grantMode must be caller or union.');
|
|
108
|
+
}
|
|
109
|
+
return input;
|
|
110
|
+
}
|
|
84
111
|
function requireProfile(input, profile) {
|
|
85
112
|
const expected = ['aud', 'delegation', 'exp', 'iat', 'iss', 'sub'];
|
|
86
113
|
const actual = Object.keys(input.claims).sort();
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { admit, authenticate } from './authentication.js';
|
|
2
2
|
import { publicationDelivery } from './delivery.js';
|
|
3
3
|
import { httpIssuer } from './errors.js';
|
|
4
|
-
import {
|
|
4
|
+
import { exchangeOptions, tokenExchange } from './exchange.js';
|
|
5
5
|
import { createVerificationBasisResolver } from './jwks.js';
|
|
6
6
|
import { signing } from './signing.js';
|
|
7
7
|
/** Create one stateless execution identity backed by an asymmetric private JWK. */
|
|
@@ -37,7 +37,10 @@ export function createIdentity(input) {
|
|
|
37
37
|
});
|
|
38
38
|
const exchangeProvider = input.tokenExchange === false
|
|
39
39
|
? undefined
|
|
40
|
-
:
|
|
40
|
+
: (() => {
|
|
41
|
+
const options = exchangeOptions(input.tokenExchange);
|
|
42
|
+
return tokenExchange(issuer, input.subject, material, policy, verification, options.maximumTtlSeconds, options.grantMode);
|
|
43
|
+
})();
|
|
41
44
|
return identity({
|
|
42
45
|
issuer,
|
|
43
46
|
subject: input.subject,
|
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
export { createIdentity, identity, type Identity, type IdentityInput } from './identity.js';
|
|
2
|
-
export { maximumExchangeTtl, tokenExchange, type ExchangeOptions } from './exchange.js';
|
|
2
|
+
export { maximumExchangeTtl, tokenExchange, type ExchangeGrantMode, type ExchangeOptions, } from './exchange.js';
|
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
export { createIdentity, identity } from './identity.js';
|
|
2
|
-
export { maximumExchangeTtl, tokenExchange } from './exchange.js';
|
|
2
|
+
export { maximumExchangeTtl, tokenExchange, } from './exchange.js';
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
export { serve } from './serve.js';
|
|
2
2
|
export type { ExecutionContext, ServedApplication, ServeInput } from './serve.js';
|
|
3
|
-
export { createIdentity, type Identity, type IdentityInput } from './identity/index.js';
|
|
3
|
+
export { createIdentity, type ExchangeGrantMode, type ExchangeOptions, type Identity, type IdentityInput, } from './identity/index.js';
|
|
4
4
|
export { InvocationError } from './invocation/index.js';
|
|
5
5
|
export type { InvocationErrorCode } from './invocation/index.js';
|
package/dist/execution/index.js
CHANGED
|
@@ -9,6 +9,7 @@ export interface ParsedArgs {
|
|
|
9
9
|
readonly port?: number;
|
|
10
10
|
readonly host?: string;
|
|
11
11
|
readonly sessionFile?: string;
|
|
12
|
+
readonly credentialFile?: string;
|
|
12
13
|
}
|
|
13
14
|
/** Parse the frozen `astrale-domain` command grammar without performing effects. */
|
|
14
15
|
export declare function parseArgs(argv: readonly string[]): ParsedArgs;
|
|
@@ -8,6 +8,7 @@ export function parseArgs(argv) {
|
|
|
8
8
|
let port;
|
|
9
9
|
let host;
|
|
10
10
|
let sessionFile;
|
|
11
|
+
let credentialFile;
|
|
11
12
|
let format;
|
|
12
13
|
let environment;
|
|
13
14
|
const cleaned = [];
|
|
@@ -38,6 +39,12 @@ export function parseArgs(argv) {
|
|
|
38
39
|
else if (argument.startsWith('--session-file=')) {
|
|
39
40
|
sessionFile = sessionFilePath(argument.slice('--session-file='.length));
|
|
40
41
|
}
|
|
42
|
+
else if (argument === '--credential-file') {
|
|
43
|
+
credentialFile = absoluteFilePath('--credential-file', rest[++index]);
|
|
44
|
+
}
|
|
45
|
+
else if (argument.startsWith('--credential-file=')) {
|
|
46
|
+
credentialFile = absoluteFilePath('--credential-file', argument.slice('--credential-file='.length));
|
|
47
|
+
}
|
|
41
48
|
else if (argument === '--format') {
|
|
42
49
|
format = lintFormat(rest[++index]);
|
|
43
50
|
}
|
|
@@ -65,9 +72,12 @@ export function parseArgs(argv) {
|
|
|
65
72
|
if (format !== undefined && command !== 'lint') {
|
|
66
73
|
throw new Error('`--format` is only valid for `lint`.');
|
|
67
74
|
}
|
|
68
|
-
if ((port !== undefined ||
|
|
75
|
+
if ((port !== undefined ||
|
|
76
|
+
host !== undefined ||
|
|
77
|
+
sessionFile !== undefined ||
|
|
78
|
+
credentialFile !== undefined) &&
|
|
69
79
|
command !== 'dev') {
|
|
70
|
-
throw new Error('`--port`, `--host`, and `--
|
|
80
|
+
throw new Error('`--port`, `--host`, `--session-file`, and `--credential-file` are only valid for `dev`.');
|
|
71
81
|
}
|
|
72
82
|
if (environment !== undefined && !['dev', 'deploy'].includes(command ?? '')) {
|
|
73
83
|
throw new Error('`--environment` is only valid for dev or deploy.');
|
|
@@ -86,6 +96,7 @@ export function parseArgs(argv) {
|
|
|
86
96
|
...(port !== undefined ? { port } : {}),
|
|
87
97
|
...(host !== undefined ? { host } : {}),
|
|
88
98
|
...(sessionFile !== undefined ? { sessionFile } : {}),
|
|
99
|
+
...(credentialFile !== undefined ? { credentialFile } : {}),
|
|
89
100
|
};
|
|
90
101
|
case 'deploy': {
|
|
91
102
|
if (positionals.length > 1)
|
|
@@ -176,9 +187,12 @@ function requiredValue(flag, input) {
|
|
|
176
187
|
return input;
|
|
177
188
|
}
|
|
178
189
|
function sessionFilePath(input) {
|
|
179
|
-
|
|
190
|
+
return absoluteFilePath('--session-file', input);
|
|
191
|
+
}
|
|
192
|
+
function absoluteFilePath(flag, input) {
|
|
193
|
+
const value = requiredValue(flag, input);
|
|
180
194
|
if (!isAbsolute(value) || normalize(value) !== value) {
|
|
181
|
-
throw new Error(
|
|
195
|
+
throw new Error(`${flag} needs a normalized absolute path, got "${value}"`);
|
|
182
196
|
}
|
|
183
197
|
return value;
|
|
184
198
|
}
|
package/dist/tooling/cli/help.js
CHANGED
|
@@ -45,6 +45,9 @@ Options:
|
|
|
45
45
|
from --port or adapter configuration.
|
|
46
46
|
--session-file <path> Write one retained DevelopmentSessionFileV1 snapshot at an
|
|
47
47
|
absolute path in a caller-owned private directory.
|
|
48
|
+
--credential-file <path>
|
|
49
|
+
Use one private operator credential for adapter-owned Kernel
|
|
50
|
+
calls without exposing it in project configuration.
|
|
48
51
|
-h, --help Show help for dev.
|
|
49
52
|
|
|
50
53
|
Behavior:
|
|
@@ -56,6 +59,8 @@ Behavior:
|
|
|
56
59
|
The session file is machine-readable and authoritative when requested; human
|
|
57
60
|
stdout/stderr remains diagnostic. It requires an adapter that reports exact
|
|
58
61
|
installed Release evidence.
|
|
62
|
+
A credential file is read only by the installation-owning adapter. Its value is
|
|
63
|
+
never forwarded to the Domain runtime or written to the session file.
|
|
59
64
|
|
|
60
65
|
Adapters:
|
|
61
66
|
cloudflare Runs the local Worker and optional Vite frontend only;
|
|
@@ -1 +1,7 @@
|
|
|
1
|
+
import { type ParsedArgs } from './arguments.js';
|
|
2
|
+
import { develop } from './development/develop.js';
|
|
1
3
|
export declare function run(argv: readonly string[]): Promise<number>;
|
|
4
|
+
export declare function executeDevelopmentCommand(parsed: ParsedArgs, input: {
|
|
5
|
+
readonly configPath: string;
|
|
6
|
+
readonly projectDir: string;
|
|
7
|
+
}, execute?: typeof develop): Promise<number>;
|
|
@@ -41,16 +41,7 @@ export async function run(argv) {
|
|
|
41
41
|
return 1;
|
|
42
42
|
}
|
|
43
43
|
if (parsed.command === 'dev') {
|
|
44
|
-
return
|
|
45
|
-
configPath,
|
|
46
|
-
projectDir,
|
|
47
|
-
environment: parsed.env,
|
|
48
|
-
overrides: Object.freeze({
|
|
49
|
-
...(parsed.host === undefined ? {} : { publicUrl: parsed.host }),
|
|
50
|
-
...(parsed.port === undefined ? {} : { localPort: parsed.port }),
|
|
51
|
-
}),
|
|
52
|
-
...(parsed.sessionFile === undefined ? {} : { sessionFile: parsed.sessionFile }),
|
|
53
|
-
});
|
|
44
|
+
return executeDevelopmentCommand(parsed, { configPath, projectDir });
|
|
54
45
|
}
|
|
55
46
|
const deployment = await loadDeployment(configPath);
|
|
56
47
|
const adapter = deployment.adapter;
|
|
@@ -93,6 +84,21 @@ export async function run(argv) {
|
|
|
93
84
|
info(`Ready: ${result.release.addressing.issuer}`);
|
|
94
85
|
return 0;
|
|
95
86
|
}
|
|
87
|
+
export function executeDevelopmentCommand(parsed, input, execute = develop) {
|
|
88
|
+
if (parsed.command !== 'dev')
|
|
89
|
+
throw new TypeError('Development execution requires the dev command.');
|
|
90
|
+
return execute({
|
|
91
|
+
configPath: input.configPath,
|
|
92
|
+
projectDir: input.projectDir,
|
|
93
|
+
environment: parsed.env,
|
|
94
|
+
overrides: Object.freeze({
|
|
95
|
+
...(parsed.host === undefined ? {} : { publicUrl: parsed.host }),
|
|
96
|
+
...(parsed.port === undefined ? {} : { localPort: parsed.port }),
|
|
97
|
+
...(parsed.credentialFile === undefined ? {} : { credentialFile: parsed.credentialFile }),
|
|
98
|
+
}),
|
|
99
|
+
...(parsed.sessionFile === undefined ? {} : { sessionFile: parsed.sessionFile }),
|
|
100
|
+
});
|
|
101
|
+
}
|
|
96
102
|
async function lint(parsed) {
|
|
97
103
|
const { formatLintResult, lintDomain, LinterToolError } = await import('../linter/index.js');
|
|
98
104
|
try {
|