@tinycloud/share-sdk 1.2.0-beta.2 → 1.2.0-beta.4

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.
@@ -1,5 +1,6 @@
1
- import { CompactUcanAuthorization } from '@tinycloud/share-envelope';
2
- import { C as CreateOwnerDelegationParams, r as OwnerDelegationReceipt, A as AddressedOwnerRootInput, i as AddressedOwnerRootReceipt, D as DelegatedShareKey, l as AddressedPublishAuthority } from './addressed-publish-CsUS7-SI.js';
1
+ import { CompactUcanAuthorization, UnifiedContentSource } from '@tinycloud/share-envelope';
2
+ import { A as AddressedOwnerRootInput, f as AddressedOwnerRootReceipt, i as AddressedPublishAuthority } from './addressed-publish-BDaGyQA9.js';
3
+ import { C as CreateOwnerDelegationParams, c as OwnerDelegationReceipt, a as DelegatedShareKey } from './owner-policy-BqzRlkb6.js';
3
4
 
4
5
  /**
5
6
  * The fields of an owner-signed SIWE CACAO that bound its authority. Read
@@ -41,47 +42,28 @@ declare const SHARE_ROOT_PREFIX = "xyz.tinycloud.share/shares/";
41
42
  declare const SHARE_ROOT_MAX_LIFETIME_MS: number;
42
43
  /** Longest lifetime of an agent root (the OpenKey device-grant cap). */
43
44
  declare const AGENT_SHARE_ROOT_MAX_LIFETIME_MS: number;
44
- /** Product cap on a bearer link or policy root signed under a root. */
45
+ /** Product cap on a bearer link, a per-share grant, or a policy root. */
45
46
  declare const SHARE_ROOT_LINK_MAX_LIFETIME_MS: number;
46
47
  /**
47
- * Signer clock skew tolerated on a signed root's `exp - iat` (only there:
48
- * `exp - now` stays strict), so an approval is not wasted when the signer
49
- * sets its own issue time.
48
+ * Signer clock skew tolerated on a signed delegation's `exp - iat` (only
49
+ * there: `exp - now` stays strict), so an approval is not wasted when the
50
+ * signer sets its own issue time.
50
51
  */
51
52
  declare const SHARE_ROOT_CLOCK_SKEW_MS: number;
52
53
  /** `ensureShareRoot` asks for the cap minus this margin, so a clamping or slow signer stays inside the cap. */
53
54
  declare const SHARE_ROOT_REQUEST_MARGIN_MS: number;
54
- /**
55
- * NODE-B2 (§9 "Advertised mode"): a Node lists these in the `features` of
56
- * its public `GET /info` (sdk-core `checkNodeInfo` → `NodeInfo.features`):
57
- * the capability marker plus exactly one active mode. Read in one place,
58
- * {@link readPolicyOwnerAuthorityMode}; the Node version is never consulted.
59
- */
60
- declare const POLICY_OWNER_AUTHORITY_FEATURES: {
61
- readonly marker: "policy-owner-authority-v1";
62
- readonly record: "policy-owner-authority-record";
63
- readonly enforce: "policy-owner-authority-enforce";
64
- };
65
- /** How long a known mode is trusted before the Node is asked again (NODE-B2: at most 5 minutes). */
66
- declare const POLICY_OWNER_AUTHORITY_PROBE_TTL_MS: number;
67
- /** `share`: Share/CLI root in `applications`. `agent`: `default` space, bearer only. */
55
+ /** `share`: Share/CLI root in `applications`. `agent`: `default` space. Both are kv/bearer only. */
68
56
  type ShareRootKind = "share" | "agent";
69
- /**
70
- * What the owner signed. `kv`: get + metadata on the share prefix only.
71
- * `kv+share-decrypt`: also decrypt on the owner's `:share` network, which a
72
- * policy owner needs; minted only while the Node advertises `enforce` and
73
- * `shareRootPolicyRoots` is on.
74
- */
75
- type ShareRootScope = "kv" | "kv+share-decrypt";
76
- type ShareRootErrorCode = "invalid-request" | "invalid-key" | "key-cleared" | "lifetime-exceeds-cap" | "expired" | "malformed-root" | "issuer-mismatch" | "audience-mismatch" | "resource-mismatch" | "ability-mismatch" | "expiry-mismatch" | "reroot-same-key" | "link-out-of-scope" | "policy-roots-disabled" | "policy-roots-not-enforced-by-node" | "root-not-policy-capable";
57
+ type ShareRootErrorCode = "invalid-request" | "invalid-key" | "key-cleared" | "lifetime-exceeds-cap" | "expired" | "malformed-root" | "issuer-mismatch" | "audience-mismatch" | "resource-mismatch" | "ability-mismatch" | "expiry-mismatch" | "reroot-same-key" | "link-out-of-scope" | "share-id-not-network-safe" | "network-not-active" | "node-refused" | "grant-revoke-incomplete" | "share-id-in-use" | "control-target-invalid";
77
58
  interface ShareRootErrorDetails {
78
59
  /** The delegation CID the Node imported (the receipt's), when the owner already signed. */
79
60
  readonly rootCid?: string;
80
61
  /** CID of the signed bytes, when it differs from `rootCid`. */
81
62
  readonly signedCid?: string;
82
- /** The imported root may grant more, or longer, than requested: the owner must revoke `rootCid`. */
63
+ /** The imported delegation may grant more, or longer, than requested: the owner must revoke `rootCid`. */
83
64
  readonly mustRevoke?: boolean;
84
65
  }
