@solarpunkltd/swarm-id 0.0.0-stage → 0.8.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.
Files changed (111) hide show
  1. package/LICENSE +202 -0
  2. package/README.md +89 -2
  3. package/dist/bus/account-bus.d.ts +104 -0
  4. package/dist/bus/account-delta.d.ts +52 -0
  5. package/dist/bus/bus-context.d.ts +7 -0
  6. package/dist/bus/envelope.d.ts +9 -0
  7. package/dist/bus/messages.d.ts +241 -0
  8. package/dist/bus/presence.d.ts +62 -0
  9. package/dist/bus/signaling-transport.d.ts +79 -0
  10. package/dist/chunk/bmt.d.ts +16 -0
  11. package/dist/chunk/cac.d.ts +17 -0
  12. package/dist/chunk/constants.d.ts +10 -0
  13. package/dist/chunk/encrypted-cac.d.ts +47 -0
  14. package/dist/chunk/encryption.d.ts +85 -0
  15. package/dist/chunk/index.d.ts +5 -0
  16. package/dist/chunks/chat-binding-DO5aGhPl.js +23 -0
  17. package/dist/chunks/chat-binding-DO5aGhPl.js.map +1 -0
  18. package/dist/errors.d.ts +35 -0
  19. package/dist/index.d.ts +27 -0
  20. package/dist/internal.d.ts +49 -0
  21. package/dist/internal.esm.js +2 -0
  22. package/dist/internal.esm.js.map +1 -0
  23. package/dist/proxy/act/act.d.ts +58 -0
  24. package/dist/proxy/act/crypto.d.ts +46 -0
  25. package/dist/proxy/act/grantee-list.d.ts +81 -0
  26. package/dist/proxy/act/history.d.ts +182 -0
  27. package/dist/proxy/act/index.d.ts +114 -0
  28. package/dist/proxy/app-stamp-limits.d.ts +62 -0
  29. package/dist/proxy/chunk-upload-stream.d.ts +37 -0
  30. package/dist/proxy/chunking.d.ts +15 -0
  31. package/dist/proxy/collection.d.ts +38 -0
  32. package/dist/proxy/download-data.d.ts +27 -0
  33. package/dist/proxy/errors.d.ts +8 -0
  34. package/dist/proxy/feed-manifest.d.ts +60 -0
  35. package/dist/proxy/feeds/epochs/async-finder.d.ts +70 -0
  36. package/dist/proxy/feeds/epochs/epoch.d.ts +79 -0
  37. package/dist/proxy/feeds/epochs/finder.d.ts +60 -0
  38. package/dist/proxy/feeds/epochs/index.d.ts +30 -0
  39. package/dist/proxy/feeds/epochs/types.d.ts +110 -0
  40. package/dist/proxy/feeds/epochs/updater.d.ts +76 -0
  41. package/dist/proxy/feeds/epochs/utils.d.ts +21 -0
  42. package/dist/proxy/feeds/index.d.ts +4 -0
  43. package/dist/proxy/feeds/sequence/async-finder.d.ts +14 -0
  44. package/dist/proxy/feeds/sequence/finder.d.ts +11 -0
  45. package/dist/proxy/feeds/sequence/index.d.ts +22 -0
  46. package/dist/proxy/feeds/sequence/types.d.ts +82 -0
  47. package/dist/proxy/feeds/sequence/updater.d.ts +20 -0
  48. package/dist/proxy/index.d.ts +5 -0
  49. package/dist/proxy/manifest-builder.d.ts +164 -0
  50. package/dist/proxy/mantaray.d.ts +76 -0
  51. package/dist/proxy/soc-restamp-slots.d.ts +48 -0
  52. package/dist/proxy/stamp-chunk.d.ts +45 -0
  53. package/dist/proxy/stamp-worker-handler.d.ts +32 -0
  54. package/dist/proxy/stamp-worker-pool.d.ts +52 -0
  55. package/dist/proxy/stamp-worker.d.ts +1 -0
  56. package/dist/proxy/types.d.ts +38 -0
  57. package/dist/proxy/upload.d.ts +207 -0
  58. package/dist/schemas.d.ts +671 -0
  59. package/dist/stamp-worker.iife.js +1 -0
  60. package/dist/storage/debounced-uploader.d.ts +61 -0
  61. package/dist/storage/utilization-store.d.ts +113 -0
  62. package/dist/swarm-id-client.d.ts +1143 -0
  63. package/dist/swarm-id-proxy.d.ts +1079 -0
  64. package/dist/swarm-id.esm.js +2 -0
  65. package/dist/swarm-id.esm.js.map +1 -0
  66. package/dist/sync/batch-write-coordinator.d.ts +348 -0
  67. package/dist/sync/device-roster.d.ts +59 -0
  68. package/dist/sync/device-state.d.ts +199 -0
  69. package/dist/sync/fold-account-from-swarm.d.ts +53 -0
  70. package/dist/sync/index.d.ts +11 -0
  71. package/dist/sync/merge-snapshot.d.ts +57 -0
  72. package/dist/sync/partition-intent.d.ts +218 -0
  73. package/dist/sync/partition-lease.d.ts +531 -0
  74. package/dist/sync/partition-lock.d.ts +218 -0
  75. package/dist/sync/partition-state.d.ts +287 -0
  76. package/dist/sync/session-check.d.ts +29 -0
  77. package/dist/sync/store-interfaces.d.ts +48 -0
  78. package/dist/sync/sync-account.d.ts +45 -0
  79. package/dist/sync/timing-constants.d.ts +32 -0
  80. package/dist/sync/types.d.ts +18 -0
  81. package/dist/types.d.ts +3042 -0
  82. package/dist/utils/account-state-snapshot.d.ts +43 -0
  83. package/dist/utils/active-devices.d.ts +45 -0
  84. package/dist/utils/avatar.d.ts +30 -0
  85. package/dist/utils/batch-utilization.d.ts +840 -0
  86. package/dist/utils/batch-write-lock.d.ts +12 -0
  87. package/dist/utils/bee-error.d.ts +11 -0
  88. package/dist/utils/chat-binding.d.ts +113 -0
  89. package/dist/utils/coalesced-task.d.ts +28 -0
  90. package/dist/utils/collection.d.ts +6 -0
  91. package/dist/utils/connection-info.d.ts +11 -0
  92. package/dist/utils/constants.d.ts +28 -0
  93. package/dist/utils/device-id.d.ts +109 -0
  94. package/dist/utils/hex.d.ts +31 -0
  95. package/dist/utils/json-rpc.d.ts +34 -0
  96. package/dist/utils/key-derivation.d.ts +124 -0
  97. package/dist/utils/lock-soc.d.ts +35 -0
  98. package/dist/utils/postage-contract.d.ts +143 -0
  99. package/dist/utils/postage-stamp-association.d.ts +39 -0
  100. package/dist/utils/promise.d.ts +37 -0
  101. package/dist/utils/sealed-values.d.ts +23 -0
  102. package/dist/utils/seed-encryption.d.ts +17 -0
  103. package/dist/utils/site-allow-list.d.ts +54 -0
  104. package/dist/utils/stamp-lifespan.d.ts +37 -0
  105. package/dist/utils/storage-managers.d.ts +62 -0
  106. package/dist/utils/storage-probe.d.ts +40 -0
  107. package/dist/utils/tag.d.ts +11 -0
  108. package/dist/utils/ttl.d.ts +120 -0
  109. package/dist/utils/url.d.ts +55 -0
  110. package/dist/utils/versioned-storage.d.ts +183 -0
  111. package/package.json +91 -3
