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

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.
@@ -42,6 +42,8 @@ __export(share_root_entry_exports, {
42
42
  ownerRootCacaoCid: () => ownerRootCacaoCid,
43
43
  perShareNetworkIdFor: () => perShareNetworkIdFor,
44
44
  planShareControlBatches: () => planShareControlBatches,
45
+ publishRootBearerLink: () => publishRootBearerLink,
46
+ revokeRootBearerLink: () => revokeRootBearerLink,
45
47
  revokeShareGrant: () => revokeShareGrant,
46
48
  runShareControlBatch: () => runShareControlBatch,
47
49
  signShareKeyDelegation: () => signShareKeyDelegation
@@ -5356,6 +5358,20 @@ var v3TargetSchema = external_exports.object({
5356
5358
  nodeAudience: external_exports.string().min(1),
5357
5359
  spaceId: external_exports.string().refine(isCanonicalPathSegment, { message: "expected a canonical space id" })
5358
5360
  }).strict();
5361
+ var ARTIFACT_ID_PATTERN = /^[a-z0-9][a-z0-9_-]{2,62}$/;
5362
+ var artifactPinSchema = external_exports.object({
5363
+ v: external_exports.literal(1),
5364
+ artifactId: external_exports.string().regex(ARTIFACT_ID_PATTERN),
5365
+ version: external_exports.number().int().min(1).max(Number.MAX_SAFE_INTEGER),
5366
+ /** SHA-256 (lowercase hex) of the exact `manifest.json` bytes. The manifest itself stays an owner-side record. */
5367
+ manifestSha256: external_exports.string().regex(/^[0-9a-f]{64}$/),
5368
+ /** An addressed share reads one exact file, so exactly one entry: the stored bytes (ciphertext) it will fetch. */
5369
+ files: external_exports.array(external_exports.object({
5370
+ path: external_exports.string().min(1).max(255),
5371
+ sha256: external_exports.string().regex(/^[0-9a-f]{64}$/),
5372
+ bytes: external_exports.number().int().nonnegative()
5373
+ }).strict()).length(1)
5374
+ }).strict();
5359
5375
  var unsignedShareEnvelopeV3BaseSchema = external_exports.object({
5360
5376
  version: external_exports.literal(3),
5361
5377
  shareId: external_exports.string().min(1),
@@ -5375,7 +5391,13 @@ var unsignedShareEnvelopeV3BaseSchema = external_exports.object({
5375
5391
  expiry: external_exports.string().datetime({ offset: true }),
5376
5392
  display: displaySchema,
5377
5393
  encrypted: external_exports.literal(true),
5378
- metadata: contentMetadataSchema
5394
+ metadata: contentMetadataSchema,
5395
+ /**
5396
+ * TC-1428: an owner-signed statement that this share reads one write-once artifact version. Additive: absent on
5397
+ * every other share, and covered by the envelope signature because the signature spans the whole unsigned object.
5398
+ * The shape is checked here; its binding to the content source is `verifyArtifactShare` in share-sdk.
5399
+ */
5400
+ artifactPin: artifactPinSchema.optional()
5379
5401
  }).strict();
5380
5402
  function validateV3Invariants(value, ctx) {
5381
5403
  const actions = [...value.actions];
@@ -6956,6 +6978,35 @@ var ShareRoot = class _ShareRoot {
6956
6978
  covers(expiresAt, now = this.#now()) {
6957
6979
  return this.#key !== void 0 && now < this.expiresAt.getTime() && expiresAt.getTime() <= this.expiresAt.getTime();
6958
6980
  }
6981
+ /**
6982
+ * Sign a 60-second `POST /revoke` invocation, by the share key, for one
6983
+ * delegation this root issued (a bearer link). Zero approvals. The Node
6984
+ * authorizes it because the share key is that delegation's delegator. The
6985
+ * root itself is never revoked this way: only the owner does that.
6986
+ */
6987
+ async signLinkRevocation(input) {
6988
+ const key = this.#activeKey();
6989
+ if (typeof input.delegationCid !== "string" || !/^b[a-z2-7]+$/.test(input.delegationCid)) throw new ShareRootError("invalid-request", "delegation CID is invalid");
6990
+ if (input.delegationCid === this.cid) throw new ShareRootError("invalid-request", "the share key does not revoke its own root; the owner does");
6991
+ try {
6992
+ ed25519PublicKeyFromDidKey(input.nodeDid);
6993
+ } catch {
6994
+ throw new ShareRootError("invalid-request", "node DID must be an Ed25519 did:key");
6995
+ }
6996
+ const now = seconds(this.#now());
6997
+ const revocation = await signCompactUcanAuthorization({
6998
+ issuerDid: key.did,
6999
+ audienceDid: input.nodeDid,
7000
+ attenuation: { [`urn:cid:${input.delegationCid}`]: { "tinycloud.delegation/revoke": [{}] } },
7001
+ facts: [{}],
7002
+ proofs: [],
7003
+ notBefore: now,
7004
+ expiresAt: now + 60,
7005
+ nonce: `urn:uuid:${crypto.randomUUID()}`,
7006
+ sign: (bytes2) => key.sign(bytes2)
7007
+ });
7008
+ return revocation.authorization;
7009
+ }
6959
7010
  /**
6960
7011
  * Sign a bearer link: `kv/get` on one exact key under the prefix, to the
6961
7012
  * link's own key, with this root as its only proof. It never outlives the
@@ -7029,6 +7080,8 @@ async function ensureShareRoot(input) {
7029
7080
 
7030
7081
  // src/artifact.ts
7031
7082
  var import_sha2563 = require("@noble/hashes/sha256");
7083
+
7084
+ // src/artifact-core.ts
7032
7085
  function parseArtifactVersionPath(path) {
7033
7086
  const match = /^artifacts\/([a-z0-9][a-z0-9_-]{2,62})\/versions\/([1-9][0-9]{0,14})\/([^/]+)$/.exec(path);
7034
7087
  if (match === null) return void 0;
@@ -7736,6 +7789,69 @@ async function runShareControlBatch(input) {
7736
7789
  key.clear();
7737
7790
  }
7738
7791
  }
7792
+
7793
+ // src/share-root-link.ts
7794
+ var import_ed255196 = require("@noble/curves/ed25519");
7795
+
7796
+ // src/native.ts
7797
+ var NATIVE_SHARE_FRAGMENT_PARAMETER = "tc1";
7798
+ function canonicalViewerUrl(viewerOrigin) {
7799
+ const url = new URL(viewerOrigin);
7800
+ if (url.protocol !== "https:" || url.origin !== viewerOrigin || url.pathname !== "/" || url.search || url.hash) throw new TypeError("viewer origin must be a canonical HTTPS origin");
7801
+ url.pathname = "/viewer";
7802
+ return url;
7803
+ }
7804
+
7805
+ // src/share-root-link.ts
7806
+ async function publishRootBearerLink(input) {
7807
+ const fetchFn = input.fetch ?? ((url, init) => globalThis.fetch(url, init));
7808
+ const viewerUrl = canonicalViewerUrl(input.viewerOrigin);
7809
+ const secret = import_ed255196.ed25519.utils.randomPrivateKey();
7810
+ try {
7811
+ const publicKey = import_ed255196.ed25519.getPublicKey(secret);
7812
+ const linkDid = didKeyFromEd25519PublicKey(publicKey);
7813
+ const link = await input.root.signBearerLink({ linkDid, path: input.path, expiresAt: input.expiresAt });
7814
+ const response = await fetchFn(new URL("/delegate", input.root.nodeOrigin), { method: "POST", redirect: "error", headers: { Authorization: link.delegation.authorization } });
7815
+ if (!response.ok) throw new ShareRootError("node-refused", `the Node refused the link delegation: HTTP ${response.status} ${(await response.text()).slice(0, 120)}`, { rootCid: input.root.cid });
7816
+ const createdAt = /* @__PURE__ */ new Date();
7817
+ const shareData = {
7818
+ key: { kty: "OKP", crv: "Ed25519", x: toBase64Url(publicKey), d: toBase64Url(secret) },
7819
+ keyDid: `${linkDid}#${linkDid.slice("did:key:".length)}`,
7820
+ delegation: {
7821
+ cid: link.delegation.cid,
7822
+ delegateDID: linkDid,
7823
+ delegatorDID: input.root.keyDid,
7824
+ spaceId: input.root.spaceId,
7825
+ path: input.path,
7826
+ actions: ["tinycloud.kv/get"],
7827
+ expiry: link.expiresAt,
7828
+ isRevoked: false,
7829
+ allowSubDelegation: true,
7830
+ createdAt,
7831
+ parentCid: input.root.cid,
7832
+ authHeader: link.delegation.authorization
7833
+ },
7834
+ path: input.path,
7835
+ host: input.root.nodeOrigin,
7836
+ spaceId: input.root.spaceId,
7837
+ version: 1
7838
+ };
7839
+ const token = `tc1:${toBase64Url(new TextEncoder().encode(JSON.stringify(shareData)))}`;
7840
+ viewerUrl.hash = `${NATIVE_SHARE_FRAGMENT_PARAMETER}=${encodeURIComponent(token)}`;
7841
+ return { url: viewerUrl.toString(), delegationCid: link.delegation.cid, rootCid: input.root.cid, spaceId: input.root.spaceId, path: input.path, expiresAt: link.expiresAt };
7842
+ } finally {
7843
+ secret.fill(0);
7844
+ }
7845
+ }
7846
+ async function revokeRootBearerLink(input) {
7847
+ const fetchFn = input.fetch ?? ((url, init) => globalThis.fetch(url, init));
7848
+ const nodeDid = await nodeDidOf(input.root.nodeOrigin, fetchFn);
7849
+ const authorization = await input.root.signLinkRevocation({ delegationCid: input.delegationCid, nodeDid });
7850
+ const response = await fetchFn(new URL("/revoke", input.root.nodeOrigin), { method: "POST", redirect: "error", headers: { Authorization: authorization } });
7851
+ if (!response.ok) throw new ShareRootError("node-refused", `the Node refused the link revocation: HTTP ${response.status} ${(await response.text()).slice(0, 120)}`, { rootCid: input.root.cid });
7852
+ if (!await isRevokeReceiptFor(response, input.delegationCid)) throw new ShareRootError("invalid-request", "link revocation receipt does not confirm this delegation");
7853
+ return { revoked: true, cid: input.delegationCid };
7854
+ }
7739
7855
  // Annotate the CommonJS export names for ESM import in node:
7740
7856
  0 && (module.exports = {
7741
7857
  AGENT_SHARE_ROOT_MAX_LIFETIME_MS,
@@ -7760,6 +7876,8 @@ async function runShareControlBatch(input) {
7760
7876
  ownerRootCacaoCid,
7761
7877
  perShareNetworkIdFor,
7762
7878
  planShareControlBatches,
7879
+ publishRootBearerLink,
7880
+ revokeRootBearerLink,
7763
7881
  revokeShareGrant,
7764
7882
  runShareControlBatch,
7765
7883
  signShareKeyDelegation