@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 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
- if (input === undefined)
12
- return DEFAULT_MAXIMUM_TTL_SECONDS;
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
- return value;
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: { kind: 'identity', credential: carried.credential },
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 { maximumExchangeTtl, tokenExchange } from './exchange.js';
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
- : tokenExchange(issuer, input.subject, material, policy, verification, maximumExchangeTtl(input.tokenExchange));
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';
@@ -1,3 +1,3 @@
1
1
  export { serve } from './serve.js';
2
- export { createIdentity } from './identity/index.js';
2
+ export { createIdentity, } from './identity/index.js';
3
3
  export { InvocationError } from './invocation/index.js';
@@ -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 || host !== undefined || sessionFile !== 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 `--session-file` are only valid for `dev`.');
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
- const value = requiredValue('--session-file', input);
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(`--session-file needs a normalized absolute path, got "${value}"`);
195
+ throw new Error(`${flag} needs a normalized absolute path, got "${value}"`);
182
196
  }
183
197
  return value;
184
198
  }
@@ -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 develop({
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 {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@astrale-os/sdk",
3
- "version": "0.5.0-beta.84",
3
+ "version": "0.5.0-beta.86",
4
4
  "description": "Schema-first SDK for defining, composing, and deploying Astrale domains",
5
5
  "keywords": [
6
6
  "astrale",