@volter/world-core 2.0.28 → 2.0.30

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.
@@ -58,6 +58,17 @@ export declare class MemoryCredentialStorage implements CredentialStorage {
58
58
  }
59
59
  export declare function sealCredential(kekSecret: string, payload: CredentialPayload, placedAt: string, slot?: string, bound?: string): Promise<SealedUnderKey>;
60
60
  export declare function openSealedCredential(kekSecret: string, sealed: SealedCredential, slot?: string, bound?: string): Promise<CredentialPayload>;
61
+ /** What a rewrap made of a record: its new form, current (already under the newest version), or not this sealer's to
62
+ * vouch for (another key's, or an answer whose version it could not read): only `current` says nothing is left. */
63
+ export type Rewrapped = {
64
+ status: 'rewrapped';
65
+ sealed: SealedCredential;
66
+ } | {
67
+ status: 'current';
68
+ } | {
69
+ status: 'foreign';
70
+ reason: string;
71
+ };
61
72
  /** Seals and opens credentials. A host chooses one (`setSealer`); a key it holds (`keySealer`), or a vault that holds
62
73
  * the key and wraps for it (`transitSealer`). */
63
74
  export interface Sealer {
@@ -65,6 +76,11 @@ export interface Sealer {
65
76
  seal(payload: CredentialPayload, placedAt: string, slot?: string, bound?: string): Promise<SealedCredential>;
66
77
  /** `bound`: the destination it is opened for; a record bound elsewhere refuses */
67
78
  open(sealed: SealedCredential, slot?: string, bound?: string): Promise<CredentialPayload>;
79
+ /** The record with its DEK wrapped under the newest version of its key, without the DEK leaving the sealer (a
80
+ * vault's `transit/rewrap` after a rotation): `rewrapped` with its new form, `current` when it already is, `foreign`
81
+ * when it is not this sealer's to vouch for. What an operator's sweep runs after rotating a key, before raising its
82
+ * minimum decryption version. */
83
+ rewrap?(sealed: SealedCredential): Promise<Rewrapped>;
68
84
  }
69
85
  /** Wraps under a key the host holds (the user's key file, a deployment secret): `v: 1` records. */
70
86
  export declare function keySealer(kekSecret: () => string): Sealer;
@@ -257,5 +257,20 @@ export function transitSealer(options) {
257
257
  throw new Error('credential unseal failed: wrong slot or corrupted record');
258
258
  }
259
259
  },
260
+ async rewrap(sealed) {
261
+ // only this sealer's own records: a key's (v1) or another transit key's are not its to rewrap
262
+ if (sealed.v !== 2)
263
+ return { status: 'foreign', reason: 'sealed under a host key' };
264
+ if (sealed.transitKey !== named)
265
+ return { status: 'foreign', reason: `sealed by ${sealed.transitKey}` };
266
+ const { ciphertext } = await call('rewrap', { ciphertext: sealed.wrappedDek });
267
+ const version = (wrapped) => { const m = typeof wrapped === 'string' ? /^vault:v(\d+):/.exec(wrapped) : null; return m ? Number(m[1]) : null; };
268
+ const before = version(sealed.wrappedDek);
269
+ const after = version(ciphertext);
270
+ if (before === null || after === null)
271
+ return { status: 'foreign', reason: 'a wrapped key whose version could not be read' };
272
+ // the vault rewraps to a fresh ciphertext at the same version when the key has not moved: current, nothing written
273
+ return after > before ? { status: 'rewrapped', sealed: { ...sealed, wrappedDek: String(ciphertext) } } : { status: 'current' };
274
+ },
260
275
  };
261
276
  }
@@ -48,7 +48,7 @@ export * as git from './git/index.js';
48
48
  export { PLACEHOLDER_REMOTE, beginPlaceholderPull, endPlaceholderPull, placeholderPullActive, placeholderPullMarkerPath, withPlaceholderPull, placeholderEventsFor } from './placeholder-remote.js';
49
49
  export { ownFields, subjectHistory, rebaseBranch, cutCheckpoint, appendParentEntry, wholeLog, branchEntries, branchLogPath, branchMetaPath, CHECKPOINT_EVERY, dropCheckpoint, foldEntries, landedCopy, landedIds, parentEntries, parentLogPath, position, readBranchMeta, readTree, toEntry, toEvent, unpushedEntries, writeBranchMeta } from './log.js';
50
50
  export type { BranchMeta, Entry, EntryOp, Receipt } from './log.js';
51
- export { sealCredential, openSealedCredential, MemoryCredentialStorage, keySealer, transitSealer, type Sealer, type TransitSealerOptions, type SealedByVault, type SealedUnderKey } from './credential.js';
51
+ export { sealCredential, openSealedCredential, MemoryCredentialStorage, keySealer, transitSealer, type Rewrapped, type Sealer, type TransitSealerOptions, type SealedByVault, type SealedUnderKey } from './credential.js';
52
52
  export type { CredentialPayload, CredentialStorage, SealedCredential } from './credential.js';
53
53
  export { validateRemoteOrigin, buildRemoteExecute, type CredentialCustody } from './executor.js';
54
54
  export type { TwinAuthStrategy } from './executor.js';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@volter/world-core",
3
- "version": "2.0.28",
3
+ "version": "2.0.30",
4
4
  "description": "The kernel of Volter World: one log per twin, branches as pointers, checkpoints, the fold that keeps a twin current, the head that performs a write against the vendor, references, and the git plane. A twin package builds on it; the runtime serves it.",
