@character-foundry/character-foundry 0.4.2-dev.1765942273 → 0.4.2-dev.1765997746

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/loader.d.cts CHANGED
@@ -1194,6 +1194,13 @@ export interface AuthoritativeMetadata {
1194
1194
  tokens: TokenCounts;
1195
1195
  /** Content hash computed server-side */
1196
1196
  contentHash: string;
1197
+ /**
1198
+ * Content hash v2 computed server-side.
1199
+ *
1200
+ * @remarks
1201
+ * v1 is preserved for backwards compatibility. Prefer v2 for new storage/deduplication.
1202
+ */
1203
+ contentHashV2?: string;
1197
1204
  /** Whether the card has a lorebook */
1198
1205
  hasLorebook: boolean;
1199
1206
  /** Number of lorebook entries */
@@ -1293,6 +1300,13 @@ export declare function validateClientMetadata(clientMetadata: ClientMetadata, p
1293
1300
  * @returns SHA-256 hash of canonical content
1294
1301
  */
1295
1302
  export declare function computeContentHash(card: CCv3Data): Promise<string>;
1303
+ /**
1304
+ * Compute content hash v2 for a card (standalone utility)
1305
+ *
1306
+ * @param card - CCv3 card data
1307
+ * @returns SHA-256 hash of canonical content v2
1308
+ */
1309
+ export declare function computeContentHashV2(card: CCv3Data): Promise<string>;
1296
1310
  /**
1297
1311
  * Options for synchronous metadata validation
1298
1312
  */
package/dist/loader.d.ts CHANGED
@@ -1194,6 +1194,13 @@ export interface AuthoritativeMetadata {
1194
1194
  tokens: TokenCounts;
1195
1195
  /** Content hash computed server-side */
1196
1196
  contentHash: string;
1197
+ /**
1198
+ * Content hash v2 computed server-side.
1199
+ *
1200
+ * @remarks
1201
+ * v1 is preserved for backwards compatibility. Prefer v2 for new storage/deduplication.
1202
+ */
1203
+ contentHashV2?: string;
1197
1204
  /** Whether the card has a lorebook */
1198
1205
  hasLorebook: boolean;
1199
1206
  /** Number of lorebook entries */
@@ -1293,6 +1300,13 @@ export declare function validateClientMetadata(clientMetadata: ClientMetadata, p
1293
1300
  * @returns SHA-256 hash of canonical content
1294
1301
  */
1295
1302
  export declare function computeContentHash(card: CCv3Data): Promise<string>;
1303
+ /**
1304
+ * Compute content hash v2 for a card (standalone utility)
1305
+ *
1306
+ * @param card - CCv3 card data
1307
+ * @returns SHA-256 hash of canonical content v2
1308
+ */
1309
+ export declare function computeContentHashV2(card: CCv3Data): Promise<string>;
1296
1310
  /**
1297
1311
  * Options for synchronous metadata validation
1298
1312
  */
package/dist/loader.js CHANGED
@@ -8697,7 +8697,7 @@ function defaultTokenCounter(_card) {
8697
8697
  total: 0
8698
8698
  };
8699
8699
  }
8700
- function getCanonicalContent(card) {
8700
+ function getCanonicalContentV1(card) {
8701
8701
  const normalized = {
8702
8702
  name: card.data.name,
8703
8703
  description: card.data.description || "",
@@ -8719,6 +8719,69 @@ function getCanonicalContent(card) {
8719
8719
  };
8720
8720
  return JSON.stringify(normalized, Object.keys(normalized).sort());
8721
8721
  }
8722
+ function stableStringify(value) {
8723
+ if (value === null) return "null";
8724
+ switch (typeof value) {
8725
+ case "string":
8726
+ return JSON.stringify(value);
8727
+ case "number":
8728
+ return Number.isFinite(value) ? String(value) : "null";
8729
+ case "boolean":
8730
+ return value ? "true" : "false";
8731
+ case "bigint":
8732
+ return JSON.stringify(value.toString());
8733
+ case "undefined":
8734
+ case "function":
8735
+ case "symbol":
8736
+ return "null";
8737
+ case "object": {
8738
+ if (Array.isArray(value)) {
8739
+ const parts2 = value.map((item) => {
8740
+ if (item === void 0 || typeof item === "function" || typeof item === "symbol") {
8741
+ return "null";
8742
+ }
8743
+ return stableStringify(item);
8744
+ });
8745
+ return `[${parts2.join(",")}]`;
8746
+ }
8747
+ const obj = value;
8748
+ const keys = Object.keys(obj).sort();
8749
+ const parts = [];
8750
+ for (const key of keys) {
8751
+ const v = obj[key];
8752
+ if (v === void 0 || typeof v === "function" || typeof v === "symbol") {
8753
+ continue;
8754
+ }
8755
+ parts.push(`${JSON.stringify(key)}:${stableStringify(v)}`);
8756
+ }
8757
+ return `{${parts.join(",")}}`;
8758
+ }
8759
+ default:
8760
+ return "null";
8761
+ }
8762
+ }
8763
+ function getCanonicalContentV2(card) {
8764
+ const normalized = {
8765
+ name: card.data.name,
8766
+ description: card.data.description || "",
8767
+ personality: card.data.personality || "",
8768
+ scenario: card.data.scenario || "",
8769
+ first_mes: card.data.first_mes || "",
8770
+ mes_example: card.data.mes_example || "",
8771
+ system_prompt: card.data.system_prompt || "",
8772
+ post_history_instructions: card.data.post_history_instructions || "",
8773
+ alternate_greetings: card.data.alternate_greetings || [],
8774
+ character_book: card.data.character_book ? {
8775
+ entries: (card.data.character_book.entries || []).map((e) => ({
8776
+ keys: e.keys,
8777
+ content: e.content,
8778
+ enabled: e.enabled
8779
+ }))
8780
+ } : null,
8781
+ creator_notes: card.data.creator_notes || ""
8782
+ };
8783
+ return stableStringify(normalized);
8784
+ }
8722
8785
  function isWithinTolerance(clientValue, computedValue, tolerance) {
8723
8786
  if (clientValue === void 0) return false;
8724
8787
  if (computedValue === 0) return clientValue === 0;
@@ -8739,15 +8802,18 @@ async function validateClientMetadata(clientMetadata, parseResult, options = {})
8739
8802
  const warnings = [];
8740
8803
  const errors = [];
8741
8804
  const computedTokens = countTokens(card);
8742
- const canonicalContent = getCanonicalContent(card);
8743
- const computedHash = await computeHash(canonicalContent);
8805
+ const canonicalContentV1 = getCanonicalContentV1(card);
8806
+ const canonicalContentV2 = getCanonicalContentV2(card);
8807
+ const computedHashV1 = await computeHash(canonicalContentV1);
8808
+ const computedHashV2 = await computeHash(canonicalContentV2);
8744
8809
  const entries = card.data.character_book?.entries || [];
8745
8810
  const computedHasLorebook = entries.length > 0;
8746
8811
  const computedLorebookCount = entries.length;
8747
8812
  const authoritative = {
8748
8813
  name: card.data.name,
8749
8814
  tokens: computedTokens,
8750
- contentHash: computedHash,
8815
+ contentHash: computedHashV1,
8816
+ contentHashV2: computedHashV2,
8751
8817
  hasLorebook: computedHasLorebook,
8752
8818
  lorebookEntriesCount: computedLorebookCount
8753
8819
  };
@@ -8759,21 +8825,27 @@ async function validateClientMetadata(clientMetadata, parseResult, options = {})
8759
8825
  withinTolerance: false
8760
8826
  });
8761
8827
  }
8762
- if (clientMetadata.contentHash !== computedHash) {
8828
+ const matchesV1 = clientMetadata.contentHash === computedHashV1;
8829
+ const matchesV2 = clientMetadata.contentHash === computedHashV2;
8830
+ if (!matchesV1 && !matchesV2) {
8763
8831
  const disc = {
8764
8832
  field: "contentHash",
8765
8833
  clientValue: clientMetadata.contentHash,
8766
- computedValue: computedHash,
8834
+ computedValue: computedHashV1,
8767
8835
  withinTolerance: false
8768
8836
  };
8769
8837
  discrepancies.push(disc);
8770
8838
  if (allowHashMismatch) {
8771
8839
  warnings.push(
8772
- `Content hash mismatch: client=${clientMetadata.contentHash.substring(0, 8)}..., server=${computedHash.substring(0, 8)}...`
8840
+ `Content hash mismatch: client=${clientMetadata.contentHash.substring(0, 8)}..., server(v1)=${computedHashV1.substring(0, 8)}..., server(v2)=${computedHashV2.substring(0, 8)}...`
8773
8841
  );
8774
8842
  } else {
8775
8843
  errors.push("Content hash mismatch - possible tampering or encoding difference");
8776
8844
  }
8845
+ } else if (matchesV1 && !matchesV2) {
8846
+ warnings.push(
8847
+ "Client contentHash matches legacy v1 canonicalization. Prefer authoritative.contentHashV2 for new storage."
8848
+ );
8777
8849
  }
8778
8850
  const tokenFields = [
8779
8851
  "description",
@@ -8854,7 +8926,11 @@ async function validateClientMetadata(clientMetadata, parseResult, options = {})
8854
8926
  };
8855
8927
  }
8856
8928
  async function computeContentHash(card) {
8857
- const content = getCanonicalContent(card);
8929
+ const content = getCanonicalContentV1(card);
8930
+ return sha256Hash(content);
8931
+ }
8932
+ async function computeContentHashV2(card) {
8933
+ const content = getCanonicalContentV2(card);
8858
8934
  return sha256Hash(content);
8859
8935
  }
8860
8936
  function validateClientMetadataSync(clientMetadata, parseResult, options) {
@@ -8870,15 +8946,18 @@ function validateClientMetadataSync(clientMetadata, parseResult, options) {
8870
8946
  const warnings = [];
8871
8947
  const errors = [];
8872
8948
  const computedTokens = countTokens(card);
8873
- const canonicalContent = getCanonicalContent(card);
8874
- const computedHash = computeHash(canonicalContent);
8949
+ const canonicalContentV1 = getCanonicalContentV1(card);
8950
+ const canonicalContentV2 = getCanonicalContentV2(card);
8951
+ const computedHashV1 = computeHash(canonicalContentV1);
8952
+ const computedHashV2 = computeHash(canonicalContentV2);
8875
8953
  const entries = card.data.character_book?.entries || [];
8876
8954
  const computedHasLorebook = entries.length > 0;
8877
8955
  const computedLorebookCount = entries.length;
8878
8956
  const authoritative = {
8879
8957
  name: card.data.name,
8880
8958
  tokens: computedTokens,
8881
- contentHash: computedHash,
8959
+ contentHash: computedHashV1,
8960
+ contentHashV2: computedHashV2,
8882
8961
  hasLorebook: computedHasLorebook,
8883
8962
  lorebookEntriesCount: computedLorebookCount
8884
8963
  };
@@ -8890,11 +8969,13 @@ function validateClientMetadataSync(clientMetadata, parseResult, options) {
8890
8969
  withinTolerance: false
8891
8970
  });
8892
8971
  }
8893
- if (clientMetadata.contentHash !== computedHash) {
8972
+ const matchesV1 = clientMetadata.contentHash === computedHashV1;
8973
+ const matchesV2 = clientMetadata.contentHash === computedHashV2;
8974
+ if (!matchesV1 && !matchesV2) {
8894
8975
  discrepancies.push({
8895
8976
  field: "contentHash",
8896
8977
  clientValue: clientMetadata.contentHash,
8897
- computedValue: computedHash,
8978
+ computedValue: computedHashV1,
8898
8979
  withinTolerance: false
8899
8980
  });
8900
8981
  if (allowHashMismatch) {
@@ -8902,6 +8983,10 @@ function validateClientMetadataSync(clientMetadata, parseResult, options) {
8902
8983
  } else {
8903
8984
  errors.push("Content hash mismatch");
8904
8985
  }
8986
+ } else if (matchesV1 && !matchesV2) {
8987
+ warnings.push(
8988
+ "Client contentHash matches legacy v1 canonicalization. Prefer authoritative.contentHashV2 for new storage."
8989
+ );
8905
8990
  }
8906
8991
  const tokenFields = [
8907
8992
  "description",
@@ -8970,6 +9055,7 @@ function validateClientMetadataSync(clientMetadata, parseResult, options) {
8970
9055
  }
8971
9056
  export {
8972
9057
  computeContentHash,
9058
+ computeContentHashV2,
8973
9059
  detectFormat,
8974
9060
  getContainerFormat,
8975
9061
  mightBeCard,