@tinycloud/share-sdk 1.4.1 → 1.4.2-beta.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/share-index.cjs +209 -42
- package/dist/share-index.cjs.map +1 -1
- package/dist/share-index.d.cts +46 -21
- package/dist/share-index.d.ts +46 -21
- package/dist/share-index.js +207 -43
- package/dist/share-index.js.map +1 -1
- package/package.json +2 -2
package/dist/share-index.cjs
CHANGED
|
@@ -46,6 +46,7 @@ __export(share_index_exports, {
|
|
|
46
46
|
agentRecordPrefix: () => agentRecordPrefix,
|
|
47
47
|
assertLinkBoundToMaterial: () => assertLinkBoundToMaterial,
|
|
48
48
|
attenuationCovers: () => attenuationCovers,
|
|
49
|
+
checkAgentRecordSchema: () => checkAgentRecordSchema,
|
|
49
50
|
checkRegistrationReceipt: () => checkRegistrationReceipt,
|
|
50
51
|
claimRank: () => claimRank,
|
|
51
52
|
classifyRecordKey: () => classifyRecordKey,
|
|
@@ -66,6 +67,8 @@ __export(share_index_exports, {
|
|
|
66
67
|
parseSignedUcan: () => parseSignedUcan,
|
|
67
68
|
parseStoredAgentRecord: () => parseStoredAgentRecord,
|
|
68
69
|
project: () => project,
|
|
70
|
+
registeredAtFits: () => registeredAtFits,
|
|
71
|
+
restoreShareRecordLink: () => restoreShareRecordLink,
|
|
69
72
|
sealAgentRecord: () => sealAgentRecord,
|
|
70
73
|
validateShareLink: () => validateShareLink,
|
|
71
74
|
verifyAgentRecordChain: () => verifyAgentRecordChain,
|
|
@@ -6137,6 +6140,16 @@ var DeliveryMaterialError = class extends Error {
|
|
|
6137
6140
|
this.name = "DeliveryMaterialError";
|
|
6138
6141
|
}
|
|
6139
6142
|
};
|
|
6143
|
+
function isObject(value) {
|
|
6144
|
+
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
6145
|
+
}
|
|
6146
|
+
function checkStoredFullShareLink(value) {
|
|
6147
|
+
if (!isObject(value) || Object.keys(value).length !== 3 || !["origin", "header", "signature"].every((key) => Object.hasOwn(value, key)) || typeof value.origin !== "string" || !isCanonicalHttpsOrigin(value.origin)) throw new DeliveryMaterialError("full link metadata is invalid");
|
|
6148
|
+
const header = (0, import_links2.validateLinkHeader)(value.header, { mode: "signed" });
|
|
6149
|
+
const signature = import_links2.linkSignatureV1Schema.parse(value.signature);
|
|
6150
|
+
if (header.type !== "share") throw new DeliveryMaterialError("full link is not a share");
|
|
6151
|
+
return { origin: value.origin, header, signature };
|
|
6152
|
+
}
|
|
6140
6153
|
function sealedParts(material) {
|
|
6141
6154
|
let sealed;
|
|
6142
6155
|
let key;
|
|
@@ -6153,18 +6166,24 @@ async function openDeliveryEnvelope(material) {
|
|
|
6153
6166
|
const { sealed, key } = sealedParts(material);
|
|
6154
6167
|
if (await computeCid(sealed) !== material.shareCid) throw new DeliveryMaterialError("sealed envelope does not match shareCid");
|
|
6155
6168
|
let text;
|
|
6156
|
-
try {
|
|
6157
|
-
text = new TextDecoder("utf-8", { fatal: true }).decode(await open(sealed, key));
|
|
6158
|
-
} catch {
|
|
6159
|
-
throw new DeliveryMaterialError("sealed envelope does not open under its key");
|
|
6160
|
-
}
|
|
6161
6169
|
let value;
|
|
6162
6170
|
try {
|
|
6163
|
-
|
|
6164
|
-
|
|
6165
|
-
|
|
6171
|
+
if (material.fullLink === void 0) {
|
|
6172
|
+
text = new TextDecoder("utf-8", { fatal: true }).decode(await open(sealed, key));
|
|
6173
|
+
value = JSON.parse(text);
|
|
6174
|
+
} else {
|
|
6175
|
+
const metadata = checkStoredFullShareLink(material.fullLink);
|
|
6176
|
+
if (metadata.header.bodyCid !== material.shareCid) throw new DeliveryMaterialError("full link names another sealed body");
|
|
6177
|
+
const opened = await (0, import_links2.openLinkRecord)({ header: metadata.header, signature: metadata.signature, body: material.sealedEnvelope }, key, { now: Date.parse(metadata.header.issuedAt) });
|
|
6178
|
+
text = new TextDecoder("utf-8", { fatal: true }).decode(opened.plaintext);
|
|
6179
|
+
value = opened.value;
|
|
6180
|
+
if (typeof opened.value.expiry !== "string" || Date.parse(opened.value.expiry) !== Date.parse(metadata.header.expiresAt)) throw new DeliveryMaterialError("full link expiry differs from the sealed envelope");
|
|
6181
|
+
}
|
|
6182
|
+
} catch (error) {
|
|
6183
|
+
if (error instanceof DeliveryMaterialError) throw error;
|
|
6184
|
+
throw new DeliveryMaterialError(material.fullLink === void 0 ? "sealed envelope does not open under its key" : "original full link is invalid");
|
|
6166
6185
|
}
|
|
6167
|
-
if (
|
|
6186
|
+
if (!isObject(value) || canonicalize(value) !== text) throw new DeliveryMaterialError("sealed envelope is not canonical JSON");
|
|
6168
6187
|
return value;
|
|
6169
6188
|
}
|
|
6170
6189
|
function assertLinkBoundToMaterial(link, material) {
|
|
@@ -6183,21 +6202,58 @@ async function compactShareRecord(record2) {
|
|
|
6183
6202
|
const material = record2.deliveryMaterial;
|
|
6184
6203
|
if (material === void 0) return record2;
|
|
6185
6204
|
if (typeof material.shareCid !== "string" || typeof material.sealedEnvelope !== "string" || typeof material.envelopeKey !== "string") throw new DeliveryMaterialError("delivery material is incomplete");
|
|
6186
|
-
|
|
6187
|
-
const opened = await openDeliveryEnvelope(stored);
|
|
6188
|
-
if (material.envelope !== void 0 && canonicalize(opened) !== canonicalize(material.envelope)) throw new DeliveryMaterialError("envelope is not the sealed envelope");
|
|
6205
|
+
let fullLink = "fullLink" in material ? checkStoredFullShareLink(material.fullLink) : void 0;
|
|
6189
6206
|
const { link, deliveryMaterial: _material, ...rest } = record2;
|
|
6190
|
-
|
|
6207
|
+
let keptLink;
|
|
6208
|
+
if (link !== void 0) {
|
|
6209
|
+
if (assertLinkBoundToMaterial(link, material) === "short") keptLink = link;
|
|
6210
|
+
else {
|
|
6211
|
+
const parsed = (0, import_links2.parseLink)(link, { trustedOrigins: [new URL(link).origin] });
|
|
6212
|
+
if (parsed.form !== "full" || !isObject(parsed.record)) throw new DeliveryMaterialError("full link record is invalid");
|
|
6213
|
+
const header = (0, import_links2.validateLinkHeader)(parsed.record.header, { mode: "signed" });
|
|
6214
|
+
const checked = await (0, import_links2.checkLinkRecord)(parsed.record, Date.parse(header.issuedAt));
|
|
6215
|
+
if (checked.signature === void 0) throw new DeliveryMaterialError("full link signature is missing");
|
|
6216
|
+
const original = checkStoredFullShareLink({ origin: parsed.origin, header: checked.header, signature: checked.signature });
|
|
6217
|
+
if (fullLink !== void 0 && canonicalize(fullLink) !== canonicalize(original)) throw new DeliveryMaterialError("full link metadata differs from the original link");
|
|
6218
|
+
fullLink = original;
|
|
6219
|
+
}
|
|
6220
|
+
}
|
|
6221
|
+
const stored = { shareCid: material.shareCid, sealedEnvelope: material.sealedEnvelope, envelopeKey: material.envelopeKey, ...fullLink === void 0 ? {} : { fullLink } };
|
|
6222
|
+
const opened = await openDeliveryEnvelope(stored);
|
|
6223
|
+
if ("envelope" in material && material.envelope !== void 0 && canonicalize(opened) !== canonicalize(material.envelope)) throw new DeliveryMaterialError("envelope is not the sealed envelope");
|
|
6224
|
+
if (fullLink !== void 0 && Date.parse(fullLink.header.expiresAt) !== Date.parse(record2.expiresAt)) throw new DeliveryMaterialError("full link expiry differs from the record");
|
|
6191
6225
|
return { ...rest, ...keptLink === void 0 ? {} : { link: keptLink }, deliveryMaterial: stored };
|
|
6192
6226
|
}
|
|
6193
6227
|
function equalBytes(left, right) {
|
|
6194
6228
|
return left.length === right.length && left.every((byte, index) => byte === right[index]);
|
|
6195
6229
|
}
|
|
6196
|
-
|
|
6230
|
+
function recordLink(record2, shareOrigins) {
|
|
6197
6231
|
const material = record2.deliveryMaterial;
|
|
6198
|
-
if (material === void 0) return record2;
|
|
6232
|
+
if (material?.fullLink === void 0) return record2.link;
|
|
6233
|
+
const metadata = checkStoredFullShareLink(material.fullLink);
|
|
6234
|
+
if (!shareOrigins.includes(metadata.origin)) throw new DeliveryMaterialError("full link origin is not trusted");
|
|
6235
|
+
if (Date.parse(metadata.header.expiresAt) !== Date.parse(record2.expiresAt)) throw new DeliveryMaterialError("full link expiry differs from the record");
|
|
6236
|
+
const full = (0, import_links2.buildFullLink)({ origin: metadata.origin, key: sealedParts(material).key, record: { header: metadata.header, signature: metadata.signature, body: material.sealedEnvelope } });
|
|
6237
|
+
if (record2.link !== void 0) {
|
|
6238
|
+
(0, import_links2.parseLink)(record2.link, { trustedOrigins: shareOrigins });
|
|
6239
|
+
assertLinkBoundToMaterial(record2.link, material);
|
|
6240
|
+
}
|
|
6241
|
+
return record2.link ?? full;
|
|
6242
|
+
}
|
|
6243
|
+
async function restoreShareRecordLink(record2, shareOrigins) {
|
|
6244
|
+
if (record2.deliveryMaterial?.fullLink !== void 0) await openDeliveryEnvelope(record2.deliveryMaterial);
|
|
6245
|
+
return recordLink(record2, shareOrigins);
|
|
6246
|
+
}
|
|
6247
|
+
async function expandShareRecord(record2, options = {}) {
|
|
6248
|
+
const material = record2.deliveryMaterial;
|
|
6249
|
+
if (material === void 0) {
|
|
6250
|
+
const { deliveryMaterial: _material, ...withoutMaterial } = record2;
|
|
6251
|
+
return withoutMaterial;
|
|
6252
|
+
}
|
|
6199
6253
|
const envelope = await openDeliveryEnvelope(material);
|
|
6200
|
-
|
|
6254
|
+
const origins = options.shareOrigins ?? (material.fullLink === void 0 ? [] : [checkStoredFullShareLink(material.fullLink).origin]);
|
|
6255
|
+
const link = recordLink(record2, origins);
|
|
6256
|
+
return { ...record2, ...link === void 0 ? {} : { link }, deliveryMaterial: { ...material, envelope } };
|
|
6201
6257
|
}
|
|
6202
6258
|
|
|
6203
6259
|
// src/share-index/record-store.ts
|
|
@@ -6369,6 +6425,14 @@ var AgentShareRecordStore = class {
|
|
|
6369
6425
|
}
|
|
6370
6426
|
};
|
|
6371
6427
|
|
|
6428
|
+
// src/artifact-core.ts
|
|
6429
|
+
var ARTIFACT_ROOT = "artifacts";
|
|
6430
|
+
function parseArtifactVersionPath(path) {
|
|
6431
|
+
const match = /^artifacts\/([a-z0-9][a-z0-9_-]{2,62})\/versions\/([1-9][0-9]{0,14})\/([^/]+)$/.exec(path);
|
|
6432
|
+
if (match === null) return void 0;
|
|
6433
|
+
return { artifactId: match[1], version: Number(match[2]), file: match[3] };
|
|
6434
|
+
}
|
|
6435
|
+
|
|
6372
6436
|
// src/share-index/schema.ts
|
|
6373
6437
|
var INDEX_SCHEMA_VERSION = 1;
|
|
6374
6438
|
function emptyIndexDocument(input) {
|
|
@@ -6385,7 +6449,7 @@ function emptyIndexDocument(input) {
|
|
|
6385
6449
|
rows: {}
|
|
6386
6450
|
};
|
|
6387
6451
|
}
|
|
6388
|
-
function
|
|
6452
|
+
function isObject2(value) {
|
|
6389
6453
|
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
6390
6454
|
}
|
|
6391
6455
|
function isIsoTime(value) {
|
|
@@ -6394,25 +6458,25 @@ function isIsoTime(value) {
|
|
|
6394
6458
|
return Number.isFinite(parsed) && new Date(parsed).toISOString() === `${match[1]}.${(match[2] ?? "").padEnd(3, "0").slice(0, 3)}Z`;
|
|
6395
6459
|
}
|
|
6396
6460
|
function isRow(value) {
|
|
6397
|
-
if (!
|
|
6398
|
-
return typeof value.n === "string" && (value.k === "file" || value.k === "folder") && typeof value.ak === "string" && typeof value.al === "string" && Array.isArray(value.a) && value.a.every((item) => typeof item === "string") && typeof value.c === "string" && typeof value.x === "string" && (value.r === void 0 ||
|
|
6461
|
+
if (!isObject2(value)) return false;
|
|
6462
|
+
return typeof value.n === "string" && (value.k === "file" || value.k === "folder") && typeof value.ak === "string" && typeof value.al === "string" && Array.isArray(value.a) && value.a.every((item) => typeof item === "string") && typeof value.c === "string" && typeof value.x === "string" && (value.r === void 0 || isObject2(value.r) && typeof value.r.at === "string" && typeof value.r.confirmed === "boolean") && typeof value.rk === "string" && (value.cid === void 0 || typeof value.cid === "string") && (value.cc === void 0 || Array.isArray(value.cc) && value.cc.every((item) => typeof item === "string")) && typeof value.src === "string";
|
|
6399
6463
|
}
|
|
6400
6464
|
function isAgentEntry(value) {
|
|
6401
|
-
return
|
|
6465
|
+
return isObject2(value) && (value.did === null || typeof value.did === "string") && Number.isSafeInteger(value.rows) && isObject2(value.rejected) && Object.values(value.rejected).every((count) => Number.isSafeInteger(count)) && Array.isArray(value.rejectedKeys) && value.rejectedKeys.every((item) => typeof item === "string") && (value.newerKeys === void 0 || Array.isArray(value.newerKeys) && value.newerKeys.every((item) => typeof item === "string")) && (value.paused === false || isObject2(value.paused) && typeof value.paused.reason === "string" && typeof value.paused.at === "string") && typeof value.firstSeen === "string";
|
|
6402
6466
|
}
|
|
6403
6467
|
function parseIndexDocument(value) {
|
|
6404
|
-
if (!
|
|
6468
|
+
if (!isObject2(value) || !Number.isSafeInteger(value.v)) return { status: "invalid" };
|
|
6405
6469
|
if (value.v > INDEX_SCHEMA_VERSION) return { status: "newer", v: value.v };
|
|
6406
6470
|
if (value.v !== INDEX_SCHEMA_VERSION || typeof value.ownerDid !== "string" || typeof value.spaceId !== "string" || typeof value.updatedAt !== "string" || typeof value.updatedBy !== "string") return { status: "invalid" };
|
|
6407
|
-
if (!Array.isArray(value.archives) || !value.archives.every((item) =>
|
|
6408
|
-
if (!Array.isArray(value.tombstones) || !value.tombstones.every((item) =>
|
|
6409
|
-
if (!
|
|
6410
|
-
if (!
|
|
6411
|
-
if (!
|
|
6471
|
+
if (!Array.isArray(value.archives) || !value.archives.every((item) => isObject2(item) && typeof item.name === "string" && Array.isArray(item.ids) && item.ids.every((id) => typeof id === "string"))) return { status: "invalid" };
|
|
6472
|
+
if (!Array.isArray(value.tombstones) || !value.tombstones.every((item) => isObject2(item) && typeof item.key === "string" && typeof item.at === "string")) return { status: "invalid" };
|
|
6473
|
+
if (!isObject2(value.reconcile) || value.reconcile.cursor !== void 0 && typeof value.reconcile.cursor !== "string") return { status: "invalid" };
|
|
6474
|
+
if (!isObject2(value.agents) || !Object.values(value.agents).every(isAgentEntry)) return { status: "invalid" };
|
|
6475
|
+
if (!isObject2(value.rows) || !Object.values(value.rows).every(isRow)) return { status: "invalid" };
|
|
6412
6476
|
return { status: "ok", doc: value };
|
|
6413
6477
|
}
|
|
6414
6478
|
function parseArchiveDocument(value) {
|
|
6415
|
-
if (!
|
|
6479
|
+
if (!isObject2(value) || value.v !== INDEX_SCHEMA_VERSION || !isObject2(value.rows) || !Object.values(value.rows).every(isRow)) return void 0;
|
|
6416
6480
|
return value;
|
|
6417
6481
|
}
|
|
6418
6482
|
var UNSAFE_TEXT = /[\u0000-\u001f\u007f-\u009f\u061c\u200b-\u200f\u2028-\u202e\u2060-\u206f\ufeff\ufff9-\ufffb]/g;
|
|
@@ -6474,7 +6538,7 @@ var FIELDS_BY_RECORD_V = {
|
|
|
6474
6538
|
};
|
|
6475
6539
|
function isShareResourcePath(path) {
|
|
6476
6540
|
if (typeof path !== "string" || path.length === 0 || path.length > 1024 || /[\\%\u0000-\u001f]/.test(path)) return false;
|
|
6477
|
-
if (!SHARE_RESOURCE_PREFIXES.some((prefix) => path.startsWith(prefix) && path.length > prefix.length)) return false;
|
|
6541
|
+
if (!SHARE_RESOURCE_PREFIXES.some((prefix) => path.startsWith(prefix) && path.length > prefix.length) && parseArtifactVersionPath(path) === void 0) return false;
|
|
6478
6542
|
return path.split("/").every((segment, index, all) => (segment !== "" || index === all.length - 1) && segment !== "." && segment !== "..");
|
|
6479
6543
|
}
|
|
6480
6544
|
function isSpaceOwnedBy(spaceId, ownerDid) {
|
|
@@ -6509,7 +6573,7 @@ function checkAgentRecordSchema(value, context) {
|
|
|
6509
6573
|
throw new ShareRecordSchemaError("schema");
|
|
6510
6574
|
};
|
|
6511
6575
|
const fields = FIELDS_BY_RECORD_V[context.recordV];
|
|
6512
|
-
if (fields === void 0 || !
|
|
6576
|
+
if (fields === void 0 || !isObject2(value)) return fail2();
|
|
6513
6577
|
const keys = Object.keys(value);
|
|
6514
6578
|
if (!fields.required.every((key) => keys.includes(key)) || !keys.every((key) => fields.required.includes(key) || fields.optional.includes(key))) return fail2();
|
|
6515
6579
|
const record2 = value;
|
|
@@ -6518,13 +6582,15 @@ function checkAgentRecordSchema(value, context) {
|
|
|
6518
6582
|
if (record2.shareId !== context.shareId) return fail2();
|
|
6519
6583
|
const target = record2.target;
|
|
6520
6584
|
const targetKeys = Object.keys(target ?? {});
|
|
6521
|
-
if (!
|
|
6585
|
+
if (!isObject2(target) || !targetKeys.every((key) => ["origin", "nodeAudience", "spaceId", "kind"].includes(key)) || typeof target.origin !== "string" || typeof target.nodeAudience !== "string" || typeof target.spaceId !== "string" || target.kind !== void 0 && (typeof target.kind !== "string" || !["bearer", "recipientDid", "email", "emailDomain"].includes(target.kind))) return fail2();
|
|
6522
6586
|
if (!isSpaceOwnedBy(target.spaceId, context.accountOwnerDid)) return fail2();
|
|
6523
6587
|
const resource = record2.resource;
|
|
6524
|
-
if (!
|
|
6588
|
+
if (!isObject2(resource) || Object.keys(resource).length !== 2 || resource.kind !== "exact" && resource.kind !== "prefix" || !isShareResourcePath(resource.path)) return fail2();
|
|
6525
6589
|
if (!Array.isArray(record2.actions) || record2.actions.length === 0 || new Set(record2.actions).size !== record2.actions.length || !record2.actions.every((action) => typeof action === "string" && SHARE_ACTIONS.includes(action))) return fail2();
|
|
6590
|
+
const artifact = parseArtifactVersionPath(resource.path) !== void 0;
|
|
6591
|
+
if (artifact && (resource.kind !== "exact" || record2.actions.some((action) => action !== "tinycloud.kv/get" && action !== "tinycloud.kv/metadata"))) return fail2();
|
|
6526
6592
|
const matcher = record2.recipientMatcher;
|
|
6527
|
-
if (!
|
|
6593
|
+
if (!isObject2(matcher) || !(matcher.kind === "bearer" ? Object.keys(matcher).length === 1 : ["exactEmail", "emailDomain", "recipientDid"].includes(matcher.kind) && Object.keys(matcher).length === 2 && typeof matcher.value === "string")) return fail2();
|
|
6528
6594
|
if (!isIsoTime(record2.registeredAt) || !isIsoTime(record2.expiresAt) || Date.parse(record2.registeredAt) >= Date.parse(record2.expiresAt)) return fail2();
|
|
6529
6595
|
for (const key of ["registrationCid", "policyCid", "ownerDelegationCid", "enforcementDelegationCid", "envelopeCid", "shareCid"]) {
|
|
6530
6596
|
if (record2[key] !== void 0 && (typeof record2[key] !== "string" || !/^[a-z0-9]{8,128}$/.test(record2[key]))) return fail2();
|
|
@@ -6539,13 +6605,27 @@ function checkAgentRecordSchema(value, context) {
|
|
|
6539
6605
|
if (record2.filename !== void 0 && (typeof record2.filename !== "string" || record2.filename.length > 512)) return fail2();
|
|
6540
6606
|
if (record2.deliveryMaterial !== void 0) {
|
|
6541
6607
|
const material = record2.deliveryMaterial;
|
|
6542
|
-
if (!
|
|
6608
|
+
if (!isObject2(material) || !STORED_DELIVERY_MATERIAL_KEYS.every((key) => typeof material[key] === "string") || Object.keys(material).some((key) => !STORED_DELIVERY_MATERIAL_KEYS.includes(key) && key !== "fullLink")) return fail2();
|
|
6609
|
+
if (Object.hasOwn(material, "fullLink")) {
|
|
6610
|
+
try {
|
|
6611
|
+
checkStoredFullShareLink(material.fullLink);
|
|
6612
|
+
} catch {
|
|
6613
|
+
return fail2();
|
|
6614
|
+
}
|
|
6615
|
+
}
|
|
6543
6616
|
if (typeof record2.link === "string" && carriesSealedRecord(record2.link)) return fail2();
|
|
6544
6617
|
}
|
|
6545
6618
|
if (record2.deliveredRecipients !== void 0 && (!Array.isArray(record2.deliveredRecipients) || !record2.deliveredRecipients.every((item) => typeof item === "string"))) return fail2();
|
|
6546
6619
|
if (record2.proofs !== void 0) {
|
|
6547
6620
|
const proofs = record2.proofs;
|
|
6548
|
-
if (!
|
|
6621
|
+
if (!isObject2(proofs) || !Object.keys(proofs).every((key) => PROOF_STRING_FIELDS.includes(key) ? typeof proofs[key] === "string" : PROOF_OBJECT_FIELDS.includes(key) && isObject2(proofs[key]))) return fail2();
|
|
6622
|
+
}
|
|
6623
|
+
if (artifact) {
|
|
6624
|
+
const proofs = record2.proofs;
|
|
6625
|
+
if (isObject2(proofs) && proofs.ownerPolicy !== void 0) return fail2();
|
|
6626
|
+
if (record2.deliveryMaterial !== void 0) {
|
|
6627
|
+
if (record2.targetKind === void 0 || record2.targetKind === "bearer" || isObject2(proofs) && proofs.link !== void 0) return fail2();
|
|
6628
|
+
} else if (record2.targetKind !== void 0 && record2.targetKind !== "bearer") return fail2();
|
|
6549
6629
|
}
|
|
6550
6630
|
return record2;
|
|
6551
6631
|
}
|
|
@@ -7992,6 +8072,76 @@ async function verifyAgentRecordChain(record2, context) {
|
|
|
7992
8072
|
}
|
|
7993
8073
|
}
|
|
7994
8074
|
|
|
8075
|
+
// src/artifact-pin.ts
|
|
8076
|
+
var ArtifactPinError = class extends Error {
|
|
8077
|
+
code;
|
|
8078
|
+
/** The code is the message: stable, short, and what callers and tests match on. */
|
|
8079
|
+
constructor(code3) {
|
|
8080
|
+
super(code3);
|
|
8081
|
+
this.name = "ArtifactPinError";
|
|
8082
|
+
this.code = code3;
|
|
8083
|
+
}
|
|
8084
|
+
};
|
|
8085
|
+
function artifactBindingView(envelope) {
|
|
8086
|
+
return {
|
|
8087
|
+
...envelope.artifactPin === void 0 ? {} : { artifactPin: envelope.artifactPin },
|
|
8088
|
+
target: envelope.target,
|
|
8089
|
+
contentSource: envelope.contentSource,
|
|
8090
|
+
resource: envelope.resource,
|
|
8091
|
+
actions: envelope.actions,
|
|
8092
|
+
policyKvActions: envelope.policy.capabilityCeiling.flatMap((capability) => capability.kind === "kv" ? capability.actions : [])
|
|
8093
|
+
};
|
|
8094
|
+
}
|
|
8095
|
+
function sourcePath(view) {
|
|
8096
|
+
const resource = view.contentSource.kvResource;
|
|
8097
|
+
const marker = "/kv/";
|
|
8098
|
+
const at = resource.indexOf(marker);
|
|
8099
|
+
if (at <= 0 || resource.slice(0, at) !== view.target.spaceId) {
|
|
8100
|
+
throw new ArtifactPinError("pin-source-mismatch");
|
|
8101
|
+
}
|
|
8102
|
+
return resource.slice(at + marker.length);
|
|
8103
|
+
}
|
|
8104
|
+
function assertArtifactBinding(view, expected = {}) {
|
|
8105
|
+
const pin = view.artifactPin;
|
|
8106
|
+
const path = sourcePath(view);
|
|
8107
|
+
const inArtifacts = path === ARTIFACT_ROOT || path.startsWith(`${ARTIFACT_ROOT}/`);
|
|
8108
|
+
if (!inArtifacts) {
|
|
8109
|
+
if (pin !== void 0) throw new ArtifactPinError("pin-source-mismatch");
|
|
8110
|
+
return void 0;
|
|
8111
|
+
}
|
|
8112
|
+
if (pin === void 0) throw new ArtifactPinError("missing-pin");
|
|
8113
|
+
const parsed = parseArtifactVersionPath(path);
|
|
8114
|
+
if (parsed === void 0) throw new ArtifactPinError("unsupported-artifact-path");
|
|
8115
|
+
const file = pin.files[0];
|
|
8116
|
+
const source = view.contentSource;
|
|
8117
|
+
if (
|
|
8118
|
+
// The stored ciphertext of one file: bounded like content (100 MiB) plus the envelope's own framing.
|
|
8119
|
+
file.bytes > 128 * 1024 * 1024 || pin.artifactId !== parsed.artifactId || pin.version !== parsed.version || file.path !== parsed.file || view.resource.kind !== "exact" || view.resource.path !== path || source.selector !== "exact" || source.mode !== "immutable" || source.initialCiphertextDigestHex !== file.sha256
|
|
8120
|
+
) {
|
|
8121
|
+
throw new ArtifactPinError("pin-source-mismatch");
|
|
8122
|
+
}
|
|
8123
|
+
if (view.actions.includes("edit") || view.policyKvActions.includes("tinycloud.kv/put")) {
|
|
8124
|
+
throw new ArtifactPinError("pinned-share-writable");
|
|
8125
|
+
}
|
|
8126
|
+
if (expected.artifactId !== void 0 && expected.artifactId !== pin.artifactId || expected.version !== void 0 && expected.version !== pin.version) {
|
|
8127
|
+
throw new ArtifactPinError("expectation-mismatch");
|
|
8128
|
+
}
|
|
8129
|
+
return pin;
|
|
8130
|
+
}
|
|
8131
|
+
function verifyArtifactShare(envelope, options) {
|
|
8132
|
+
if (envelope.signature.signerDid !== options.expectedOwnerDid) {
|
|
8133
|
+
throw new ArtifactPinError("wrong-signer");
|
|
8134
|
+
}
|
|
8135
|
+
let valid;
|
|
8136
|
+
try {
|
|
8137
|
+
valid = verifyEnvelopeV3SignatureOnly(envelope) && envelope.policy.ownerDid === options.expectedOwnerDid;
|
|
8138
|
+
} catch {
|
|
8139
|
+
valid = false;
|
|
8140
|
+
}
|
|
8141
|
+
if (!valid) throw new ArtifactPinError("bad-signature");
|
|
8142
|
+
return assertArtifactBinding(artifactBindingView(envelope), options.expected);
|
|
8143
|
+
}
|
|
8144
|
+
|
|
7995
8145
|
// src/share-index/sender-share-index.ts
|
|
7996
8146
|
var DAY_MS = 24 * 60 * 60 * 1e3;
|
|
7997
8147
|
var DEFAULT_SHARE_INDEX_LIMITS = {
|
|
@@ -8037,13 +8187,13 @@ function basename(key) {
|
|
|
8037
8187
|
function agentOf(row) {
|
|
8038
8188
|
return row.src.startsWith("agent:") ? row.src.slice("agent:".length) : void 0;
|
|
8039
8189
|
}
|
|
8040
|
-
function
|
|
8190
|
+
function isObject3(item) {
|
|
8041
8191
|
return item !== null && typeof item === "object" && !Array.isArray(item);
|
|
8042
8192
|
}
|
|
8043
8193
|
function isWebRecord(value) {
|
|
8044
|
-
if (!
|
|
8194
|
+
if (!isObject3(value)) return false;
|
|
8045
8195
|
const { shareId, target, resource, actions, recipientMatcher, registeredAt, expiresAt } = value;
|
|
8046
|
-
return typeof shareId === "string" &&
|
|
8196
|
+
return typeof shareId === "string" && isObject3(target) && typeof target.spaceId === "string" && isObject3(resource) && typeof resource.path === "string" && Array.isArray(actions) && actions.every((item) => typeof item === "string") && isObject3(recipientMatcher) && typeof recipientMatcher.kind === "string" && typeof registeredAt === "string" && typeof expiresAt === "string";
|
|
8047
8197
|
}
|
|
8048
8198
|
function unreadableRow(now) {
|
|
8049
8199
|
const at = now.toISOString();
|
|
@@ -8277,10 +8427,16 @@ var SenderShareIndex = class {
|
|
|
8277
8427
|
} catch (error) {
|
|
8278
8428
|
return { kind: "reject", reason: error instanceof ShareRecordSchemaError && error.reason === "provenance" ? "provenance" : "schema" };
|
|
8279
8429
|
}
|
|
8280
|
-
|
|
8281
|
-
|
|
8430
|
+
let link;
|
|
8431
|
+
try {
|
|
8432
|
+
link = await restoreShareRecordLink(record2, this.options.shareOrigins);
|
|
8433
|
+
} catch {
|
|
8434
|
+
return { kind: "reject", reason: "link" };
|
|
8435
|
+
}
|
|
8436
|
+
if (link !== void 0 && !validateShareLink(link, this.options.shareOrigins).ok) return { kind: "reject", reason: "link" };
|
|
8437
|
+
if (link !== void 0 && record2.deliveryMaterial !== void 0) {
|
|
8282
8438
|
try {
|
|
8283
|
-
assertLinkBoundToMaterial(
|
|
8439
|
+
assertLinkBoundToMaterial(link, record2.deliveryMaterial);
|
|
8284
8440
|
} catch {
|
|
8285
8441
|
return { kind: "reject", reason: "link" };
|
|
8286
8442
|
}
|
|
@@ -8293,6 +8449,14 @@ var SenderShareIndex = class {
|
|
|
8293
8449
|
...this.options.registrationKey === void 0 ? {} : { registrationKey: this.options.registrationKey }
|
|
8294
8450
|
});
|
|
8295
8451
|
if (!verdict.ok) return { kind: "reject", reason: "proof" };
|
|
8452
|
+
if (parseArtifactVersionPath(record2.resource.path) !== void 0 && record2.deliveryMaterial !== void 0) {
|
|
8453
|
+
try {
|
|
8454
|
+
const envelope = shareEnvelopeV3Schema.parse(await openDeliveryEnvelope(record2.deliveryMaterial));
|
|
8455
|
+
if (verifyArtifactShare(envelope, { expectedOwnerDid: envelope.policy.ownerDid }) === void 0) return { kind: "reject", reason: "proof" };
|
|
8456
|
+
} catch {
|
|
8457
|
+
return { kind: "reject", reason: "proof" };
|
|
8458
|
+
}
|
|
8459
|
+
}
|
|
8296
8460
|
const window = input.source === "migrated" ? { ...verdict.issued.latest === void 0 ? {} : { latest: verdict.issued.latest } } : verdict.issued;
|
|
8297
8461
|
if (!registeredAtFits(record2.registeredAt, window, this.now())) return { kind: "reject", reason: "schema" };
|
|
8298
8462
|
const row = project(record2, { key: input.key, src: input.src, claims: verdict.claims, proof: { state: verdict.state, expired: verdict.expired } });
|
|
@@ -8305,7 +8469,7 @@ var SenderShareIndex = class {
|
|
|
8305
8469
|
peekRecordV(bytes) {
|
|
8306
8470
|
try {
|
|
8307
8471
|
const value = JSON.parse(new TextDecoder().decode(bytes));
|
|
8308
|
-
const recordV =
|
|
8472
|
+
const recordV = isObject3(value) && isObject3(value.hdr) ? value.hdr.recordV : void 0;
|
|
8309
8473
|
return typeof recordV === "number" && Number.isSafeInteger(recordV) ? recordV : SUPPORTED_AGENT_RECORD_V + 1;
|
|
8310
8474
|
} catch {
|
|
8311
8475
|
return SUPPORTED_AGENT_RECORD_V + 1;
|
|
@@ -8391,7 +8555,7 @@ var SenderShareIndex = class {
|
|
|
8391
8555
|
if (classified.kind === "web") {
|
|
8392
8556
|
fetches++;
|
|
8393
8557
|
const read3 = await this.backend.readVault(key);
|
|
8394
|
-
if (read3.status === "ok" &&
|
|
8558
|
+
if (read3.status === "ok" && isObject3(read3.value) && read3.value.provenance === "migrated") {
|
|
8395
8559
|
const checked = await this.checkRecord(read3.value, { key, shareId: classified.shareId, src: "web", source: "migrated", agentDid: "", recordV: SUPPORTED_AGENT_RECORD_V }, archivedOnce);
|
|
8396
8560
|
findings.push({ kind: "web", key, row: checked.kind === "admit" ? checked.row : { ...unreadableRow(this.now()), pv: "migrated" } });
|
|
8397
8561
|
} else if (read3.status === "ok") {
|
|
@@ -8667,6 +8831,7 @@ var SenderShareIndex = class {
|
|
|
8667
8831
|
agentRecordPrefix,
|
|
8668
8832
|
assertLinkBoundToMaterial,
|
|
8669
8833
|
attenuationCovers,
|
|
8834
|
+
checkAgentRecordSchema,
|
|
8670
8835
|
checkRegistrationReceipt,
|
|
8671
8836
|
claimRank,
|
|
8672
8837
|
classifyRecordKey,
|
|
@@ -8687,6 +8852,8 @@ var SenderShareIndex = class {
|
|
|
8687
8852
|
parseSignedUcan,
|
|
8688
8853
|
parseStoredAgentRecord,
|
|
8689
8854
|
project,
|
|
8855
|
+
registeredAtFits,
|
|
8856
|
+
restoreShareRecordLink,
|
|
8690
8857
|
sealAgentRecord,
|
|
8691
8858
|
validateShareLink,
|
|
8692
8859
|
verifyAgentRecordChain,
|