@tinycloud/share-sdk 1.2.0-beta.1 → 1.2.0-beta.3

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