5
5
  "keywords": [
6
6
  "twin",
package/src/credential.ts CHANGED
@@ -178,6 +178,10 @@ export async function openSealedCredential(kekSecret: string, sealed: SealedCred
178
178
 
179
179
  // ── a sealer: what seals and opens a host's credentials ─────────────────────────────────────
180
180
 
181
+ /** What a rewrap made of a record: its new form, current (already under the newest version), or not this sealer's to
182
+ * vouch for (another key's, or an answer whose version it could not read): only `current` says nothing is left. */
183
+ export type Rewrapped = { status: 'rewrapped'; sealed: SealedCredential } | { status: 'current' } | { status: 'foreign'; reason: string };
184
+
181
185
  /** Seals and opens credentials. A host chooses one (`setSealer`); a key it holds (`keySealer`), or a vault that holds
182
186
  * the key and wraps for it (`transitSealer`). */
183
187
  export interface Sealer {
@@ -185,6 +189,11 @@ export interface Sealer {
185
189
  seal(payload: CredentialPayload, placedAt: string, slot?: string, bound?: string): Promise<SealedCredential>;
186
190
  /** `bound`: the destination it is opened for; a record bound elsewhere refuses */
187
191
  open(sealed: SealedCredential, slot?: string, bound?: string): Promise<CredentialPayload>;
192
+ /** The record with its DEK wrapped under the newest version of its key, without the DEK leaving the sealer (a
193
+ * vault's `transit/rewrap` after a rotation): `rewrapped` with its new form, `current` when it already is, `foreign`
194
+ * when it is not this sealer's to vouch for. What an operator's sweep runs after rotating a key, before raising its
195
+ * minimum decryption version. */
196
+ rewrap?(sealed: SealedCredential): Promise<Rewrapped>;
188
197
  }
189
198
 
190
199
  /** Wraps under a key the host holds (the user's key file, a deployment secret): `v: 1` records. */
@@ -262,7 +271,7 @@ export function transitSealer(options: TransitSealerOptions): Sealer {
262
271
  if (status >= 500) renewedAt = 0;
263
272
  if (status >= 300) console.warn(`[credential] the vault at ${addr} did not renew this host's token (${status})`);
264
273
  };
265
- const call = async (op: 'encrypt' | 'decrypt' | 'hmac', body: Record<string, string | number>, key = options.key): Promise<Record<string, string>> => {
274
+ const call = async (op: 'encrypt' | 'decrypt' | 'hmac' | 'rewrap', body: Record<string, string | number>, key = options.key): Promise<Record<string, string>> => {
266
275
  const which = `${mount}/${key}`;
267
276
  let response: Response;
268
277
  try {
@@ -328,5 +337,16 @@ export function transitSealer(options: TransitSealerOptions): Sealer {
328
337
  throw new Error('credential unseal failed: wrong slot or corrupted record');
329
338
  }
330
339
  },
340
+ async rewrap(sealed) {
341
+ // only this sealer's own records: a key's (v1) or another transit key's are not its to rewrap
342
+ if (sealed.v !== 2) return { status: 'foreign', reason: 'sealed under a host key' };
343
+ if (sealed.transitKey !== named) return { status: 'foreign', reason: `sealed by ${sealed.transitKey}` };
344
+ const { ciphertext } = await call('rewrap', { ciphertext: sealed.wrappedDek });
345
+ const version = (wrapped: unknown): number | null => { const m = typeof wrapped === 'string' ? /^vault:v(\d+):/.exec(wrapped) : null; return m ? Number(m[1]) : null; };
346
+ const before = version(sealed.wrappedDek); const after = version(ciphertext);
347
+ if (before === null || after === null) return { status: 'foreign', reason: 'a wrapped key whose version could not be read' };
348
+ // the vault rewraps to a fresh ciphertext at the same version when the key has not moved: current, nothing written
349
+ return after > before ? { status: 'rewrapped', sealed: { ...sealed, wrappedDek: String(ciphertext) } } : { status: 'current' };
350
+ },
331
351
  };
332
352
  }
package/src/index.ts CHANGED
@@ -296,7 +296,7 @@ export { PLACEHOLDER_REMOTE, beginPlaceholderPull, endPlaceholderPull, placehold
296
296
 
297
297
  export { ownFields, subjectHistory, rebaseBranch, cutCheckpoint, appendParentEntry, wholeLog, branchEntries, branchLogPath, branchMetaPath, CHECKPOINT_EVERY, dropCheckpoint, foldEntries, landedCopy, landedIds, parentEntries, parentLogPath, position, readBranchMeta, readTree, toEntry, toEvent, unpushedEntries, writeBranchMeta } from './log.ts';
298
298
  export type { BranchMeta, Entry, EntryOp, Receipt } from './log.ts';
299
- export { sealCredential, openSealedCredential, MemoryCredentialStorage, keySealer, transitSealer, type Sealer, type TransitSealerOptions, type SealedByVault, type SealedUnderKey } from './credential.ts';
299
+ export { sealCredential, openSealedCredential, MemoryCredentialStorage, keySealer, transitSealer, type Rewrapped, type Sealer, type TransitSealerOptions, type SealedByVault, type SealedUnderKey } from './credential.ts';
300
300
  export type { CredentialPayload, CredentialStorage, SealedCredential } from './credential.ts';
301
301
  export { validateRemoteOrigin, buildRemoteExecute, type CredentialCustody } from './executor.ts';
302
302
  export type { TwinAuthStrategy } from './executor.ts';