@aithos/sdk 0.1.0-alpha.43 → 0.1.0-alpha.45
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +34 -0
- package/dist/src/compute.d.ts +218 -0
- package/dist/src/compute.js +457 -0
- package/dist/src/data.d.ts +128 -1
- package/dist/src/data.js +159 -15
- package/dist/src/index.d.ts +5 -3
- package/dist/src/index.js +3 -2
- package/dist/src/internal/envelope.d.ts +16 -0
- package/dist/src/internal/envelope.js +6 -0
- package/dist/src/mandates.d.ts +22 -1
- package/dist/src/mandates.js +44 -3
- package/dist/src/react/index.d.ts +1 -0
- package/dist/src/react/index.js +1 -0
- package/dist/src/react/use-transcribe-pending.d.ts +21 -0
- package/dist/src/react/use-transcribe-pending.js +47 -0
- package/dist/src/transcribe-resilience.d.ts +57 -0
- package/dist/src/transcribe-resilience.js +203 -0
- package/dist/test/transcribe-invoke.test.d.ts +2 -0
- package/dist/test/transcribe-invoke.test.js +204 -0
- package/dist/test/transcribe.test.d.ts +2 -0
- package/dist/test/transcribe.test.js +186 -0
- package/package.json +1 -1
package/dist/src/data.js
CHANGED
|
@@ -38,6 +38,7 @@ import { hkdf } from "@noble/hashes/hkdf.js";
|
|
|
38
38
|
import { sha256, sha512 } from "@noble/hashes/sha2.js";
|
|
39
39
|
import { XChaCha20Poly1305 } from "@stablelib/xchacha20poly1305";
|
|
40
40
|
import * as ed from "@noble/ed25519";
|
|
41
|
+
import { multibaseToEd25519PublicKey, edPubToX25519Pub, } from "@aithos/protocol-client";
|
|
41
42
|
import { contactsV1 } from "./data-schema-contacts-v1.js";
|
|
42
43
|
import { signOwnerEnvelope } from "./internal/envelope.js";
|
|
43
44
|
// noble/ed25519 v2 needs sha512 wired in for sync sign/verify
|
|
@@ -57,15 +58,48 @@ const SCHEMAS = new Map([
|
|
|
57
58
|
export function createDataClient(args) {
|
|
58
59
|
return new DataClientImpl(args);
|
|
59
60
|
}
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
61
|
+
/**
|
|
62
|
+
* Build a read-only data client that reads a subject's collections under
|
|
63
|
+
* a mandate (delegate path). The returned {@link ReadonlyDataClient}
|
|
64
|
+
* signs every request as the delegate (bare-multibase verificationMethod
|
|
65
|
+
* + the mandate attached to the envelope), and decrypts records using the
|
|
66
|
+
* CMK the owner re-wrapped for this delegate via
|
|
67
|
+
* {@link DataClient.authorizeDelegate}.
|
|
68
|
+
*
|
|
69
|
+
* Writes are not available on the returned type and throw `-32042` if
|
|
70
|
+
* forced.
|
|
71
|
+
*/
|
|
72
|
+
export function createDelegateDataClient(args) {
|
|
73
|
+
const granteePubMb = args.granteePubkeyMultibase ??
|
|
74
|
+
args.mandate.grantee?.pubkey;
|
|
75
|
+
if (!granteePubMb) {
|
|
76
|
+
throw new Error("createDelegateDataClient: mandate.grantee.pubkey is missing; pass granteePubkeyMultibase explicitly");
|
|
77
|
+
}
|
|
78
|
+
return new DataClientImpl({
|
|
79
|
+
pdsUrl: args.pdsUrl,
|
|
80
|
+
did: args.subjectDid,
|
|
81
|
+
// In delegate mode the seed is used only to derive the X25519 key that
|
|
82
|
+
// unwraps the re-wrapped CMK; envelope signing goes through the
|
|
83
|
+
// delegate path (buildSignedEnvelope), never signOwnerEnvelope.
|
|
84
|
+
sphereSeed: args.delegateSeed,
|
|
85
|
+
verificationMethod: granteePubMb,
|
|
86
|
+
...(args.schemas ? { schemas: args.schemas } : {}),
|
|
87
|
+
...(args.fetch ? { fetch: args.fetch } : {}),
|
|
88
|
+
delegate: {
|
|
89
|
+
delegateSeed: args.delegateSeed,
|
|
90
|
+
granteePubkeyMultibase: granteePubMb,
|
|
91
|
+
mandate: args.mandate,
|
|
92
|
+
},
|
|
93
|
+
});
|
|
94
|
+
}
|
|
63
95
|
class DataClientImpl {
|
|
64
96
|
#pdsUrl;
|
|
65
97
|
#did;
|
|
66
98
|
#seed;
|
|
67
99
|
#vm;
|
|
68
100
|
#fetch;
|
|
101
|
+
/** Delegate session, or undefined for the owner path. */
|
|
102
|
+
#delegate;
|
|
69
103
|
/**
|
|
70
104
|
* Per-client schema overrides, populated from `args.schemas` at
|
|
71
105
|
* construction. Looked up BEFORE the bundled SCHEMAS map (so an app
|
|
@@ -82,8 +116,22 @@ class DataClientImpl {
|
|
|
82
116
|
this.#seed = args.sphereSeed;
|
|
83
117
|
this.#vm = args.verificationMethod;
|
|
84
118
|
this.#fetch = args.fetch ?? globalThis.fetch.bind(globalThis);
|
|
119
|
+
if (args.delegate)
|
|
120
|
+
this.#delegate = args.delegate;
|
|
85
121
|
this.#localSchemas = new Map((args.schemas ?? []).map((s) => [s.schema, s]));
|
|
86
122
|
}
|
|
123
|
+
/** Throw a read-only error when a mutating verb is called on a delegate
|
|
124
|
+
* client. The PDS rejects these server-side too; this is the fast,
|
|
125
|
+
* local guard with a precise message. */
|
|
126
|
+
#assertOwner(op) {
|
|
127
|
+
if (this.#delegate) {
|
|
128
|
+
const e = new Error(`sdk.data: "${op}" is not permitted for a delegate client (read-only mandate). ` +
|
|
129
|
+
`A data.<collection>.read mandate grants get/list only; writes require the owner ` +
|
|
130
|
+
`or a data.<collection>.write mandate (not yet supported on the delegate path).`);
|
|
131
|
+
e.code = -32042;
|
|
132
|
+
throw e;
|
|
133
|
+
}
|
|
134
|
+
}
|
|
87
135
|
/**
|
|
88
136
|
* Resolve a schema id to its lite definition. App-supplied schemas
|
|
89
137
|
* (via `createDataClient({ schemas })`) take precedence over the
|
|
@@ -95,7 +143,45 @@ class DataClientImpl {
|
|
|
95
143
|
collection(name) {
|
|
96
144
|
return new DataCollectionImpl(this, name);
|
|
97
145
|
}
|
|
146
|
+
async authorizeDelegate(args) {
|
|
147
|
+
this.#assertOwner("authorizeDelegate");
|
|
148
|
+
const granteePubMb = args.mandate.grantee?.pubkey;
|
|
149
|
+
if (!granteePubMb) {
|
|
150
|
+
throw new Error("sdk.data.authorizeDelegate: mandate.grantee.pubkey is required (data read mandates bind to a grantee key).");
|
|
151
|
+
}
|
|
152
|
+
// Ensure we hold the collection's CMK (owner unwrap path).
|
|
153
|
+
const state = await this._ensureCollection(args.collectionName);
|
|
154
|
+
const cmk = this.#cmkCache.get(args.collectionName);
|
|
155
|
+
if (!cmk) {
|
|
156
|
+
throw new Error(`sdk.data.authorizeDelegate: CMK for "${args.collectionName}" not loaded`);
|
|
157
|
+
}
|
|
158
|
+
// Derive the grantee's X25519 wrap-recipient key from its Ed25519
|
|
159
|
+
// public key (the same birational map the owner uses for its own key).
|
|
160
|
+
const granteeEdPub = multibaseToEd25519PublicKey(granteePubMb);
|
|
161
|
+
const granteeX25519Pub = edPubToX25519Pub(granteeEdPub);
|
|
162
|
+
const recipientDidUrl = delegateRecipientDidUrl(granteePubMb);
|
|
163
|
+
const wrap = this.#wrapCmkForRecipient({
|
|
164
|
+
cmk,
|
|
165
|
+
recipientPublicKey: granteeX25519Pub,
|
|
166
|
+
recipientDidUrl,
|
|
167
|
+
collectionUrn: state.urn,
|
|
168
|
+
});
|
|
169
|
+
await this.#call("/mcp/primitives/write", "aithos.data.authorize_app", {
|
|
170
|
+
collection_urn: state.urn,
|
|
171
|
+
mandate: args.mandate,
|
|
172
|
+
wrap,
|
|
173
|
+
});
|
|
174
|
+
}
|
|
175
|
+
async revokeDelegate(args) {
|
|
176
|
+
this.#assertOwner("revokeDelegate");
|
|
177
|
+
await this.#call("/mcp/primitives/write", "aithos.data.revoke_app", {
|
|
178
|
+
collection_urn: this.#collectionUrn(args.collectionName),
|
|
179
|
+
mandate_id: args.mandateId,
|
|
180
|
+
...(args.reason ? { revocation: { reason: args.reason } } : {}),
|
|
181
|
+
});
|
|
182
|
+
}
|
|
98
183
|
async createCollection(args) {
|
|
184
|
+
this.#assertOwner("createCollection");
|
|
99
185
|
const cmk = randomBytes32();
|
|
100
186
|
const recipientDidUrl = `${this.#did}#data-kex`;
|
|
101
187
|
const collectionUrn = this.#collectionUrn(args.name);
|
|
@@ -124,6 +210,20 @@ class DataClientImpl {
|
|
|
124
210
|
// CMK is retained in cache, only zero the local var if we didn't cache.
|
|
125
211
|
}
|
|
126
212
|
}
|
|
213
|
+
async ensureCollection(args) {
|
|
214
|
+
this.#assertOwner("ensureCollection");
|
|
215
|
+
try {
|
|
216
|
+
await this.createCollection(args);
|
|
217
|
+
}
|
|
218
|
+
catch (e) {
|
|
219
|
+
// -32073 AITHOS_DATA_COLLECTION_EXISTS → already created (possibly by a
|
|
220
|
+
// concurrent caller). That's the success case for get-or-create. Any
|
|
221
|
+
// other error propagates.
|
|
222
|
+
if (e.code === -32073)
|
|
223
|
+
return;
|
|
224
|
+
throw e;
|
|
225
|
+
}
|
|
226
|
+
}
|
|
127
227
|
async listCollections() {
|
|
128
228
|
const r = await this.#call("/mcp/primitives/read", "aithos.data.list_collections", {
|
|
129
229
|
subject_did: this.#did,
|
|
@@ -146,6 +246,7 @@ class DataClientImpl {
|
|
|
146
246
|
return this.#call("/mcp/primitives/read", "aithos.data.list_gamma_entries", params);
|
|
147
247
|
}
|
|
148
248
|
async registerSchema(schemaDoc) {
|
|
249
|
+
this.#assertOwner("registerSchema");
|
|
149
250
|
if (schemaDoc === null || typeof schemaDoc !== "object" || Array.isArray(schemaDoc)) {
|
|
150
251
|
throw new Error("sdk.data.registerSchema: schemaDoc must be a JSON object");
|
|
151
252
|
}
|
|
@@ -210,16 +311,27 @@ class DataClientImpl {
|
|
|
210
311
|
err.code = -32020;
|
|
211
312
|
throw err;
|
|
212
313
|
}
|
|
213
|
-
// Look up our wrap and unwrap the CMK
|
|
214
|
-
|
|
314
|
+
// Look up our wrap and unwrap the CMK. Owner: the wrap addressed to
|
|
315
|
+
// `${did}#data-kex`, unwrapped with the owner sphere seed. Delegate:
|
|
316
|
+
// the wrap the owner re-wrapped for this grantee
|
|
317
|
+
// (`did:key:${granteePubkeyMultibase}#data-kex`), unwrapped with the
|
|
318
|
+
// delegate's own X25519 key (derived from its grantee seed).
|
|
319
|
+
const ourRecipient = this.#delegate
|
|
320
|
+
? delegateRecipientDidUrl(this.#delegate.granteePubkeyMultibase)
|
|
321
|
+
: `${this.#did}#data-kex`;
|
|
215
322
|
const wrap = meta.cmk_envelope.wraps.find((w) => w.recipient === ourRecipient);
|
|
216
323
|
if (!wrap) {
|
|
217
|
-
throw new Error(
|
|
324
|
+
throw new Error(this.#delegate
|
|
325
|
+
? `sdk.data: no CMK wrap for ${ourRecipient} in collection "${name}". ` +
|
|
326
|
+
`The owner has not authorized this delegate on the collection yet — ` +
|
|
327
|
+
`ask them to call sdk.data...authorizeDelegate({ collectionName, mandate }).`
|
|
328
|
+
: `sdk.data: no CMK wrap for ${ourRecipient} in collection ${name}. The collection was either created with a different recipient, or this client is not the owner.`);
|
|
218
329
|
}
|
|
330
|
+
const unwrapSeed = this.#delegate ? this.#delegate.delegateSeed : this.#seed;
|
|
219
331
|
const cmk = this.#unwrapCmk({
|
|
220
332
|
wrap,
|
|
221
333
|
collectionUrn: meta.urn,
|
|
222
|
-
privateKey: ed25519SeedToX25519PrivateKey(
|
|
334
|
+
privateKey: ed25519SeedToX25519PrivateKey(unwrapSeed),
|
|
223
335
|
});
|
|
224
336
|
this.#cmkCache.set(name, cmk);
|
|
225
337
|
const schema = this.#resolveSchema(meta.schema);
|
|
@@ -234,6 +346,7 @@ class DataClientImpl {
|
|
|
234
346
|
return state;
|
|
235
347
|
}
|
|
236
348
|
async _insert(state, record) {
|
|
349
|
+
this.#assertOwner("insert");
|
|
237
350
|
const cmk = this.#cmkCache.get(state.name);
|
|
238
351
|
if (!cmk)
|
|
239
352
|
throw new Error("CMK not loaded");
|
|
@@ -288,6 +401,7 @@ class DataClientImpl {
|
|
|
288
401
|
};
|
|
289
402
|
}
|
|
290
403
|
async _update(state, recordId, record) {
|
|
404
|
+
this.#assertOwner("update");
|
|
291
405
|
const cmk = this.#cmkCache.get(state.name);
|
|
292
406
|
if (!cmk)
|
|
293
407
|
throw new Error("CMK not loaded");
|
|
@@ -306,6 +420,7 @@ class DataClientImpl {
|
|
|
306
420
|
});
|
|
307
421
|
}
|
|
308
422
|
async _delete(state, recordId) {
|
|
423
|
+
this.#assertOwner("delete");
|
|
309
424
|
await this.#call("/mcp/primitives/write", "aithos.data.delete_record", {
|
|
310
425
|
collection_urn: state.urn,
|
|
311
426
|
record_id: recordId,
|
|
@@ -314,14 +429,31 @@ class DataClientImpl {
|
|
|
314
429
|
/* -- JSON-RPC dispatch -- */
|
|
315
430
|
async #call(path, method, params) {
|
|
316
431
|
const aud = `${this.#pdsUrl}${path}`;
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
432
|
+
// Both paths use the SAME §11.2 envelope scheme (the data PDS verifies
|
|
433
|
+
// with @aithos/protocol-core, which canonicalizes the full envelope
|
|
434
|
+
// INCLUDING `proof` with proofValue=""). Delegate path: sign with the
|
|
435
|
+
// grantee key, bare-multibase verificationMethod, and attach the mandate
|
|
436
|
+
// so the PDS resolves the delegation and enforces its scopes.
|
|
437
|
+
const envelope = this.#delegate
|
|
438
|
+
? await signOwnerEnvelope({
|
|
439
|
+
iss: this.#did, // the SUBJECT DID (mandate issuer), not the delegate
|
|
440
|
+
aud,
|
|
441
|
+
method,
|
|
442
|
+
params,
|
|
443
|
+
verificationMethod: this.#delegate.granteePubkeyMultibase,
|
|
444
|
+
signer: {
|
|
445
|
+
sign: async (msg) => ed.sign(msg, this.#delegate.delegateSeed),
|
|
446
|
+
},
|
|
447
|
+
mandate: this.#delegate.mandate,
|
|
448
|
+
})
|
|
449
|
+
: await signOwnerEnvelope({
|
|
450
|
+
iss: this.#did,
|
|
451
|
+
aud,
|
|
452
|
+
method,
|
|
453
|
+
params,
|
|
454
|
+
verificationMethod: this.#vm,
|
|
455
|
+
signer: { sign: async (msg) => ed.sign(msg, this.#seed) },
|
|
456
|
+
});
|
|
325
457
|
const body = {
|
|
326
458
|
jsonrpc: "2.0",
|
|
327
459
|
id: makeUlid(),
|
|
@@ -508,6 +640,18 @@ function cryptoRandom(n) {
|
|
|
508
640
|
function utf8(s) {
|
|
509
641
|
return new TextEncoder().encode(s);
|
|
510
642
|
}
|
|
643
|
+
/**
|
|
644
|
+
* Recipient DID URL used for a delegate's CMK wrap. Built from the
|
|
645
|
+
* grantee's Ed25519 public-key multibase so that (a) the owner side
|
|
646
|
+
* (`authorizeDelegate`) and the delegate side (`_ensureCollection`)
|
|
647
|
+
* derive the EXACT same string — it's bound into the wrap AAD and the
|
|
648
|
+
* HKDF info, so any mismatch fails the unwrap — and (b) the string
|
|
649
|
+
* contains `mandate.grantee.pubkey`, which the PDS `authorize_app`
|
|
650
|
+
* handler requires (`wrap.recipient.includes(grantee.pubkey)`).
|
|
651
|
+
*/
|
|
652
|
+
function delegateRecipientDidUrl(granteePubkeyMultibase) {
|
|
653
|
+
return `did:key:${granteePubkeyMultibase}#data-kex`;
|
|
654
|
+
}
|
|
511
655
|
function aadCmkWrap(collectionUrn, recipient) {
|
|
512
656
|
const p = utf8("aithos-data-cmk-v1\0");
|
|
513
657
|
const c = utf8(collectionUrn);
|
package/dist/src/index.d.ts
CHANGED
|
@@ -1,12 +1,14 @@
|
|
|
1
|
-
export declare const VERSION = "0.1.0-alpha.
|
|
1
|
+
export declare const VERSION = "0.1.0-alpha.44";
|
|
2
2
|
export { AithosSDK } from "./sdk.js";
|
|
3
3
|
export type { AithosSDKConfig } from "./types.js";
|
|
4
4
|
export { AithosSDKError } from "./types.js";
|
|
5
5
|
export { AithosRpcError } from "@aithos/protocol-client";
|
|
6
6
|
export type { AithosSdkEndpoints } from "./endpoints.js";
|
|
7
7
|
export { DEFAULT_SDK_ENDPOINTS } from "./endpoints.js";
|
|
8
|
-
export type { ComputeMessage, ImageAspectRatio, ImageModelId, InvokeBedrockArgs, InvokeBedrockResult, InvokeBedrockVisionArgs, InvokeBedrockVisionResult, InvokeImageArgs, InvokeImageImage, InvokeImageResult, InvokeSegmentationArgs, InvokeSegmentationResult, SegmentPolygon, StopReason, } from "./compute.js";
|
|
8
|
+
export type { ComputeMessage, ImageAspectRatio, ImageModelId, InvokeBedrockArgs, InvokeBedrockResult, InvokeBedrockVisionArgs, InvokeBedrockVisionResult, InvokeImageArgs, InvokeImageImage, InvokeImageResult, InvokeSegmentationArgs, InvokeSegmentationResult, SegmentPolygon, StopReason, TranscribeModelId, TranscribeProgressState, TranscribeSegment, TranscribeWord, InvokeTranscribeArgs, InvokeTranscribeResult, PrepareTranscribeArgs, PrepareTranscribeResult, StartTranscribeArgs, StartTranscribeResult, TranscribeStatusResult, TranscribeJobSummary, } from "./compute.js";
|
|
9
9
|
export { ComputeNamespace } from "./compute.js";
|
|
10
|
+
export type { LocalPendingEntry, LocalPendingStatus, TranscribeDraftMeta, TranscribeDraftRecord, } from "./transcribe-resilience.js";
|
|
11
|
+
export { LocalPendingTranscribeTracker, TranscribeDraftStore, TranscribeDraftUnavailableError, } from "./transcribe-resilience.js";
|
|
10
12
|
export type { CreditPackId, CreateTopupSessionArgs, CreateTopupSessionResult, GetBalanceArgs, GetBalanceResult, } from "./wallet.js";
|
|
11
13
|
export { WalletNamespace } from "./wallet.js";
|
|
12
14
|
export type { ComponentStyle, ExtractArgs, ExtractContent, ExtractData, ExtractForm, ExtractFormField, ExtractHeading, ExtractIconDeclaration, ExtractImage, ExtractLink, ExtractLogo, ExtractMeta, ExtractResult, ExtractSection, ExtractStructure, ExtractStyles, FetchAssetArgs, FetchAssetResult, PaletteEntry, VisualSignature, WebNamespaceDeps, } from "./web.js";
|
|
@@ -25,6 +27,6 @@ export type { AudienceSet, AppCreditPackId, CreateAppTopupSessionArgs, CreateApp
|
|
|
25
27
|
export * as onboarding from "./onboarding.js";
|
|
26
28
|
export { createBrowserIdentity, browserIdentityFromStored, type BrowserIdentity, } from "@aithos/protocol-client";
|
|
27
29
|
export type { Section } from "@aithos/protocol-client";
|
|
28
|
-
export { createDataClient, type CreateDataClientArgs, type DataClient, type DataCollection, type ListOpts, type AithosSchemaLite, } from "./data.js";
|
|
30
|
+
export { createDataClient, createDelegateDataClient, type CreateDataClientArgs, type CreateDelegateDataClientArgs, type DataClient, type DataCollection, type ReadonlyDataClient, type ReadonlyDataCollection, type ListOpts, type AithosSchemaLite, } from "./data.js";
|
|
29
31
|
export { createAssetsClient, AssetsClient, type CreateAssetsClientArgs, type AttachedContext, type AssetUploadInput, type AssetUploadResult, type AssetFetchResult, type AssetBrief, type ListAssetsOpts, type ThumbnailUploadInput, type ThumbnailUploadResult, type RecipientResolver, type RecipientSet, } from "./assets.js";
|
|
30
32
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/src/index.js
CHANGED
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
// Public types specific to the SDK (`AithosSDKConfig`, `AithosSDKError`)
|
|
18
18
|
// are exported from here. Endpoint config (`AithosSdkEndpoints`,
|
|
19
19
|
// `DEFAULT_SDK_ENDPOINTS`) likewise.
|
|
20
|
-
export const VERSION = "0.1.0-alpha.
|
|
20
|
+
export const VERSION = "0.1.0-alpha.44";
|
|
21
21
|
export { AithosSDK } from "./sdk.js";
|
|
22
22
|
export { AithosSDKError } from "./types.js";
|
|
23
23
|
// Re-export protocol-client's JSON-RPC error type so consumers can
|
|
@@ -26,6 +26,7 @@ export { AithosSDKError } from "./types.js";
|
|
|
26
26
|
export { AithosRpcError } from "@aithos/protocol-client";
|
|
27
27
|
export { DEFAULT_SDK_ENDPOINTS } from "./endpoints.js";
|
|
28
28
|
export { ComputeNamespace } from "./compute.js";
|
|
29
|
+
export { LocalPendingTranscribeTracker, TranscribeDraftStore, TranscribeDraftUnavailableError, } from "./transcribe-resilience.js";
|
|
29
30
|
export { WalletNamespace } from "./wallet.js";
|
|
30
31
|
export { WebNamespace, WEB_EXTRACT_SCOPE } from "./web.js";
|
|
31
32
|
// Sign-up, sign-in, sign-in-with-Google. Lives outside the AithosSDK
|
|
@@ -67,7 +68,7 @@ export { createBrowserIdentity, browserIdentityFromStored, } from "@aithos/proto
|
|
|
67
68
|
// `sdk.data` namespace — Aithos data sub-protocol PDS client. Manages
|
|
68
69
|
// the lifecycle of subject-owned, encrypted, schema-validated records.
|
|
69
70
|
// See spec/data/ in the aithos-protocol repo.
|
|
70
|
-
export { createDataClient, } from "./data.js";
|
|
71
|
+
export { createDataClient, createDelegateDataClient, } from "./data.js";
|
|
71
72
|
// `sdk.assets` — Aithos assets sub-protocol PDS client. Upload,
|
|
72
73
|
// fetch, list, ref/unref binary content (images, PDFs, audio, video)
|
|
73
74
|
// owned by a subject. AEAD-encrypted per-asset under AMKs wrapped for
|
|
@@ -22,6 +22,15 @@ export interface SignedEnvelope {
|
|
|
22
22
|
readonly exp: number;
|
|
23
23
|
readonly nonce: string;
|
|
24
24
|
readonly params_hash: string;
|
|
25
|
+
/**
|
|
26
|
+
* Full signed mandate — present ONLY for a delegate-signed envelope
|
|
27
|
+
* (§11.6). When present, `proof.verificationMethod` is the delegate's
|
|
28
|
+
* bare Ed25519 multibase (matching `mandate.grantee.pubkey`) and the
|
|
29
|
+
* server resolves the signer to that key after verifying the mandate.
|
|
30
|
+
* The field is part of the signed bytes (the signature commits to the
|
|
31
|
+
* delegation context), so it cannot be swapped out in transit.
|
|
32
|
+
*/
|
|
33
|
+
readonly mandate?: unknown;
|
|
25
34
|
readonly proof: {
|
|
26
35
|
readonly type: "Ed25519Signature2020";
|
|
27
36
|
readonly verificationMethod: string;
|
|
@@ -54,6 +63,13 @@ export interface SignOwnerEnvelopeArgs {
|
|
|
54
63
|
* matching public key.
|
|
55
64
|
*/
|
|
56
65
|
readonly verificationMethod: string;
|
|
66
|
+
/**
|
|
67
|
+
* Full signed mandate, attached to the envelope for a delegate-signed
|
|
68
|
+
* call (§11.6). When set, `verificationMethod` MUST be the delegate's
|
|
69
|
+
* bare Ed25519 multibase (matching `mandate.grantee.pubkey`) and
|
|
70
|
+
* `signer` MUST be the delegate's key. Omit for owner-path envelopes.
|
|
71
|
+
*/
|
|
72
|
+
readonly mandate?: unknown;
|
|
57
73
|
/** Envelope lifetime in seconds. Default 60. Server caps at 300. */
|
|
58
74
|
readonly ttlSeconds?: number;
|
|
59
75
|
/** Clock override for deterministic tests. Defaults to `new Date()`. */
|
|
@@ -47,6 +47,12 @@ export async function signOwnerEnvelope(args) {
|
|
|
47
47
|
exp,
|
|
48
48
|
nonce,
|
|
49
49
|
params_hash: paramsHash,
|
|
50
|
+
// Attach the mandate (delegate path) so the signature commits to the
|
|
51
|
+
// delegation context. JCS sorts keys, so placement here is irrelevant
|
|
52
|
+
// to the canonical bytes — what matters is that the server (which
|
|
53
|
+
// canonicalizes the full envelope incl. mandate + proof/proofValue="")
|
|
54
|
+
// sees the exact same object.
|
|
55
|
+
...(args.mandate !== undefined ? { mandate: args.mandate } : {}),
|
|
50
56
|
proof: {
|
|
51
57
|
type: "Ed25519Signature2020",
|
|
52
58
|
verificationMethod: args.verificationMethod,
|
package/dist/src/mandates.d.ts
CHANGED
|
@@ -8,7 +8,28 @@ import type { AithosSdkEndpoints } from "./endpoints.js";
|
|
|
8
8
|
* directly in `scopes` is rejected at runtime; the compiler can't enforce
|
|
9
9
|
* it (callers who up-cast to string[] would slip through), so the runtime
|
|
10
10
|
* check is the real gate. */
|
|
11
|
-
export type Scope = "ethos.read.public" | "ethos.read.circle" | "ethos.read.self" | "ethos.write.public" | "ethos.write.circle" | "ethos.write.self";
|
|
11
|
+
export type Scope = "ethos.read.public" | "ethos.read.circle" | "ethos.read.self" | "ethos.write.public" | "ethos.write.circle" | "ethos.write.self" | DataScope;
|
|
12
|
+
/** Action a data mandate may authorize on a collection. `write` implies
|
|
13
|
+
* `read`; `admin` implies `write`. Mirrors the data sub-protocol grammar
|
|
14
|
+
* `data.<collection>.<action>` (Aithos-protocol `spec/data/04-mandates.md`
|
|
15
|
+
* §4.2) and the server-side check `requireScope` in data-backend. */
|
|
16
|
+
export type DataAction = "read" | "write" | "admin";
|
|
17
|
+
/**
|
|
18
|
+
* A data-access scope: `data.<collection>.<action>`, or the cross-collection
|
|
19
|
+
* wildcard `data.*.<action>`. Examples: `data.contacts.read`,
|
|
20
|
+
* `data.depots.write`, `data.*.read`.
|
|
21
|
+
*
|
|
22
|
+
* Note on `actor_sphere`: data mandates are minted under `actor_sphere:
|
|
23
|
+
* "self"` (the owner's highest-authority sphere). The sphere is *not* the
|
|
24
|
+
* access axis for data — the collection is. `actor_sphere` is informative
|
|
25
|
+
* here; the cryptographic binding is the grantee's key + the CMK wrap, per
|
|
26
|
+
* spec §4.4. A dedicated `#data` sphere key (independent rotation) MAY be
|
|
27
|
+
* introduced later without changing this scope grammar.
|
|
28
|
+
*
|
|
29
|
+
* Collection names MUST NOT contain `.` (the server splits the scope on
|
|
30
|
+
* `.` and reads the first three segments).
|
|
31
|
+
*/
|
|
32
|
+
export type DataScope = `data.${string}.${DataAction}`;
|
|
12
33
|
/**
|
|
13
34
|
* The opt-in scope that authorizes a delegate to spend the subject's
|
|
14
35
|
* compute credits via the Aithos compute proxy. Mirror of
|
package/dist/src/mandates.js
CHANGED
|
@@ -64,6 +64,17 @@ export class MandatesNamespace {
|
|
|
64
64
|
`not by adding "${COMPUTE_INVOKE_SCOPE}" to scopes[]. The namespace forces ` +
|
|
65
65
|
`an explicit budget and is what a consent UI reviews.`);
|
|
66
66
|
}
|
|
67
|
+
// Fail fast on malformed data scopes so the misuse surfaces at the SDK
|
|
68
|
+
// boundary, not as an opaque server rejection at first delegate call.
|
|
69
|
+
// Accepts `data.<collection>.<action>` and the wildcard `data.*.<action>`.
|
|
70
|
+
for (const s of input.scopes) {
|
|
71
|
+
if (s.startsWith("data.") &&
|
|
72
|
+
!isWellFormedDataScope(s)) {
|
|
73
|
+
throw new AithosSDKError("mandates_invalid_scopes", `Malformed data scope "${s}". Expected data.<collection>.<action> ` +
|
|
74
|
+
`(action = read | write | admin), e.g. "data.contacts.read" or ` +
|
|
75
|
+
`"data.*.read". Collection names must not contain ".".`);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
67
78
|
// Validate + project the compute namespace if present, then derive
|
|
68
79
|
// the final scopes/constraints to send to the protocol layer.
|
|
69
80
|
const computeProjection = projectCompute(input.compute);
|
|
@@ -207,11 +218,41 @@ export class MandatesNamespace {
|
|
|
207
218
|
/* Helpers */
|
|
208
219
|
/* -------------------------------------------------------------------------- */
|
|
209
220
|
function defaultSphereFromScopes(scopes) {
|
|
210
|
-
|
|
221
|
+
// Data scopes are sphere-NEUTRAL: they're permitted under self & circle (and
|
|
222
|
+
// public once the allowlist includes them), and the data access axis is the
|
|
223
|
+
// collection, not the sphere (the sphere is informative — see {@link DataScope}).
|
|
224
|
+
// So the actor_sphere of a combined Ethos+data mandate is decided by the
|
|
225
|
+
// ETHOS scopes alone; data scopes neither raise nor lower it. This makes
|
|
226
|
+
// `ethos.read.public + data.X.read` default to `public`, `ethos.read.circle
|
|
227
|
+
// + data.X.read` to `circle`, etc. A caller can always override via
|
|
228
|
+
// `actorSphere`.
|
|
229
|
+
const ethos = scopes.filter((s) => !s.startsWith("data."));
|
|
230
|
+
// Ethos write scopes pin the sphere EXACTLY (a write mandate must be signed
|
|
231
|
+
// by the sphere it writes to — `validateScopesAgainstSphere`).
|
|
232
|
+
if (ethos.some((s) => s === "ethos.write.public"))
|
|
233
|
+
return "public";
|
|
234
|
+
if (ethos.some((s) => s === "ethos.write.circle"))
|
|
235
|
+
return "circle";
|
|
236
|
+
if (ethos.some((s) => s === "ethos.write.self"))
|
|
211
237
|
return "self";
|
|
212
|
-
|
|
238
|
+
// Ethos read scopes: narrowest sphere that permits them.
|
|
239
|
+
if (ethos.some((s) => s.endsWith(".self")))
|
|
240
|
+
return "self";
|
|
241
|
+
if (ethos.some((s) => s.endsWith(".circle")))
|
|
213
242
|
return "circle";
|
|
214
|
-
|
|
243
|
+
// Remaining Ethos scopes (ethos.read.public / .all / gamma.read) → public.
|
|
244
|
+
if (ethos.length > 0)
|
|
245
|
+
return "public";
|
|
246
|
+
// Data-only mandate: sign under `self`. self is the owner's highest-trust
|
|
247
|
+
// sphere, always permits the scope at mint time (no dependency on the
|
|
248
|
+
// public-sphere allowlist), and keeps the data grant off the most-exposed
|
|
249
|
+
// #public key. (Override with `actorSphere` for a public/circle label.)
|
|
250
|
+
return "self";
|
|
251
|
+
}
|
|
252
|
+
/** `true` iff `s` is a well-formed data scope `data.<collection>.<action>`
|
|
253
|
+
* with no filter suffix and a non-empty, dot-free collection name. */
|
|
254
|
+
function isWellFormedDataScope(s) {
|
|
255
|
+
return /^data\.[^.]+\.(read|write|admin)$/.test(s);
|
|
215
256
|
}
|
|
216
257
|
/**
|
|
217
258
|
* Validate the SDK-side `compute` namespace and project it onto the
|
|
@@ -25,4 +25,5 @@
|
|
|
25
25
|
export { AssetsClientProvider, useAssetsClient, type AssetsClientProviderProps, } from "./context.js";
|
|
26
26
|
export { useAithosAsset, type UseAithosAssetState, type UseAithosAssetOptions, } from "./use-aithos-asset.js";
|
|
27
27
|
export { AithosAsset, type AithosAssetProps, type AithosImageProps, type AithosVideoProps, type AithosAudioProps, type AithosDownloadProps, } from "./AithosAsset.js";
|
|
28
|
+
export { useAithosTranscribePendingJobs, type UseAithosTranscribePendingJobs, } from "./use-transcribe-pending.js";
|
|
28
29
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/src/react/index.js
CHANGED
|
@@ -27,4 +27,5 @@
|
|
|
27
27
|
export { AssetsClientProvider, useAssetsClient, } from "./context.js";
|
|
28
28
|
export { useAithosAsset, } from "./use-aithos-asset.js";
|
|
29
29
|
export { AithosAsset, } from "./AithosAsset.js";
|
|
30
|
+
export { useAithosTranscribePendingJobs, } from "./use-transcribe-pending.js";
|
|
30
31
|
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { ComputeNamespace, InvokeTranscribeResult, TranscribeProgressState } from "../compute.js";
|
|
2
|
+
import type { LocalPendingEntry } from "../transcribe-resilience.js";
|
|
3
|
+
export interface UseAithosTranscribePendingJobs {
|
|
4
|
+
/** Locally-tracked in-flight jobs (survives reloads via localStorage). */
|
|
5
|
+
readonly pending: readonly LocalPendingEntry[];
|
|
6
|
+
/** Resume polling a job by id; resolves with the final transcript. */
|
|
7
|
+
readonly resume: (jobId: string, opts?: {
|
|
8
|
+
readonly mandateId?: string;
|
|
9
|
+
readonly onProgress?: (state: TranscribeProgressState) => void;
|
|
10
|
+
readonly signal?: AbortSignal;
|
|
11
|
+
readonly pollIntervalMs?: number;
|
|
12
|
+
}) => Promise<InvokeTranscribeResult>;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Subscribe a React component to the SDK's local pending-transcription
|
|
16
|
+
* registry. Re-renders whenever a job is added, advances, or clears.
|
|
17
|
+
*
|
|
18
|
+
* @param compute the SDK compute namespace (`sdk.compute`).
|
|
19
|
+
*/
|
|
20
|
+
export declare function useAithosTranscribePendingJobs(compute: ComputeNamespace): UseAithosTranscribePendingJobs;
|
|
21
|
+
//# sourceMappingURL=use-transcribe-pending.d.ts.map
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
2
|
+
// Copyright 2026 Mathieu Colla
|
|
3
|
+
/**
|
|
4
|
+
* `useAithosTranscribePendingJobs(sdk.compute)` — a thin React adapter over
|
|
5
|
+
* the framework-agnostic local pending-jobs tracker exposed by the compute
|
|
6
|
+
* namespace. The tracker itself (subscribe/getSnapshot) is plain vanilla JS
|
|
7
|
+
* and works with any framework; this hook just wires it into React's
|
|
8
|
+
* `useSyncExternalStore`. Vue/Svelte/vanilla users can call
|
|
9
|
+
* `sdk.compute.subscribeLocalPendingTranscribes` directly.
|
|
10
|
+
*
|
|
11
|
+
* function PendingBanner({ sdk }) {
|
|
12
|
+
* const { pending, resume } = useAithosTranscribePendingJobs(sdk.compute);
|
|
13
|
+
* if (pending.length === 0) return null;
|
|
14
|
+
* return (
|
|
15
|
+
* <div>
|
|
16
|
+
* {pending.map((p) => (
|
|
17
|
+
* <button key={p.jobId} onClick={() => resume(p.jobId)}>
|
|
18
|
+
* Resume {p.jobId} ({p.status})
|
|
19
|
+
* </button>
|
|
20
|
+
* ))}
|
|
21
|
+
* </div>
|
|
22
|
+
* );
|
|
23
|
+
* }
|
|
24
|
+
*/
|
|
25
|
+
import { useCallback, useEffect, useState } from "react";
|
|
26
|
+
/**
|
|
27
|
+
* Subscribe a React component to the SDK's local pending-transcription
|
|
28
|
+
* registry. Re-renders whenever a job is added, advances, or clears.
|
|
29
|
+
*
|
|
30
|
+
* @param compute the SDK compute namespace (`sdk.compute`).
|
|
31
|
+
*/
|
|
32
|
+
export function useAithosTranscribePendingJobs(compute) {
|
|
33
|
+
const [pending, setPending] = useState(() => compute.getLocalPendingTranscribesSnapshot());
|
|
34
|
+
useEffect(() => {
|
|
35
|
+
// Sync immediately (the snapshot may have changed before mount) then
|
|
36
|
+
// subscribe. getSnapshot returns a stable reference between mutations,
|
|
37
|
+
// so identical states bail out of a re-render.
|
|
38
|
+
setPending(compute.getLocalPendingTranscribesSnapshot());
|
|
39
|
+
const unsubscribe = compute.subscribeLocalPendingTranscribes(() => {
|
|
40
|
+
setPending(compute.getLocalPendingTranscribesSnapshot());
|
|
41
|
+
});
|
|
42
|
+
return unsubscribe;
|
|
43
|
+
}, [compute]);
|
|
44
|
+
const resume = useCallback((jobId, opts) => compute.resumeTranscribe(jobId, opts), [compute]);
|
|
45
|
+
return { pending, resume };
|
|
46
|
+
}
|
|
47
|
+
//# sourceMappingURL=use-transcribe-pending.js.map
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
export type LocalPendingStatus = "uploading" | "running" | "completed" | "failed";
|
|
2
|
+
export interface LocalPendingEntry {
|
|
3
|
+
readonly jobId: string;
|
|
4
|
+
readonly status: LocalPendingStatus;
|
|
5
|
+
readonly createdAt: number;
|
|
6
|
+
readonly updatedAt: number;
|
|
7
|
+
readonly meta?: Record<string, unknown>;
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* Framework-agnostic observable registry of in-flight transcription jobs.
|
|
11
|
+
* Persisted to localStorage when available (so it survives reloads), with
|
|
12
|
+
* an in-memory fallback otherwise. Subscribe with `subscribe(listener)`;
|
|
13
|
+
* read with `getSnapshot()` (stable reference between mutations, so it
|
|
14
|
+
* plugs directly into React's `useSyncExternalStore`).
|
|
15
|
+
*/
|
|
16
|
+
export declare class LocalPendingTranscribeTracker {
|
|
17
|
+
#private;
|
|
18
|
+
constructor();
|
|
19
|
+
/** Current entries. Stable reference until the next mutation. */
|
|
20
|
+
getSnapshot(): readonly LocalPendingEntry[];
|
|
21
|
+
list(): readonly LocalPendingEntry[];
|
|
22
|
+
/** Subscribe to changes. Returns an unsubscribe function. */
|
|
23
|
+
subscribe(listener: () => void): () => void;
|
|
24
|
+
upsert(jobId: string, status: LocalPendingStatus, meta?: Record<string, unknown>): void;
|
|
25
|
+
remove(jobId: string): void;
|
|
26
|
+
clear(): void;
|
|
27
|
+
}
|
|
28
|
+
export interface TranscribeDraftMeta {
|
|
29
|
+
readonly title?: string;
|
|
30
|
+
readonly tag?: string;
|
|
31
|
+
readonly contentType?: string;
|
|
32
|
+
}
|
|
33
|
+
export interface TranscribeDraftRecord {
|
|
34
|
+
readonly draftId: string;
|
|
35
|
+
readonly blob: Blob;
|
|
36
|
+
readonly metadata: TranscribeDraftMeta;
|
|
37
|
+
readonly createdAt: number;
|
|
38
|
+
}
|
|
39
|
+
export declare class TranscribeDraftUnavailableError extends Error {
|
|
40
|
+
constructor();
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* IndexedDB-backed queue of recorded audio Blobs. Save a recording the
|
|
44
|
+
* instant it finishes (before any network), then `upload` it when the
|
|
45
|
+
* user confirms — so a flaky network or a closed tab never loses audio.
|
|
46
|
+
* Browser-only: methods reject with {@link TranscribeDraftUnavailableError}
|
|
47
|
+
* when IndexedDB is absent.
|
|
48
|
+
*/
|
|
49
|
+
export declare class TranscribeDraftStore {
|
|
50
|
+
save(blob: Blob, meta?: TranscribeDraftMeta): Promise<{
|
|
51
|
+
readonly draftId: string;
|
|
52
|
+
}>;
|
|
53
|
+
list(): Promise<readonly TranscribeDraftRecord[]>;
|
|
54
|
+
get(draftId: string): Promise<TranscribeDraftRecord | null>;
|
|
55
|
+
delete(draftId: string): Promise<void>;
|
|
56
|
+
}
|
|
57
|
+
//# sourceMappingURL=transcribe-resilience.d.ts.map
|