@dnsid-ai/sdk 0.31.0 → 0.33.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/LICENSE CHANGED
@@ -1,3 +1,5 @@
1
+ Copyright 2026 Known Systems AI, Inc.
2
+
1
3
  Apache License
2
4
  Version 2.0, January 2004
3
5
  http://www.apache.org/licenses/
package/NOTICE.txt ADDED
@@ -0,0 +1,348 @@
1
+ THIRD-PARTY SOFTWARE NOTICES
2
+ dnsid-ts
3
+ Copyright 2026 Known Systems AI, Inc.
4
+
5
+ This SDK includes, in the form it is distributed to users, the
6
+ third-party open-source software components listed below. Each
7
+ component's own copyright notice and license terms are reproduced
8
+ in full beneath its entry, as required by the applicable license.
9
+
10
+ This file lists only components that ship as part of the compiled
11
+ or packaged SDK delivered to users. It does not include
12
+ development-only tooling (test frameworks, linters, build tools) or
13
+ sample/example applications, which are not distributed as part of
14
+ the product.
15
+
16
+ Snapshot date: August 5, 2026
17
+ Scope note: covers dependencies shared across the published
18
+ @dnsid-ai/* packages, plus the package-specific
19
+ dependency used only by the key-aws package (for users who
20
+ choose AWS KMS-based key storage). Development tooling and the
21
+ examples/ sample applications are not included, as they are not
22
+ part of the packages published and distributed to users.
23
+
24
+ ===============================================================================
25
+ jose (^6.2.3)
26
+ License: MIT License
27
+ ===============================================================================
28
+ The MIT License (MIT)
29
+
30
+ Copyright (c) 2018 Filip Skokan
31
+
32
+ Permission is hereby granted, free of charge, to any person obtaining a copy
33
+ of this software and associated documentation files (the "Software"), to deal
34
+ in the Software without restriction, including without limitation the rights
35
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
36
+ copies of the Software, and to permit persons to whom the Software is
37
+ furnished to do so, subject to the following conditions:
38
+
39
+ The above copyright notice and this permission notice shall be included in all
40
+ copies or substantial portions of the Software.
41
+
42
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
43
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
44
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
45
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
46
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
47
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
48
+ SOFTWARE.
49
+
50
+ ===============================================================================
51
+ undici (^8.9.0)
52
+ License: MIT License
53
+ ===============================================================================
54
+ MIT License
55
+
56
+ Copyright (c) Matteo Collina and Undici contributors
57
+
58
+ Permission is hereby granted, free of charge, to any person obtaining a copy
59
+ of this software and associated documentation files (the "Software"), to deal
60
+ in the Software without restriction, including without limitation the rights
61
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
62
+ copies of the Software, and to permit persons to whom the Software is
63
+ furnished to do so, subject to the following conditions:
64
+
65
+ The above copyright notice and this permission notice shall be included in all
66
+ copies or substantial portions of the Software.
67
+
68
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
69
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
70
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
71
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
72
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
73
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
74
+ SOFTWARE.
75
+
76
+ ===============================================================================
77
+ uuid (^14.0.1)
78
+ License: MIT License
79
+ ===============================================================================
80
+ The MIT License (MIT)
81
+
82
+ Copyright (c) 2010-2020 Robert Kieffer and other contributors
83
+
84
+ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
85
+
86
+ The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
87
+
88
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
89
+
90
+ ===============================================================================
91
+ @noble/hashes (^2.2.0)
92
+ License: MIT License
93
+ ===============================================================================
94
+ The MIT License (MIT)
95
+
96
+ Copyright (c) 2022 Paul Miller (https://paulmillr.com)
97
+
98
+ Permission is hereby granted, free of charge, to any person obtaining a copy
99
+ of this software and associated documentation files (the “Software”), to deal
100
+ in the Software without restriction, including without limitation the rights
101
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
102
+ copies of the Software, and to permit persons to whom the Software is
103
+ furnished to do so, subject to the following conditions:
104
+
105
+ The above copyright notice and this permission notice shall be included in
106
+ all copies or substantial portions of the Software.
107
+
108
+ THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
109
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
110
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
111
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
112
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
113
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
114
+ THE SOFTWARE.
115
+
116
+ ===============================================================================
117
+ structured-headers (^2.0.3)
118
+ License: MIT License
119
+ ===============================================================================
120
+ MIT License
121
+
122
+ Copyright (c) 2018-2025 Evert Pot
123
+
124
+ Permission is hereby granted, free of charge, to any person obtaining a copy
125
+ of this software and associated documentation files (the "Software"), to deal
126
+ in the Software without restriction, including without limitation the rights
127
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
128
+ copies of the Software, and to permit persons to whom the Software is
129
+ furnished to do so, subject to the following conditions:
130
+
131
+ The above copyright notice and this permission notice shall be included in all
132
+ copies or substantial portions of the Software.
133
+
134
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
135
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
136
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
137
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
138
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
139
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
140
+ SOFTWARE.
141
+
142
+ ===============================================================================
143
+ @aws-sdk/client-kms (^3.1092.0)
144
+ License: Apache License 2.0
145
+ ===============================================================================
146
+ [Note: Used only by users who choose AWS KMS-based key storage (key-aws package).]
147
+
148
+ Apache License
149
+ Version 2.0, January 2004
150
+ http://www.apache.org/licenses/
151
+
152
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
153
+
154
+ 1. Definitions.
155
+
156
+ "License" shall mean the terms and conditions for use, reproduction,
157
+ and distribution as defined by Sections 1 through 9 of this document.
158
+
159
+ "Licensor" shall mean the copyright owner or entity authorized by
160
+ the copyright owner that is granting the License.
161
+
162
+ "Legal Entity" shall mean the union of the acting entity and all
163
+ other entities that control, are controlled by, or are under common
164
+ control with that entity. For the purposes of this definition,
165
+ "control" means (i) the power, direct or indirect, to cause the
166
+ direction or management of such entity, whether by contract or
167
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
168
+ outstanding shares, or (iii) beneficial ownership of such entity.
169
+
170
+ "You" (or "Your") shall mean an individual or Legal Entity
171
+ exercising permissions granted by this License.
172
+
173
+ "Source" form shall mean the preferred form for making modifications,
174
+ including but not limited to software source code, documentation
175
+ source, and configuration files.
176
+
177
+ "Object" form shall mean any form resulting from mechanical
178
+ transformation or translation of a Source form, including but
179
+ not limited to compiled object code, generated documentation,
180
+ and conversions to other media types.
181
+
182
+ "Work" shall mean the work of authorship, whether in Source or
183
+ Object form, made available under the License, as indicated by a
184
+ copyright notice that is included in or attached to the work
185
+ (an example is provided in the Appendix below).
186
+
187
+ "Derivative Works" shall mean any work, whether in Source or Object
188
+ form, that is based on (or derived from) the Work and for which the
189
+ editorial revisions, annotations, elaborations, or other modifications
190
+ represent, as a whole, an original work of authorship. For the purposes
191
+ of this License, Derivative Works shall not include works that remain
192
+ separable from, or merely link (or bind by name) to the interfaces of,
193
+ the Work and Derivative Works thereof.
194
+
195
+ "Contribution" shall mean any work of authorship, including
196
+ the original version of the Work and any modifications or additions
197
+ to that Work or Derivative Works thereof, that is intentionally
198
+ submitted to Licensor for inclusion in the Work by the copyright owner
199
+ or by an individual or Legal Entity authorized to submit on behalf of
200
+ the copyright owner. For the purposes of this definition, "submitted"
201
+ means any form of electronic, verbal, or written communication sent
202
+ to the Licensor or its representatives, including but not limited to
203
+ communication on electronic mailing lists, source code control systems,
204
+ and issue tracking systems that are managed by, or on behalf of, the
205
+ Licensor for the purpose of discussing and improving the Work, but
206
+ excluding communication that is conspicuously marked or otherwise
207
+ designated in writing by the copyright owner as "Not a Contribution."
208
+
209
+ "Contributor" shall mean Licensor and any individual or Legal Entity
210
+ on behalf of whom a Contribution has been received by Licensor and
211
+ subsequently incorporated within the Work.
212
+
213
+ 2. Grant of Copyright License. Subject to the terms and conditions of
214
+ this License, each Contributor hereby grants to You a perpetual,
215
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
216
+ copyright license to reproduce, prepare Derivative Works of,
217
+ publicly display, publicly perform, sublicense, and distribute the
218
+ Work and such Derivative Works in Source or Object form.
219
+
220
+ 3. Grant of Patent License. Subject to the terms and conditions of
221
+ this License, each Contributor hereby grants to You a perpetual,
222
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
223
+ (except as stated in this section) patent license to make, have made,
224
+ use, offer to sell, sell, import, and otherwise transfer the Work,
225
+ where such license applies only to those patent claims licensable
226
+ by such Contributor that are necessarily infringed by their
227
+ Contribution(s) alone or by combination of their Contribution(s)
228
+ with the Work to which such Contribution(s) was submitted. If You
229
+ institute patent litigation against any entity (including a
230
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
231
+ or a Contribution incorporated within the Work constitutes direct
232
+ or contributory patent infringement, then any patent licenses
233
+ granted to You under this License for that Work shall terminate
234
+ as of the date such litigation is filed.
235
+
236
+ 4. Redistribution. You may reproduce and distribute copies of the
237
+ Work or Derivative Works thereof in any medium, with or without
238
+ modifications, and in Source or Object form, provided that You
239
+ meet the following conditions:
240
+
241
+ (a) You must give any other recipients of the Work or
242
+ Derivative Works a copy of this License; and
243
+
244
+ (b) You must cause any modified files to carry prominent notices
245
+ stating that You changed the files; and
246
+
247
+ (c) You must retain, in the Source form of any Derivative Works
248
+ that You distribute, all copyright, patent, trademark, and
249
+ attribution notices from the Source form of the Work,
250
+ excluding those notices that do not pertain to any part of
251
+ the Derivative Works; and
252
+
253
+ (d) If the Work includes a "NOTICE" text file as part of its
254
+ distribution, then any Derivative Works that You distribute must
255
+ include a readable copy of the attribution notices contained
256
+ within such NOTICE file, excluding those notices that do not
257
+ pertain to any part of the Derivative Works, in at least one
258
+ of the following places: within a NOTICE text file distributed
259
+ as part of the Derivative Works; within the Source form or
260
+ documentation, if provided along with the Derivative Works; or,
261
+ within a display generated by the Derivative Works, if and
262
+ wherever such third-party notices normally appear. The contents
263
+ of the NOTICE file are for informational purposes only and
264
+ do not modify the License. You may add Your own attribution
265
+ notices within Derivative Works that You distribute, alongside
266
+ or as an addendum to the NOTICE text from the Work, provided
267
+ that such additional attribution notices cannot be construed
268
+ as modifying the License.
269
+
270
+ You may add Your own copyright statement to Your modifications and
271
+ may provide additional or different license terms and conditions
272
+ for use, reproduction, or distribution of Your modifications, or
273
+ for any such Derivative Works as a whole, provided Your use,
274
+ reproduction, and distribution of the Work otherwise complies with
275
+ the conditions stated in this License.
276
+
277
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
278
+ any Contribution intentionally submitted for inclusion in the Work
279
+ by You to the Licensor shall be under the terms and conditions of
280
+ this License, without any additional terms or conditions.
281
+ Notwithstanding the above, nothing herein shall supersede or modify
282
+ the terms of any separate license agreement you may have executed
283
+ with Licensor regarding such Contributions.
284
+
285
+ 6. Trademarks. This License does not grant permission to use the trade
286
+ names, trademarks, service marks, or product names of the Licensor,
287
+ except as required for reasonable and customary use in describing the
288
+ origin of the Work and reproducing the content of the NOTICE file.
289
+
290
+ 7. Disclaimer of Warranty. Unless required by applicable law or
291
+ agreed to in writing, Licensor provides the Work (and each
292
+ Contributor provides its Contributions) on an "AS IS" BASIS,
293
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
294
+ implied, including, without limitation, any warranties or conditions
295
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
296
+ PARTICULAR PURPOSE. You are solely responsible for determining the
297
+ appropriateness of using or redistributing the Work and assume any
298
+ risks associated with Your exercise of permissions under this License.
299
+
300
+ 8. Limitation of Liability. In no event and under no legal theory,
301
+ whether in tort (including negligence), contract, or otherwise,
302
+ unless required by applicable law (such as deliberate and grossly
303
+ negligent acts) or agreed to in writing, shall any Contributor be
304
+ liable to You for damages, including any direct, indirect, special,
305
+ incidental, or consequential damages of any character arising as a
306
+ result of this License or out of the use or inability to use the
307
+ Work (including but not limited to damages for loss of goodwill,
308
+ work stoppage, computer failure or malfunction, or any and all
309
+ other commercial damages or losses), even if such Contributor
310
+ has been advised of the possibility of such damages.
311
+
312
+ 9. Accepting Warranty or Additional Liability. While redistributing
313
+ the Work or Derivative Works thereof, You may choose to offer,
314
+ and charge a fee for, acceptance of support, warranty, indemnity,
315
+ or other liability obligations and/or rights consistent with this
316
+ License. However, in accepting such obligations, You may act only
317
+ on Your own behalf and on Your sole responsibility, not on behalf
318
+ of any other Contributor, and only if You agree to indemnify,
319
+ defend, and hold each Contributor harmless for any liability
320
+ incurred by, or claims asserted against, such Contributor by reason
321
+ of your accepting any such warranty or additional liability.
322
+
323
+ END OF TERMS AND CONDITIONS
324
+
325
+ APPENDIX: How to apply the Apache License to your work.
326
+
327
+ To apply the Apache License to your work, attach the following
328
+ boilerplate notice, with the fields enclosed by brackets "[]"
329
+ replaced with your own identifying information. (Don't include
330
+ the brackets!) The text should be enclosed in the appropriate
331
+ comment syntax for the file format. We also recommend that a
332
+ file or class name and description of purpose be included on the
333
+ same "printed page" as the copyright notice for easier
334
+ identification within third-party archives.
335
+
336
+ Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved.
337
+
338
+ Licensed under the Apache License, Version 2.0 (the "License");
339
+ you may not use this file except in compliance with the License.
340
+ You may obtain a copy of the License at
341
+
342
+ http://www.apache.org/licenses/LICENSE-2.0
343
+
344
+ Unless required by applicable law or agreed to in writing, software
345
+ distributed under the License is distributed on an "AS IS" BASIS,
346
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
347
+ See the License for the specific language governing permissions and
348
+ limitations under the License.
package/dist/index.cjs CHANGED
@@ -167,11 +167,8 @@ async function rotateManagedOperationalKey(options) {
167
167
  cause
168
168
  });
169
169
  }
170
- if (raw.logReference !== options.logReference) {
171
- throw permanentRotationError(`registry preparation returned unexpected log reference ${raw.logReference}`);
172
- }
173
170
  const context = { expectedFqdn: domain, previousOperationalKey: previousKey };
174
- let prepared = await (0, import_writer.parsePreparedC2spTlogEvent)(raw.entryBytes, raw.logReference, context);
171
+ let prepared = await (0, import_writer.parsePreparedC2spTlogEvent)(raw.entryBytes, options.logReference, context);
175
172
  if (prepared.envelope.type !== "KEY_ROTATION") {
176
173
  throw permanentRotationError(`registry preparation returned ${String(prepared.envelope.type)} instead of KEY_ROTATION`);
177
174
  }
@@ -212,22 +209,27 @@ async function rotateManagedOperationalKey(options) {
212
209
  async function resumeManagedOperationalKeyRotation(options) {
213
210
  assertCoordination(options);
214
211
  if (typeof options.resolveKeyProvider !== "function") throw new import_protocol.ArgumentError("managed rotation recovery requires resolveKeyProvider");
215
- const rotation = options.rotation;
212
+ let rotation = options.rotation;
216
213
  try {
217
214
  assertOperationReferences(rotation.previousProviderReference, rotation.newProviderReference, rotation.idempotencyKey);
218
215
  const previous = await options.resolveKeyProvider(rotation.previousProviderReference);
219
216
  const target = rotation.newProviderReference === rotation.previousProviderReference ? previous : await options.resolveKeyProvider(rotation.newProviderReference);
220
217
  await validateSavedRotation(rotation, previous, target);
218
+ if (rotation.activated) {
219
+ if (rotation.submission?.state !== "accepted") throw permanentRotationError("activated rotation has no accepted submission");
220
+ await assertSubmissionMatchesRotation(rotation, rotation.submission);
221
+ return restoreActivatedRotation(options, previous, target, rotation);
222
+ }
221
223
  if (rotation.submission?.state === "rejected") {
222
224
  throw new ManagedKeyRotationSubmissionError("cannot resume a rejected managed key rotation", rotation, false);
223
225
  }
226
+ rotation = { ...rotation, applicationSigningPaused: true };
224
227
  await options.setApplicationSigningPaused(true);
228
+ await options.persistRotation(rotation);
225
229
  if (rotation.submission?.state === "accepted") {
226
230
  await assertSubmissionMatchesRotation(rotation, rotation.submission);
227
231
  return activateRotation(options, previous, target, rotation);
228
232
  }
229
- if (rotation.activated) throw permanentRotationError("activated rotation has no accepted submission");
230
- await options.persistRotation(rotation);
231
233
  return submitManagedRotation(options, previous, target, rotation);
232
234
  } catch (cause) {
233
235
  if (cause instanceof ManagedKeyRotationSubmissionError || cause instanceof ManagedKeyRotationActivationError) throw cause;
@@ -268,7 +270,7 @@ async function activateRotation(options, previous, target, rotation) {
268
270
  let recoverable = rotation;
269
271
  try {
270
272
  await verifyAcceptedPublication(options, rotation);
271
- if (!rotation.activated) await reconcileAcceptedRotation(previous, target, rotation);
273
+ await reconcileAcceptedRotation(previous, target, rotation);
272
274
  if (previous !== target && await activeKey(previous, await previous.listKeyIds())) {
273
275
  throw permanentRotationError("superseded source provider still has an active signing key");
274
276
  }
@@ -289,6 +291,26 @@ async function activateRotation(options, previous, target, rotation) {
289
291
  );
290
292
  }
291
293
  }
294
+ async function restoreActivatedRotation(options, previous, target, rotation) {
295
+ try {
296
+ if (previous !== target && await activeKey(previous, await previous.listKeyIds())) {
297
+ throw permanentRotationError("superseded source provider still has an active signing key");
298
+ }
299
+ await assertKeyBinding(await target.signingKey(), rotation.newKid, rotation.newThumbprint);
300
+ await options.setApplicationSigner(target, rotation.newKid);
301
+ if (!rotation.applicationSigningPaused) return rotation;
302
+ await options.setApplicationSigningPaused(false);
303
+ const completed = { ...rotation, applicationSigningPaused: false };
304
+ await options.persistRotation(completed);
305
+ return completed;
306
+ } catch (cause) {
307
+ throw new ManagedKeyRotationActivationError(
308
+ "managed key rotation is activated, but restoring the application signer failed; resume with this rotation state",
309
+ rotation,
310
+ cause
311
+ );
312
+ }
313
+ }
292
314
  async function reconcileAcceptedRotation(previous, target, rotation) {
293
315
  const active = await target.signingKey();
294
316
  if (active.kid !== rotation.newKid) {
@@ -299,9 +321,12 @@ async function reconcileAcceptedRotation(previous, target, rotation) {
299
321
  if ((await previous.listKeyIds()).includes(rotation.previousKid)) await previous.supersede(rotation.previousKid);
300
322
  }
301
323
  function assertCoordination(options) {
302
- for (const name of ["persistRotation", "setApplicationSigningPaused", "setApplicationSigner", "verifyPublicationEvidence"]) {
324
+ for (const name of ["persistRotation", "setApplicationSigningPaused", "setApplicationSigner"]) {
303
325
  if (typeof options[name] !== "function") throw new import_protocol.ArgumentError(`managed rotation requires ${name}`);
304
326
  }
327
+ if (typeof options.identityManager?.verifyLocalPublication !== "function") {
328
+ throw new import_protocol.ArgumentError("managed rotation requires identityManager");
329
+ }
305
330
  }
306
331
  function assertOperationReferences(previous, next, idempotencyKey) {
307
332
  if (!previous?.trim() || !next?.trim() || !idempotencyKey || idempotencyKey.trim() !== idempotencyKey || idempotencyKey.length > 200) {
@@ -354,7 +379,7 @@ async function validateSavedRotation(rotation, previous, target) {
354
379
  if (rotation.submission) await assertSubmissionMatchesRotation(rotation, rotation.submission);
355
380
  }
356
381
  async function verifyAcceptedPublication(options, rotation) {
357
- const verified = await options.verifyPublicationEvidence(rotation.domain);
382
+ const verified = await options.identityManager.verifyLocalPublication();
358
383
  if (verified.domain !== rotation.domain || verified.record.lr !== rotation.logReference) {
359
384
  throw permanentRotationError("current signed publication does not match managed rotation");
360
385
  }
@@ -512,12 +537,9 @@ async function prepareAndSubmit(options, intent, entityKey, operationalKey) {
512
537
  };
513
538
  let prepared;
514
539
  try {
515
- if (options.logReference && raw.logReference !== options.logReference) {
516
- throw permanentLogError(`registry preparation returned unexpected log reference ${raw.logReference}`);
517
- }
518
- prepared = await (0, import_writer2.parsePreparedC2spTlogEvent)(raw.entryBytes, raw.logReference, context);
519
- if (prepared.envelope.type !== "ISSUANCE") {
520
- throw permanentLogError(`registry preparation returned ${String(prepared.envelope.type)} instead of ISSUANCE`);
540
+ prepared = await (0, import_writer2.parsePreparedC2spTlogIssuance)(raw.entryBytes, context);
541
+ if (options.logReference && prepared.reference.lr !== options.logReference) {
542
+ throw permanentLogError(`registry preparation returned unexpected log reference ${prepared.reference.lr}`);
521
543
  }
522
544
  const existingSignatures = prepared.envelope.sigs;
523
545
  if (!existingSignatures?.ae) throw permanentLogError("registry ISSUANCE preparation is missing the accountable-entity signature");
@@ -533,7 +555,7 @@ async function prepareAndSubmit(options, intent, entityKey, operationalKey) {
533
555
  const entryBytes = await (0, import_writer2.c2spTlogEntryBytes)(prepared, context);
534
556
  const completed = {
535
557
  ...intent,
536
- logReference: raw.logReference,
558
+ logReference: prepared.reference.lr,
537
559
  entryBytes: entryBytes.slice(),
538
560
  entryHash: await sha256Hex2(entryBytes)
539
561
  };
package/dist/index.d.cts CHANGED
@@ -1,4 +1,4 @@
1
- import { VerificationError, DnsIdJWK, KeyProvider, VerifiedDomain, IdentityManagerDependencies, DnsidConfig, IdentityManager } from '@dnsid-ai/protocol';
1
+ import { VerificationError, DnsIdJWK, KeyProvider, IdentityManager, IdentityManagerDependencies, DnsidConfig } from '@dnsid-ai/protocol';
2
2
  export * from '@dnsid-ai/protocol';
3
3
  export { ArgumentError, DNSID_VERSION, DNSSECMode, DNSSECState, DnsIdTxtRecord, DomainLog, DomainSnapshot, IdentityManager, InMemoryIdentityCache, JWKS, JWKS_MAX_RESPONSE_BYTES, LogRegistry, NoopLogReader, ParseError, SIGNING_ALGS, STATUS_MAX_RESPONSE_BYTES, ValidationError, VerificationCode, VerificationError, VerifiedDomain, activeStatusDocument, fromBase64Url, isDomainName, isTransientVerificationError, jwkSignatureAlg, jwkThumbprint, normalizeFQDN, parseKaDuration, parseKeyId, retryTransientVerification, toArrayBuffer, toBase64Url, validateAgentStatus, verifyWithKey } from '@dnsid-ai/protocol';
4
4
  import * as jose from '@dnsid-ai/jose';
@@ -27,8 +27,11 @@ interface ManagedKeyRotationCoordination {
27
27
  setApplicationSigningPaused: (paused: boolean) => Promise<void>;
28
28
  /** Restores the application signer before signing is resumed. */