66
+ /** The error every share-root, per-share grant and owner-control operation throws. */
85
67
  declare class ShareRootError extends Error {
86
68
  readonly code: ShareRootErrorCode;
87
69
  readonly rootCid?: string;
@@ -98,26 +80,16 @@ interface ShareRootOwnerAuthority {
98
80
  }
99
81
  interface ShareRootOptions {
100
82
  readonly kind?: ShareRootKind;
101
- /**
102
- * Feature flag `shareRootPolicyRoots`, off by default. A root gains `:share`
103
- * decrypt, and can sign policy roots through {@link ShareRoot.policyRootSigner},
104
- * only while this is on and the root's Node advertises owner-authority
105
- * mode `enforce`.
106
- */
107
- readonly shareRootPolicyRoots?: boolean;
108
- /** Defaults to {@link defaultPolicyOwnerModeProbe}. */
109
- readonly policyOwnerModeProbe?: PolicyOwnerModeProbe;
110
83
  readonly now?: () => number;
111
84
  }
112
85
  /** Persistable root metadata. Holds no key material: the key lives in its own store. */
113
86
  interface ShareRootRecord {
114
87
  readonly version: 1;
115
88
  readonly kind: ShareRootKind;
116
- readonly scope: ShareRootScope;
117
- /** The Node the root was created against; its advertised mode gates policy signing. */
89
+ /** The Node the root was created against. */
118
90
  readonly nodeOrigin: string;
119
91
  readonly cid: string;
120
- /** Base64url of the exact owner-signed CACAO bytes. */
92
+ /** Base64url (unpadded) of the exact owner-signed CACAO bytes. */
121
93
  readonly authorization: string;
122
94
  readonly spaceId: string;
123
95
  readonly ownerDid: string;
@@ -133,68 +105,33 @@ interface ShareRootLink {
133
105
  readonly delegation: CompactUcanAuthorization;
134
106
  }
135
107
  /**
136
- * Policy/v3 owner signing for a `kv+share-decrypt` root. Shaped like
137
- * `AddressedPublishAuthority` without `registerPolicy`. Every call re-checks
138
- * the whole gate (flag, the Node's advertised mode within the probe TTL, root
139
- * live, key present), so a signer kept past a mode change stops working.
108
+ * Policy/v3 owner signing for one per-share grant. Shaped like
109
+ * `AddressedPublishAuthority` without `registerPolicy`. Every call checks the
110
+ * key is held and the grant is live.
140
111
  */
141
- interface ShareRootPolicySigner {
142
- /** The share key DID: the `ownerDid` of the policy roots. */
112
+ interface SharePolicySigner {
113
+ /** The per-share key DID: the `ownerDid` of the policy roots. */
143
114
  readonly ownerDid: string;
115
+ /** The owner delegation (grant) CID that authorizes the key. */
144
116
  readonly rootCid: string;
145
- /** Interim rule: no policy root may outlive the share root. */
117
+ /** No policy root may outlive the grant. */
146
118
  readonly notAfter: Date;
147
119
  /** Build and sign one proofless policy root; its expiry must be within `notAfter` and 31 days. */
148
120
  createOwnerRoot(input: AddressedOwnerRootInput): Promise<AddressedOwnerRootReceipt>;
149
121
  /** Sign a 32-byte digest (policy or envelope); nothing else. */
150
122
  sign(digest: Uint8Array): Promise<Uint8Array>;
151
123
  }
152
- /**
153
- * The single adapter over the Node's advertisement. Takes the `features`
154
- * list of `GET /info` and returns `"enforce"` only for the marker plus the
155
- * `-enforce` mode and no `-record`; `"record"` for the marker plus
156
- * `-record` only; `undefined` for anything else (no marker, as on every
157
- * Node up to 1.21.0, both modes, or a malformed list). Only `"enforce"`
158
- * enables policy-capable roots.
159
- */
160
- declare function readPolicyOwnerAuthorityMode(features: unknown): "enforce" | "record" | undefined;
161
- /** Per-Node cache of "does this Node enforce owner-authority expiry". */
162
- interface PolicyOwnerModeProbe {
163
- /** True only when the Node currently advertises `enforce`. Never throws. */
164
- isEnforced(nodeOrigin: string): Promise<boolean>;
165
- /**
166
- * Record the `features` a sign-in just read from `GET /info`
167
- * (`NodeInfo.features`), so each sign-in refreshes the answer without a
168
- * second fetch.
169
- */
170
- observe(nodeOrigin: string, features: readonly string[]): void;
171
- }
172
- /**
173
- * Keep each Node's answer in memory for `ttlMs` (default and ceiling 5
174
- * minutes), then read `GET /info` again, so a Node that stops advertising
175
- * `enforce` turns the gate off within one TTL. Only `https:` origins (and
176
- * `http:` on loopback) are probed. A failure, a non-JSON or oversized
177
- * (64 KiB) answer, or a malformed list counts as not enforcing and is cached
178
- * for the same TTL.
179
- */
180
- declare function createPolicyOwnerModeProbe(options?: {
181
- readonly fetch?: (input: URL, init: RequestInit) => Promise<Response>;
182
- readonly ttlMs?: number;
183
- readonly now?: () => number;
184
- }): PolicyOwnerModeProbe;
185
- /** Process-wide default; sign-in code should feed it `NodeInfo.features` through `observe`. */
186
- declare const defaultPolicyOwnerModeProbe: PolicyOwnerModeProbe;
187
124
  /**
188
125
  * A wallet-signed, read-only root delegated to one non-extractable share key,
189
- * so shares outlive the owner's login session (TC-1423 D3, option B').
126
+ * so bearer links outlive the owner's login session (TC-1423 D3, option B').
190
127
  *
191
- * - Scope: `kv/get` + `kv/metadata` on the share prefix. `:share` decrypt is
192
- * added (scope `kv+share-decrypt`) only when the root is created while
193
- * `shareRootPolicyRoots` is on and its Node advertises owner-authority mode
194
- * `enforce`; a `kv` root holds no `:share` decrypt, so its key cannot own a
195
- * policy over `:share`-encrypted content (the Node answers
196
- * `policy-owner-not-authorized`). Never put, del, list, `capabilities/read`,
197
- * `network.create` or `:default`.
128
+ * - Scope: `kv/get` + `kv/metadata` on the share prefix, and nothing else.
129
+ * The root never decrypts, before or after the Node enforces owner-authority
130
+ * binding: every addressed (email/domain) or edit share uses its own
131
+ * per-share grant and network (`ShareGrant`). Without decrypt, the key
132
+ * cannot own a policy over encrypted content (the Node answers
133
+ * `policy-owner-not-authorized`). Never put, del, list,
134
+ * `capabilities/read`, `network.create` or any encryption network.
198
135
  * - Created only through `createOwnerDelegation({ permissions })`; the signed
199
136
  * bytes are decoded and checked here.
200
137
  * - Lifetime: at most 37 days (`agent`: 30). One key per root.
@@ -203,14 +140,10 @@ declare const defaultPolicyOwnerModeProbe: PolicyOwnerModeProbe;
203
140
  * share key requires the composer to be on its own origin first (REVIEW-D3
204
141
  * V2-2); this package provides none. CLI profiles may keep a key in a 0600
205
142
  * file and rebuild with `restore`.
206
- * - The client flag and `policyRootSigner` gate only this API: whoever holds
207
- * the key can sign through other APIs. What bounds that is the scope of the
208
- * root (no decrypt before `enforce`) and the Node.
209
143
  */
210
144
  declare class ShareRoot {
211
145
  #private;
212
146
  readonly kind: ShareRootKind;
213
- readonly scope: ShareRootScope;
214
147
  readonly nodeOrigin: string;
215
148
  readonly cid: string;
216
149
  readonly ownerDid: string;
@@ -220,12 +153,10 @@ declare class ShareRoot {
220
153
  readonly expiresAt: Date;
221
154
  private constructor();
222
155
  /**
223
- * Ask the owner for a new root (one approval) for a fresh share key. The
224
- * scope is chosen now: `kv+share-decrypt` only for a `share` root with the
225
- * flag on and `nodeOrigin` advertising `enforce`, otherwise `kv`. All request
226
- * checks run before anything is signed. If the signed result is rejected,
227
- * the key is cleared and the error carries the imported `rootCid` (and
228
- * `mustRevoke` when the root may be wider than requested).
156
+ * Ask the owner for a new root (one approval) for a fresh share key. All
157
+ * request checks run before anything is signed. If the signed result is
158
+ * rejected, the key is cleared and the error carries the imported `rootCid`
159
+ * (and `mustRevoke` when the root may be wider than requested).
229
160
  *
230
161
  * `keyFactory` defaults to a fresh non-extractable WebCrypto key that only
231
162
  * this root holds. A custom factory must not persist the key before
@@ -245,10 +176,12 @@ declare class ShareRoot {
245
176
  readonly key: DelegatedShareKey;
246
177
  readonly options?: ShareRootOptions;
247
178
  }): Promise<ShareRoot>;
248
- /** The `Authorization` value the Node knows this root by (base64url CACAO). */
179
+ /**
180
+ * This root's CACAO as base64url, UNPADDED (the record form). As a Node
181
+ * `Authorization` header it must be padded to a multiple of 4 characters:
182
+ * the Node refuses an unpadded value otherwise.
183
+ */
249
184
  get authorization(): string;
250
- /** True when the root holds `:share` decrypt and may act as a policy owner (still gated per call). */
251
- get policyCapable(): boolean;
252
185
  toRecord(): ShareRootRecord;
253
186
  /** True while the root is live, its key is held, and it lasts at least until `expiresAt`. Revocation is not visible here. */
254
187
  covers(expiresAt: Date, now?: number): boolean;
@@ -262,27 +195,17 @@ declare class ShareRoot {
262
195
  readonly path: string;
263
196
  readonly expiresAt: Date;
264
197
  }): Promise<ShareRootLink>;
265
- /**
266
- * The only ShareRoot API that signs Policy/v3 (email/domain) owner material.
267
- * Needs a `kv+share-decrypt` root, the `shareRootPolicyRoots` flag, a live
268
- * root and key, and the root's Node advertising `enforce`; the returned
269
- * signer re-checks all of it on every call. Until then addressed shares use
270
- * per-share owner grants. Agent roots are bearer-only.
271
- */
272
- policyRootSigner(): Promise<ShareRootPolicySigner>;
273
198
  /** Drop this root's key handle. It does not revoke the root: only the owner does that. */
274
199
  clear(): void;
275
200
  }
276
201
  /**
277
- * Re-root only when needed. `purpose: "bearer"` (default) keeps any live root
278
- * of the same kind, space and Node that covers `expiresAt`. `purpose:
279
- * "policy"` additionally needs a `kv+share-decrypt` root: it throws unless the
280
- * flag is on and the Node enforces, and re-roots when the current root is
281
- * `kv`; it never hands back a `kv` root. A re-root costs one approval, uses a
282
- * new key and requests the kind's cap minus {@link SHARE_ROOT_REQUEST_MARGIN_MS}.
202
+ * Re-root only when needed: keep a live root of the same kind, space and
203
+ * Node that covers `expiresAt`; otherwise mint one new root (one approval)
204
+ * with a new key, requesting the kind's cap minus
205
+ * {@link SHARE_ROOT_REQUEST_MARGIN_MS}.
283
206
  *
284
- * - Links and policies signed under the old root stay valid until their own
285
- * expiry; keep the old root (and its key) to revoke them (REVIEW-D3 V2-5).
207
+ * - Links signed under the old root stay valid until their own expiry; keep
208
+ * the old root (and its key) to revoke them (REVIEW-D3 V2-5).
286
209
  * - Concurrent calls each re-root (two approvals, and one orphaned root if
287
210
  * both write the same store slot): callers serialize, e.g. with a lock.
288
211
  * - A revoked root still "covers" here: re-root when `/delegate` answers
@@ -291,7 +214,6 @@ declare class ShareRoot {
291
214
  declare function ensureShareRoot(input: {
292
215
  readonly current?: ShareRoot;
293
216
  readonly expiresAt: Date;
294
- readonly purpose?: "bearer" | "policy";
295
217
  readonly owner: ShareRootOwnerAuthority;
296
218
  readonly spaceId: string;
297
219
  readonly nodeOrigin: string;
@@ -342,9 +264,33 @@ declare function signShareKeyDelegation(input: ShareKeyDelegationInput): Promise
342
264
 
343
265
  /** `view`: kv get + metadata. `edit`: also kv put. Both on one exact key. */
344
266
  type ShareGrantAccess = "view" | "edit";
345
- /** Persistable grant metadata. Holds no key material. */
267
+ /** P2(a): a read-back key lives at most one hour. */
268
+ declare const EDIT_SHARE_READER_MAX_LIFETIME_MS: number;
269
+ type FetchFn$1 = (input: URL, init?: RequestInit) => Promise<Response>;
270
+ /**
271
+ * The share's one content key (I1): `xyz.tinycloud.share/shares/<shareId>/<file>`,
272
+ * or, for `view` only, a TC-1428 version file
273
+ * `xyz.tinycloud.share/artifacts/<aid>/versions/<n>/<file>` (versions are
274
+ * immutable, and that path is not bound to the shareId, so `edit` is refused).
275
+ */
276
+ declare function assertShareGrantPath(path: string, shareId: string, access: ShareGrantAccess): void;
277
+ /** Progress of one addressed share, for the consumer's history/index row (the SDK does not store rows). */
278
+ type ShareGrantRowState = "pending" | "granted" | "network" | "active" | "abandoned" | "retired";
279
+ /** The history/index row to persist at each step. Write `pending` BEFORE the approval prompt. */
280
+ interface ShareGrantRow {
281
+ readonly shareId: string;
282
+ /** Network name, `share-<shareId>`. */
283
+ readonly nw: string;
284
+ readonly networkId: string;
285
+ readonly keyDid: string;
286
+ readonly access: ShareGrantAccess;
287
+ readonly path: string;
288
+ readonly state: ShareGrantRowState;
289
+ readonly grantCid?: string;
290
+ }
291
+ /** Persistable grant metadata (record v2). Holds no key material. */
346
292
  interface ShareGrantRecord {
347
- readonly version: 1;
293
+ readonly version: 2;
348
294
  readonly cid: string;
349
295
  /** Base64url of the exact owner-signed CACAO bytes. */
350
296
  readonly authorization: string;
@@ -352,38 +298,89 @@ interface ShareGrantRecord {
352
298
  readonly ownerDid: string;
353
299
  readonly keyDid: string;
354
300
  readonly nodeOrigin: string;
355
- /** The one exact KV key under `xyz.tinycloud.share/shares/`. */
301
+ readonly shareId: string;
302
+ readonly networkId: string;
356
303
  readonly path: string;
357
304
  readonly access: ShareGrantAccess;
358
305
  readonly expiresAt: string;
359
306
  }
307
+ /** What one addressed or edit share asks the owner for. */
308
+ interface ShareGrantRequest {
309
+ readonly owner: ShareRootOwnerAuthority;
310
+ /** The owner's `applications` space. */
311
+ readonly spaceId: string;
312
+ readonly nodeOrigin: string;
313
+ /** Names the network `share-<shareId>`: must match `^[a-z0-9][a-z0-9-]{15,35}$`. */
314
+ readonly shareId: string;
315
+ /** The share's one content key (see `assertShareGrantPath`). */
316
+ readonly path: string;
317
+ readonly access: ShareGrantAccess;
318
+ /** At most 30 days ahead. */
319
+ readonly expiresAt: Date;
320
+ /**
321
+ * Key-gone recovery only: accept the share's network when it already
322
+ * exists and is `active` (a half-published share whose key was lost), and
323
+ * grant a new key on it. Without it, `prepare` refuses an existing network
324
+ * (`share-id-in-use`) before the prompt. A retired network is always
325
+ * refused.
326
+ */
327
+ readonly resumeExistingNetwork?: boolean;
328
+ readonly keyFactory?: () => Promise<DelegatedShareKey>;
329
+ readonly fetch?: FetchFn$1;
330
+ readonly now?: () => number;
331
+ }
332
+ /** A per-share grant whose key exists but which the owner has not approved yet. Persist `row` first, then `approve()`. */
333
+ interface PendingShareGrant {
334
+ readonly shareId: string;
335
+ readonly networkId: string;
336
+ readonly keyDid: string;
337
+ /** The `pending` row: write it before calling `approve()`. */
338
+ readonly row: ShareGrantRow;
339
+ /** Ask the owner for the grant: exactly one approval. */
340
+ approve(): Promise<ShareGrant>;
341
+ /** Drop the key without asking (e.g. the row write failed). */
342
+ discard(): void;
343
+ }
360
344
  /**
361
- * A per-share owner grant (TC-1423 D3, the interim path for addressed shares
362
- * while the Node does not enforce owner-authority binding).
345
+ * A per-share owner grant on the share's own encryption network (TC-1423,
346
+ * PERSHARE-NETWORK design v2). Every addressed (email/domain) or edit share
347
+ * gets one, before and after the Node enforces owner-authority binding; the
348
+ * share root never decrypts.
363
349
  *
364
- * The owner signs one CACAO (one approval per share) to a FRESH per-share
365
- * key, never the share-root key:
366
- * - kv `get` + `metadata` (+ `put` for `edit`) on exactly one KV key under
367
- * the share prefix, in the `applications` space;
368
- * - `decrypt` on the owner's `:share` network, which a Policy/v3 owner must
369
- * hold (every v3 policy's capability ceiling includes decrypt on the
370
- * source's network; without it the Node answers
371
- * `policy-owner-not-authorized`);
372
- * - at most 30 days, and exactly the requested expiry.
373
- * The per-share key then owns that one share's policy: `publishAuthority`
374
- * signs its two policy roots (bounded by the grant's expiry and exact path)
375
- * and its 32-byte policy and envelope digests.
350
+ * One owner approval signs one CACAO to a FRESH non-extractable per-share key:
351
+ * - kv `get` + `metadata` (+ `put` for `edit`) on the share's one content key;
352
+ * - `network.create`, `network.revoke` and `decrypt` on exactly
353
+ * `urn:tinycloud:encryption:<owner did:pkh>:share-<shareId>`, and nothing
354
+ * else (the Node matches network resources by exact parsed id, so these
355
+ * reach no other network).
356
+ * The key creates that network (owned by the owner) and owns the share's
357
+ * policy. Decrypt therefore covers this one share's ciphertext only, for the
358
+ * key and for the share's recipients.
376
359
  *
377
- * Revoking the grant (one owner SIWE `POST /revoke` on `cid`) cuts the
378
- * per-share key's own reads and its decrypt, for this share only. Until the
379
- * Node binds policy registrations to the owner delegation (NODE-B2), an
380
- * already-registered policy keeps admitting recipients until its own expiry,
381
- * which never exceeds the grant's.
360
+ * Publish order (the consumer stores the rows; see `ShareGrantRow`):
361
+ * 0. `ShareGrant.prepare`: shareId rule, path, access, expiry, space and
362
+ * origin are checked, and the network is looked up (an existing or
363
+ * retired one is refused); a typed error costs no approval.
364
+ * 1. Persist `pending.row` (state `pending`), and a CLI persists the key file.
365
+ * 2. `pending.approve()`: one approval; row `granted` with `grantCid`.
366
+ * 3. `grant.ensureNetwork()`: creates the network if absent, re-reads it after
367
+ * any answer (racing creates can answer 500), accepts only `active`; row
368
+ * `network`. Retry at no approval while the key is held.
369
+ * 4. The owner session encrypts to `grant.networkId` and stores the object
370
+ * (`grant.assertContentSource`); on failure `grant.retireNetwork()`, row
371
+ * `abandoned`.
372
+ * 5. `publishAddressedShare({ authority: grant.publishAuthority(registerPolicy) })`:
373
+ * the network is re-read as `active` right before registration.
374
+ * 6. Row `active`.
382
375
  *
383
- * Key custody: `create` generates a non-extractable key and never exposes
384
- * it. Keep the grant (in memory, or a CLI 0600 key file via `restore`) until
385
- * the share expires, to sign `RootRevocation` later. No browser persistence:
386
- * that needs the composer on its own origin first (REVIEW-D3 V2-2).
376
+ * Revoke: `revokeShareGrant` retires the network first (permanent; every
377
+ * recipient loses decrypt at the next request; no approval while the key is
378
+ * held), then revokes the grant (one owner approval). Revoking the grant
379
+ * first would strand an active network the dead key can no longer retire.
380
+ *
381
+ * Key custody: no browser persistence (needs the composer on its own origin
382
+ * first, REVIEW-D3 V2-2); keep the grant in memory, or in a CLI 0600 key file
383
+ * via `restore`, until the share is retired or expired.
387
384
  */
388
385
  declare class ShareGrant {
389
386
  #private;
@@ -392,87 +389,237 @@ declare class ShareGrant {
392
389
  readonly spaceId: string;
393
390
  readonly keyDid: string;
394
391
  readonly nodeOrigin: string;
392
+ readonly shareId: string;
393
+ readonly networkId: string;
395
394
  readonly path: string;
396
395
  readonly access: ShareGrantAccess;
397
396
  readonly issuedAt: Date;
398
397
  readonly expiresAt: Date;
399
398
  private constructor();
400
399
  /**
401
- * Ask the owner for one per-share grant (one approval). Path, access,
402
- * expiry, space and Node origin are checked before the wallet is asked; the
403
- * signed CACAO is decoded and must match exactly, or the key is cleared and
404
- * the error carries the imported CID (`mustRevoke` when it may be wider).
400
+ * Step 0: check everything that does not need the owner, and make the key.
401
+ * Also looks the share's network up (no approval): a shareId whose network
402
+ * is retired (burned), or already exists from an earlier grant, is refused
403
+ * with `share-id-in-use` before the prompt (see `resumeExistingNetwork`).
404
+ * Throws `share-id-not-network-safe`, `invalid-request`, `lifetime-exceeds-cap`,
405
+ * `share-id-in-use`, `node-refused` or `invalid-key` with no approval spent.
405
406
  */
406
- static create(input: {
407
- readonly owner: ShareRootOwnerAuthority;
408
- readonly spaceId: string;
409
- readonly nodeOrigin: string;
410
- readonly path: string;
411
- readonly access: ShareGrantAccess;
412
- readonly expiresAt: Date;
413
- readonly keyFactory?: () => Promise<DelegatedShareKey>;
414
- readonly now?: () => number;
415
- }): Promise<ShareGrant>;
416
- /** Rebuild a stored grant with its key (e.g. a CLI 0600 key file); the signed bytes are checked again. */
407
+ static prepare(input: ShareGrantRequest): Promise<PendingShareGrant>;
408
+ /** `prepare` then `approve`, for callers that keep no `pending` row (tests, one-shot tools). */
409
+ static create(input: ShareGrantRequest): Promise<ShareGrant>;
410
+ /** Rebuild a stored grant with its key (e.g. a CLI 0600 key file); the signed bytes are checked again. Only record v2. */
417
411
  static restore(input: {
418
412
  readonly record: ShareGrantRecord;
419
413
  readonly key: DelegatedShareKey;
420
414
  readonly now?: () => number;
415
+ readonly fetch?: FetchFn$1;
421
416
  }): Promise<ShareGrant>;
422
- /** The `Authorization` value the Node knows this grant by (base64url CACAO). */
417
+ /**
418
+ * This grant's CACAO as base64url, UNPADDED (the record form). As a Node
419
+ * `Authorization` header it must be padded to a multiple of 4 characters:
420
+ * the Node refuses an unpadded value otherwise.
421
+ */
423
422
  get authorization(): string;
424
423
  /** The exact KV resource this grant covers. */
425
424
  get resource(): string;
426
425
  toRecord(): ShareGrantRecord;
426
+ /** The history/index row for `state`. */
427
+ row(state: ShareGrantRowState): ShareGrantRow;
428
+ /**
429
+ * Step 3: make sure the share's network exists and is usable. Creates it
430
+ * when absent (key-signed, no approval), then decides on a fresh lookup
431
+ * after any answer. Returns `networkId` (the grant's lowercase spelling;
432
+ * the Node echoes the owner in EIP-55 case). A retired network is never
433
+ * re-created (the Node answers 409): `network-not-active`.
434
+ */
435
+ ensureNetwork(): Promise<string>;
436
+ /** Throw `network-not-active` unless the share's network is `active` now (fresh lookup). */
437
+ assertNetworkActive(): Promise<string>;
438
+ /**
439
+ * Retire the share's network with the per-share key (no approval):
440
+ * PERMANENT, and every holder of decrypt on it (recipients included) is
441
+ * refused at their next request. Idempotent: a repeat or a never-created
442
+ * network counts as done.
443
+ */
444
+ retireNetwork(): Promise<"retired" | "absent">;
445
+ /** Step 4 check (I1): the stored object belongs to this share, on its exact key and network. */
446
+ assertContentSource(contentSource: UnifiedContentSource): void;
427
447
  /**
428
448
  * Policy/v3 owner signing for this one share. Every call checks the key is
429
449
  * held and the grant is live. Policy roots must be owned by the per-share
430
450
  * key, expire no later than the grant, and name only this grant's exact
431
- * KV resource (with no action the grant lacks) and decrypt on `:share`.
451
+ * KV resource (no action the grant lacks) and decrypt on this share's
452
+ * network (parsed equality).
453
+ */
454
+ policySigner(): SharePolicySigner;
455
+ /**
456
+ * Step 5: `publishAddressedShare`'s `authority` for this share. Its
457
+ * `registerPolicy` first re-reads the network and refuses unless it is
458
+ * `active` (released Nodes accept registrations on missing or retired
459
+ * networks).
432
460
  */
433
- policySigner(): ShareRootPolicySigner;
434
- /** `publishAddressedShare`'s `authority` for this share: the policy signer plus the caller's `registerPolicy`. */
435
461
  publishAuthority(registerPolicy: AddressedPublishAuthority["registerPolicy"]): AddressedPublishAuthority;
436
- /** Drop the per-share key. It does not revoke the grant: only the owner does that. */
462
+ /** Drop the per-share key. It does not retire the network or revoke the grant. */
437
463
  clear(): void;
438
464
  }
439
-
440
- interface ShareNetworkResult {
441
- /** `urn:tinycloud:encryption:<owner did:pkh>:share`. */
465
+ /**
466
+ * Revoke one addressed share: retire its network with the per-share key
467
+ * (permanent, no approval), THEN revoke the grant with an owner SIWE (one
468
+ * approval). Only a `{ revoked: true, cid }` receipt counts. If the owner
469
+ * step fails, decrypt is already cut: the error `grant-revoke-incomplete`
470
+ * says so, and the caller can retry the grant revoke. Without the key, use
471
+ * the owner control batch (`runShareControlBatch`).
472
+ */
473
+ declare function revokeShareGrant(input: {
474
+ readonly grant: ShareGrant;
475
+ readonly owner: {
476
+ signOwnerRevocation(cid: string): Promise<string>;
477
+ };
478
+ readonly fetch?: FetchFn$1;
479
+ }): Promise<{
480
+ readonly network: "retired" | "absent";
481
+ readonly grant: "revoked";
482
+ }>;
483
+ /** A short-lived key that lets the owner read back one share's content (P2(a)). */
484
+ interface EditShareReader {
485
+ readonly cid: string;
486
+ readonly keyDid: string;
442
487
  readonly networkId: string;
443
- /** Owner wallet approvals this call used: 0 when the network already existed, else 1. */
444
- readonly approvals: 0 | 1;
488
+ readonly path: string;
489
+ readonly expiresAt: Date;
490
+ /** Signs this reader's kv/get and decrypt invocations; refuses after expiry or `clear()`. */
491
+ readonly signer: ShareKeySigner;
492
+ clear(): void;
445
493
  }
446
494
  /**
447
- * The owner's `:share` network URN, `urn:tinycloud:encryption:<owner
448
- * did:pkh>:share`: where Share sources are encrypted, and the network a
449
- * per-share grant or a `kv+share-decrypt` root may decrypt. The Node compares
450
- * the did:pkh address case-insensitively.
495
+ * Let the owner read back an edit share (P2(a)): one approval for kv `get`
496
+ * on the share's key and `decrypt` on its network, to a throwaway in-memory
497
+ * key that lives at most one hour. Nothing is stored.
498
+ */
499
+ declare function openEditShare(input: {
500
+ readonly owner: ShareRootOwnerAuthority;
501
+ readonly record: Pick<ShareGrantRecord, "spaceId" | "ownerDid" | "shareId" | "networkId" | "path">;
502
+ readonly lifetimeMs?: number;
503
+ readonly now?: () => number;
504
+ }): Promise<EditShareReader>;
505
+
506
+ /**
507
+ * One encryption network per addressed share (TC-1423, PERSHARE-NETWORK
508
+ * design): `urn:tinycloud:encryption:<owner did:pkh, lowercase>:share-<shareId>`.
509
+ * A stored ciphertext is wrapped to exactly one share's network, so a key or
510
+ * recipient session holding decrypt on it reaches that one share only.
511
+ */
512
+ declare const SHARE_NETWORK_NAME_PREFIX = "share-";
513
+ /** Throws `share-id-not-network-safe` unless `shareId` matches `^[a-z0-9][a-z0-9-]{15,35}$` (UUIDs and 32-hex ids do). */
514
+ declare function assertNetworkSafeShareId(shareId: string): void;
515
+ /** The share's own network: `urn:tinycloud:encryption:<owner did:pkh, lowercase>:share-<shareId>`. */
516
+ declare function perShareNetworkIdFor(ownerDid: string, shareId: string): string;
517
+
518
+ /**
519
+ * Largest `Authorization` value an owner control may carry. Measured on the
520
+ * production ingress (nginx 1.27.4, `large_client_header_buffers 4 32k`,
521
+ * HTTP/1.1): 32,751 bytes pass, 32,752 bytes get `400 Request Header Or
522
+ * Cookie Too Large`. A direct Node accepts ~483 KB and Cloudflare 128 KB.
523
+ * 30,000 bytes leave 8.4% for header-name and proxy overhead. Each share
524
+ * (network URN with `delegation/revoke` + `network.revoke`) costs ~845
525
+ * bytes: 34 shares per control with the space rows (29,800 bytes measured
526
+ * with the `applications` row, imported by the Node), 35 without.
451
527
  */
452
- declare function shareNetworkIdFor(ownerDid: string): string;
528
+ declare const SHARE_CONTROL_HEADER_BUDGET_BYTES = 30000;
529
+ /** A control lives ten minutes: long enough to retire and revoke one batch. */
530
+ declare const SHARE_CONTROL_LIFETIME_MS: number;
453
531
  type FetchFn = (input: URL, init?: RequestInit) => Promise<Response>;
532
+ /** The owner wallet, as a SIWE signer: one call = one approval. */
533
+ interface ShareControlOwner {
534
+ /** `did:pkh:eip155:<chain>:<address>`. */
535
+ readonly ownerDid: string;
536
+ /** The SIWE `domain` the wallet shows (the composer's host). */
537
+ readonly domain: string;
538
+ /** EIP-191 `personal_sign` of exactly `message`; returns the 65-byte signature as 0x hex. */
539
+ signMessage(message: string): Promise<string>;
540
+ }
541
+ /** One share to cut: its network, and its grant when known. */
542
+ interface ShareControlTarget {
543
+ readonly networkId: string;
544
+ readonly grantCid?: string;
545
+ }
546
+ /** One owner control: one approval, sized to the header budget before signing. */
547
+ interface ShareControlBatch {
548
+ readonly targets: readonly ShareControlTarget[];
549
+ /** `<space>/delegation` rows (first batch only): revoke any delegation in those spaces (share roots, links). */
550
+ readonly spaceDelegationResources: readonly string[];
551
+ /** Exact `Authorization` value length of the signed control, in bytes. */
552
+ readonly headerBytes: number;
553
+ }
454
554
  /**
455
- * Make sure the owner's dedicated `:share` encryption network exists (D3
456
- * addendum §1, review B1). Per owner: `urn:tinycloud:encryption:<owner
457
- * did:pkh>:share`. The owner owns it, never a share key or per-share key: a
458
- * network whose owner is the key would be root authority for that key
459
- * (tinycloud-node `delegation.rs:792-809`), so revoking its grant would not
460
- * cut decrypt.
555
+ * Split a kill-all into owner controls (one approval each), sized by the
556
+ * exact `Authorization` bytes of each signed control, never above
557
+ * `budgetBytes` (default {@link SHARE_CONTROL_HEADER_BUDGET_BYTES}).
461
558
  *
462
- * Idempotent: when the Node already has it, nothing is signed (0 approvals).
463
- * Otherwise the owner signs one short-lived CACAO (one approval, once per
464
- * owner) granting only `network.create` on that URN to a throwaway
465
- * non-extractable key, which creates the network with the owner as
466
- * `ownerDid` and is then cleared. A 409 from a concurrent creator counts as
467
- * created. Share sources encrypt to it; vault and secrets stay on `:default`.
559
+ * EVERY control carries the same `<space>/delegation` row for each of
560
+ * `spaceIds`: a grant's kv row is revoked through the `applications` row,
561
+ * and Nodes that accept control revokes (v1.20.6, NODE-B2 N4') need it in
562
+ * the control that revokes that grant. The `default` row covers agent roots
563
+ * and their links. `createOwnerDelegation` cannot express these rows (and
564
+ * caps a delegation at 16 permissions), so controls are built here.
565
+ *
566
+ * Targets are restricted to this owner's per-share networks
567
+ * (`control-target-invalid` otherwise), checked before anything is signed.
468
568
  */
469
- declare function ensureShareNetwork(input: {
470
- readonly owner: ShareRootOwnerAuthority;
471
- readonly spaceId: string;
569
+ declare function planShareControlBatches(input: {
570
+ readonly owner: Pick<ShareControlOwner, "ownerDid" | "domain">;
571
+ readonly targets: readonly ShareControlTarget[];
572
+ /** Spaces whose delegations every control may revoke (e.g. the owner's `applications` and `default`). */
573
+ readonly spaceIds?: readonly string[];
574
+ readonly budgetBytes?: number;
575
+ }): ShareControlBatch[];
576
+ interface ShareControlResult {
577
+ readonly controlCid: string;
578
+ readonly networks: readonly {
579
+ readonly networkId: string;
580
+ readonly result: "retired" | "absent" | "failed";
581
+ readonly error?: string;
582
+ }[];
583
+ /**
584
+ * `revoked`: by the control. `revoked-by-owner`: the Node refused the
585
+ * control (released Nodes refuse it for grants that carry decrypt) and
586
+ * the per-grant owner fallback (one approval each) revoked it. `refused`:
587
+ * still live.
588
+ */
589
+ readonly grants: readonly {
590
+ readonly grantCid: string;
591
+ readonly result: "revoked" | "revoked-by-owner" | "refused";
592
+ readonly status: number;
593
+ }[];
594
+ }
595
+ /**
596
+ * Run one control (one approval): sign it to a fresh throwaway key, import
597
+ * it (`POST /delegate`), retire EVERY target network first (decrypt is cut
598
+ * for everyone at the next request), then revoke each known grant through
599
+ * the control. A revoke counts only with a `{ revoked: true, cid }` receipt
600
+ * for that grant. With `revokeFallback`, a grant the Node refuses to revoke
601
+ * through the control (1.21.0 refuses it for decrypt-bearing grants) is
602
+ * revoked by the owner directly (one approval each). The throwaway key is
603
+ * cleared before returning.
604
+ *
605
+ * The batch is checked again here (targets, space rows, domain, and the
606
+ * byte budget on the real values) before the owner is asked, so a stored or
607
+ * hand-built batch gets the same checks as a planned one.
608
+ *
609
+ * A grant is revoked even if its network's retire failed: re-run (the same
610
+ * batch, or a new plan for the leftovers) until every network is `retired`
611
+ * or `absent`. Re-runs are idempotent.
612
+ */
613
+ declare function runShareControlBatch(input: {
614
+ readonly owner: ShareControlOwner;
472
615
  readonly nodeOrigin: string;
616
+ readonly batch: ShareControlBatch;
617
+ readonly revokeFallback?: {
618
+ signOwnerRevocation(cid: string): Promise<string>;
619
+ };
473
620
  readonly fetch?: FetchFn;
474
- readonly keyFactory?: () => Promise<DelegatedShareKey>;
475
621
  readonly now?: () => number;
476
- }): Promise<ShareNetworkResult>;
622
+ readonly keyFactory?: () => Promise<DelegatedShareKey>;
623
+ }): Promise<ShareControlResult>;
477
624
 
478
- export { AGENT_SHARE_ROOT_MAX_LIFETIME_MS, type DecodedOwnerRootCacao, POLICY_OWNER_AUTHORITY_FEATURES, POLICY_OWNER_AUTHORITY_PROBE_TTL_MS, type PolicyOwnerModeProbe, SHARE_ROOT_CLOCK_SKEW_MS, SHARE_ROOT_LINK_MAX_LIFETIME_MS, SHARE_ROOT_MAX_LIFETIME_MS, SHARE_ROOT_PREFIX, SHARE_ROOT_REQUEST_MARGIN_MS, ShareGrant, type ShareGrantAccess, type ShareGrantRecord, type ShareKeyDelegationInput, type ShareKeySigner, type ShareNetworkResult, ShareRoot, ShareRootError, type ShareRootErrorCode, type ShareRootErrorDetails, type ShareRootKind, type ShareRootLink, type ShareRootOptions, type ShareRootOwnerAuthority, type ShareRootPolicySigner, type ShareRootRecord, type ShareRootScope, assertNonExtractableShareKey, createPolicyOwnerModeProbe, decodeOwnerRootCacao, defaultPolicyOwnerModeProbe, ensureShareNetwork, ensureShareRoot, ownerRootCacaoCid, readPolicyOwnerAuthorityMode, shareNetworkIdFor, signShareKeyDelegation };
625
+ export { AGENT_SHARE_ROOT_MAX_LIFETIME_MS, type DecodedOwnerRootCacao, EDIT_SHARE_READER_MAX_LIFETIME_MS, type EditShareReader, type PendingShareGrant, SHARE_CONTROL_HEADER_BUDGET_BYTES, SHARE_CONTROL_LIFETIME_MS, SHARE_NETWORK_NAME_PREFIX, SHARE_ROOT_CLOCK_SKEW_MS, SHARE_ROOT_LINK_MAX_LIFETIME_MS, SHARE_ROOT_MAX_LIFETIME_MS, SHARE_ROOT_PREFIX, SHARE_ROOT_REQUEST_MARGIN_MS, type ShareControlBatch, type ShareControlOwner, type ShareControlResult, type ShareControlTarget, ShareGrant, type ShareGrantAccess, type ShareGrantRecord, type ShareGrantRequest, type ShareGrantRow, type ShareGrantRowState, type ShareKeyDelegationInput, type ShareKeySigner, type SharePolicySigner, ShareRoot, ShareRootError, type ShareRootErrorCode, type ShareRootErrorDetails, type ShareRootKind, type ShareRootLink, type ShareRootOptions, type ShareRootOwnerAuthority, type ShareRootRecord, assertNetworkSafeShareId, assertNonExtractableShareKey, assertShareGrantPath, decodeOwnerRootCacao, ensureShareRoot, openEditShare, ownerRootCacaoCid, perShareNetworkIdFor, planShareControlBatches, revokeShareGrant, runShareControlBatch, signShareKeyDelegation };