@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.js
CHANGED
|
@@ -6058,7 +6058,7 @@ async function runCas(key, policy, attempt) {
|
|
|
6058
6058
|
}
|
|
6059
6059
|
|
|
6060
6060
|
// src/share-index/delivery.ts
|
|
6061
|
-
import { parseLink as parseLink2 } from "@tinycloud/share-envelope/links";
|
|
6061
|
+
import { buildFullLink, checkLinkRecord, linkSignatureV1Schema, openLinkRecord, parseLink as parseLink2, validateLinkHeader } from "@tinycloud/share-envelope/links";
|
|
6062
6062
|
var STORED_DELIVERY_MATERIAL_KEYS = ["shareCid", "sealedEnvelope", "envelopeKey"];
|
|
6063
6063
|
var DeliveryMaterialError = class extends Error {
|
|
6064
6064
|
constructor(message) {
|
|
@@ -6066,6 +6066,16 @@ var DeliveryMaterialError = class extends Error {
|
|
|
6066
6066
|
this.name = "DeliveryMaterialError";
|
|
6067
6067
|
}
|
|
6068
6068
|
};
|
|
6069
|
+
function isObject(value) {
|
|
6070
|
+
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
6071
|
+
}
|
|
6072
|
+
function checkStoredFullShareLink(value) {
|
|
6073
|
+
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");
|
|
6074
|
+
const header = validateLinkHeader(value.header, { mode: "signed" });
|
|
6075
|
+
const signature = linkSignatureV1Schema.parse(value.signature);
|
|
6076
|
+
if (header.type !== "share") throw new DeliveryMaterialError("full link is not a share");
|
|
6077
|
+
return { origin: value.origin, header, signature };
|
|
6078
|
+
}
|
|
6069
6079
|
function sealedParts(material) {
|
|
6070
6080
|
let sealed;
|
|
6071
6081
|
let key;
|
|
@@ -6082,18 +6092,24 @@ async function openDeliveryEnvelope(material) {
|
|
|
6082
6092
|
const { sealed, key } = sealedParts(material);
|
|
6083
6093
|
if (await computeCid(sealed) !== material.shareCid) throw new DeliveryMaterialError("sealed envelope does not match shareCid");
|
|
6084
6094
|
let text;
|
|
6085
|
-
try {
|
|
6086
|
-
text = new TextDecoder("utf-8", { fatal: true }).decode(await open(sealed, key));
|
|
6087
|
-
} catch {
|
|
6088
|
-
throw new DeliveryMaterialError("sealed envelope does not open under its key");
|
|
6089
|
-
}
|
|
6090
6095
|
let value;
|
|
6091
6096
|
try {
|
|
6092
|
-
|
|
6093
|
-
|
|
6094
|
-
|
|
6097
|
+
if (material.fullLink === void 0) {
|
|
6098
|
+
text = new TextDecoder("utf-8", { fatal: true }).decode(await open(sealed, key));
|
|
6099
|
+
value = JSON.parse(text);
|
|
6100
|
+
} else {
|
|
6101
|
+
const metadata = checkStoredFullShareLink(material.fullLink);
|
|
6102
|
+
if (metadata.header.bodyCid !== material.shareCid) throw new DeliveryMaterialError("full link names another sealed body");
|
|
6103
|
+
const opened = await openLinkRecord({ header: metadata.header, signature: metadata.signature, body: material.sealedEnvelope }, key, { now: Date.parse(metadata.header.issuedAt) });
|
|
6104
|
+
text = new TextDecoder("utf-8", { fatal: true }).decode(opened.plaintext);
|
|
6105
|
+
value = opened.value;
|
|
6106
|
+
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");
|
|
6107
|
+
}
|
|
6108
|
+
} catch (error) {
|
|
6109
|
+
if (error instanceof DeliveryMaterialError) throw error;
|
|
6110
|
+
throw new DeliveryMaterialError(material.fullLink === void 0 ? "sealed envelope does not open under its key" : "original full link is invalid");
|
|
6095
6111
|
}
|
|
6096
|
-
if (
|
|
6112
|
+
if (!isObject(value) || canonicalize(value) !== text) throw new DeliveryMaterialError("sealed envelope is not canonical JSON");
|
|
6097
6113
|
return value;
|
|
6098
6114
|
}
|
|
6099
6115
|
function assertLinkBoundToMaterial(link, material) {
|
|
@@ -6112,21 +6128,58 @@ async function compactShareRecord(record2) {
|
|
|
6112
6128
|
const material = record2.deliveryMaterial;
|
|
6113
6129
|
if (material === void 0) return record2;
|
|
6114
6130
|
if (typeof material.shareCid !== "string" || typeof material.sealedEnvelope !== "string" || typeof material.envelopeKey !== "string") throw new DeliveryMaterialError("delivery material is incomplete");
|
|
6115
|
-
|
|
6116
|
-
const opened = await openDeliveryEnvelope(stored);
|
|
6117
|
-
if (material.envelope !== void 0 && canonicalize(opened) !== canonicalize(material.envelope)) throw new DeliveryMaterialError("envelope is not the sealed envelope");
|
|
6131
|
+
let fullLink = "fullLink" in material ? checkStoredFullShareLink(material.fullLink) : void 0;
|
|
6118
6132
|
const { link, deliveryMaterial: _material, ...rest } = record2;
|
|
6119
|
-
|
|
6133
|
+
let keptLink;
|
|
6134
|
+
if (link !== void 0) {
|
|
6135
|
+
if (assertLinkBoundToMaterial(link, material) === "short") keptLink = link;
|
|
6136
|
+
else {
|
|
6137
|
+
const parsed = parseLink2(link, { trustedOrigins: [new URL(link).origin] });
|
|
6138
|
+
if (parsed.form !== "full" || !isObject(parsed.record)) throw new DeliveryMaterialError("full link record is invalid");
|
|
6139
|
+
const header = validateLinkHeader(parsed.record.header, { mode: "signed" });
|
|
6140
|
+
const checked = await checkLinkRecord(parsed.record, Date.parse(header.issuedAt));
|
|
6141
|
+
if (checked.signature === void 0) throw new DeliveryMaterialError("full link signature is missing");
|
|
6142
|
+
const original = checkStoredFullShareLink({ origin: parsed.origin, header: checked.header, signature: checked.signature });
|
|
6143
|
+
if (fullLink !== void 0 && canonicalize(fullLink) !== canonicalize(original)) throw new DeliveryMaterialError("full link metadata differs from the original link");
|
|
6144
|
+
fullLink = original;
|
|
6145
|
+
}
|
|
6146
|
+
}
|
|
6147
|
+
const stored = { shareCid: material.shareCid, sealedEnvelope: material.sealedEnvelope, envelopeKey: material.envelopeKey, ...fullLink === void 0 ? {} : { fullLink } };
|
|
6148
|
+
const opened = await openDeliveryEnvelope(stored);
|
|
6149
|
+
if ("envelope" in material && material.envelope !== void 0 && canonicalize(opened) !== canonicalize(material.envelope)) throw new DeliveryMaterialError("envelope is not the sealed envelope");
|
|
6150
|
+
if (fullLink !== void 0 && Date.parse(fullLink.header.expiresAt) !== Date.parse(record2.expiresAt)) throw new DeliveryMaterialError("full link expiry differs from the record");
|
|
6120
6151
|
return { ...rest, ...keptLink === void 0 ? {} : { link: keptLink }, deliveryMaterial: stored };
|
|
6121
6152
|
}
|
|
6122
6153
|
function equalBytes(left, right) {
|
|
6123
6154
|
return left.length === right.length && left.every((byte, index) => byte === right[index]);
|
|
6124
6155
|
}
|
|
6125
|
-
|
|
6156
|
+
function recordLink(record2, shareOrigins) {
|
|
6126
6157
|
const material = record2.deliveryMaterial;
|
|
6127
|
-
if (material === void 0) return record2;
|
|
6158
|
+
if (material?.fullLink === void 0) return record2.link;
|
|
6159
|
+
const metadata = checkStoredFullShareLink(material.fullLink);
|
|
6160
|
+
if (!shareOrigins.includes(metadata.origin)) throw new DeliveryMaterialError("full link origin is not trusted");
|
|
6161
|
+
if (Date.parse(metadata.header.expiresAt) !== Date.parse(record2.expiresAt)) throw new DeliveryMaterialError("full link expiry differs from the record");
|
|
6162
|
+
const full = buildFullLink({ origin: metadata.origin, key: sealedParts(material).key, record: { header: metadata.header, signature: metadata.signature, body: material.sealedEnvelope } });
|
|
6163
|
+
if (record2.link !== void 0) {
|
|
6164
|
+
parseLink2(record2.link, { trustedOrigins: shareOrigins });
|
|
6165
|
+
assertLinkBoundToMaterial(record2.link, material);
|
|
6166
|
+
}
|
|
6167
|
+
return record2.link ?? full;
|
|
6168
|
+
}
|
|
6169
|
+
async function restoreShareRecordLink(record2, shareOrigins) {
|
|
6170
|
+
if (record2.deliveryMaterial?.fullLink !== void 0) await openDeliveryEnvelope(record2.deliveryMaterial);
|
|
6171
|
+
return recordLink(record2, shareOrigins);
|
|
6172
|
+
}
|
|
6173
|
+
async function expandShareRecord(record2, options = {}) {
|
|
6174
|
+
const material = record2.deliveryMaterial;
|
|
6175
|
+
if (material === void 0) {
|
|
6176
|
+
const { deliveryMaterial: _material, ...withoutMaterial } = record2;
|
|
6177
|
+
return withoutMaterial;
|
|
6178
|
+
}
|
|
6128
6179
|
const envelope = await openDeliveryEnvelope(material);
|
|
6129
|
-
|
|
6180
|
+
const origins = options.shareOrigins ?? (material.fullLink === void 0 ? [] : [checkStoredFullShareLink(material.fullLink).origin]);
|
|
6181
|
+
const link = recordLink(record2, origins);
|
|
6182
|
+
return { ...record2, ...link === void 0 ? {} : { link }, deliveryMaterial: { ...material, envelope } };
|
|
6130
6183
|
}
|
|
6131
6184
|
|
|
6132
6185
|
// src/share-index/record-store.ts
|
|
@@ -6298,6 +6351,14 @@ var AgentShareRecordStore = class {
|
|
|
6298
6351
|
}
|
|
6299
6352
|
};
|
|
6300
6353
|
|
|
6354
|
+
// src/artifact-core.ts
|
|
6355
|
+
var ARTIFACT_ROOT = "artifacts";
|
|
6356
|
+
function parseArtifactVersionPath(path) {
|
|
6357
|
+
const match = /^artifacts\/([a-z0-9][a-z0-9_-]{2,62})\/versions\/([1-9][0-9]{0,14})\/([^/]+)$/.exec(path);
|
|
6358
|
+
if (match === null) return void 0;
|
|
6359
|
+
return { artifactId: match[1], version: Number(match[2]), file: match[3] };
|
|
6360
|
+
}
|
|
6361
|
+
|
|
6301
6362
|
// src/share-index/schema.ts
|
|
6302
6363
|
var INDEX_SCHEMA_VERSION = 1;
|
|
6303
6364
|
function emptyIndexDocument(input) {
|
|
@@ -6314,7 +6375,7 @@ function emptyIndexDocument(input) {
|
|
|
6314
6375
|
rows: {}
|
|
6315
6376
|
};
|
|
6316
6377
|
}
|
|
6317
|
-
function
|
|
6378
|
+
function isObject2(value) {
|
|
6318
6379
|
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
6319
6380
|
}
|
|
6320
6381
|
function isIsoTime(value) {
|
|
@@ -6323,25 +6384,25 @@ function isIsoTime(value) {
|
|
|
6323
6384
|
return Number.isFinite(parsed) && new Date(parsed).toISOString() === `${match[1]}.${(match[2] ?? "").padEnd(3, "0").slice(0, 3)}Z`;
|
|
6324
6385
|
}
|
|
6325
6386
|
function isRow(value) {
|
|
6326
|
-
if (!
|
|
6327
|
-
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 ||
|
|
6387
|
+
if (!isObject2(value)) return false;
|
|
6388
|
+
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";
|
|
6328
6389
|
}
|
|
6329
6390
|
function isAgentEntry(value) {
|
|
6330
|
-
return
|
|
6391
|
+
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";
|
|
6331
6392
|
}
|
|
6332
6393
|
function parseIndexDocument(value) {
|
|
6333
|
-
if (!
|
|
6394
|
+
if (!isObject2(value) || !Number.isSafeInteger(value.v)) return { status: "invalid" };
|
|
6334
6395
|
if (value.v > INDEX_SCHEMA_VERSION) return { status: "newer", v: value.v };
|
|
6335
6396
|
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" };
|
|
6336
|
-
if (!Array.isArray(value.archives) || !value.archives.every((item) =>
|
|
6337
|
-
if (!Array.isArray(value.tombstones) || !value.tombstones.every((item) =>
|
|
6338
|
-
if (!
|
|
6339
|
-
if (!
|
|
6340
|
-
if (!
|
|
6397
|
+
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" };
|
|
6398
|
+
if (!Array.isArray(value.tombstones) || !value.tombstones.every((item) => isObject2(item) && typeof item.key === "string" && typeof item.at === "string")) return { status: "invalid" };
|
|
6399
|
+
if (!isObject2(value.reconcile) || value.reconcile.cursor !== void 0 && typeof value.reconcile.cursor !== "string") return { status: "invalid" };
|
|
6400
|
+
if (!isObject2(value.agents) || !Object.values(value.agents).every(isAgentEntry)) return { status: "invalid" };
|
|
6401
|
+
if (!isObject2(value.rows) || !Object.values(value.rows).every(isRow)) return { status: "invalid" };
|
|
6341
6402
|
return { status: "ok", doc: value };
|
|
6342
6403
|
}
|
|
6343
6404
|
function parseArchiveDocument(value) {
|
|
6344
|
-
if (!
|
|
6405
|
+
if (!isObject2(value) || value.v !== INDEX_SCHEMA_VERSION || !isObject2(value.rows) || !Object.values(value.rows).every(isRow)) return void 0;
|
|
6345
6406
|
return value;
|
|
6346
6407
|
}
|
|
6347
6408
|
var UNSAFE_TEXT = /[\u0000-\u001f\u007f-\u009f\u061c\u200b-\u200f\u2028-\u202e\u2060-\u206f\ufeff\ufff9-\ufffb]/g;
|
|
@@ -6403,7 +6464,7 @@ var FIELDS_BY_RECORD_V = {
|
|
|
6403
6464
|
};
|
|
6404
6465
|
function isShareResourcePath(path) {
|
|
6405
6466
|
if (typeof path !== "string" || path.length === 0 || path.length > 1024 || /[\\%\u0000-\u001f]/.test(path)) return false;
|
|
6406
|
-
if (!SHARE_RESOURCE_PREFIXES.some((prefix) => path.startsWith(prefix) && path.length > prefix.length)) return false;
|
|
6467
|
+
if (!SHARE_RESOURCE_PREFIXES.some((prefix) => path.startsWith(prefix) && path.length > prefix.length) && parseArtifactVersionPath(path) === void 0) return false;
|
|
6407
6468
|
return path.split("/").every((segment, index, all) => (segment !== "" || index === all.length - 1) && segment !== "." && segment !== "..");
|
|
6408
6469
|
}
|
|
6409
6470
|
function isSpaceOwnedBy(spaceId, ownerDid) {
|
|
@@ -6438,7 +6499,7 @@ function checkAgentRecordSchema(value, context) {
|
|
|
6438
6499
|
throw new ShareRecordSchemaError("schema");
|
|
6439
6500
|
};
|
|
6440
6501
|
const fields = FIELDS_BY_RECORD_V[context.recordV];
|
|
6441
|
-
if (fields === void 0 || !
|
|
6502
|
+
if (fields === void 0 || !isObject2(value)) return fail2();
|
|
6442
6503
|
const keys = Object.keys(value);
|
|
6443
6504
|
if (!fields.required.every((key) => keys.includes(key)) || !keys.every((key) => fields.required.includes(key) || fields.optional.includes(key))) return fail2();
|
|
6444
6505
|
const record2 = value;
|
|
@@ -6447,13 +6508,15 @@ function checkAgentRecordSchema(value, context) {
|
|
|
6447
6508
|
if (record2.shareId !== context.shareId) return fail2();
|
|
6448
6509
|
const target = record2.target;
|
|
6449
6510
|
const targetKeys = Object.keys(target ?? {});
|
|
6450
|
-
if (!
|
|
6511
|
+
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();
|
|
6451
6512
|
if (!isSpaceOwnedBy(target.spaceId, context.accountOwnerDid)) return fail2();
|
|
6452
6513
|
const resource = record2.resource;
|
|
6453
|
-
if (!
|
|
6514
|
+
if (!isObject2(resource) || Object.keys(resource).length !== 2 || resource.kind !== "exact" && resource.kind !== "prefix" || !isShareResourcePath(resource.path)) return fail2();
|
|
6454
6515
|
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();
|
|
6516
|
+
const artifact = parseArtifactVersionPath(resource.path) !== void 0;
|
|
6517
|
+
if (artifact && (resource.kind !== "exact" || record2.actions.some((action) => action !== "tinycloud.kv/get" && action !== "tinycloud.kv/metadata"))) return fail2();
|
|
6455
6518
|
const matcher = record2.recipientMatcher;
|
|
6456
|
-
if (!
|
|
6519
|
+
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();
|
|
6457
6520
|
if (!isIsoTime(record2.registeredAt) || !isIsoTime(record2.expiresAt) || Date.parse(record2.registeredAt) >= Date.parse(record2.expiresAt)) return fail2();
|
|
6458
6521
|
for (const key of ["registrationCid", "policyCid", "ownerDelegationCid", "enforcementDelegationCid", "envelopeCid", "shareCid"]) {
|
|
6459
6522
|
if (record2[key] !== void 0 && (typeof record2[key] !== "string" || !/^[a-z0-9]{8,128}$/.test(record2[key]))) return fail2();
|
|
@@ -6468,13 +6531,27 @@ function checkAgentRecordSchema(value, context) {
|
|
|
6468
6531
|
if (record2.filename !== void 0 && (typeof record2.filename !== "string" || record2.filename.length > 512)) return fail2();
|
|
6469
6532
|
if (record2.deliveryMaterial !== void 0) {
|
|
6470
6533
|
const material = record2.deliveryMaterial;
|
|
6471
|
-
if (!
|
|
6534
|
+
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();
|
|
6535
|
+
if (Object.hasOwn(material, "fullLink")) {
|
|
6536
|
+
try {
|
|
6537
|
+
checkStoredFullShareLink(material.fullLink);
|
|
6538
|
+
} catch {
|
|
6539
|
+
return fail2();
|
|
6540
|
+
}
|
|
6541
|
+
}
|
|
6472
6542
|
if (typeof record2.link === "string" && carriesSealedRecord(record2.link)) return fail2();
|
|
6473
6543
|
}
|
|
6474
6544
|
if (record2.deliveredRecipients !== void 0 && (!Array.isArray(record2.deliveredRecipients) || !record2.deliveredRecipients.every((item) => typeof item === "string"))) return fail2();
|
|
6475
6545
|
if (record2.proofs !== void 0) {
|
|
6476
6546
|
const proofs = record2.proofs;
|
|
6477
|
-
if (!
|
|
6547
|
+
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();
|
|
6548
|
+
}
|
|
6549
|
+
if (artifact) {
|
|
6550
|
+
const proofs = record2.proofs;
|
|
6551
|
+
if (isObject2(proofs) && proofs.ownerPolicy !== void 0) return fail2();
|
|
6552
|
+
if (record2.deliveryMaterial !== void 0) {
|
|
6553
|
+
if (record2.targetKind === void 0 || record2.targetKind === "bearer" || isObject2(proofs) && proofs.link !== void 0) return fail2();
|
|
6554
|
+
} else if (record2.targetKind !== void 0 && record2.targetKind !== "bearer") return fail2();
|
|
6478
6555
|
}
|
|
6479
6556
|
return record2;
|
|
6480
6557
|
}
|
|
@@ -7921,6 +7998,76 @@ async function verifyAgentRecordChain(record2, context) {
|
|
|
7921
7998
|
}
|
|
7922
7999
|
}
|
|
7923
8000
|
|
|
8001
|
+
// src/artifact-pin.ts
|
|
8002
|
+
var ArtifactPinError = class extends Error {
|
|
8003
|
+
code;
|
|
8004
|
+
/** The code is the message: stable, short, and what callers and tests match on. */
|
|
8005
|
+
constructor(code3) {
|
|
8006
|
+
super(code3);
|
|
8007
|
+
this.name = "ArtifactPinError";
|
|
8008
|
+
this.code = code3;
|
|
8009
|
+
}
|
|
8010
|
+
};
|
|
8011
|
+
function artifactBindingView(envelope) {
|
|
8012
|
+
return {
|
|
8013
|
+
...envelope.artifactPin === void 0 ? {} : { artifactPin: envelope.artifactPin },
|
|
8014
|
+
target: envelope.target,
|
|
8015
|
+
contentSource: envelope.contentSource,
|
|
8016
|
+
resource: envelope.resource,
|
|
8017
|
+
actions: envelope.actions,
|
|
8018
|
+
policyKvActions: envelope.policy.capabilityCeiling.flatMap((capability) => capability.kind === "kv" ? capability.actions : [])
|
|
8019
|
+
};
|
|
8020
|
+
}
|
|
8021
|
+
function sourcePath(view) {
|
|
8022
|
+
const resource = view.contentSource.kvResource;
|
|
8023
|
+
const marker = "/kv/";
|
|
8024
|
+
const at = resource.indexOf(marker);
|
|
8025
|
+
if (at <= 0 || resource.slice(0, at) !== view.target.spaceId) {
|
|
8026
|
+
throw new ArtifactPinError("pin-source-mismatch");
|
|
8027
|
+
}
|
|
8028
|
+
return resource.slice(at + marker.length);
|
|
8029
|
+
}
|
|
8030
|
+
function assertArtifactBinding(view, expected = {}) {
|
|
8031
|
+
const pin = view.artifactPin;
|
|
8032
|
+
const path = sourcePath(view);
|
|
8033
|
+
const inArtifacts = path === ARTIFACT_ROOT || path.startsWith(`${ARTIFACT_ROOT}/`);
|
|
8034
|
+
if (!inArtifacts) {
|
|
8035
|
+
if (pin !== void 0) throw new ArtifactPinError("pin-source-mismatch");
|
|
8036
|
+
return void 0;
|
|
8037
|
+
}
|
|
8038
|
+
if (pin === void 0) throw new ArtifactPinError("missing-pin");
|
|
8039
|
+
const parsed = parseArtifactVersionPath(path);
|
|
8040
|
+
if (parsed === void 0) throw new ArtifactPinError("unsupported-artifact-path");
|
|
8041
|
+
const file = pin.files[0];
|
|
8042
|
+
const source = view.contentSource;
|
|
8043
|
+
if (
|
|
8044
|
+
// The stored ciphertext of one file: bounded like content (100 MiB) plus the envelope's own framing.
|
|
8045
|
+
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
|
|
8046
|
+
) {
|
|
8047
|
+
throw new ArtifactPinError("pin-source-mismatch");
|
|
8048
|
+
}
|
|
8049
|
+
if (view.actions.includes("edit") || view.policyKvActions.includes("tinycloud.kv/put")) {
|
|
8050
|
+
throw new ArtifactPinError("pinned-share-writable");
|
|
8051
|
+
}
|
|
8052
|
+
if (expected.artifactId !== void 0 && expected.artifactId !== pin.artifactId || expected.version !== void 0 && expected.version !== pin.version) {
|
|
8053
|
+
throw new ArtifactPinError("expectation-mismatch");
|
|
8054
|
+
}
|
|
8055
|
+
return pin;
|
|
8056
|
+
}
|
|
8057
|
+
function verifyArtifactShare(envelope, options) {
|
|
8058
|
+
if (envelope.signature.signerDid !== options.expectedOwnerDid) {
|
|
8059
|
+
throw new ArtifactPinError("wrong-signer");
|
|
8060
|
+
}
|
|
8061
|
+
let valid;
|
|
8062
|
+
try {
|
|
8063
|
+
valid = verifyEnvelopeV3SignatureOnly(envelope) && envelope.policy.ownerDid === options.expectedOwnerDid;
|
|
8064
|
+
} catch {
|
|
8065
|
+
valid = false;
|
|
8066
|
+
}
|
|
8067
|
+
if (!valid) throw new ArtifactPinError("bad-signature");
|
|
8068
|
+
return assertArtifactBinding(artifactBindingView(envelope), options.expected);
|
|
8069
|
+
}
|
|
8070
|
+
|
|
7924
8071
|
// src/share-index/sender-share-index.ts
|
|
7925
8072
|
var DAY_MS = 24 * 60 * 60 * 1e3;
|
|
7926
8073
|
var DEFAULT_SHARE_INDEX_LIMITS = {
|
|
@@ -7966,13 +8113,13 @@ function basename(key) {
|
|
|
7966
8113
|
function agentOf(row) {
|
|
7967
8114
|
return row.src.startsWith("agent:") ? row.src.slice("agent:".length) : void 0;
|
|
7968
8115
|
}
|
|
7969
|
-
function
|
|
8116
|
+
function isObject3(item) {
|
|
7970
8117
|
return item !== null && typeof item === "object" && !Array.isArray(item);
|
|
7971
8118
|
}
|
|
7972
8119
|
function isWebRecord(value) {
|
|
7973
|
-
if (!
|
|
8120
|
+
if (!isObject3(value)) return false;
|
|
7974
8121
|
const { shareId, target, resource, actions, recipientMatcher, registeredAt, expiresAt } = value;
|
|
7975
|
-
return typeof shareId === "string" &&
|
|
8122
|
+
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";
|
|
7976
8123
|
}
|
|
7977
8124
|
function unreadableRow(now) {
|
|
7978
8125
|
const at = now.toISOString();
|
|
@@ -8206,10 +8353,16 @@ var SenderShareIndex = class {
|
|
|
8206
8353
|
} catch (error) {
|
|
8207
8354
|
return { kind: "reject", reason: error instanceof ShareRecordSchemaError && error.reason === "provenance" ? "provenance" : "schema" };
|
|
8208
8355
|
}
|
|
8209
|
-
|
|
8210
|
-
|
|
8356
|
+
let link;
|
|
8357
|
+
try {
|
|
8358
|
+
link = await restoreShareRecordLink(record2, this.options.shareOrigins);
|
|
8359
|
+
} catch {
|
|
8360
|
+
return { kind: "reject", reason: "link" };
|
|
8361
|
+
}
|
|
8362
|
+
if (link !== void 0 && !validateShareLink(link, this.options.shareOrigins).ok) return { kind: "reject", reason: "link" };
|
|
8363
|
+
if (link !== void 0 && record2.deliveryMaterial !== void 0) {
|
|
8211
8364
|
try {
|
|
8212
|
-
assertLinkBoundToMaterial(
|
|
8365
|
+
assertLinkBoundToMaterial(link, record2.deliveryMaterial);
|
|
8213
8366
|
} catch {
|
|
8214
8367
|
return { kind: "reject", reason: "link" };
|
|
8215
8368
|
}
|
|
@@ -8222,6 +8375,14 @@ var SenderShareIndex = class {
|
|
|
8222
8375
|
...this.options.registrationKey === void 0 ? {} : { registrationKey: this.options.registrationKey }
|
|
8223
8376
|
});
|
|
8224
8377
|
if (!verdict.ok) return { kind: "reject", reason: "proof" };
|
|
8378
|
+
if (parseArtifactVersionPath(record2.resource.path) !== void 0 && record2.deliveryMaterial !== void 0) {
|
|
8379
|
+
try {
|
|
8380
|
+
const envelope = shareEnvelopeV3Schema.parse(await openDeliveryEnvelope(record2.deliveryMaterial));
|
|
8381
|
+
if (verifyArtifactShare(envelope, { expectedOwnerDid: envelope.policy.ownerDid }) === void 0) return { kind: "reject", reason: "proof" };
|
|
8382
|
+
} catch {
|
|
8383
|
+
return { kind: "reject", reason: "proof" };
|
|
8384
|
+
}
|
|
8385
|
+
}
|
|
8225
8386
|
const window = input.source === "migrated" ? { ...verdict.issued.latest === void 0 ? {} : { latest: verdict.issued.latest } } : verdict.issued;
|
|
8226
8387
|
if (!registeredAtFits(record2.registeredAt, window, this.now())) return { kind: "reject", reason: "schema" };
|
|
8227
8388
|
const row = project(record2, { key: input.key, src: input.src, claims: verdict.claims, proof: { state: verdict.state, expired: verdict.expired } });
|
|
@@ -8234,7 +8395,7 @@ var SenderShareIndex = class {
|
|
|
8234
8395
|
peekRecordV(bytes) {
|
|
8235
8396
|
try {
|
|
8236
8397
|
const value = JSON.parse(new TextDecoder().decode(bytes));
|
|
8237
|
-
const recordV =
|
|
8398
|
+
const recordV = isObject3(value) && isObject3(value.hdr) ? value.hdr.recordV : void 0;
|
|
8238
8399
|
return typeof recordV === "number" && Number.isSafeInteger(recordV) ? recordV : SUPPORTED_AGENT_RECORD_V + 1;
|
|
8239
8400
|
} catch {
|
|
8240
8401
|
return SUPPORTED_AGENT_RECORD_V + 1;
|
|
@@ -8320,7 +8481,7 @@ var SenderShareIndex = class {
|
|
|
8320
8481
|
if (classified.kind === "web") {
|
|
8321
8482
|
fetches++;
|
|
8322
8483
|
const read3 = await this.backend.readVault(key);
|
|
8323
|
-
if (read3.status === "ok" &&
|
|
8484
|
+
if (read3.status === "ok" && isObject3(read3.value) && read3.value.provenance === "migrated") {
|
|
8324
8485
|
const checked = await this.checkRecord(read3.value, { key, shareId: classified.shareId, src: "web", source: "migrated", agentDid: "", recordV: SUPPORTED_AGENT_RECORD_V }, archivedOnce);
|
|
8325
8486
|
findings.push({ kind: "web", key, row: checked.kind === "admit" ? checked.row : { ...unreadableRow(this.now()), pv: "migrated" } });
|
|
8326
8487
|
} else if (read3.status === "ok") {
|
|
@@ -8595,6 +8756,7 @@ export {
|
|
|
8595
8756
|
agentRecordPrefix,
|
|
8596
8757
|
assertLinkBoundToMaterial,
|
|
8597
8758
|
attenuationCovers,
|
|
8759
|
+
checkAgentRecordSchema,
|
|
8598
8760
|
checkRegistrationReceipt,
|
|
8599
8761
|
claimRank,
|
|
8600
8762
|
classifyRecordKey,
|
|
@@ -8615,6 +8777,8 @@ export {
|
|
|
8615
8777
|
parseSignedUcan,
|
|
8616
8778
|
parseStoredAgentRecord,
|
|
8617
8779
|
project,
|
|
8780
|
+
registeredAtFits,
|
|
8781
|
+
restoreShareRecordLink,
|
|
8618
8782
|
sealAgentRecord,
|
|
8619
8783
|
validateShareLink,
|
|
8620
8784
|
verifyAgentRecordChain,
|