@@ -0,0 +1,57 @@
1
+ /**
2
+ * Merge a locally-built account snapshot with the most recent on-Swarm
3
+ * snapshot before publishing.
4
+ *
5
+ * Sync overwrites the feed entry on each write, so a naïve "publish local
6
+ * state" stomps anything a peer just wrote. The fix is to fold the remote
7
+ * snapshot into the local one and publish the union.
8
+ *
9
+ * Strategy per field:
10
+ *
11
+ * - `devices`: last-writer-wins per `deviceId` with a `removedAt` tombstone.
12
+ * The recency clock is `max(removedAt, lastSignedInAt)`, so a removal beats an
13
+ * older sign-in and a newer sign-in re-activates a removed device. The name
14
+ * overlays on its own `nameUpdatedAt` clock, since a rename moves neither.
15
+ * - `connectedApps`: last-writer-wins per `appUrl` (with `revokedAt` tombstone).
16
+ * The chat binding overlays on its own signed date, since a copy handed to a
17
+ * proxy frame carries only that frame's site's binding.
18
+ * - `postageStamps`: last-writer-wins per `batchID` with a `deletedAt`
19
+ * tombstone (recency `max(deletedAt, createdAt)`), so deletions propagate and
20
+ * a fresh re-add (newer `createdAt`) re-activates a deleted stamp.
21
+ * - `metadata.accountName`, `metadata.defaultPostageStampBatchID`,
22
+ * `metadata.partitionCount`: local wins. These are scalar fields set
23
+ * by user actions.
24
+ * - `metadata.createdAt`: keep local's (it's the account's birth date,
25
+ * should be identical anyway).
26
+ * - `timestamp` / `metadata.lastModified`: refreshed to `Date.now()`.
27
+ *
28
+ * The function is pure and deterministic so it's easy to unit-test. The
29
+ * per-collection primitives are exported so the read/refresh path can reuse the
30
+ * exact same rules instead of redefining them.
31
+ */
32
+ import type { AccountStateSnapshot } from "../utils/account-state-snapshot";
33
+ import type { Device, ConnectedApp, PostageStamp } from "../schemas";
34
+ export declare function mergeSnapshotWithRemote(local: AccountStateSnapshot, remote: AccountStateSnapshot | undefined): AccountStateSnapshot;
35
+ export declare function mergeDevicesList(local: Device[], remote: Device[]): Device[];
36
+ /**
37
+ * @param accountId - the account whose connected apps these are, the only
38
+ * signer whose chat binding a merge ranks by date over another
39
+ */
40
+ export declare function mergeConnectedApps(local: ConnectedApp[], remote: ConnectedApp[], accountId: string): ConnectedApp[];
41
+ export declare function mergePostageStamps(local: PostageStamp[], remote: PostageStamp[]): PostageStamp[];
42
+ /**
43
+ * True when `latest` already includes every device / connected-app /
44
+ * postage-stamp present in `mine` (compared by their natural keys — the same
45
+ * keys `mergeSnapshotWithRemote` unions on; timestamps and scalar metadata are
46
+ * ignored).
47
+ *
48
+ * Used by the publish path to tell a *real* last-writer-wins loss (our entities
49
+ * were dropped) apart from a harmless co-writer that published the same account
50
+ * (e.g. the proxy announcing this device while the UI also syncs it) — whose
51
+ * union still contains us, so convergence held and there is nothing to retry.
52
+ *
53
+ * Note: this is membership-only. Tombstone/scalar nuances (a revoke or an
54
+ * account rename stomped by a co-writer) are NOT detected here; the union
55
+ * merge's recency rules reconcile those on the next sync.
56
+ */
57
+ export declare function snapshotContainsContribution(mine: AccountStateSnapshot, latest: AccountStateSnapshot): boolean;
@@ -0,0 +1,218 @@
1
+ /**
2
+ * Per-device partition-INTENT SOCs — Phase 2 of the partition-lock hardening
3
+ * (see docs/Partition-Lock-Hardening-Plan.md, "Extension that covers the
4
+ * symmetric case: per-device intent SOCs").
5
+ *
6
+ * The lock SOC lives at a single static address. Across disjoint gateways a
7
+ * node serves that address from a frozen cache (no TTL, no invalidation), so
8
+ * contenders for a *free* partition never see each other and all bind it —
9
+ * the disjoint-gateway dual-acquire bug. Tweaking the guard/TTL timeouts can't
10
+ * help: the failure is non-visibility, not propagation latency.
11
+ *
12
+ * Intent SOCs convert "mutable-SOC cache coherence" (which Swarm doesn't
13
+ * guarantee) into "push-sync receipt + retrieval routing" (which it does):
14
+ *
15
+ * identifier = keccak256("swarm-id-partition-intent-v1:" || partition
16
+ * || ":" || deviceId || ":" || epochBucket)
17
+ * owner = backup signer (shared across the account's devices)
18
+ * encryption = swarmEncryptionKey
19
+ *
20
+ * Each contender writes ONLY its own intent address and reads every other
21
+ * known device's intent address. A read of an address your node has never
22
+ * written (and, because the address rotates per epoch, never retrieved
23
+ * before) cannot be short-circuited from the local store — it forces a
24
+ * network retrieval to the address's storage neighborhood, where the rival's
25
+ * receipt-backed push-sync placed its replicas. So contenders see each other,
26
+ * order themselves by `generation`, and only the unique global-minimum binds
27
+ * the lock.
28
+ *
29
+ * Irreducible limit (documented): absence is unprovable on Swarm — a retrieval
30
+ * timeout can't distinguish "no rival intent" from "the network failed to find
31
+ * it". We treat a timed-out read as "no intent" to keep liveness (a single
32
+ * flaky device must not deadlock), so the guarantee is "when the network
33
+ * works, rivals are seen"; genuine network partition still falls back to the
34
+ * existing guard + TTL + refresh backstop.
35
+ *
36
+ * Slot safety: an intent is stamped into the CONTENDED partition's reserved
37
+ * slot (= the partition index, `< DATA_COUNTER_START`), via
38
+ * `UtilizationAwareStamper.reserveIntentSocSlot`. It therefore can NEVER share
39
+ * a postage `(bucket, slot)` with user data (data lives at
40
+ * `>= DATA_COUNTER_START`) — a deterministic guarantee, not probabilistic.
41
+ *
42
+ * Two residuals remain (both ~1/65536 per contending pair per round, far rarer
43
+ * than the systematic disjoint-gateway failure, and NOT closed here):
44
+ * - Two contenders for the same partition whose per-epoch intent addresses
45
+ * hash into the same bucket both route to that partition's reserved slot;
46
+ * the older loses its stamp and reads as "no intent", so on disjoint
47
+ * gateways both may "win" → a rare dual-acquire. (A verify-own-intent
48
+ * back-off or per-device slot would close it.)
49
+ * - An intent may overstamp the contended partition's own (stale) lock SOC
50
+ * (harmless — re-acquired) or a published state chunk (→ `readFailed` →
51
+ * read-only fallback). Liveness-only, never data loss.
52
+ */
53
+ import { Bee, Identifier, PrivateKey, type BatchId, type EthAddress, type Stamper } from "@ethersphere/bee-js";
54
+ import { INTENT_EPOCH_MS } from "./timing-constants";
55
+ import { type PartitionIntentPayload } from "../schemas";
56
+ import { type PartitionLockGeneration } from "./partition-lock";
57
+ export type { PartitionIntentPayload };
58
+ export { INTENT_EPOCH_MS };
59
+ export declare const INTENT_READ_TIMEOUT_MS = 2500;
60
+ /**
61
+ * Total window an intent round polls rivals before binding a fresh claim.
62
+ * Sized to the gateway's write→cross-device-retrieve propagation delay: a peer
63
+ * that wrote its intent/beacon seconds ago isn't immediately readable, so a
64
+ * single read races ahead of propagation and misses it. Polling over this
65
+ * window lets the peer surface. Tunable per gateway (lease/coordinator opts +
66
+ * the `swarm-id-partition-tuning` localStorage override).
67
+ */
68
+ export declare const INTENT_GUARD_WINDOW_MS = 12000;
69
+ /** Interval between rival re-reads within the guard window. */
70
+ export declare const INTENT_GUARD_POLL_MS = 2500;
71
+ /**
72
+ * Grace applied to a found beacon's `leasedUntil` when deciding liveness. A
73
+ * beacon is only ever read at the current/previous epoch bucket, so a found one
74
+ * was written ≤ ~2 epochs ago — bounded-fresh by its rotating address. Its
75
+ * `leasedUntil` (writeTime + lock TTL ≈ 30s) can still lapse before the holder's
76
+ * NEXT beacon becomes cross-device retrievable, so a live holder whose freshest
77
+ * beacon hasn't propagated would be misread as departed. Treating a beacon as
78
+ * live while `leasedUntil > now - GRACE` bridges that propagation gap; the
79
+ * bucket-bounded read window means this can never resurrect an aged-out beacon.
80
+ */
81
+ export declare const INTENT_LIVENESS_GRACE_MS: number;
82
+ /** `floor(nowMs / INTENT_EPOCH_MS)` — the current intent epoch bucket. */
83
+ export declare function intentEpochBucket(nowMs: number): number;
84
+ /**
85
+ * Build the per-(partition, device, epoch) intent-SOC identifier. The address
86
+ * rotates with `epochBucket`, so it is fresh per contention round — neither
87
+ * the writer's nor a reader's node has a cached copy.
88
+ */
89
+ export declare function makePartitionIntentIdentifier(batchId: BatchId, partition: number, deviceId: string, epochBucket: number): Identifier;
90
+ /**
91
+ * 32-byte SOC chunk address for an intent — `keccak256(identifier ‖ owner)`,
92
+ * the same derivation `uploadSOC` uses. Computable before the upload so the
93
+ * stamper can reserve the chunk's slot (route it to the contended partition's
94
+ * reserved index instead of a data lane).
95
+ */
96
+ export declare function intentSocAddress(batchId: BatchId, partition: number, deviceId: string, epochBucket: number, owner: EthAddress): Uint8Array;
97
+ /** Build the deviceId-INDEPENDENT occupancy identifier for (batch, partition,
98
+ * epoch). Batch-scoped for the same reason the lock is (#589): a beacon on
99
+ * lane p of batch X says nothing about lane p of batch Y. */
100
+ export declare function makePartitionOccupancyIdentifier(batchId: BatchId, partition: number, epochBucket: number): Identifier;
101
+ /** 32-byte SOC address for an occupancy beacon (for stamper slot reservation). */
102
+ export declare function partitionOccupancyAddress(batchId: BatchId, partition: number, epochBucket: number, owner: EthAddress): Uint8Array;
103
+ /**
104
+ * Write this device's intent for (partition, epochBucket). The payload carries
105
+ * the generation the device intends to claim with, so rivals can order against
106
+ * it. Uses the shared backup signer as owner (same as the lock SOC); each
107
+ * device only ever writes its own identifier.
108
+ */
109
+ export declare function writePartitionIntent(opts: {
110
+ bee: Bee;
111
+ stamper: Stamper;
112
+ backupSigner: PrivateKey;
113
+ swarmEncryptionKey: Uint8Array;
114
+ batchId: BatchId;
115
+ partition: number;
116
+ deviceId: string;
117
+ epochBucket: number;
118
+ generation: PartitionLockGeneration;
119
+ /**
120
+ * Present when this write is a holder PRESENCE BEACON (re-published each
121
+ * refresh): lets a reading joiner apply a `leasedUntil > now` liveness test.
122
+ * Omitted for a pre-claim intent (the symmetric-race announce).
123
+ */
124
+ leasedUntil?: number;
125
+ }): Promise<void>;
126
+ /**
127
+ * Publish this device's OCCUPANCY beacon for (partition, epochBucket) — same
128
+ * payload as a presence beacon but at the deviceId-independent occupancy
129
+ * address, so a contender that doesn't know this device can still detect it
130
+ * holds the partition. Always carries `leasedUntil` (it only ever announces a
131
+ * live hold).
132
+ */
133
+ export declare function writePartitionOccupancy(opts: {
134
+ bee: Bee;
135
+ stamper: Stamper;
136
+ backupSigner: PrivateKey;
137
+ swarmEncryptionKey: Uint8Array;
138
+ batchId: BatchId;
139
+ partition: number;
140
+ deviceId: string;
141
+ epochBucket: number;
142
+ generation: PartitionLockGeneration;
143
+ leasedUntil: number;
144
+ }): Promise<void>;
145
+ /**
146
+ * Read a single rival's intent for (partition, epochBucket). Returns `undefined`
147
+ * when missing / timed out / malformed (treated as "no intent" — see header).
148
+ */
149
+ export declare function readPartitionIntent(opts: {
150
+ bee: Bee;
151
+ backupSigner: PrivateKey;
152
+ swarmEncryptionKey: Uint8Array;
153
+ batchId: BatchId;
154
+ partition: number;
155
+ deviceId: string;
156
+ epochBucket: number;
157
+ timeoutMs?: number;
158
+ /** See `readPartitionSoc`. The polling round passes false. */
159
+ retryOn5xx?: boolean;
160
+ }): Promise<PartitionIntentPayload | undefined>;
161
+ /**
162
+ * Read the deviceId-INDEPENDENT occupancy beacon for (partition, epochBucket).
163
+ * Lets a contender detect a live holder it doesn't know about.
164
+ */
165
+ export declare function readPartitionOccupancy(opts: {
166
+ bee: Bee;
167
+ backupSigner: PrivateKey;
168
+ swarmEncryptionKey: Uint8Array;
169
+ batchId: BatchId;
170
+ partition: number;
171
+ epochBucket: number;
172
+ timeoutMs?: number;
173
+ }): Promise<PartitionIntentPayload | undefined>;
174
+ /**
175
+ * Run an intent round for a fresh claim of `partition`: announce our intent,
176
+ * then read every other known device's intent for the current AND previous
177
+ * epoch bucket (covering the rotation boundary). Returns whether this device
178
+ * is the round's winner.
179
+ *
180
+ * Winner = the strictly-minimum generation (earliest `timestampMs`, then
181
+ * smallest `tiebreaker`). We lose iff any rival's intent orders below ours.
182
+ * `compareGenerations` is a total order and distinct devices never tie
183
+ * (tiebreaker derives from `keccak256(deviceId)`), so exactly one contender
184
+ * wins. With no known rivals we win by default (a brand-new device not yet in
185
+ * the registry falls back to the existing guard + TTL behaviour).
186
+ *
187
+ * @returns `"win"` to proceed with the claim, `"lose"` to back off (read-only).
188
+ */
189
+ export declare function resolveIntentRound(opts: {
190
+ bee: Bee;
191
+ stamper: Stamper;
192
+ backupSigner: PrivateKey;
193
+ swarmEncryptionKey: Uint8Array;
194
+ batchId: BatchId;
195
+ partition: number;
196
+ deviceId: string;
197
+ generation: PartitionLockGeneration;
198
+ knownDeviceIds: string[];
199
+ now: number;
200
+ timeoutMs?: number;
201
+ /**
202
+ * Poll rivals over this window before deciding (default 0 = a single read,
203
+ * used by tests). Sized to the gateway propagation delay so a peer's recent
204
+ * intent/beacon surfaces before we bind. See `INTENT_GUARD_WINDOW_MS`.
205
+ */
206
+ guardWindowMs?: number;
207
+ /** Interval between polls within the guard window. */
208
+ guardPollMs?: number;
209
+ /**
210
+ * Release WATERMARK for this partition, when the caller observed it freed by
211
+ * an explicit release sentinel. A rival's live presence beacon at or below
212
+ * this generation is the just-released holder's lingering GHOST (still within
213
+ * its lease TTL at a live per-epoch address, but no longer a claim), so it
214
+ * must NOT lose us the round. A genuine post-release claimant has a strictly
215
+ * newer generation and still beats us. Omitted → every live beacon counts.
216
+ */
217
+ releasedGeneration?: PartitionLockGeneration;
218
+ }): Promise<"win" | "lose">;