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

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.
@@ -1,4 +1,4 @@
1
- import { ShareEnvelopeV2, UnifiedPolicyCapability, ShareAction, UnifiedContentSource, PolicyCredentialRequirementV1 } from '@tinycloud/share-envelope';
1
+ import { ShareEnvelopeV2, UnifiedPolicyCapability, ShareAction, UnifiedContentSource, PolicyCredentialRequirementV1, ArtifactPin } from '@tinycloud/share-envelope';
2
2
  import { p as SharePublishTarget, P as PublishedShare, o as SharePublishOptions, d as OwnerShareAction, j as PublishedShareDeliveryMaterial } from './owner-policy-BqzRlkb6.js';
3
3
 
4
4
  /** The only interactive boundary exposed by the headless SDK. */
@@ -206,6 +206,8 @@ interface AddressedSharePublishOptions {
206
206
  readonly mediaType: string;
207
207
  readonly byteLength: number;
208
208
  readonly artifact?: "html";
209
+ /** TC-1428: pins the share to one write-once artifact version; signed with the envelope. Required when the content source is under `artifacts/`. */
210
+ readonly artifactPin?: ArtifactPin;
209
211
  readonly deliveryEmail?: string;
210
212
  readonly expiresAt: Date;
211
213
  /** Sender-history material required by Node's Policy/v3 delivery authorization. */
@@ -1,4 +1,4 @@
1
- import { ShareEnvelopeV2, UnifiedPolicyCapability, ShareAction, UnifiedContentSource, PolicyCredentialRequirementV1 } from '@tinycloud/share-envelope';
1
+ import { ShareEnvelopeV2, UnifiedPolicyCapability, ShareAction, UnifiedContentSource, PolicyCredentialRequirementV1, ArtifactPin } from '@tinycloud/share-envelope';
2
2
  import { p as SharePublishTarget, P as PublishedShare, o as SharePublishOptions, d as OwnerShareAction, j as PublishedShareDeliveryMaterial } from './owner-policy-BqzRlkb6.cjs';
3
3
 
4
4
  /** The only interactive boundary exposed by the headless SDK. */
@@ -206,6 +206,8 @@ interface AddressedSharePublishOptions {
206
206
  readonly mediaType: string;
207
207
  readonly byteLength: number;
208
208
  readonly artifact?: "html";
209
+ /** TC-1428: pins the share to one write-once artifact version; signed with the envelope. Required when the content source is under `artifacts/`. */
210
+ readonly artifactPin?: ArtifactPin;
209
211
  readonly deliveryEmail?: string;
210
212
  readonly expiresAt: Date;
211
213
  /** Sender-history material required by Node's Policy/v3 delivery authorization. */
package/dist/artifact.cjs CHANGED
@@ -29,16 +29,18 @@ __export(artifact_exports, {
29
29
  artifactVersionResource: () => artifactVersionResource,
30
30
  artifactVersionsPrefix: () => artifactVersionsPrefix,
31
31
  assertArtifactId: () => assertArtifactId,
32
+ buildArtifactPin: () => buildArtifactPin,
32
33
  listArtifactVersions: () => listArtifactVersions,
33
34
  manifestBytes: () => manifestBytes,
34
35
  newArtifactId: () => newArtifactId,
35
36
  parseArtifactManifest: () => parseArtifactManifest,
36
37
  parseArtifactVersionPath: () => parseArtifactVersionPath,
37
38
  publishArtifactVersion: () => publishArtifactVersion,
38
- sha256Hex: () => sha256Hex
39
+ sha256Hex: () => sha256Hex,
40
+ verifyArtifactFileBytes: () => verifyArtifactFileBytes,
41
+ verifyArtifactManifestBytes: () => verifyArtifactManifestBytes
39
42
  });
40
43
  module.exports = __toCommonJS(artifact_exports);
41
- var import_sha2562 = require("@noble/hashes/sha256");
42
44
 
43
45
  // ../share-envelope/dist/index.js
44
46
  var import_sha2 = require("@noble/hashes/sha2");
@@ -5322,6 +5324,20 @@ var v3TargetSchema = external_exports.object({
5322
5324
  nodeAudience: external_exports.string().min(1),
5323
5325
  spaceId: external_exports.string().refine(isCanonicalPathSegment, { message: "expected a canonical space id" })
5324
5326
  }).strict();
5327
+ var ARTIFACT_ID_PATTERN = /^[a-z0-9][a-z0-9_-]{2,62}$/;
5328
+ var artifactPinSchema = external_exports.object({
5329
+ v: external_exports.literal(1),
5330
+ artifactId: external_exports.string().regex(ARTIFACT_ID_PATTERN),
5331
+ version: external_exports.number().int().min(1).max(Number.MAX_SAFE_INTEGER),
5332
+ /** SHA-256 (lowercase hex) of the exact `manifest.json` bytes. The manifest itself stays an owner-side record. */
5333
+ manifestSha256: external_exports.string().regex(/^[0-9a-f]{64}$/),
5334
+ /** An addressed share reads one exact file, so exactly one entry: the stored bytes (ciphertext) it will fetch. */
5335
+ files: external_exports.array(external_exports.object({
5336
+ path: external_exports.string().min(1).max(255),
5337
+ sha256: external_exports.string().regex(/^[0-9a-f]{64}$/),
5338
+ bytes: external_exports.number().int().nonnegative()
5339
+ }).strict()).length(1)
5340
+ }).strict();
5325
5341
  var unsignedShareEnvelopeV3BaseSchema = external_exports.object({
5326
5342
  version: external_exports.literal(3),
5327
5343
  shareId: external_exports.string().min(1),
@@ -5341,7 +5357,13 @@ var unsignedShareEnvelopeV3BaseSchema = external_exports.object({
5341
5357
  expiry: external_exports.string().datetime({ offset: true }),
5342
5358
  display: displaySchema,
5343
5359
  encrypted: external_exports.literal(true),
5344
- metadata: contentMetadataSchema
5360
+ metadata: contentMetadataSchema,
5361
+ /**
5362
+ * TC-1428: an owner-signed statement that this share reads one write-once artifact version. Additive: absent on
5363
+ * every other share, and covered by the envelope signature because the signature spans the whole unsigned object.
5364
+ * The shape is checked here; its binding to the content source is `verifyArtifactShare` in share-sdk.
5365
+ */
5366
+ artifactPin: artifactPinSchema.optional()
5345
5367
  }).strict();
5346
5368
  function validateV3Invariants(value, ctx) {
5347
5369
  const actions = [...value.actions];
@@ -5440,6 +5462,17 @@ function canonicalize(value) {
5440
5462
  }
5441
5463
  var MAX_INLINE_BYTES = 256 * 1024;
5442
5464
 
5465
+ // src/artifact.ts
5466
+ var import_sha2562 = require("@noble/hashes/sha256");
5467
+
5468
+ // src/artifact-core.ts
5469
+ var ARTIFACT_ROOT = "artifacts";
5470
+ function parseArtifactVersionPath(path) {
5471
+ const match = /^artifacts\/([a-z0-9][a-z0-9_-]{2,62})\/versions\/([1-9][0-9]{0,14})\/([^/]+)$/.exec(path);
5472
+ if (match === null) return void 0;
5473
+ return { artifactId: match[1], version: Number(match[2]), file: match[3] };
5474
+ }
5475
+
5443
5476
  // src/filename-policy.ts
5444
5477
  var UNSAFE_FILENAME_CODE_POINT = /[\p{Cc}\p{Cf}\p{Cs}\u2028\u2029]/u;
5445
5478
  function hasUnsafeFilenameCodePoint(value) {
@@ -5453,8 +5486,21 @@ function canonicalShareFilename(value) {
5453
5486
  return canonical;
5454
5487
  }
5455
5488
 
5489
+ // src/artifact-pin.ts
5490
+ var ArtifactPinError = class extends Error {
5491
+ code;
5492
+ /** The code is the message: stable, short, and what callers and tests match on. */
5493
+ constructor(code2) {
5494
+ super(code2);
5495
+ this.name = "ArtifactPinError";
5496
+ this.code = code2;
5497
+ }
5498
+ };
5499
+
5456
5500
  // src/artifact.ts
5457
- var ARTIFACT_ROOT = "artifacts";
5501
+ function sha256Hex(bytes) {
5502
+ return Array.from((0, import_sha2562.sha256)(bytes), (byte) => byte.toString(16).padStart(2, "0")).join("");
5503
+ }
5458
5504
  var ARTIFACT_MANIFEST_NAME = "manifest.json";
5459
5505
  var ARTIFACT_MANIFEST_VERSION = 1;
5460
5506
  var ARTIFACT_MAX_PUBLISH_ATTEMPTS = 16;
@@ -5468,9 +5514,6 @@ var ArtifactError = class extends Error {
5468
5514
  this.code = code2;
5469
5515
  }
5470
5516
  };
5471
- function sha256Hex(bytes) {
5472
- return Array.from((0, import_sha2562.sha256)(bytes), (byte) => byte.toString(16).padStart(2, "0")).join("");
5473
- }
5474
5517
  function newArtifactId(random = (length2) => crypto.getRandomValues(new Uint8Array(length2))) {
5475
5518
  return `art_${Array.from(random(8), (byte) => byte.toString(16).padStart(2, "0")).join("")}`;
5476
5519
  }
@@ -5493,11 +5536,6 @@ function artifactVersionResource(artifactId, version) {
5493
5536
  function artifactVersionFilePath(artifactId, version, filename) {
5494
5537
  return `${artifactVersionResource(artifactId, version)}/${filename}`;
5495
5538
  }
5496
- function parseArtifactVersionPath(path) {
5497
- const match = /^artifacts\/([a-z0-9][a-z0-9_-]{2,62})\/versions\/([1-9][0-9]{0,14})\/([^/]+)$/.exec(path);
5498
- if (match === null) return void 0;
5499
- return { artifactId: match[1], version: Number(match[2]), file: match[3] };
5500
- }
5501
5539
  function manifestBytes(manifest) {
5502
5540
  return new TextEncoder().encode(canonicalize(manifest));
5503
5541
  }
@@ -5643,6 +5681,40 @@ async function publishArtifactVersion(store, input) {
5643
5681
  }
5644
5682
  throw new ArtifactError("conflict", "could not claim an artifact version number after repeated races; retry");
5645
5683
  }
5684
+ function buildArtifactPin(version, filename) {
5685
+ const entry = version.manifest.files.find((candidate) => candidate.path === filename);
5686
+ if (entry === void 0) throw new ArtifactError("invalid-argument", "artifact manifest does not list that file");
5687
+ return {
5688
+ v: 1,
5689
+ artifactId: version.artifactId,
5690
+ version: version.version,
5691
+ manifestSha256: version.manifestSha256,
5692
+ files: [{ path: entry.path, sha256: entry.sha256, bytes: entry.bytes }]
5693
+ };
5694
+ }
5695
+ function verifyArtifactManifestBytes(pin, manifestBytes2) {
5696
+ if (sha256Hex(manifestBytes2) !== pin.manifestSha256) {
5697
+ throw new ArtifactPinError("manifest-mismatch");
5698
+ }
5699
+ let manifest;
5700
+ try {
5701
+ manifest = parseArtifactManifest(manifestBytes2);
5702
+ } catch {
5703
+ throw new ArtifactPinError("manifest-mismatch");
5704
+ }
5705
+ const file = pin.files[0];
5706
+ const listed = manifest.files.find((candidate) => candidate.path === file.path);
5707
+ if (listed === void 0 || listed.sha256 !== file.sha256 || listed.bytes !== file.bytes) {
5708
+ throw new ArtifactPinError("manifest-mismatch");
5709
+ }
5710
+ return manifest;
5711
+ }
5712
+ function verifyArtifactFileBytes(pin, storedBytes) {
5713
+ const file = pin.files[0];
5714
+ if (storedBytes.byteLength !== file.bytes || sha256Hex(storedBytes) !== file.sha256) {
5715
+ throw new ArtifactPinError("file-digest-mismatch");
5716
+ }
5717
+ }
5646
5718
  // Annotate the CommonJS export names for ESM import in node:
5647
5719
  0 && (module.exports = {
5648
5720
  ARTIFACT_MANIFEST_NAME,
@@ -5654,12 +5726,15 @@ async function publishArtifactVersion(store, input) {
5654
5726
  artifactVersionResource,
5655
5727
  artifactVersionsPrefix,
5656
5728
  assertArtifactId,
5729
+ buildArtifactPin,
5657
5730
  listArtifactVersions,
5658
5731
  manifestBytes,
5659
5732
  newArtifactId,
5660
5733
  parseArtifactManifest,
5661
5734
  parseArtifactVersionPath,
5662
5735
  publishArtifactVersion,
5663
- sha256Hex
5736
+ sha256Hex,
5737
+ verifyArtifactFileBytes,
5738
+ verifyArtifactManifestBytes
5664
5739
  });
5665
5740
  //# sourceMappingURL=artifact.cjs.map