@forgesworn/fold-kit 0.5.0 → 0.5.1

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/dist/epoch.d.ts CHANGED
@@ -257,6 +257,10 @@ export type EpochGrant = {
257
257
  epoch: 0;
258
258
  };
259
259
  removed: string[];
260
+ /** Present on a member's answer through `requestRoomEpoch({ members })`:
261
+ * the epochs it carried between the requester's and `epoch`. See
262
+ * `MemberEpochGrant.passed`. The authority's own answer has none. */
263
+ passed?: RoomEpoch[];
260
264
  refused?: undefined;
261
265
  } | {
262
266
  refused: EpochRefusal;
@@ -156,6 +156,11 @@ export interface MemberEpochGrant {
156
156
  epoch: RoomEpoch;
157
157
  /** Cumulative: `opts.removed` plus every removal in the chain. */
158
158
  removed: string[];
159
+ /** The epochs between the requester's and `epoch`, oldest first, each
160
+ * proven by the next rekey in the chain decrypting under it. Empty for a
161
+ * grant one epoch ahead. Kept, they let the requester read what was said
162
+ * in the epochs it skipped, and hand them on from its own member desk. */
163
+ passed: RoomEpoch[];
159
164
  }
160
165
  /**
161
166
  * Verify a member's answer to one of this device's member requests. Null
@@ -278,6 +278,7 @@ export function decodeMemberEpochGrant(event, opts) {
278
278
  const removed = new Set([...(opts.removed ?? [])].map(normaliseHex));
279
279
  let previous = opts.current;
280
280
  let secret;
281
+ const passed = [];
281
282
  for (let i = 0; i < length; i += 1) {
282
283
  const evidence = readRekeyEvidence(body.rekeys[i], { roomId, authority: opts.authority, previous });
283
284
  if (!evidence)
@@ -304,8 +305,10 @@ export function decodeMemberEpochGrant(event, opts) {
304
305
  return null;
305
306
  }
306
307
  previous = deriveEpoch({ epoch: evidence.epoch, secret });
308
+ if (i < length - 1)
309
+ passed.push({ epoch: evidence.epoch, secret });
307
310
  }
308
- return { epoch: { epoch: top, secret: secret }, removed: [...removed].sort() };
311
+ return { epoch: { epoch: top, secret: secret }, removed: [...removed].sort(), passed };
309
312
  }
310
313
  catch {
311
314
  return null;
package/llms.txt CHANGED
@@ -79,7 +79,7 @@ Not yet published to npm under this API. Until it is, pin an immutable Git commi
79
79
  - `encodeEpochGrant(opts)` / `decodeEpochGrant(event, opts)` - the authority's answer (secret, epoch-0, or a refusal `'removed' | 'closed'`)
80
80
  - `epochRequestAdmission(opts)`, `deriveEpochRequestKey(roomKey)`
81
81
  - `hostRoomEpoch(opts)` / `requestRoomEpoch(opts)` / `EpochRefusedError` - the live request/grant desk, both sides
82
- - Member epoch catch-up (see `docs/member-epoch-catch-up.md`): `encodeRekeyEvent({ ..., commit: true })` writes `epochCommitment(roomId, epoch, secret)` into the rekey body; `hostMemberEpochDesk(opts)` lets any current member answer; `requestRoomEpoch({ ..., members: memberEpochSource(opts) })` asks the authority and members, first verifiable answer wins; `requestMemberEpoch(opts)` asks members alone. Kinds `MEMBER_EPOCH_KINDS` = `{ REQUEST: 20471, GRANT: 20472 }`. A member grant carries the authority-signed rekeys from the asker's epoch to the current one; the asker verifies the chain itself (`decodeMemberEpochGrant`) and refuses a last epoch with no commitment (legacy: authority only), a chain that removes it or closes the room, and one that stops short of `expected`. Codecs: `encodeMemberEpochRequest`, `decodeMemberEpochRequest`, `encodeMemberEpochGrant`, `readRekeyEvidence`, `deriveMemberEpochRequestKey`, `MAX_MEMBER_EPOCH_CHAIN` (32)
82
+ - Member epoch catch-up (see `docs/member-epoch-catch-up.md`): `encodeRekeyEvent({ ..., commit: true })` writes `epochCommitment(roomId, epoch, secret)` into the rekey body; `hostMemberEpochDesk(opts)` lets any current member answer; `requestRoomEpoch({ ..., members: memberEpochSource(opts) })` asks the authority and members, first verifiable answer wins; `requestMemberEpoch(opts)` asks members alone. Kinds `MEMBER_EPOCH_KINDS` = `{ REQUEST: 20471, GRANT: 20472 }`. A member grant carries the authority-signed rekeys from the asker's epoch to the current one; the asker verifies the chain itself (`decodeMemberEpochGrant`) and refuses a last epoch with no commitment (legacy: authority only), a chain that removes it or closes the room, and one that stops short of `expected`. A verified grant is `{ epoch, removed, passed }`: `passed` holds the epochs between the asker's and `epoch`, oldest first, and `requestRoomEpoch` carries it as an optional `passed` when a member answered. Codecs: `encodeMemberEpochRequest`, `decodeMemberEpochRequest`, `encodeMemberEpochGrant`, `readRekeyEvidence`, `deriveMemberEpochRequestKey`, `MAX_MEMBER_EPOCH_CHAIN` (32)
83
83
  - `canonicalAdmins(admins)` / `signAdmins(opts)` / `verifyAdmins(opts)` - the authority's signed admin list
84
84
  - `canonicalChannels(channels)` / `signChannels(opts)` / `verifyChannels(opts)` - the authority's signed channel list; `CHANNEL_NAME`, `RESERVED_CHANNELS`
85
85
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@forgesworn/fold-kit",
3
- "version": "0.5.0",
3
+ "version": "0.5.1",
4
4
  "description": "Circle primitives for private Nostr groups nobody operates: epoch keys, link invites, removal by rekey, device credentials.",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",