29
29
  setApplicationSigner: (provider: KeyProvider, kid: string) => Promise<void>;
30
- /** Must return fresh, protocol-verified signed TXT/ku evidence, not cached pre-rotation evidence. */
31
- verifyPublicationEvidence: (domain: string) => Promise<VerifiedDomain>;
30
+ /**
31
+ * The local identity's manager. Before activation the SDK verifies fresh evidence for the
32
+ * manager's own publication, without counterparty acceptance.
33
+ */
34
+ identityManager: IdentityManager;
32
35
  }
33
36
  interface RotateManagedOperationalKeyOptions extends ManagedKeyRotationCoordination {
34
37
  domain: string;
@@ -88,7 +91,14 @@ declare class ManagedKeyRotationActivationError extends VerificationError {
88
91
  * result for the exact, durably persisted bytes while application signing is paused.
89
92
  */
90
93
  declare function rotateManagedOperationalKey(options: RotateManagedOperationalKeyOptions): Promise<ManagedKeyRotationResult>;
91
- /** Retries only the persisted completed bytes and activates on acceptance. */
94
+ /**
95
+ * Continues a persisted rotation from its saved state:
96
+ * - not accepted: pause signing and resubmit the exact saved bytes with the same idempotency key;
97
+ * - accepted, not activated: pause signing, verify the new publication, activate, and resume signing;
98
+ * - activated, signing paused: restore the signer and resume signing without network calls;
99
+ * - activated, signing resumed: restore the signer only. This state is terminal: archive it, and
100
+ * start the next rotation with a new state.
101
+ */
92
102
  declare function resumeManagedOperationalKeyRotation(options: ResumeManagedOperationalKeyRotationOptions): Promise<ManagedKeyRotationResult>;
93
103
 
94
104
  interface ManagedIssuanceRegistry {
package/dist/index.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { VerificationError, DnsIdJWK, KeyProvider, VerifiedDomain, IdentityManagerDependencies, DnsidConfig, IdentityManager } from '@dnsid-ai/protocol';
1
+ import { VerificationError, DnsIdJWK, KeyProvider, IdentityManager, IdentityManagerDependencies, DnsidConfig } from '@dnsid-ai/protocol';
2
2
  export * from '@dnsid-ai/protocol';
3
3
  export { ArgumentError, DNSID_VERSION, DNSSECMode, DNSSECState, DnsIdTxtRecord, DomainLog, DomainSnapshot, IdentityManager, InMemoryIdentityCache, JWKS, JWKS_MAX_RESPONSE_BYTES, LogRegistry, NoopLogReader, ParseError, SIGNING_ALGS, STATUS_MAX_RESPONSE_BYTES, ValidationError, VerificationCode, VerificationError, VerifiedDomain, activeStatusDocument, fromBase64Url, isDomainName, isTransientVerificationError, jwkSignatureAlg, jwkThumbprint, normalizeFQDN, parseKaDuration, parseKeyId, retryTransientVerification, toArrayBuffer, toBase64Url, validateAgentStatus, verifyWithKey } from '@dnsid-ai/protocol';
4
4
  import * as jose from '@dnsid-ai/jose';
@@ -27,8 +27,11 @@ interface ManagedKeyRotationCoordination {
27
27
  setApplicationSigningPaused: (paused: boolean) => Promise<void>;
28
28
  /** Restores the application signer before signing is resumed. */
29
29
  setApplicationSigner: (provider: KeyProvider, kid: string) => Promise<void>;
30
- /** Must return fresh, protocol-verified signed TXT/ku evidence, not cached pre-rotation evidence. */
31
- verifyPublicationEvidence: (domain: string) => Promise<VerifiedDomain>;
30
+ /**
31
+ * The local identity's manager. Before activation the SDK verifies fresh evidence for the
32
+ * manager's own publication, without counterparty acceptance.
33
+ */
34
+ identityManager: IdentityManager;
32
35
  }
33
36
  interface RotateManagedOperationalKeyOptions extends ManagedKeyRotationCoordination {
34
37
  domain: string;
@@ -88,7 +91,14 @@ declare class ManagedKeyRotationActivationError extends VerificationError {
88
91
  * result for the exact, durably persisted bytes while application signing is paused.
89
92
  */
90
93
  declare function rotateManagedOperationalKey(options: RotateManagedOperationalKeyOptions): Promise<ManagedKeyRotationResult>;
91
- /** Retries only the persisted completed bytes and activates on acceptance. */
94
+ /**
95
+ * Continues a persisted rotation from its saved state:
96
+ * - not accepted: pause signing and resubmit the exact saved bytes with the same idempotency key;
97
+ * - accepted, not activated: pause signing, verify the new publication, activate, and resume signing;
98
+ * - activated, signing paused: restore the signer and resume signing without network calls;
99
+ * - activated, signing resumed: restore the signer only. This state is terminal: archive it, and
100
+ * start the next rotation with a new state.
101
+ */
92
102
  declare function resumeManagedOperationalKeyRotation(options: ResumeManagedOperationalKeyRotationOptions): Promise<ManagedKeyRotationResult>;
93
103
 
94
104
  interface ManagedIssuanceRegistry {
package/dist/index.js CHANGED
@@ -125,11 +125,8 @@ async function rotateManagedOperationalKey(options) {
125
125
  cause
126
126
  });
127
127
  }
128
- if (raw.logReference !== options.logReference) {
129
- throw permanentRotationError(`registry preparation returned unexpected log reference ${raw.logReference}`);
130
- }
131
128
  const context = { expectedFqdn: domain, previousOperationalKey: previousKey };
132
- let prepared = await parsePreparedC2spTlogEvent(raw.entryBytes, raw.logReference, context);
129
+ let prepared = await parsePreparedC2spTlogEvent(raw.entryBytes, options.logReference, context);
133
130
  if (prepared.envelope.type !== "KEY_ROTATION") {
134
131
  throw permanentRotationError(`registry preparation returned ${String(prepared.envelope.type)} instead of KEY_ROTATION`);
135
132
  }
@@ -170,22 +167,27 @@ async function rotateManagedOperationalKey(options) {
170
167
  async function resumeManagedOperationalKeyRotation(options) {
171
168
  assertCoordination(options);
172
169
  if (typeof options.resolveKeyProvider !== "function") throw new ArgumentError("managed rotation recovery requires resolveKeyProvider");
173
- const rotation = options.rotation;
170
+ let rotation = options.rotation;
174
171
  try {
175
172
  assertOperationReferences(rotation.previousProviderReference, rotation.newProviderReference, rotation.idempotencyKey);
176
173
  const previous = await options.resolveKeyProvider(rotation.previousProviderReference);
177
174
  const target = rotation.newProviderReference === rotation.previousProviderReference ? previous : await options.resolveKeyProvider(rotation.newProviderReference);
178
175
  await validateSavedRotation(rotation, previous, target);
176
+ if (rotation.activated) {
177
+ if (rotation.submission?.state !== "accepted") throw permanentRotationError("activated rotation has no accepted submission");
178
+ await assertSubmissionMatchesRotation(rotation, rotation.submission);
179
+ return restoreActivatedRotation(options, previous, target, rotation);
180
+ }
179
181
  if (rotation.submission?.state === "rejected") {
180
182
  throw new ManagedKeyRotationSubmissionError("cannot resume a rejected managed key rotation", rotation, false);
181
183
  }
184
+ rotation = { ...rotation, applicationSigningPaused: true };
182
185
  await options.setApplicationSigningPaused(true);
186
+ await options.persistRotation(rotation);
183
187
  if (rotation.submission?.state === "accepted") {
184
188
  await assertSubmissionMatchesRotation(rotation, rotation.submission);
185
189
  return activateRotation(options, previous, target, rotation);
186
190
  }
187
- if (rotation.activated) throw permanentRotationError("activated rotation has no accepted submission");
188
- await options.persistRotation(rotation);
189
191
  return submitManagedRotation(options, previous, target, rotation);
190
192
  } catch (cause) {
191
193
  if (cause instanceof ManagedKeyRotationSubmissionError || cause instanceof ManagedKeyRotationActivationError) throw cause;
@@ -226,7 +228,7 @@ async function activateRotation(options, previous, target, rotation) {
226
228
  let recoverable = rotation;
227
229
  try {
228
230
  await verifyAcceptedPublication(options, rotation);
229
- if (!rotation.activated) await reconcileAcceptedRotation(previous, target, rotation);
231
+ await reconcileAcceptedRotation(previous, target, rotation);
230
232
  if (previous !== target && await activeKey(previous, await previous.listKeyIds())) {
231
233
  throw permanentRotationError("superseded source provider still has an active signing key");
232
234
  }
@@ -247,6 +249,26 @@ async function activateRotation(options, previous, target, rotation) {
247
249
  );
248
250
  }
249
251
  }
252
+ async function restoreActivatedRotation(options, previous, target, rotation) {
253
+ try {
254
+ if (previous !== target && await activeKey(previous, await previous.listKeyIds())) {
255
+ throw permanentRotationError("superseded source provider still has an active signing key");
256
+ }
257
+ await assertKeyBinding(await target.signingKey(), rotation.newKid, rotation.newThumbprint);
258
+ await options.setApplicationSigner(target, rotation.newKid);
259
+ if (!rotation.applicationSigningPaused) return rotation;
260
+ await options.setApplicationSigningPaused(false);
261
+ const completed = { ...rotation, applicationSigningPaused: false };
262
+ await options.persistRotation(completed);
263
+ return completed;
264
+ } catch (cause) {
265
+ throw new ManagedKeyRotationActivationError(
266
+ "managed key rotation is activated, but restoring the application signer failed; resume with this rotation state",
267
+ rotation,
268
+ cause
269
+ );
270
+ }
271
+ }
250
272
  async function reconcileAcceptedRotation(previous, target, rotation) {
251
273
  const active = await target.signingKey();
252
274
  if (active.kid !== rotation.newKid) {
@@ -257,9 +279,12 @@ async function reconcileAcceptedRotation(previous, target, rotation) {
257
279
  if ((await previous.listKeyIds()).includes(rotation.previousKid)) await previous.supersede(rotation.previousKid);
258
280
  }
259
281
  function assertCoordination(options) {
260
- for (const name of ["persistRotation", "setApplicationSigningPaused", "setApplicationSigner", "verifyPublicationEvidence"]) {
282
+ for (const name of ["persistRotation", "setApplicationSigningPaused", "setApplicationSigner"]) {
261
283
  if (typeof options[name] !== "function") throw new ArgumentError(`managed rotation requires ${name}`);
262
284
  }
285
+ if (typeof options.identityManager?.verifyLocalPublication !== "function") {
286
+ throw new ArgumentError("managed rotation requires identityManager");
287
+ }
263
288
  }
264
289
  function assertOperationReferences(previous, next, idempotencyKey) {
265
290
  if (!previous?.trim() || !next?.trim() || !idempotencyKey || idempotencyKey.trim() !== idempotencyKey || idempotencyKey.length > 200) {
@@ -312,7 +337,7 @@ async function validateSavedRotation(rotation, previous, target) {
312
337
  if (rotation.submission) await assertSubmissionMatchesRotation(rotation, rotation.submission);
313
338
  }
314
339
  async function verifyAcceptedPublication(options, rotation) {
315
- const verified = await options.verifyPublicationEvidence(rotation.domain);
340
+ const verified = await options.identityManager.verifyLocalPublication();
316
341
  if (verified.domain !== rotation.domain || verified.record.lr !== rotation.logReference) {
317
342
  throw permanentRotationError("current signed publication does not match managed rotation");
318
343
  }
@@ -382,6 +407,7 @@ import {
382
407
  import {
383
408
  c2spTlogEntryBytes as c2spTlogEntryBytes2,
384
409
  parsePreparedC2spTlogEvent as parsePreparedC2spTlogEvent2,
410
+ parsePreparedC2spTlogIssuance,
385
411
  signPreparedC2spTlogEvent as signPreparedC2spTlogEvent2
386
412
  } from "@dnsid-ai/log-c2sp-tlog/writer";
387
413
  var ManagedIssuanceSubmissionError = class extends VerificationError2 {
@@ -480,12 +506,9 @@ async function prepareAndSubmit(options, intent, entityKey, operationalKey) {
480
506
  };
481
507
  let prepared;
482
508
  try {
483
- if (options.logReference && raw.logReference !== options.logReference) {
484
- throw permanentLogError(`registry preparation returned unexpected log reference ${raw.logReference}`);
485
- }
486
- prepared = await parsePreparedC2spTlogEvent2(raw.entryBytes, raw.logReference, context);
487
- if (prepared.envelope.type !== "ISSUANCE") {
488
- throw permanentLogError(`registry preparation returned ${String(prepared.envelope.type)} instead of ISSUANCE`);
509
+ prepared = await parsePreparedC2spTlogIssuance(raw.entryBytes, context);
510
+ if (options.logReference && prepared.reference.lr !== options.logReference) {
511
+ throw permanentLogError(`registry preparation returned unexpected log reference ${prepared.reference.lr}`);
489
512
  }
490
513
  const existingSignatures = prepared.envelope.sigs;
491
514
  if (!existingSignatures?.ae) throw permanentLogError("registry ISSUANCE preparation is missing the accountable-entity signature");
@@ -501,7 +524,7 @@ async function prepareAndSubmit(options, intent, entityKey, operationalKey) {
501
524
  const entryBytes = await c2spTlogEntryBytes2(prepared, context);
502
525
  const completed = {
503
526
  ...intent,
504
- logReference: raw.logReference,
527
+ logReference: prepared.reference.lr,
505
528
  entryBytes: entryBytes.slice(),
506
529
  entryHash: await sha256Hex2(entryBytes)
507
530
  };
package/dist/node.cjs CHANGED
@@ -38,6 +38,7 @@ __export(node_exports, {
38
38
  createNodeIdentityManagerFromFile: () => createNodeIdentityManagerFromFile,
39
39
  createNodeIdentityVerifier: () => createNodeIdentityVerifier,
40
40
  createRegistryClientFromEnvironment: () => createRegistryClientFromEnvironment,
41
+ createResponseSigningMiddleware: () => createResponseSigningMiddleware,
41
42
  loadCliDirectory: () => loadCliDirectory,
42
43
  loadEnvironment: () => loadEnvironment,
43
44
  loadFile: () => loadFile,
@@ -321,6 +322,124 @@ async function importPrivateKey(key) {
321
322
  );
322
323
  }
323
324
 
325
+ // src/node-response-signing.ts
326
+ var MAX_BUFFERED_BYTES = 8 * 1024 * 1024;
327
+ var SIGNED_FIELDS = ["content-digest", "cache-control", "signature-input", "signature"];
328
+ function createResponseSigningMiddleware(profile, options = {}) {
329
+ return (req, res, next) => {
330
+ if (req.headers["accept-signature"] === void 0) return next();
331
+ if (res.headersSent) return next(new Error("response headers already sent; register response signing first"));
332
+ const writeHead = res.writeHead.bind(res);
333
+ const end = res.end.bind(res);
334
+ const chunks = [];
335
+ let size = 0;
336
+ let overflow = false;
337
+ let ended = false;
338
+ let done = false;
339
+ let releasing = false;
340
+ const collect = (chunk, encoding) => {
341
+ if (chunk === void 0 || chunk === null || typeof chunk === "function") return true;
342
+ const bytes = typeof chunk === "string" ? Buffer.from(chunk, typeof encoding === "string" ? encoding : "utf8") : Buffer.from(chunk);
343
+ size += bytes.length;
344
+ if (size > MAX_BUFFERED_BYTES) {
345
+ overflow = true;
346
+ chunks.length = 0;
347
+ return false;
348
+ }
349
+ chunks.push(bytes);
350
+ return true;
351
+ };
352
+ const send = (body) => {
353
+ done = true;
354
+ releasing = true;
355
+ end(body);
356
+ };
357
+ const fail = () => {
358
+ if (done) return;
359
+ for (const name of res.getHeaderNames()) res.removeHeader(name);
360
+ res.statusCode = 500;
361
+ send();
362
+ };
363
+ res.writeHead = ((status, ...args) => {
364
+ if (releasing) return writeHead(status);
365
+ res.statusCode = status;
366
+ if (typeof args[0] === "string") res.statusMessage = args.shift();
367
+ setHeaders(res, args[0]);
368
+ return res;
369
+ });
370
+ res.flushHeaders = () => fail();
371
+ res.write = ((chunk, encoding, callback) => {
372
+ const cb = typeof encoding === "function" ? encoding : callback;
373
+ if (ended || done || !collect(chunk, encoding)) {
374
+ if (typeof cb === "function") process.nextTick(cb, new Error("response not writable"));
375
+ return false;
376
+ }
377
+ if (typeof cb === "function") process.nextTick(cb);
378
+ return true;
379
+ });
380
+ res.end = ((chunk, encoding, callback) => {
381
+ if (ended || done) return res;
382
+ ended = true;
383
+ const cb = [chunk, encoding, callback].find((arg) => typeof arg === "function");
384
+ if (typeof cb === "function") res.once("finish", cb);
385
+ collect(chunk, encoding);
386
+ void release();
387
+ return res;
388
+ });
389
+ async function release() {
390
+ if (done) return;
391
+ if (overflow) return fail();
392
+ try {
393
+ const noContent = req.method === "HEAD" || [204, 205, 304].includes(res.statusCode);
394
+ const body = Buffer.concat(chunks);
395
+ const signed = await profile.signRequestedHttpResponse(
396
+ toRequest(req, options.origin),
397
+ new Response(noContent ? null : body, { status: res.statusCode, headers: toHeaders(res.getHeaders()) })
398
+ );
399
+ if (done) return;
400
+ for (const name of SIGNED_FIELDS) {
401
+ const value = signed.headers.get(name);
402
+ if (value !== null) res.setHeader(name, value);
403
+ }
404
+ send(noContent ? void 0 : body);
405
+ } catch {
406
+ fail();
407
+ }
408
+ }
409
+ next();
410
+ };
411
+ }
412
+ function toRequest(req, origin) {
413
+ const path3 = req.originalUrl ?? req.url ?? "";
414
+ if (!path3.startsWith("/")) throw new Error("only origin-form request targets can be signed");
415
+ const scheme = req.socket.encrypted ? "https" : "http";
416
+ const base = origin ?? `${scheme}://${req.headers.host ?? ""}`;
417
+ return new Request(`${new URL(base).origin}${path3}`, { method: req.method, headers: toHeaders(req.headers) });
418
+ }
419
+ function toHeaders(fields) {
420
+ const headers = new Headers();
421
+ for (const [name, value] of Object.entries(fields)) {
422
+ for (const item of Array.isArray(value) ? value : [value]) {
423
+ if (item !== void 0) headers.append(name, String(item));
424
+ }
425
+ }
426
+ return headers;
427
+ }
428
+ function setHeaders(res, headers) {
429
+ if (Array.isArray(headers)) {
430
+ const grouped = /* @__PURE__ */ new Map();
431
+ for (let i = 0; i + 1 < headers.length; i += 2) {
432
+ const name = String(headers[i]).toLowerCase();
433
+ grouped.set(name, [...grouped.get(name) ?? [], String(headers[i + 1])]);
434
+ }
435
+ for (const [name, values] of grouped) res.setHeader(name, values);
436
+ } else if (typeof headers === "object" && headers !== null) {
437
+ for (const [name, value] of Object.entries(headers)) {
438
+ if (value !== void 0) res.setHeader(name, value);
439
+ }
440
+ }
441
+ }
442
+
324
443
  // src/node-identity-manager.ts
325
444
  var import_protocol2 = require("@dnsid-ai/protocol");
326
445
  async function createNodeIdentityManager(config, deps = {}) {
@@ -597,7 +716,7 @@ async function constructIdentityManager(loaded, deps = {}) {
597
716
  }
598
717
  const manager = await createNodeIdentityManager(loaded.dnsid ?? {}, filled);
599
718
  if (identity && !deps.keyProvider && filled.keyProvider) {
600
- const verified = await manager.verifyPublicationEvidence(manager.config.identity.domain);
719
+ const verified = await manager.verifyLocalPublication();
601
720
  const selected = await filled.keyProvider.signingKey();
602
721
  const published = verified.jwks.currentOperationalSigningKey();
603
722
  if (selected.kid !== published.kid || selected.alg !== published.alg || await (0, import_protocol3.jwkThumbprint)(selected) !== await (0, import_protocol3.jwkThumbprint)(published)) {
@@ -736,6 +855,7 @@ function optional(raw, key, type, source) {
736
855
  createNodeIdentityManagerFromFile,
737
856
  createNodeIdentityVerifier,
738
857
  createRegistryClientFromEnvironment,
858
+ createResponseSigningMiddleware,
739
859
  loadCliDirectory,
740
860
  loadEnvironment,
741
861
  loadFile,
package/dist/node.d.cts CHANGED
@@ -1,9 +1,10 @@
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 { IncomingMessage, ServerResponse } from 'node:http';
5
+ import { HttpSignaturesProfile } from '@dnsid-ai/http-signatures';
4
6
  import { RegistryClientOptions, RegistryClient } from '@dnsid-ai/registry';
5
7
  import '@dnsid-ai/jose';
6
- import '@dnsid-ai/http-signatures';
7
8
  import '@dnsid-ai/web-bot-auth';
8
9
 
9
10
  type LocalKeyAlgorithm = 'EdDSA' | 'ES256';
@@ -45,6 +46,29 @@ declare class LocalKeyProvider implements KeyProvider {
45
46
  private mutate;
46
47
  }
47
48
 
49
+ /** Options for {@link createResponseSigningMiddleware}. */
50
+ interface ResponseSigningMiddlewareOptions {
51
+ /**
52
+ * Public origin the client used, such as `https://bob.test`. Set it behind a
53
+ * TLS-terminating proxy. Default: the socket's scheme and the Host header.
54
+ */
55
+ origin?: string;
56
+ }
57
+ /** Connect-style middleware, usable with Express, Connect, or a plain `node:http` handler. */
58
+ type NodeMiddleware = (req: IncomingMessage, res: ServerResponse, next: (err?: unknown) => void) => void;
59
+ /**
60
+ * Signs responses to requests that carry Accept-Signature, for `node:http` servers.
61
+ * Requests without negotiation pass through unbuffered. Negotiated responses are
62
+ * buffered up to 8 MiB and signed with `signRequestedHttpResponse` before any header
63
+ * is sent. On a signing failure, an exceeded bound, or a `flushHeaders()` call
64
+ * (streaming, such as SSE) it sends an empty 500; it never sends the unsigned
65
+ * response. Register it before request verification, compression, and handlers
66
+ * so that it sees their final status, headers, and content coding.
67
+ * Informational responses written with `writeContinue` or `writeEarlyHints` are
68
+ * sent directly and are not signed.
69
+ */
70
+ declare function createResponseSigningMiddleware(profile: HttpSignaturesProfile, options?: ResponseSigningMiddlewareOptions): NodeMiddleware;
71
+
48
72
  /**
49
73
  * Creates an IdentityManager with Node.js DNS and HTTPS defaults.
50
74
  *
@@ -150,4 +174,4 @@ declare function createNodeIdentityManagerFromFile(filePath: string, overlay?: L
150
174
  /** `RegistryClient` from `DNSID_REGISTRY_URL` and `DNSID_API_KEY`; explicit options win, including transport overrides. The constructor defaults to the local registry. */
151
175
  declare function createRegistryClientFromEnvironment(env?: EnvironmentSource, options?: RegistryClientOptions): Promise<RegistryClient>;
152
176
 
153
- 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 };
177
+ export { type EnvironmentSource, type KeySource, type LoadedConfig, type LoadedDnsidConfig, type LoadedRegistryConfig, type LocalKeyAlgorithm, LocalKeyProvider, type LogTrust, type ManagedRegistrationConfig, type NodeMiddleware, type ResponseSigningMiddlewareOptions, constructIdentityManager, createNodeIdentityManager, createNodeIdentityManagerFromDnsid, createNodeIdentityManagerFromEnvironment, createNodeIdentityManagerFromFile, createNodeIdentityVerifier, createRegistryClientFromEnvironment, createResponseSigningMiddleware, loadCliDirectory, loadEnvironment, loadFile, mergeLoadedConfig };
package/dist/node.d.ts CHANGED
@@ -1,9 +1,10 @@
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 { IncomingMessage, ServerResponse } from 'node:http';
5
+ import { HttpSignaturesProfile } from '@dnsid-ai/http-signatures';
4
6
  import { RegistryClientOptions, RegistryClient } from '@dnsid-ai/registry';
5
7
  import '@dnsid-ai/jose';
6
- import '@dnsid-ai/http-signatures';
7
8
  import '@dnsid-ai/web-bot-auth';
8
9
 
9
10
  type LocalKeyAlgorithm = 'EdDSA' | 'ES256';
@@ -45,6 +46,29 @@ declare class LocalKeyProvider implements KeyProvider {
45
46
  private mutate;
46
47
  }
47
48
 
49
+ /** Options for {@link createResponseSigningMiddleware}. */
50
+ interface ResponseSigningMiddlewareOptions {
51
+ /**
52
+ * Public origin the client used, such as `https://bob.test`. Set it behind a
53
+ * TLS-terminating proxy. Default: the socket's scheme and the Host header.
54
+ */
55
+ origin?: string;
56
+ }
57
+ /** Connect-style middleware, usable with Express, Connect, or a plain `node:http` handler. */
58
+ type NodeMiddleware = (req: IncomingMessage, res: ServerResponse, next: (err?: unknown) => void) => void;
59
+ /**
60
+ * Signs responses to requests that carry Accept-Signature, for `node:http` servers.
61
+ * Requests without negotiation pass through unbuffered. Negotiated responses are
62
+ * buffered up to 8 MiB and signed with `signRequestedHttpResponse` before any header
63
+ * is sent. On a signing failure, an exceeded bound, or a `flushHeaders()` call
64
+ * (streaming, such as SSE) it sends an empty 500; it never sends the unsigned
65
+ * response. Register it before request verification, compression, and handlers
66
+ * so that it sees their final status, headers, and content coding.
67
+ * Informational responses written with `writeContinue` or `writeEarlyHints` are
68
+ * sent directly and are not signed.
69
+ */
70
+ declare function createResponseSigningMiddleware(profile: HttpSignaturesProfile, options?: ResponseSigningMiddlewareOptions): NodeMiddleware;
71
+
48
72
  /**
49
73
  * Creates an IdentityManager with Node.js DNS and HTTPS defaults.
50
74
  *
@@ -150,4 +174,4 @@ declare function createNodeIdentityManagerFromFile(filePath: string, overlay?: L
150
174
  /** `RegistryClient` from `DNSID_REGISTRY_URL` and `DNSID_API_KEY`; explicit options win, including transport overrides. The constructor defaults to the local registry. */
151
175
  declare function createRegistryClientFromEnvironment(env?: EnvironmentSource, options?: RegistryClientOptions): Promise<RegistryClient>;
152
176
 
153
- 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 };
177
+ export { type EnvironmentSource, type KeySource, type LoadedConfig, type LoadedDnsidConfig, type LoadedRegistryConfig, type LocalKeyAlgorithm, LocalKeyProvider, type LogTrust, type ManagedRegistrationConfig, type NodeMiddleware, type ResponseSigningMiddlewareOptions, constructIdentityManager, createNodeIdentityManager, createNodeIdentityManagerFromDnsid, createNodeIdentityManagerFromEnvironment, createNodeIdentityManagerFromFile, createNodeIdentityVerifier, createRegistryClientFromEnvironment, createResponseSigningMiddleware, loadCliDirectory, loadEnvironment, loadFile, mergeLoadedConfig };
package/dist/node.js CHANGED
@@ -274,6 +274,124 @@ async function importPrivateKey(key) {
274
274
  );
275
275
  }
276
276
 
277
+ // src/node-response-signing.ts
278
+ var MAX_BUFFERED_BYTES = 8 * 1024 * 1024;
279
+ var SIGNED_FIELDS = ["content-digest", "cache-control", "signature-input", "signature"];
280
+ function createResponseSigningMiddleware(profile, options = {}) {
281
+ return (req, res, next) => {
282
+ if (req.headers["accept-signature"] === void 0) return next();
283
+ if (res.headersSent) return next(new Error("response headers already sent; register response signing first"));
284
+ const writeHead = res.writeHead.bind(res);
285
+ const end = res.end.bind(res);
286
+ const chunks = [];
287
+ let size = 0;
288
+ let overflow = false;
289
+ let ended = false;
290
+ let done = false;
291
+ let releasing = false;
292
+ const collect = (chunk, encoding) => {
293
+ if (chunk === void 0 || chunk === null || typeof chunk === "function") return true;
294
+ const bytes = typeof chunk === "string" ? Buffer.from(chunk, typeof encoding === "string" ? encoding : "utf8") : Buffer.from(chunk);
295
+ size += bytes.length;
296
+ if (size > MAX_BUFFERED_BYTES) {
297
+ overflow = true;
298
+ chunks.length = 0;
299
+ return false;
300
+ }
301
+ chunks.push(bytes);
302
+ return true;
303
+ };
304
+ const send = (body) => {
305
+ done = true;
306
+ releasing = true;
307
+ end(body);
308
+ };
309
+ const fail = () => {
310
+ if (done) return;
311
+ for (const name of res.getHeaderNames()) res.removeHeader(name);
312
+ res.statusCode = 500;
313
+ send();
314
+ };
315
+ res.writeHead = ((status, ...args) => {
316
+ if (releasing) return writeHead(status);
317
+ res.statusCode = status;
318
+ if (typeof args[0] === "string") res.statusMessage = args.shift();
319
+ setHeaders(res, args[0]);
320
+ return res;
321
+ });
322
+ res.flushHeaders = () => fail();
323
+ res.write = ((chunk, encoding, callback) => {
324
+ const cb = typeof encoding === "function" ? encoding : callback;
325
+ if (ended || done || !collect(chunk, encoding)) {
326
+ if (typeof cb === "function") process.nextTick(cb, new Error("response not writable"));
327
+ return false;
328
+ }
329
+ if (typeof cb === "function") process.nextTick(cb);
330
+ return true;
331
+ });
332
+ res.end = ((chunk, encoding, callback) => {
333
+ if (ended || done) return res;
334
+ ended = true;
335
+ const cb = [chunk, encoding, callback].find((arg) => typeof arg === "function");
336
+ if (typeof cb === "function") res.once("finish", cb);
337
+ collect(chunk, encoding);
338
+ void release();
339
+ return res;
340
+ });
341
+ async function release() {
342
+ if (done) return;
343
+ if (overflow) return fail();
344
+ try {
345
+ const noContent = req.method === "HEAD" || [204, 205, 304].includes(res.statusCode);
346
+ const body = Buffer.concat(chunks);
347
+ const signed = await profile.signRequestedHttpResponse(
348
+ toRequest(req, options.origin),
349
+ new Response(noContent ? null : body, { status: res.statusCode, headers: toHeaders(res.getHeaders()) })
350
+ );
351
+ if (done) return;
352
+ for (const name of SIGNED_FIELDS) {
353
+ const value = signed.headers.get(name);
354
+ if (value !== null) res.setHeader(name, value);
355
+ }
356
+ send(noContent ? void 0 : body);
357
+ } catch {
358
+ fail();
359
+ }
360
+ }
361
+ next();
362
+ };
363
+ }
364
+ function toRequest(req, origin) {
365
+ const path3 = req.originalUrl ?? req.url ?? "";
366
+ if (!path3.startsWith("/")) throw new Error("only origin-form request targets can be signed");
367
+ const scheme = req.socket.encrypted ? "https" : "http";
368
+ const base = origin ?? `${scheme}://${req.headers.host ?? ""}`;
369
+ return new Request(`${new URL(base).origin}${path3}`, { method: req.method, headers: toHeaders(req.headers) });
370
+ }
371
+ function toHeaders(fields) {
372
+ const headers = new Headers();
373
+ for (const [name, value] of Object.entries(fields)) {
374
+ for (const item of Array.isArray(value) ? value : [value]) {
375
+ if (item !== void 0) headers.append(name, String(item));
376
+ }
377
+ }
378
+ return headers;
379
+ }
380
+ function setHeaders(res, headers) {
381
+ if (Array.isArray(headers)) {
382
+ const grouped = /* @__PURE__ */ new Map();
383
+ for (let i = 0; i + 1 < headers.length; i += 2) {
384
+ const name = String(headers[i]).toLowerCase();
385
+ grouped.set(name, [...grouped.get(name) ?? [], String(headers[i + 1])]);
386
+ }
387
+ for (const [name, values] of grouped) res.setHeader(name, values);
388
+ } else if (typeof headers === "object" && headers !== null) {
389
+ for (const [name, value] of Object.entries(headers)) {
390
+ if (value !== void 0) res.setHeader(name, value);
391
+ }
392
+ }
393
+ }
394
+
277
395
  // src/node-identity-manager.ts
278
396
  import { IdentityManager, ArgumentError as ArgumentError2, validateDnsidConfig } from "@dnsid-ai/protocol";
279
397
  async function createNodeIdentityManager(config, deps = {}) {
@@ -561,7 +679,7 @@ async function constructIdentityManager(loaded, deps = {}) {
561
679
  }
562
680
  const manager = await createNodeIdentityManager(loaded.dnsid ?? {}, filled);
563
681
  if (identity && !deps.keyProvider && filled.keyProvider) {
564
- const verified = await manager.verifyPublicationEvidence(manager.config.identity.domain);
682
+ const verified = await manager.verifyLocalPublication();
565
683
  const selected = await filled.keyProvider.signingKey();
566
684
  const published = verified.jwks.currentOperationalSigningKey();
567
685
  if (selected.kid !== published.kid || selected.alg !== published.alg || await jwkThumbprint2(selected) !== await jwkThumbprint2(published)) {
@@ -699,6 +817,7 @@ export {
699
817
  createNodeIdentityManagerFromFile,
700
818
  createNodeIdentityVerifier,
701
819
  createRegistryClientFromEnvironment,
820
+ createResponseSigningMiddleware,
702
821
  loadCliDirectory,
703
822
  loadEnvironment,
704
823
  loadFile,
package/package.json CHANGED
@@ -1,6 +1,7 @@
1
1
  {
2
2
  "name": "@dnsid-ai/sdk",
3
- "version": "0.31.0",
3
+ "author": "Known Systems AI, Inc.",
4
+ "version": "0.33.0",
4
5
  "description": "Runtime-neutral DNSid SDK entrypoint with optional Node.js convenience helpers.",
5
6
  "type": "module",
6
7
  "types": "./dist/index.d.ts",
@@ -17,7 +18,8 @@
17
18
  }
18
19
  },
19
20
  "files": [
20
- "dist"
21
+ "dist",
22
+ "NOTICE.txt"
21
23
  ],
22
24
  "repository": {
23
25
  "type": "git",
@@ -35,20 +37,21 @@
35
37
  "identity"
36
38
  ],
37
39
  "scripts": {
40
+ "prepack": "node ../../scripts/copy-notice.mjs",
38
41
  "build": "tsup src/index.ts src/node.ts --format esm,cjs --dts --clean",
39
42
  "typecheck": "tsc --noEmit"
40
43
  },
41
44
  "dependencies": {
42
- "@dnsid-ai/protocol": "^0.31.0",
43
- "@dnsid-ai/http-signatures": "^0.31.0",
44
- "@dnsid-ai/jose": "^0.31.0",
45
- "@dnsid-ai/log-c2sp-tlog": "^0.31.0",
46
- "@dnsid-ai/registry": "^0.31.0",
47
- "@dnsid-ai/web-bot-auth": "^0.31.0"
45
+ "@dnsid-ai/protocol": "^0.33.0",
46
+ "@dnsid-ai/http-signatures": "^0.33.0",
47
+ "@dnsid-ai/jose": "^0.33.0",
48
+ "@dnsid-ai/log-c2sp-tlog": "^0.33.0",
49
+ "@dnsid-ai/registry": "^0.33.0",
50
+ "@dnsid-ai/web-bot-auth": "^0.33.0"
48
51
  },
49
52
  "peerDependencies": {
50
- "@dnsid-ai/key-aws": "^0.31.0",
51
- "@dnsid-ai/transport": "^0.31.0"
53
+ "@dnsid-ai/key-aws": "^0.33.0",
54
+ "@dnsid-ai/transport": "^0.33.0"
52
55
  },
53
56
  "peerDependenciesMeta": {
54
57
  "@dnsid-ai/key-aws": {