@dnsid-ai/sdk 0.24.2 → 0.26.0

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 CHANGED
@@ -124,6 +124,31 @@ The CLI loader maps persisted fields as written: a missing `status_url` or `log_
124
124
 
125
125
  DNSSEC modes are: `auto` (default), which rejects `FAILED` and permits `VALID`, `UNSIGNED`, or `UNKNOWN`; `validated`, which permits `VALID` or `UNSIGNED`; and `required`, which permits only `VALID`.
126
126
 
127
+ ## Registration and provider groundwork
128
+
129
+ Deployment files support `registration.organizationId`, optional `governanceId`, and an
130
+ independently selected `entityKeyUrl`. These are setup expectations, not registry-root selectors
131
+ or counterparty acceptance defaults. `RegistryClient.getOrganizationOnboarding()` reads the
132
+ existing authenticated endpoint and exposes organization/GI proof and delegation readiness;
133
+ it does not supply an entity-JWKS URL or establish trust.
134
+
135
+ `keySource` selects file custody or an optional existing AWS KMS key using `provider`, `keyRef`,
136
+ and non-secret `settings` (`region` and EdDSA/ES256 `algorithm`). Injected providers win, and
137
+ only the selected factory is loaded. AWS authentication uses ambient credential chains, not
138
+ secrets in deployment files. Unavailable factories or invalid settings fail without file fallback.
139
+ Ordinary manager construction opens existing keys only; generation requires workflow coordination.
140
+ Configuration-selected file and AWS keys must match the current operational key of the verified published
141
+ identity, including key ID, algorithm, and public material. Missing publication or unavailable
142
+ verification fails construction. Initial setup supplies an injected provider and owns its binding
143
+ checks. Selecting local files emits a production-safety warning.
144
+ Google KMS and Azure factories are unavailable in this binding.
145
+
146
+ This groundwork does not expose automatic managed registration or named recovery. Those require
147
+ a separate SDK workflow and verified permanent server replay/name claims; the existing expiring
148
+ store is insufficient. Existing low-level registration remains available without assuming those
149
+ server capabilities. Moving custody to another provider requires authorized, publicly verified
150
+ rotation, not configuration alone.
151
+
127
152
  ## Included surfaces
128
153
 
129
154
  `@dnsid-ai/sdk` re-exports the common core and profile surfaces and namespaces:
package/dist/node.cjs CHANGED
@@ -386,9 +386,9 @@ async function loadEnvironment(env = process.env) {
386
386
  }
387
387
  async function loadFile(filePath) {
388
388
  const root = jsonObject(await fs3.readFile(filePath), filePath);
389
- rejectUnknown(root, filePath, ["dnsid", "logTrust", "registry"]);
389
+ rejectUnknown(root, filePath, ["dnsid", "logTrust", "registry", "registration", "keySource"]);
390
390
  const loaded = {};
391
- if (root.dnsid !== void 0) loaded.dnsid = loadedDnsid(root.dnsid, `${filePath}: dnsid`);
391
+ if (root.dnsid !== void 0) loaded.dnsid = validateLoadedDnsidConfig(root.dnsid, `${filePath}: dnsid`);
392
392
  if (root.logTrust !== void 0) {
393
393
  const trust = object(root.logTrust, `${filePath}: logTrust`);
394
394
  rejectUnknown(trust, `${filePath}: logTrust`, ["managed", "profile", "policyUrl"]);
@@ -403,9 +403,32 @@ async function loadFile(filePath) {
403
403
  rejectUnknown(registry, `${filePath}: registry`, ["registryUrl"]);
404
404
  loaded.registry = compact({ registryUrl: optional(registry, "registryUrl", "string", `${filePath}: registry`) });
405
405
  }
406
+ if (root.registration !== void 0) {
407
+ const registration = object(root.registration, `${filePath}: registration`);
408
+ rejectUnknown(registration, `${filePath}: registration`, ["organizationId", "governanceId", "entityKeyUrl"]);
409
+ loaded.registration = compact({
410
+ organizationId: optional(registration, "organizationId", "string", `${filePath}: registration`),
411
+ governanceId: optional(registration, "governanceId", "string", `${filePath}: registration`),
412
+ entityKeyUrl: optional(registration, "entityKeyUrl", "string", `${filePath}: registration`)
413
+ });
414
+ }
415
+ if (root.keySource !== void 0) {
416
+ const source = object(root.keySource, `${filePath}: keySource`);
417
+ rejectUnknown(source, "keySource", ["provider", "keyRef", "generation", "settings", "cliDirectory", "keyStorePath", "entityKeyPath"]);
418
+ for (const field of ["provider", "keyRef", "cliDirectory", "keyStorePath", "entityKeyPath"]) optional(source, field, "string", "keySource");
419
+ if (source.settings !== void 0) object(source.settings, "keySource.settings");
420
+ if (source.generation !== void 0) {
421
+ const generation = object(source.generation, "keySource.generation");
422
+ rejectUnknown(generation, "keySource.generation", ["locator", "algorithm"]);
423
+ for (const field of ["locator", "algorithm"]) {
424
+ if (typeof generation[field] !== "string") throw new import_protocol3.ArgumentError(`keySource.generation.${field} must be a string`);
425
+ }
426
+ }
427
+ loaded.keySource = source;
428
+ }
406
429
  return loaded;
407
430
  }
408
- function loadedDnsid(value, source) {
431
+ function validateLoadedDnsidConfig(value, source = "dnsid") {
409
432
  const raw = object(value, source);
410
433
  rejectUnknown(raw, source, ["identity", "verification", "transport"]);
411
434
  if (raw.identity !== void 0) {
@@ -494,9 +517,15 @@ function mergeLoadedConfig(base, overlay) {
494
517
  dnsid: mergeDnsid(base.dnsid, overlay.dnsid),
495
518
  logTrust: overlay.logTrust ?? base.logTrust,
496
519
  registry: mergeSection(base.registry, overlay.registry),
497
- keySource: mergeSection(base.keySource, overlay.keySource)
520
+ registration: mergeSection(base.registration, overlay.registration),
521
+ keySource: mergeKeySource(base.keySource, overlay.keySource)
498
522
  });
499
523
  }
524
+ function mergeKeySource(base, overlay) {
525
+ if (!overlay) return base;
526
+ const replacesOperational = ["provider", "keyRef", "generation", "settings", "cliDirectory", "keyStorePath"].some((field) => overlay[field] !== void 0);
527
+ return mergeSection(replacesOperational ? compact({ entityKeyPath: base?.entityKeyPath }) : base, overlay);
528
+ }
500
529
  function mergeDnsid(base, overlay) {
501
530
  if (!base || !overlay) return overlay ?? base;
502
531
  return compact({
@@ -523,7 +552,16 @@ async function constructIdentityManager(loaded, deps = {}) {
523
552
  filled.entityKeyProvider = await LocalKeyProvider.fromFile(loaded.keySource.entityKeyPath);
524
553
  }
525
554
  }
526
- return createNodeIdentityManager(loaded.dnsid ?? {}, filled);
555
+ const manager = await createNodeIdentityManager(loaded.dnsid ?? {}, filled);
556
+ if (identity && !deps.keyProvider && filled.keyProvider) {
557
+ const verified = await manager.verifyPublicationEvidence(manager.config.identity.domain);
558
+ const selected = await filled.keyProvider.signingKey();
559
+ const published = verified.jwks.currentOperationalSigningKey();
560
+ if (selected.kid !== published.kid || selected.alg !== published.alg || await (0, import_protocol3.jwkThumbprint)(selected) !== await (0, import_protocol3.jwkThumbprint)(published)) {
561
+ throw new import_protocol3.ArgumentError("configured operational key does not match the verified identity; use authorized key rotation");
562
+ }
563
+ }
564
+ return manager;
527
565
  }
528
566
  async function logRegistryFromTrust(trust, transport) {
529
567
  const variants = ["managed", "profile", "policyDocument", "policyUrl"].filter((k) => trust[k] !== void 0);
@@ -545,12 +583,46 @@ async function logRegistryFromTrust(trust, transport) {
545
583
  });
546
584
  }
547
585
  async function operationalKeyProvider(source, domain) {
586
+ await validateKeySource(source);
587
+ if (source.generation) throw new import_protocol3.ArgumentError("identity-manager construction opens existing keys only; use managed setup for generation");
588
+ if (source.provider === "aws-kms") {
589
+ const { createAwsKmsKeyProvider } = await import("@dnsid-ai/key-aws");
590
+ return createAwsKmsKeyProvider(source.keyRef, source.settings);
591
+ }
592
+ if (source.keyRef !== void 0 || source.cliDirectory !== void 0 || source.keyStorePath !== void 0) {
593
+ console.warn("Local private-key files are unsuitable for production. Configure keySource.provider/keyRef for cloud custody.");
594
+ }
595
+ if (source.keyRef !== void 0) return LocalKeyProvider.load(source.keyRef);
548
596
  if (source.cliDirectory !== void 0) {
549
597
  return LocalKeyProvider.fromDirectory(await identityKeyDir(source.cliDirectory, domain));
550
598
  }
551
599
  if (source.keyStorePath !== void 0) return LocalKeyProvider.load(source.keyStorePath);
552
600
  return void 0;
553
601
  }
602
+ async function validateKeySource(source = {}) {
603
+ const provider = source.provider ?? "file";
604
+ if (!["file", "aws-kms", "google-kms", "azure-key-vault"].includes(provider)) throw new import_protocol3.ArgumentError(`unknown key provider: ${provider}`);
605
+ for (const field of ["keyRef", "keyStorePath", "cliDirectory", "entityKeyPath"]) {
606
+ if (source[field] !== void 0 && (typeof source[field] !== "string" || !source[field].trim())) throw new import_protocol3.ArgumentError(`keySource.${field} must be nonempty`);
607
+ }
608
+ if (source.keyRef && source.generation) throw new import_protocol3.ArgumentError("keyRef and generation are mutually exclusive");
609
+ if ((source.cliDirectory || source.keyStorePath) && (provider !== "file" || source.keyRef || source.generation)) throw new import_protocol3.ArgumentError("CLI/keyStorePath cannot combine with cloud, keyRef, or generation");
610
+ if (source.generation && (typeof source.generation.locator !== "string" || !source.generation.locator.trim() || !["EdDSA", "ES256"].includes(source.generation.algorithm))) throw new import_protocol3.ArgumentError("generation requires a stable locator and supported EdDSA or ES256 algorithm");
611
+ if (provider === "file") {
612
+ if (source.settings && Object.keys(source.settings).length) throw new import_protocol3.ArgumentError("file provider has no settings");
613
+ return;
614
+ }
615
+ if (provider !== "aws-kms") throw new import_protocol3.ArgumentError(`${provider} is unavailable; install/link a supported provider package (Google KMS and Azure factories are not implemented)`);
616
+ if (!source.keyRef || source.generation) throw new import_protocol3.ArgumentError("AWS KMS requires an existing keyRef; atomic generation discovery is not supported");
617
+ try {
618
+ const factory = await import("@dnsid-ai/key-aws");
619
+ if (typeof factory.createAwsKmsKeyProvider !== "function" || typeof factory.validateAwsKmsSettings !== "function") throw new Error("provider factory unavailable");
620
+ factory.validateAwsKmsSettings(source.settings);
621
+ } catch (cause) {
622
+ if (cause instanceof import_protocol3.ArgumentError) throw cause;
623
+ throw new import_protocol3.ArgumentError("aws-kms unavailable; install a compatible @dnsid-ai/key-aws and include it in the build");
624
+ }
625
+ }
554
626
  async function identityKeyDir(dnsidDir, domain) {
555
627
  for (const filename of ["private.jwk", "private.pem"]) {
556
628
  try {
@@ -571,10 +643,10 @@ function createNodeIdentityManagerFromDnsid(dnsidDir, overlay, deps) {
571
643
  function createNodeIdentityManagerFromFile(filePath, overlay, deps) {
572
644
  return loadFile(filePath).then((loaded) => constructIdentityManager(mergeLoadedConfig(loaded, top({ dnsid: overlay })), deps));
573
645
  }
574
- async function createRegistryClientFromEnvironment(env) {
646
+ async function createRegistryClientFromEnvironment(env, options = {}) {
575
647
  const source = env ?? process.env;
576
648
  const loaded = await loadEnvironment(source);
577
- return new import_registry.RegistryClient(compact({ baseUrl: loaded.registry?.registryUrl, token: source.DNSID_API_KEY?.trim() || void 0 }));
649
+ return new import_registry.RegistryClient({ baseUrl: loaded.registry?.registryUrl, token: source.DNSID_API_KEY?.trim() || void 0, ...options });
578
650
  }
579
651
  function definedEntries(value) {
580
652
  return Object.fromEntries(Object.entries(value).filter(([, v]) => v !== void 0));
package/dist/node.d.cts CHANGED
@@ -1,7 +1,7 @@
1
1
  export { CreateIdentityManagerDependencies } from './index.cjs';
2
2
  import * as _dnsid_ai_protocol from '@dnsid-ai/protocol';
3
3
  import { KeyProvider, DnsIdJWK, DnsidConfig, IdentityManagerDependencies, IdentityManager, IdentityConfig } from '@dnsid-ai/protocol';
4
- import { RegistryClient } from '@dnsid-ai/registry';
4
+ import { RegistryClientOptions, RegistryClient } from '@dnsid-ai/registry';
5
5
  import '@dnsid-ai/jose';
6
6
  import '@dnsid-ai/http-signatures';
7
7
  import '@dnsid-ai/web-bot-auth';
@@ -76,6 +76,15 @@ interface LogTrust {
76
76
  policyUrl?: string;
77
77
  }
78
78
  interface KeySource {
79
+ provider?: 'file' | 'aws-kms' | 'google-kms' | 'azure-key-vault';
80
+ /** Existing stable key reference; never combined with generation. */
81
+ keyRef?: string;
82
+ /** File generation locator; cloud factories without atomic discovery require keyRef. */
83
+ generation?: {
84
+ locator: string;
85
+ algorithm: string;
86
+ };
87
+ settings?: Record<string, unknown>;
79
88
  /** DNSid CLI identity directory; key files are located under the effective identity domain. */
80
89
  cliDirectory?: string;
81
90
  /** Accountable-entity private JWK file. */
@@ -83,6 +92,11 @@ interface KeySource {
83
92
  /** `LocalKeyProvider` key-store file; used only when `cliDirectory` is absent. */
84
93
  keyStorePath?: string;
85
94
  }
95
+ interface ManagedRegistrationConfig {
96
+ organizationId?: string;
97
+ governanceId?: string;
98
+ entityKeyUrl: string;
99
+ }
86
100
  interface LoadedRegistryConfig {
87
101
  registryUrl?: string;
88
102
  }
@@ -95,12 +109,14 @@ interface LoadedConfig {
95
109
  dnsid?: LoadedDnsidConfig;
96
110
  logTrust?: LogTrust;
97
111
  registry?: LoadedRegistryConfig;
112
+ /** Setup expectations only; never identity or counterparty acceptance defaults. */
113
+ registration?: Partial<ManagedRegistrationConfig>;
98
114
  keySource?: KeySource;
99
115
  }
100
116
  /** Reads configuration from `DNSID_*`. Secrets such as `DNSID_API_KEY` stay out of `LoadedConfig`. */
101
117
  declare function loadEnvironment(env?: EnvironmentSource): Promise<LoadedConfig>;
102
118
  /**
103
- * Reads a JSON deployment file: `{ dnsid?, logTrust?, registry? }`. Unknown members, mistyped
119
+ * Reads a JSON deployment file: `{ dnsid?, logTrust?, registry?, registration? }`. Unknown members, mistyped
104
120
  * values, and duplicate members are rejected; semantic `dnsid` validation stays with the constructor.
105
121
  */
106
122
  declare function loadFile(filePath: string): Promise<LoadedConfig>;
@@ -113,18 +129,21 @@ declare function loadFile(filePath: string): Promise<LoadedConfig>;
113
129
  declare function loadCliDirectory(dnsidDir?: string): Promise<LoadedConfig>;
114
130
  /**
115
131
  * Field-wise merge; presence wins, not truthiness. Lists replace. `logTrust` is replaced as a
116
- * whole section when `overlay` sets any variant.
132
+ * whole section when `overlay` sets any variant. Operational key-source fields replace together;
133
+ * `entityKeyPath` merges independently.
117
134
  */
118
135
  declare function mergeLoadedConfig(base: LoadedConfig, overlay: LoadedConfig): LoadedConfig;
119
136
  /**
120
137
  * Fills `deps.logRegistry` from `logTrust` and key providers from `keySource` only when the caller
121
138
  * did not supply them, then calls {@link createNodeIdentityManager}. Adds no configuration values.
139
+ * Configuration-selected operational keys must match verified local publication before return.
140
+ * Initial setup injects its provider and owns its binding checks; missing publication fails closed.
122
141
  */
123
142
  declare function constructIdentityManager(loaded: LoadedConfig, deps?: IdentityManagerDependencies): Promise<_dnsid_ai_protocol.IdentityManager>;
124
143
  declare function createNodeIdentityManagerFromEnvironment(env?: EnvironmentSource, overlay?: LoadedDnsidConfig, deps?: IdentityManagerDependencies): Promise<_dnsid_ai_protocol.IdentityManager>;
125
144
  declare function createNodeIdentityManagerFromDnsid(dnsidDir?: string, overlay?: LoadedDnsidConfig, deps?: IdentityManagerDependencies): Promise<_dnsid_ai_protocol.IdentityManager>;
126
145
  declare function createNodeIdentityManagerFromFile(filePath: string, overlay?: LoadedDnsidConfig, deps?: IdentityManagerDependencies): Promise<_dnsid_ai_protocol.IdentityManager>;
127
- /** `RegistryClient` from `DNSID_REGISTRY_URL` and `DNSID_API_KEY`; the constructor defaults to the local registry. */
128
- declare function createRegistryClientFromEnvironment(env?: EnvironmentSource): Promise<RegistryClient>;
146
+ /** `RegistryClient` from `DNSID_REGISTRY_URL` and `DNSID_API_KEY`; explicit options win, including transport overrides. The constructor defaults to the local registry. */
147
+ declare function createRegistryClientFromEnvironment(env?: EnvironmentSource, options?: RegistryClientOptions): Promise<RegistryClient>;
129
148
 
130
- export { type EnvironmentSource, type KeySource, type LoadedConfig, type LoadedDnsidConfig, type LoadedRegistryConfig, type LocalKeyAlgorithm, LocalKeyProvider, type LogTrust, constructIdentityManager, createNodeIdentityManager, createNodeIdentityManagerFromDnsid, createNodeIdentityManagerFromEnvironment, createNodeIdentityManagerFromFile, createNodeIdentityVerifier, createRegistryClientFromEnvironment, loadCliDirectory, loadEnvironment, loadFile, mergeLoadedConfig };
149
+ export { type EnvironmentSource, type KeySource, type LoadedConfig, type LoadedDnsidConfig, type LoadedRegistryConfig, type LocalKeyAlgorithm, LocalKeyProvider, type LogTrust, type ManagedRegistrationConfig, constructIdentityManager, createNodeIdentityManager, createNodeIdentityManagerFromDnsid, createNodeIdentityManagerFromEnvironment, createNodeIdentityManagerFromFile, createNodeIdentityVerifier, createRegistryClientFromEnvironment, loadCliDirectory, loadEnvironment, loadFile, mergeLoadedConfig };
package/dist/node.d.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  export { CreateIdentityManagerDependencies } from './index.js';
2
2
  import * as _dnsid_ai_protocol from '@dnsid-ai/protocol';
3
3
  import { KeyProvider, DnsIdJWK, DnsidConfig, IdentityManagerDependencies, IdentityManager, IdentityConfig } from '@dnsid-ai/protocol';
4
- import { RegistryClient } from '@dnsid-ai/registry';
4
+ import { RegistryClientOptions, RegistryClient } from '@dnsid-ai/registry';
5
5
  import '@dnsid-ai/jose';
6
6
  import '@dnsid-ai/http-signatures';
7
7
  import '@dnsid-ai/web-bot-auth';
@@ -76,6 +76,15 @@ interface LogTrust {
76
76
  policyUrl?: string;
77
77
  }
78
78
  interface KeySource {
79
+ provider?: 'file' | 'aws-kms' | 'google-kms' | 'azure-key-vault';
80
+ /** Existing stable key reference; never combined with generation. */
81
+ keyRef?: string;
82
+ /** File generation locator; cloud factories without atomic discovery require keyRef. */
83
+ generation?: {
84
+ locator: string;
85
+ algorithm: string;
86
+ };
87
+ settings?: Record<string, unknown>;
79
88
  /** DNSid CLI identity directory; key files are located under the effective identity domain. */
80
89
  cliDirectory?: string;
81
90
  /** Accountable-entity private JWK file. */
@@ -83,6 +92,11 @@ interface KeySource {
83
92
  /** `LocalKeyProvider` key-store file; used only when `cliDirectory` is absent. */
84
93
  keyStorePath?: string;
85
94
  }
95
+ interface ManagedRegistrationConfig {
96
+ organizationId?: string;
97
+ governanceId?: string;
98
+ entityKeyUrl: string;
99
+ }
86
100
  interface LoadedRegistryConfig {
87
101
  registryUrl?: string;
88
102
  }
@@ -95,12 +109,14 @@ interface LoadedConfig {
95
109
  dnsid?: LoadedDnsidConfig;
96
110
  logTrust?: LogTrust;
97
111
  registry?: LoadedRegistryConfig;
112
+ /** Setup expectations only; never identity or counterparty acceptance defaults. */
113
+ registration?: Partial<ManagedRegistrationConfig>;
98
114
  keySource?: KeySource;
99
115
  }
100
116
  /** Reads configuration from `DNSID_*`. Secrets such as `DNSID_API_KEY` stay out of `LoadedConfig`. */
101
117
  declare function loadEnvironment(env?: EnvironmentSource): Promise<LoadedConfig>;
102
118
  /**
103
- * Reads a JSON deployment file: `{ dnsid?, logTrust?, registry? }`. Unknown members, mistyped
119
+ * Reads a JSON deployment file: `{ dnsid?, logTrust?, registry?, registration? }`. Unknown members, mistyped
104
120
  * values, and duplicate members are rejected; semantic `dnsid` validation stays with the constructor.
105
121
  */
106
122
  declare function loadFile(filePath: string): Promise<LoadedConfig>;
@@ -113,18 +129,21 @@ declare function loadFile(filePath: string): Promise<LoadedConfig>;
113
129
  declare function loadCliDirectory(dnsidDir?: string): Promise<LoadedConfig>;
114
130
  /**
115
131
  * Field-wise merge; presence wins, not truthiness. Lists replace. `logTrust` is replaced as a
116
- * whole section when `overlay` sets any variant.
132
+ * whole section when `overlay` sets any variant. Operational key-source fields replace together;
133
+ * `entityKeyPath` merges independently.
117
134
  */
118
135
  declare function mergeLoadedConfig(base: LoadedConfig, overlay: LoadedConfig): LoadedConfig;
119
136
  /**
120
137
  * Fills `deps.logRegistry` from `logTrust` and key providers from `keySource` only when the caller
121
138
  * did not supply them, then calls {@link createNodeIdentityManager}. Adds no configuration values.
139
+ * Configuration-selected operational keys must match verified local publication before return.
140
+ * Initial setup injects its provider and owns its binding checks; missing publication fails closed.
122
141
  */
123
142
  declare function constructIdentityManager(loaded: LoadedConfig, deps?: IdentityManagerDependencies): Promise<_dnsid_ai_protocol.IdentityManager>;
124
143
  declare function createNodeIdentityManagerFromEnvironment(env?: EnvironmentSource, overlay?: LoadedDnsidConfig, deps?: IdentityManagerDependencies): Promise<_dnsid_ai_protocol.IdentityManager>;
125
144
  declare function createNodeIdentityManagerFromDnsid(dnsidDir?: string, overlay?: LoadedDnsidConfig, deps?: IdentityManagerDependencies): Promise<_dnsid_ai_protocol.IdentityManager>;
126
145
  declare function createNodeIdentityManagerFromFile(filePath: string, overlay?: LoadedDnsidConfig, deps?: IdentityManagerDependencies): Promise<_dnsid_ai_protocol.IdentityManager>;
127
- /** `RegistryClient` from `DNSID_REGISTRY_URL` and `DNSID_API_KEY`; the constructor defaults to the local registry. */
128
- declare function createRegistryClientFromEnvironment(env?: EnvironmentSource): Promise<RegistryClient>;
146
+ /** `RegistryClient` from `DNSID_REGISTRY_URL` and `DNSID_API_KEY`; explicit options win, including transport overrides. The constructor defaults to the local registry. */
147
+ declare function createRegistryClientFromEnvironment(env?: EnvironmentSource, options?: RegistryClientOptions): Promise<RegistryClient>;
129
148
 
130
- export { type EnvironmentSource, type KeySource, type LoadedConfig, type LoadedDnsidConfig, type LoadedRegistryConfig, type LocalKeyAlgorithm, LocalKeyProvider, type LogTrust, constructIdentityManager, createNodeIdentityManager, createNodeIdentityManagerFromDnsid, createNodeIdentityManagerFromEnvironment, createNodeIdentityManagerFromFile, createNodeIdentityVerifier, createRegistryClientFromEnvironment, loadCliDirectory, loadEnvironment, loadFile, mergeLoadedConfig };
149
+ export { type EnvironmentSource, type KeySource, type LoadedConfig, type LoadedDnsidConfig, type LoadedRegistryConfig, type LocalKeyAlgorithm, LocalKeyProvider, type LogTrust, type ManagedRegistrationConfig, constructIdentityManager, createNodeIdentityManager, createNodeIdentityManagerFromDnsid, createNodeIdentityManagerFromEnvironment, createNodeIdentityManagerFromFile, createNodeIdentityVerifier, createRegistryClientFromEnvironment, loadCliDirectory, loadEnvironment, loadFile, mergeLoadedConfig };
package/dist/node.js CHANGED
@@ -292,6 +292,7 @@ import {
292
292
  ArgumentError as ArgumentError3,
293
293
  DNSSECMode,
294
294
  normalizeFQDN as normalizeFQDN2,
295
+ jwkThumbprint as jwkThumbprint2,
295
296
  parseJsonNoDuplicateMembers,
296
297
  validateDnsidConfig as validateDnsidConfig2
297
298
  } from "@dnsid-ai/protocol";
@@ -349,9 +350,9 @@ async function loadEnvironment(env = process.env) {
349
350
  }
350
351
  async function loadFile(filePath) {
351
352
  const root = jsonObject(await fs3.readFile(filePath), filePath);
352
- rejectUnknown(root, filePath, ["dnsid", "logTrust", "registry"]);
353
+ rejectUnknown(root, filePath, ["dnsid", "logTrust", "registry", "registration", "keySource"]);
353
354
  const loaded = {};
354
- if (root.dnsid !== void 0) loaded.dnsid = loadedDnsid(root.dnsid, `${filePath}: dnsid`);
355
+ if (root.dnsid !== void 0) loaded.dnsid = validateLoadedDnsidConfig(root.dnsid, `${filePath}: dnsid`);
355
356
  if (root.logTrust !== void 0) {
356
357
  const trust = object(root.logTrust, `${filePath}: logTrust`);
357
358
  rejectUnknown(trust, `${filePath}: logTrust`, ["managed", "profile", "policyUrl"]);
@@ -366,9 +367,32 @@ async function loadFile(filePath) {
366
367
  rejectUnknown(registry, `${filePath}: registry`, ["registryUrl"]);
367
368
  loaded.registry = compact({ registryUrl: optional(registry, "registryUrl", "string", `${filePath}: registry`) });
368
369
  }
370
+ if (root.registration !== void 0) {
371
+ const registration = object(root.registration, `${filePath}: registration`);
372
+ rejectUnknown(registration, `${filePath}: registration`, ["organizationId", "governanceId", "entityKeyUrl"]);
373
+ loaded.registration = compact({
374
+ organizationId: optional(registration, "organizationId", "string", `${filePath}: registration`),
375
+ governanceId: optional(registration, "governanceId", "string", `${filePath}: registration`),
376
+ entityKeyUrl: optional(registration, "entityKeyUrl", "string", `${filePath}: registration`)
377
+ });
378
+ }
379
+ if (root.keySource !== void 0) {
380
+ const source = object(root.keySource, `${filePath}: keySource`);
381
+ rejectUnknown(source, "keySource", ["provider", "keyRef", "generation", "settings", "cliDirectory", "keyStorePath", "entityKeyPath"]);
382
+ for (const field of ["provider", "keyRef", "cliDirectory", "keyStorePath", "entityKeyPath"]) optional(source, field, "string", "keySource");
383
+ if (source.settings !== void 0) object(source.settings, "keySource.settings");
384
+ if (source.generation !== void 0) {
385
+ const generation = object(source.generation, "keySource.generation");
386
+ rejectUnknown(generation, "keySource.generation", ["locator", "algorithm"]);
387
+ for (const field of ["locator", "algorithm"]) {
388
+ if (typeof generation[field] !== "string") throw new ArgumentError3(`keySource.generation.${field} must be a string`);
389
+ }
390
+ }
391
+ loaded.keySource = source;
392
+ }
369
393
  return loaded;
370
394
  }
371
- function loadedDnsid(value, source) {
395
+ function validateLoadedDnsidConfig(value, source = "dnsid") {
372
396
  const raw = object(value, source);
373
397
  rejectUnknown(raw, source, ["identity", "verification", "transport"]);
374
398
  if (raw.identity !== void 0) {
@@ -457,9 +481,15 @@ function mergeLoadedConfig(base, overlay) {
457
481
  dnsid: mergeDnsid(base.dnsid, overlay.dnsid),
458
482
  logTrust: overlay.logTrust ?? base.logTrust,
459
483
  registry: mergeSection(base.registry, overlay.registry),
460
- keySource: mergeSection(base.keySource, overlay.keySource)
484
+ registration: mergeSection(base.registration, overlay.registration),
485
+ keySource: mergeKeySource(base.keySource, overlay.keySource)
461
486
  });
462
487
  }
488
+ function mergeKeySource(base, overlay) {
489
+ if (!overlay) return base;
490
+ const replacesOperational = ["provider", "keyRef", "generation", "settings", "cliDirectory", "keyStorePath"].some((field) => overlay[field] !== void 0);
491
+ return mergeSection(replacesOperational ? compact({ entityKeyPath: base?.entityKeyPath }) : base, overlay);
492
+ }
463
493
  function mergeDnsid(base, overlay) {
464
494
  if (!base || !overlay) return overlay ?? base;
465
495
  return compact({
@@ -486,7 +516,16 @@ async function constructIdentityManager(loaded, deps = {}) {
486
516
  filled.entityKeyProvider = await LocalKeyProvider.fromFile(loaded.keySource.entityKeyPath);
487
517
  }
488
518
  }
489
- return createNodeIdentityManager(loaded.dnsid ?? {}, filled);
519
+ const manager = await createNodeIdentityManager(loaded.dnsid ?? {}, filled);
520
+ if (identity && !deps.keyProvider && filled.keyProvider) {
521
+ const verified = await manager.verifyPublicationEvidence(manager.config.identity.domain);
522
+ const selected = await filled.keyProvider.signingKey();
523
+ const published = verified.jwks.currentOperationalSigningKey();
524
+ if (selected.kid !== published.kid || selected.alg !== published.alg || await jwkThumbprint2(selected) !== await jwkThumbprint2(published)) {
525
+ throw new ArgumentError3("configured operational key does not match the verified identity; use authorized key rotation");
526
+ }
527
+ }
528
+ return manager;
490
529
  }
491
530
  async function logRegistryFromTrust(trust, transport) {
492
531
  const variants = ["managed", "profile", "policyDocument", "policyUrl"].filter((k) => trust[k] !== void 0);
@@ -508,12 +547,46 @@ async function logRegistryFromTrust(trust, transport) {
508
547
  });
509
548
  }
510
549
  async function operationalKeyProvider(source, domain) {
550
+ await validateKeySource(source);
551
+ if (source.generation) throw new ArgumentError3("identity-manager construction opens existing keys only; use managed setup for generation");
552
+ if (source.provider === "aws-kms") {
553
+ const { createAwsKmsKeyProvider } = await import("@dnsid-ai/key-aws");
554
+ return createAwsKmsKeyProvider(source.keyRef, source.settings);
555
+ }
556
+ if (source.keyRef !== void 0 || source.cliDirectory !== void 0 || source.keyStorePath !== void 0) {
557
+ console.warn("Local private-key files are unsuitable for production. Configure keySource.provider/keyRef for cloud custody.");
558
+ }
559
+ if (source.keyRef !== void 0) return LocalKeyProvider.load(source.keyRef);
511
560
  if (source.cliDirectory !== void 0) {
512
561
  return LocalKeyProvider.fromDirectory(await identityKeyDir(source.cliDirectory, domain));
513
562
  }
514
563
  if (source.keyStorePath !== void 0) return LocalKeyProvider.load(source.keyStorePath);
515
564
  return void 0;
516
565
  }
566
+ async function validateKeySource(source = {}) {
567
+ const provider = source.provider ?? "file";
568
+ if (!["file", "aws-kms", "google-kms", "azure-key-vault"].includes(provider)) throw new ArgumentError3(`unknown key provider: ${provider}`);
569
+ for (const field of ["keyRef", "keyStorePath", "cliDirectory", "entityKeyPath"]) {
570
+ if (source[field] !== void 0 && (typeof source[field] !== "string" || !source[field].trim())) throw new ArgumentError3(`keySource.${field} must be nonempty`);
571
+ }
572
+ if (source.keyRef && source.generation) throw new ArgumentError3("keyRef and generation are mutually exclusive");
573
+ if ((source.cliDirectory || source.keyStorePath) && (provider !== "file" || source.keyRef || source.generation)) throw new ArgumentError3("CLI/keyStorePath cannot combine with cloud, keyRef, or generation");
574
+ if (source.generation && (typeof source.generation.locator !== "string" || !source.generation.locator.trim() || !["EdDSA", "ES256"].includes(source.generation.algorithm))) throw new ArgumentError3("generation requires a stable locator and supported EdDSA or ES256 algorithm");
575
+ if (provider === "file") {
576
+ if (source.settings && Object.keys(source.settings).length) throw new ArgumentError3("file provider has no settings");
577
+ return;
578
+ }
579
+ if (provider !== "aws-kms") throw new ArgumentError3(`${provider} is unavailable; install/link a supported provider package (Google KMS and Azure factories are not implemented)`);
580
+ if (!source.keyRef || source.generation) throw new ArgumentError3("AWS KMS requires an existing keyRef; atomic generation discovery is not supported");
581
+ try {
582
+ const factory = await import("@dnsid-ai/key-aws");
583
+ if (typeof factory.createAwsKmsKeyProvider !== "function" || typeof factory.validateAwsKmsSettings !== "function") throw new Error("provider factory unavailable");
584
+ factory.validateAwsKmsSettings(source.settings);
585
+ } catch (cause) {
586
+ if (cause instanceof ArgumentError3) throw cause;
587
+ throw new ArgumentError3("aws-kms unavailable; install a compatible @dnsid-ai/key-aws and include it in the build");
588
+ }
589
+ }
517
590
  async function identityKeyDir(dnsidDir, domain) {
518
591
  for (const filename of ["private.jwk", "private.pem"]) {
519
592
  try {
@@ -534,10 +607,10 @@ function createNodeIdentityManagerFromDnsid(dnsidDir, overlay, deps) {
534
607
  function createNodeIdentityManagerFromFile(filePath, overlay, deps) {
535
608
  return loadFile(filePath).then((loaded) => constructIdentityManager(mergeLoadedConfig(loaded, top({ dnsid: overlay })), deps));
536
609
  }
537
- async function createRegistryClientFromEnvironment(env) {
610
+ async function createRegistryClientFromEnvironment(env, options = {}) {
538
611
  const source = env ?? process.env;
539
612
  const loaded = await loadEnvironment(source);
540
- return new RegistryClient(compact({ baseUrl: loaded.registry?.registryUrl, token: source.DNSID_API_KEY?.trim() || void 0 }));
613
+ return new RegistryClient({ baseUrl: loaded.registry?.registryUrl, token: source.DNSID_API_KEY?.trim() || void 0, ...options });
541
614
  }
542
615
  function definedEntries(value) {
543
616
  return Object.fromEntries(Object.entries(value).filter(([, v]) => v !== void 0));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dnsid-ai/sdk",
3
- "version": "0.24.2",
3
+ "version": "0.26.0",
4
4
  "description": "Runtime-neutral DNSid SDK entrypoint with optional Node.js convenience helpers.",
5
5
  "type": "module",
6
6
  "types": "./dist/index.d.ts",
@@ -39,17 +39,21 @@
39
39
  "typecheck": "tsc --noEmit"
40
40
  },
41
41
  "dependencies": {
42
- "@dnsid-ai/protocol": "^0.24.2",
43
- "@dnsid-ai/http-signatures": "^0.24.2",
44
- "@dnsid-ai/jose": "^0.24.2",
45
- "@dnsid-ai/log-c2sp-tlog": "^0.24.2",
46
- "@dnsid-ai/registry": "^0.24.2",
47
- "@dnsid-ai/web-bot-auth": "^0.24.2"
42
+ "@dnsid-ai/protocol": "^0.26.0",
43
+ "@dnsid-ai/http-signatures": "^0.26.0",
44
+ "@dnsid-ai/jose": "^0.26.0",
45
+ "@dnsid-ai/log-c2sp-tlog": "^0.26.0",
46
+ "@dnsid-ai/registry": "^0.26.0",
47
+ "@dnsid-ai/web-bot-auth": "^0.26.0"
48
48
  },
49
49
  "peerDependencies": {
50
- "@dnsid-ai/transport": "^0.24.2"
50
+ "@dnsid-ai/key-aws": "^0.26.0",
51
+ "@dnsid-ai/transport": "^0.26.0"
51
52
  },
52
53
  "peerDependenciesMeta": {
54
+ "@dnsid-ai/key-aws": {
55
+ "optional": true
56
+ },
53
57
  "@dnsid-ai/transport": {
54
58
  "optional": true
55
59
  }