@tinycloud/cli 1.3.0-beta.19 → 1.3.0-beta.21

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/index.js CHANGED
@@ -12822,6 +12822,20 @@ var v3TargetSchema = external_exports.object({
12822
12822
  nodeAudience: external_exports.string().min(1),
12823
12823
  spaceId: external_exports.string().refine(isCanonicalPathSegment, { message: "expected a canonical space id" })
12824
12824
  }).strict();
12825
+ var ARTIFACT_ID_PATTERN = /^[a-z0-9][a-z0-9_-]{2,62}$/;
12826
+ var artifactPinSchema = external_exports.object({
12827
+ v: external_exports.literal(1),
12828
+ artifactId: external_exports.string().regex(ARTIFACT_ID_PATTERN),
12829
+ version: external_exports.number().int().min(1).max(Number.MAX_SAFE_INTEGER),
12830
+ /** SHA-256 (lowercase hex) of the exact `manifest.json` bytes. The manifest itself stays an owner-side record. */
12831
+ manifestSha256: external_exports.string().regex(/^[0-9a-f]{64}$/),
12832
+ /** An addressed share reads one exact file, so exactly one entry: the stored bytes (ciphertext) it will fetch. */
12833
+ files: external_exports.array(external_exports.object({
12834
+ path: external_exports.string().min(1).max(255),
12835
+ sha256: external_exports.string().regex(/^[0-9a-f]{64}$/),
12836
+ bytes: external_exports.number().int().nonnegative()
12837
+ }).strict()).length(1)
12838
+ }).strict();
12825
12839
  var unsignedShareEnvelopeV3BaseSchema = external_exports.object({
12826
12840
  version: external_exports.literal(3),
12827
12841
  shareId: external_exports.string().min(1),
@@ -12841,7 +12855,13 @@ var unsignedShareEnvelopeV3BaseSchema = external_exports.object({
12841
12855
  expiry: external_exports.string().datetime({ offset: true }),
12842
12856
  display: displaySchema,
12843
12857
  encrypted: external_exports.literal(true),
12844
- metadata: contentMetadataSchema
12858
+ metadata: contentMetadataSchema,
12859
+ /**
12860
+ * TC-1428: an owner-signed statement that this share reads one write-once artifact version. Additive: absent on
12861
+ * every other share, and covered by the envelope signature because the signature spans the whole unsigned object.
12862
+ * The shape is checked here; its binding to the content source is `verifyArtifactShare` in share-sdk.
12863
+ */
12864
+ artifactPin: artifactPinSchema.optional()
12845
12865
  }).strict();
12846
12866
  function validateV3Invariants(value, ctx) {
12847
12867
  const actions = [...value.actions];
@@ -12939,6 +12959,12 @@ function canonicalize(value) {
12939
12959
  return serialize(value);
12940
12960
  }
12941
12961
  var MAX_INLINE_BYTES = 256 * 1024;
12962
+ var ARTIFACT_ROOT = "artifacts";
12963
+ function parseArtifactVersionPath(path) {
12964
+ const match = /^artifacts\/([a-z0-9][a-z0-9_-]{2,62})\/versions\/([1-9][0-9]{0,14})\/([^/]+)$/.exec(path);
12965
+ if (match === null) return void 0;
12966
+ return { artifactId: match[1], version: Number(match[2]), file: match[3] };
12967
+ }
12942
12968
  var UNSAFE_FILENAME_CODE_POINT = /[\p{Cc}\p{Cf}\p{Cs}\u2028\u2029]/u;
12943
12969
  function hasUnsafeFilenameCodePoint(value) {
12944
12970
  return UNSAFE_FILENAME_CODE_POINT.test(value);
@@ -12950,7 +12976,9 @@ function canonicalShareFilename(value) {
12950
12976
  }
12951
12977
  return canonical;
12952
12978
  }
12953
- var ARTIFACT_ROOT = "artifacts";
12979
+ function sha256Hex(bytes) {
12980
+ return Array.from(sha2562(bytes), (byte) => byte.toString(16).padStart(2, "0")).join("");
12981
+ }
12954
12982
  var ARTIFACT_MANIFEST_NAME = "manifest.json";
12955
12983
  var ARTIFACT_MANIFEST_VERSION = 1;
12956
12984
  var ARTIFACT_MAX_PUBLISH_ATTEMPTS = 16;
@@ -12964,9 +12992,6 @@ var ArtifactError = class extends Error {
12964
12992
  this.code = code22;
12965
12993
  }
12966
12994
  };
12967
- function sha256Hex(bytes) {
12968
- return Array.from(sha2562(bytes), (byte) => byte.toString(16).padStart(2, "0")).join("");
12969
- }
12970
12995
  function newArtifactId(random = (length23) => crypto.getRandomValues(new Uint8Array(length23))) {
12971
12996
  return `art_${Array.from(random(8), (byte) => byte.toString(16).padStart(2, "0")).join("")}`;
12972
12997
  }
@@ -12989,11 +13014,6 @@ function artifactVersionResource(artifactId, version2) {
12989
13014
  function artifactVersionFilePath(artifactId, version2, filename) {
12990
13015
  return `${artifactVersionResource(artifactId, version2)}/${filename}`;
12991
13016
  }
12992
- function parseArtifactVersionPath(path) {
12993
- const match = /^artifacts\/([a-z0-9][a-z0-9_-]{2,62})\/versions\/([1-9][0-9]{0,14})\/([^/]+)$/.exec(path);
12994
- if (match === null) return void 0;
12995
- return { artifactId: match[1], version: Number(match[2]), file: match[3] };
12996
- }
12997
13017
  function manifestBytes(manifest) {
12998
13018
  return new TextEncoder().encode(canonicalize(manifest));
12999
13019
  }
@@ -13139,6 +13159,17 @@ async function publishArtifactVersion(store, input) {
13139
13159
  }
13140
13160
  throw new ArtifactError("conflict", "could not claim an artifact version number after repeated races; retry");
13141
13161
  }
13162
+ function buildArtifactPin(version2, filename) {
13163
+ const entry = version2.manifest.files.find((candidate) => candidate.path === filename);
13164
+ if (entry === void 0) throw new ArtifactError("invalid-argument", "artifact manifest does not list that file");
13165
+ return {
13166
+ v: 1,
13167
+ artifactId: version2.artifactId,
13168
+ version: version2.version,
13169
+ manifestSha256: version2.manifestSha256,
13170
+ files: [{ path: entry.path, sha256: entry.sha256, bytes: entry.bytes }]
13171
+ };
13172
+ }
13142
13173
 
13143
13174
  // ../share-sdk/dist/index.js
13144
13175
  init_sha2();
@@ -18442,6 +18473,20 @@ var v3TargetSchema2 = external_exports2.object({
18442
18473
  nodeAudience: external_exports2.string().min(1),
18443
18474
  spaceId: external_exports2.string().refine(isCanonicalPathSegment2, { message: "expected a canonical space id" })
18444
18475
  }).strict();
18476
+ var ARTIFACT_ID_PATTERN2 = /^[a-z0-9][a-z0-9_-]{2,62}$/;
18477
+ var artifactPinSchema2 = external_exports2.object({
18478
+ v: external_exports2.literal(1),
18479
+ artifactId: external_exports2.string().regex(ARTIFACT_ID_PATTERN2),
18480
+ version: external_exports2.number().int().min(1).max(Number.MAX_SAFE_INTEGER),
18481
+ /** SHA-256 (lowercase hex) of the exact `manifest.json` bytes. The manifest itself stays an owner-side record. */
18482
+ manifestSha256: external_exports2.string().regex(/^[0-9a-f]{64}$/),
18483
+ /** An addressed share reads one exact file, so exactly one entry: the stored bytes (ciphertext) it will fetch. */
18484
+ files: external_exports2.array(external_exports2.object({
18485
+ path: external_exports2.string().min(1).max(255),
18486
+ sha256: external_exports2.string().regex(/^[0-9a-f]{64}$/),
18487
+ bytes: external_exports2.number().int().nonnegative()
18488
+ }).strict()).length(1)
18489
+ }).strict();
18445
18490
  var unsignedShareEnvelopeV3BaseSchema2 = external_exports2.object({
18446
18491
  version: external_exports2.literal(3),
18447
18492
  shareId: external_exports2.string().min(1),
@@ -18461,7 +18506,13 @@ var unsignedShareEnvelopeV3BaseSchema2 = external_exports2.object({
18461
18506
  expiry: external_exports2.string().datetime({ offset: true }),
18462
18507
  display: displaySchema2,
18463
18508
  encrypted: external_exports2.literal(true),
18464
- metadata: contentMetadataSchema2
18509
+ metadata: contentMetadataSchema2,
18510
+ /**
18511
+ * TC-1428: an owner-signed statement that this share reads one write-once artifact version. Additive: absent on
18512
+ * every other share, and covered by the envelope signature because the signature spans the whole unsigned object.
18513
+ * The shape is checked here; its binding to the content source is `verifyArtifactShare` in share-sdk.
18514
+ */
18515
+ artifactPin: artifactPinSchema2.optional()
18465
18516
  }).strict();
18466
18517
  function validateV3Invariants2(value, ctx) {
18467
18518
  const actions = [...value.actions];
@@ -18843,6 +18894,80 @@ async function parseSealedInlineShareUrl(url, options = {}) {
18843
18894
  if (await computeCid(ciphertext) !== record.cid) throw new TypeError("sealed inline ciphertext does not match its CID");
18844
18895
  return { kind: "inline", ciphertextCid: record.cid, ciphertext, key32 };
18845
18896
  }
18897
+ var ARTIFACT_ROOT2 = "artifacts";
18898
+ function parseArtifactVersionPath2(path) {
18899
+ const match = /^artifacts\/([a-z0-9][a-z0-9_-]{2,62})\/versions\/([1-9][0-9]{0,14})\/([^/]+)$/.exec(path);
18900
+ if (match === null) return void 0;
18901
+ return { artifactId: match[1], version: Number(match[2]), file: match[3] };
18902
+ }
18903
+ var ArtifactPinError = class extends Error {
18904
+ code;
18905
+ /** The code is the message: stable, short, and what callers and tests match on. */
18906
+ constructor(code32) {
18907
+ super(code32);
18908
+ this.name = "ArtifactPinError";
18909
+ this.code = code32;
18910
+ }
18911
+ };
18912
+ function artifactBindingView(envelope) {
18913
+ return {
18914
+ ...envelope.artifactPin === void 0 ? {} : { artifactPin: envelope.artifactPin },
18915
+ target: envelope.target,
18916
+ contentSource: envelope.contentSource,
18917
+ resource: envelope.resource,
18918
+ actions: envelope.actions,
18919
+ policyKvActions: envelope.policy.capabilityCeiling.flatMap((capability) => capability.kind === "kv" ? capability.actions : [])
18920
+ };
18921
+ }
18922
+ function sourcePath(view) {
18923
+ const resource = view.contentSource.kvResource;
18924
+ const marker = "/kv/";
18925
+ const at = resource.indexOf(marker);
18926
+ if (at <= 0 || resource.slice(0, at) !== view.target.spaceId) {
18927
+ throw new ArtifactPinError("pin-source-mismatch");
18928
+ }
18929
+ return resource.slice(at + marker.length);
18930
+ }
18931
+ function assertArtifactBinding(view, expected = {}) {
18932
+ const pin = view.artifactPin;
18933
+ const path = sourcePath(view);
18934
+ const inArtifacts = path === ARTIFACT_ROOT2 || path.startsWith(`${ARTIFACT_ROOT2}/`);
18935
+ if (!inArtifacts) {
18936
+ if (pin !== void 0) throw new ArtifactPinError("pin-source-mismatch");
18937
+ return void 0;
18938
+ }
18939
+ if (pin === void 0) throw new ArtifactPinError("missing-pin");
18940
+ const parsed = parseArtifactVersionPath2(path);
18941
+ if (parsed === void 0) throw new ArtifactPinError("unsupported-artifact-path");
18942
+ const file = pin.files[0];
18943
+ const source = view.contentSource;
18944
+ if (
18945
+ // The stored ciphertext of one file: bounded like content (100 MiB) plus the envelope's own framing.
18946
+ 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
18947
+ ) {
18948
+ throw new ArtifactPinError("pin-source-mismatch");
18949
+ }
18950
+ if (view.actions.includes("edit") || view.policyKvActions.includes("tinycloud.kv/put")) {
18951
+ throw new ArtifactPinError("pinned-share-writable");
18952
+ }
18953
+ if (expected.artifactId !== void 0 && expected.artifactId !== pin.artifactId || expected.version !== void 0 && expected.version !== pin.version) {
18954
+ throw new ArtifactPinError("expectation-mismatch");
18955
+ }
18956
+ return pin;
18957
+ }
18958
+ function verifyArtifactShare(envelope, options) {
18959
+ if (envelope.signature.signerDid !== options.expectedOwnerDid) {
18960
+ throw new ArtifactPinError("wrong-signer");
18961
+ }
18962
+ let valid;
18963
+ try {
18964
+ valid = verifyEnvelopeV3SignatureOnly(envelope) && envelope.policy.ownerDid === options.expectedOwnerDid;
18965
+ } catch {
18966
+ valid = false;
18967
+ }
18968
+ if (!valid) throw new ArtifactPinError("bad-signature");
18969
+ return assertArtifactBinding(artifactBindingView(envelope), options.expected);
18970
+ }
18846
18971
  var SHARE_RESULT_VERSION = 1;
18847
18972
  var DEFAULT_MAX_SEALED_BLOB_BYTES = 100 * 1024 * 1024 + 29;
18848
18973
  var ShareReceiveError = class extends Error {
@@ -18873,7 +18998,8 @@ function metadataFor(envelope, origin) {
18873
18998
  ...envelope.display.senderName === void 0 ? {} : { senderName: envelope.display.senderName },
18874
18999
  ...envelope.display.filename === void 0 ? {} : { filename: envelope.display.filename },
18875
19000
  ...envelope.display.mode === void 0 ? {} : { mode: envelope.display.mode }
18876
- }
19001
+ },
19002
+ ...envelope.artifactPin === void 0 ? {} : { artifact: { id: envelope.artifactPin.artifactId, version: envelope.artifactPin.version } }
18877
19003
  };
18878
19004
  }
18879
19005
  async function resolvePolicyShare(link3, options) {
@@ -18904,6 +19030,12 @@ async function resolvePolicyShare(link3, options) {
18904
19030
  } catch {
18905
19031
  throw new ShareReceiveError("signature-invalid", "share signature is invalid");
18906
19032
  }
19033
+ try {
19034
+ verifyArtifactShare(envelope, { expectedOwnerDid: envelope.policy.ownerDid });
19035
+ } catch (error) {
19036
+ if (!(error instanceof ArtifactPinError)) throw error;
19037
+ throw new ShareReceiveError("envelope-invalid", `share artifact pin is invalid (${error.code})`);
19038
+ }
18907
19039
  options.signal?.throwIfAborted();
18908
19040
  const expiry = Date.parse(envelope.expiry);
18909
19041
  if (!Number.isFinite(expiry)) throw new ShareReceiveError("envelope-invalid", "share expiry is invalid");
@@ -19568,6 +19700,14 @@ function assertMailboxCommitment(commitment, target) {
19568
19700
  async function publishAddressedShare(input) {
19569
19701
  assertSafeInput(input);
19570
19702
  const { target } = prepareAddressedShare(input);
19703
+ assertArtifactBinding({
19704
+ ...input.artifactPin === void 0 ? {} : { artifactPin: input.artifactPin },
19705
+ target: { spaceId: input.spaceId },
19706
+ contentSource: input.contentSource,
19707
+ resource: input.resource,
19708
+ actions: input.actions,
19709
+ policyKvActions: input.policyActions
19710
+ });
19571
19711
  const options = { ...input, filename: canonicalShareFilename2(input.filename) };
19572
19712
  if (target.kind !== "recipientDid") assertMailboxCommitment(options.credentialRequirement, target);
19573
19713
  const expiry = rfc3339Seconds(options.expiresAt);
@@ -19634,7 +19774,8 @@ async function publishAddressedShare(input) {
19634
19774
  filename: options.filename,
19635
19775
  ...options.mediaType.startsWith("text/") ? { encoding: "utf-8" } : {},
19636
19776
  ...options.artifact === void 0 ? {} : { artifact: options.artifact }
19637
- }
19777
+ },
19778
+ ...options.artifactPin === void 0 ? {} : { artifactPin: options.artifactPin }
19638
19779
  };
19639
19780
  unsignedShareEnvelopeV3Schema2.parse(unsigned);
19640
19781
  const envelopeSignature = await options.authority.sign(sha2562(textEncoder.encode(`${ENVELOPE_V3_DOMAIN}${canonicalize2(unsigned)}`)));
@@ -25805,6 +25946,20 @@ var v3TargetSchema3 = external_exports3.object({
25805
25946
  nodeAudience: external_exports3.string().min(1),
25806
25947
  spaceId: external_exports3.string().refine(isCanonicalPathSegment3, { message: "expected a canonical space id" })
25807
25948
  }).strict();
25949
+ var ARTIFACT_ID_PATTERN3 = /^[a-z0-9][a-z0-9_-]{2,62}$/;
25950
+ var artifactPinSchema3 = external_exports3.object({
25951
+ v: external_exports3.literal(1),
25952
+ artifactId: external_exports3.string().regex(ARTIFACT_ID_PATTERN3),
25953
+ version: external_exports3.number().int().min(1).max(Number.MAX_SAFE_INTEGER),
25954
+ /** SHA-256 (lowercase hex) of the exact `manifest.json` bytes. The manifest itself stays an owner-side record. */
25955
+ manifestSha256: external_exports3.string().regex(/^[0-9a-f]{64}$/),
25956
+ /** An addressed share reads one exact file, so exactly one entry: the stored bytes (ciphertext) it will fetch. */
25957
+ files: external_exports3.array(external_exports3.object({
25958
+ path: external_exports3.string().min(1).max(255),
25959
+ sha256: external_exports3.string().regex(/^[0-9a-f]{64}$/),
25960
+ bytes: external_exports3.number().int().nonnegative()
25961
+ }).strict()).length(1)
25962
+ }).strict();
25808
25963
  var unsignedShareEnvelopeV3BaseSchema3 = external_exports3.object({
25809
25964
  version: external_exports3.literal(3),
25810
25965
  shareId: external_exports3.string().min(1),
@@ -25824,7 +25979,13 @@ var unsignedShareEnvelopeV3BaseSchema3 = external_exports3.object({
25824
25979
  expiry: external_exports3.string().datetime({ offset: true }),
25825
25980
  display: displaySchema3,
25826
25981
  encrypted: external_exports3.literal(true),
25827
- metadata: contentMetadataSchema3
25982
+ metadata: contentMetadataSchema3,
25983
+ /**
25984
+ * TC-1428: an owner-signed statement that this share reads one write-once artifact version. Additive: absent on
25985
+ * every other share, and covered by the envelope signature because the signature spans the whole unsigned object.
25986
+ * The shape is checked here; its binding to the content source is `verifyArtifactShare` in share-sdk.
25987
+ */
25988
+ artifactPin: artifactPinSchema3.optional()
25828
25989
  }).strict();
25829
25990
  function validateV3Invariants3(value, ctx) {
25830
25991
  const actions = [...value.actions];
@@ -27256,6 +27417,8 @@ function createShareAuthorityAdapters(input = {}) {
27256
27417
  actions,
27257
27418
  policyActions,
27258
27419
  contentSource,
27420
+ // The owner-signed pin: artifact id, version, manifest digest and the digest of the stored ciphertext this share reads.
27421
+ ...artifactVersion === void 0 ? {} : { artifactPin: buildArtifactPin(artifactVersion, storageName) },
27259
27422
  ...prepared.credentialRequirement === void 0 ? {} : { credentialRequirement: prepared.credentialRequirement },
27260
27423
  // tinycloud-node 1.17.2 signs a delivery receipt only for the envelope's
27261
27424
  // own signed delivery address (1.17.3 accepts it and no longer requires