@odla-ai/brand 0.5.0 → 0.6.0

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.cjs CHANGED
@@ -153,11 +153,13 @@ __export(src_exports, {
153
153
  parseDesignBundle: () => parseDesignBundle,
154
154
  parseHex: () => parseHex,
155
155
  pickTextOn: () => pickTextOn,
156
+ proposalAsWritten: () => proposalAsWritten,
156
157
  proposalReviewSnapshot: () => proposalReviewSnapshot,
157
158
  proposePaletteOps: () => proposePaletteOps,
158
159
  proposeSectionOps: () => proposeSectionOps,
159
160
  readDesignManifest: () => readDesignManifest,
160
161
  readTools: () => readTools,
162
+ receiptAsWritten: () => receiptAsWritten,
161
163
  recordAnalysisOps: () => recordAnalysisOps,
162
164
  rejectProposalOps: () => rejectProposalOps,
163
165
  relativeLuminance: () => relativeLuminance,
@@ -167,6 +169,7 @@ __export(src_exports, {
167
169
  rgbToHsl: () => rgbToHsl,
168
170
  rgbToOklab: () => rgbToOklab,
169
171
  rotateHue: () => rotateHue,
172
+ rowAsWritten: () => rowAsWritten,
170
173
  safeFileName: () => safeFileName,
171
174
  scanCustomProperties: () => scanCustomProperties,
172
175
  sectionKey: () => sectionKey,
@@ -690,8 +693,40 @@ function assertAnalysis(value) {
690
693
  };
691
694
  }
692
695
 
696
+ // src/read-shape.ts
697
+ var record = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
698
+ var DATE_FIELDS = /* @__PURE__ */ new Map();
699
+ for (const [ns, entity] of Object.entries(BRAND_SCHEMA.entities))
700
+ DATE_FIELDS.set(
701
+ ns,
702
+ new Set(
703
+ Object.entries(entity.attrs).filter(([, attr]) => attr.type === "date").map(([label]) => label)
704
+ )
705
+ );
706
+ function rowAsWritten(ns, row) {
707
+ const dates = DATE_FIELDS.get(ns);
708
+ if (!dates?.size || !record(row)) return row;
709
+ let changed = false;
710
+ const out = { ...row };
711
+ for (const field of dates) {
712
+ const value = out[field];
713
+ if (typeof value !== "string") continue;
714
+ const parsed = Date.parse(value);
715
+ if (!Number.isSafeInteger(parsed) || parsed < 0) continue;
716
+ out[field] = parsed;
717
+ changed = true;
718
+ }
719
+ return changed ? out : row;
720
+ }
721
+ function receiptAsWritten(row) {
722
+ return rowAsWritten(BRAND_NS.approvalReceipt, row);
723
+ }
724
+ function proposalAsWritten(row) {
725
+ return rowAsWritten(BRAND_NS.proposal, row);
726
+ }
727
+
693
728
  // src/review-json.ts
694
- var record = (value) => typeof value === "object" && value !== null && !Array.isArray(value) && (Object.getPrototypeOf(value) === Object.prototype || Object.getPrototypeOf(value) === null);
729
+ var record2 = (value) => typeof value === "object" && value !== null && !Array.isArray(value) && (Object.getPrototypeOf(value) === Object.prototype || Object.getPrototypeOf(value) === null);
695
730
  function isBoundedBrandJson(root, limits = {}) {
696
731
  const maxDepth = limits.maxDepth ?? 12;
697
732
  const maxNodes = limits.maxNodes ?? 2048;
@@ -715,7 +750,7 @@ function isBoundedBrandJson(root, limits = {}) {
715
750
  if (Array.isArray(value)) {
716
751
  for (const child of value)
717
752
  stack.push({ value: child, depth: depth + 1 });
718
- } else if (record(value)) {
753
+ } else if (record2(value)) {
719
754
  for (const child of Object.values(value))
720
755
  stack.push({ value: child, depth: depth + 1 });
721
756
  } else {
@@ -752,7 +787,7 @@ async function brandJsonDigest(value) {
752
787
 
753
788
  // src/review.ts
754
789
  var DIGEST = /^sha256:[0-9a-f]{64}$/;
755
- var record2 = (value) => typeof value === "object" && value !== null && !Array.isArray(value) && (Object.getPrototypeOf(value) === Object.prototype || Object.getPrototypeOf(value) === null);
790
+ var record3 = (value) => typeof value === "object" && value !== null && !Array.isArray(value) && (Object.getPrototypeOf(value) === Object.prototype || Object.getPrototypeOf(value) === null);
756
791
  var exactKeys = (value, required, optional = []) => {
757
792
  const allowed = /* @__PURE__ */ new Set([...required, ...optional]);
758
793
  return required.every((key) => Object.hasOwn(value, key)) && Object.keys(value).every((key) => allowed.has(key));
@@ -777,15 +812,15 @@ var SNAPSHOT_REQUIRED = [
777
812
  "createdAt"
778
813
  ];
779
814
  function validSnapshotShape(value) {
780
- if (!record2(value) || !exactKeys(value, SNAPSHOT_REQUIRED)) return false;
815
+ if (!record3(value) || !exactKeys(value, SNAPSHOT_REQUIRED)) return false;
781
816
  const provenance = value.provenance;
782
- return boundedString(value.id) && boundedString(value.bookId) && typeof value.kind === "string" && PROPOSAL_KINDS.includes(value.kind) && value.status === "open" && record2(value.payload) && boundedString(value.rationale, 2e3) && record2(provenance) && exactKeys(provenance, [
817
+ return boundedString(value.id) && boundedString(value.bookId) && typeof value.kind === "string" && PROPOSAL_KINDS.includes(value.kind) && value.status === "open" && record3(value.payload) && boundedString(value.rationale, 2e3) && record3(provenance) && exactKeys(provenance, [
783
818
  "sourceAssetId",
784
819
  "sourceAsset",
785
820
  "messageId",
786
821
  "turnId",
787
822
  "taintLabels"
788
- ]) && (provenance.sourceAssetId === null || boundedString(provenance.sourceAssetId)) && (provenance.sourceAsset === null || record2(provenance.sourceAsset) && exactKeys(provenance.sourceAsset, [
823
+ ]) && (provenance.sourceAssetId === null || boundedString(provenance.sourceAssetId)) && (provenance.sourceAsset === null || record3(provenance.sourceAsset) && exactKeys(provenance.sourceAsset, [
789
824
  "assetId",
790
825
  "contentDigest",
791
826
  "objectEtag",
@@ -818,8 +853,8 @@ var CONSUMPTION_REQUIRED = [
818
853
  "consumedAt"
819
854
  ];
820
855
  function isBrandHumanAuthorityConsumption(value) {
821
- if (!record2(value) || !exactKeys(value, CONSUMPTION_REQUIRED)) return false;
822
- return boundedString(value.id) && boundedString(value.grantId) && Number.isSafeInteger(value.grantVersion) && value.grantVersion > 0 && Number.isSafeInteger(value.useNumber) && value.useNumber > 0 && boundedString(value.actorPrincipalId) && value.actorKind === "human" && boundedString(value.credentialId) && value.credentialKind === "clerk" && boundedString(value.appId) && typeof value.appIncarnation === "string" && /^[a-f0-9]{32}$/.test(value.appIncarnation) && value.capability === "brand.proposal.resolve" && value.projectCapability === "brand.approve" && value.effect === "internal" && typeof value.actionDigest === "string" && DIGEST.test(value.actionDigest) && typeof value.resourceDigest === "string" && DIGEST.test(value.resourceDigest) && record2(value.constraintEvidence) && boundedString(value.consumptionIdempotencyKey) && typeof value.requestDigest === "string" && DIGEST.test(value.requestDigest) && safeTime(value.consumedAt);
856
+ if (!record3(value) || !exactKeys(value, CONSUMPTION_REQUIRED)) return false;
857
+ return boundedString(value.id) && boundedString(value.grantId) && Number.isSafeInteger(value.grantVersion) && value.grantVersion > 0 && Number.isSafeInteger(value.useNumber) && value.useNumber > 0 && boundedString(value.actorPrincipalId) && value.actorKind === "human" && boundedString(value.credentialId) && value.credentialKind === "clerk" && boundedString(value.appId) && typeof value.appIncarnation === "string" && /^[a-f0-9]{32}$/.test(value.appIncarnation) && value.capability === "brand.proposal.resolve" && value.projectCapability === "brand.approve" && value.effect === "internal" && typeof value.actionDigest === "string" && DIGEST.test(value.actionDigest) && typeof value.resourceDigest === "string" && DIGEST.test(value.resourceDigest) && record3(value.constraintEvidence) && boundedString(value.consumptionIdempotencyKey) && typeof value.requestDigest === "string" && DIGEST.test(value.requestDigest) && safeTime(value.consumedAt);
823
858
  }
824
859
  var RECEIPT_REQUIRED = [
825
860
  "version",
@@ -845,10 +880,10 @@ async function verifyBrandApprovalReceipt(receipt2) {
845
880
  try {
846
881
  if (!isBoundedBrandJson(receipt2, { maxDepth: 14, maxNodes: 2500, maxBytes: 48 * 1024 }))
847
882
  return false;
848
- if (!record2(receipt2) || !exactKeys(receipt2, RECEIPT_REQUIRED, ["paletteId", "resolutionNote"]))
883
+ if (!record3(receipt2) || !exactKeys(receipt2, RECEIPT_REQUIRED, ["paletteId", "resolutionNote"]))
849
884
  return false;
850
885
  const binding = receipt2.decisionBinding;
851
- if (receipt2.version !== 1 || !boundedString(receipt2.id) || !boundedString(receipt2.mutationKey) || !boundedString(receipt2.bookId) || !boundedString(receipt2.proposalId) || receipt2.resolution !== "accepted" && receipt2.resolution !== "rejected" || !validSnapshotShape(receipt2.reviewedProposal) || typeof receipt2.actionDigest !== "string" || !DIGEST.test(receipt2.actionDigest) || !record2(binding) || !exactKeys(binding, [
886
+ if (receipt2.version !== 1 || !boundedString(receipt2.id) || !boundedString(receipt2.mutationKey) || !boundedString(receipt2.bookId) || !boundedString(receipt2.proposalId) || receipt2.resolution !== "accepted" && receipt2.resolution !== "rejected" || !validSnapshotShape(receipt2.reviewedProposal) || typeof receipt2.actionDigest !== "string" || !DIGEST.test(receipt2.actionDigest) || !record3(binding) || !exactKeys(binding, [
852
887
  "version",
853
888
  "bookVersion",
854
889
  "activationRevision",
@@ -3568,7 +3603,7 @@ async function linkedAsset(ctx, book, assetId, allowDeleting = false) {
3568
3603
  book: {}
3569
3604
  }
3570
3605
  });
3571
- const row = (result[BRAND_NS.asset] ?? [])[0];
3606
+ const row = rowAsWritten(BRAND_NS.asset, (result[BRAND_NS.asset] ?? [])[0]);
3572
3607
  const links = row?.book;
3573
3608
  if (!row || row.bookId !== book.id || !Array.isArray(links) || links.length !== 1 || links[0]?.id !== book.id || row.status !== "live" && !(allowDeleting && row.status === "deleting")) throw new BrandNotFoundError(`asset ${assetId}`);
3574
3609
  return row;
@@ -3642,7 +3677,7 @@ async function loadBook(db, bookId) {
3642
3677
  const res = await db.query({ [BRAND_NS.book]: { $: { where: { id: bookId } } } });
3643
3678
  const row = (res[BRAND_NS.book] ?? [])[0];
3644
3679
  if (!row) throw new BrandNotFoundError(`brand book ${bookId}`);
3645
- return row;
3680
+ return rowAsWritten(BRAND_NS.book, row);
3646
3681
  }
3647
3682
  async function loadMemberBook(db, bookId, actorId) {
3648
3683
  const book = await loadBook(db, bookId);
@@ -3697,14 +3732,14 @@ async function uploadAsset(ctx, req, bookId) {
3697
3732
  "internal",
3698
3733
  book.id
3699
3734
  );
3700
- const record6 = await ctx.db.storage.upload(
3735
+ const record7 = await ctx.db.storage.upload(
3701
3736
  path,
3702
3737
  file,
3703
3738
  contentType,
3704
3739
  { private: true }
3705
3740
  );
3706
- if (record6.path !== path) {
3707
- await ctx.db.storage.delete(record6.path);
3741
+ if (record7.path !== path) {
3742
+ await ctx.db.storage.delete(record7.path);
3708
3743
  throw new BrandConflictError(
3709
3744
  "private storage returned an unexpected asset path"
3710
3745
  );
@@ -3723,11 +3758,11 @@ async function uploadAsset(ctx, req, bookId) {
3723
3758
  id,
3724
3759
  bookId: book.id,
3725
3760
  kind,
3726
- path: record6.path,
3727
- storageObjectId: record6.id,
3761
+ path: record7.path,
3762
+ storageObjectId: record7.id,
3728
3763
  contentDigest: await contentDigest(file),
3729
3764
  contentType,
3730
- size: record6.size,
3765
+ size: record7.size,
3731
3766
  uploadedBy: ctx.actor.id,
3732
3767
  uploadedAuthorityRef: authority.authorityRef,
3733
3768
  audience: book.memberIds,
@@ -3754,7 +3789,7 @@ async function uploadAsset(ctx, req, bookId) {
3754
3789
  asPrincipalKind: "human"
3755
3790
  });
3756
3791
  } catch (error) {
3757
- await ctx.db.storage.delete(record6.path);
3792
+ await ctx.db.storage.delete(record7.path);
3758
3793
  throw error;
3759
3794
  }
3760
3795
  return json(await linkedAsset(ctx, book, id), 201);
@@ -3869,7 +3904,7 @@ async function handleAssetItem(ctx, req, bookId, assetId) {
3869
3904
  async function handleBooksRoot(ctx, req) {
3870
3905
  if (req.method === "GET") {
3871
3906
  const res = await ctx.db.query({ [BRAND_NS.book]: { $: { order: { createdAt: "asc" } } } });
3872
- const books = (res[BRAND_NS.book] ?? []).filter(
3907
+ const books = (res[BRAND_NS.book] ?? []).map((b) => rowAsWritten(BRAND_NS.book, b)).filter(
3873
3908
  (b) => isMember(b, ctx.actor.id)
3874
3909
  );
3875
3910
  return json({ books });
@@ -4034,7 +4069,7 @@ var SNAPSHOT_KEYS = [
4034
4069
  "reviewDigest",
4035
4070
  "status"
4036
4071
  ];
4037
- var record3 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
4072
+ var record4 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
4038
4073
  function parseProposalResolutionRequest(body, bookId, proposalId) {
4039
4074
  const allowed = /* @__PURE__ */ new Set([
4040
4075
  "mutationId",
@@ -4078,13 +4113,13 @@ function proposalReviewSnapshot(proposal) {
4078
4113
  function parseReviewedProposal(value, bookId, proposalId) {
4079
4114
  if (!isBoundedBrandJson(value, { maxDepth: 12, maxNodes: 1500, maxBytes: 32 * 1024 }))
4080
4115
  throw new BrandInputError('"reviewedProposal" is too deeply nested, complex, or large');
4081
- if (!record3(value)) throw new BrandInputError('"reviewedProposal" must be an object');
4116
+ if (!record4(value)) throw new BrandInputError('"reviewedProposal" must be an object');
4082
4117
  const keys = Object.keys(value).sort();
4083
4118
  if (keys.length !== SNAPSHOT_KEYS.length || !SNAPSHOT_KEYS.every((key, index) => key === keys[index])) throw new BrandInputError('"reviewedProposal" has an invalid shape');
4084
4119
  if (value.id !== proposalId || value.bookId !== bookId || value.status !== "open")
4085
4120
  throw new BrandInputError('"reviewedProposal" must identify this open proposal');
4086
- if (typeof value.kind !== "string" || !PROPOSAL_KINDS.includes(value.kind) || !record3(value.payload) || typeof value.rationale !== "string" || !Array.isArray(value.audience) || value.audience.length < 1 || value.audience.length > 100 || value.audience.some((id) => typeof id !== "string" || id.length < 1 || id.length > 200) || new Set(value.audience).size !== value.audience.length || typeof value.createdBy !== "string" || value.createdBy.length < 1 || value.createdBy.length > 200 || typeof value.createdAuthorityRef !== "string" || value.createdAuthorityRef.length < 1 || value.createdAuthorityRef.length > 200 || !Number.isSafeInteger(value.createdAt) || value.createdAt < 0 || typeof value.reviewDigest !== "string" || !/^sha256:[0-9a-f]{64}$/.test(value.reviewDigest)) throw new BrandInputError('"reviewedProposal" has invalid fields');
4087
- if (!record3(value.provenance) || Object.keys(value.provenance).sort().join(",") !== "messageId,sourceAsset,sourceAssetId,taintLabels,turnId" || value.provenance.sourceAssetId !== null && (typeof value.provenance.sourceAssetId !== "string" || value.provenance.sourceAssetId.length < 1 || value.provenance.sourceAssetId.length > 200) || value.provenance.sourceAsset !== null && (!record3(value.provenance.sourceAsset) || Object.keys(value.provenance.sourceAsset).sort().join(",") !== "analysisDigest,analysisRevision,assetId,contentDigest,contentType,objectEtag,objectSize,pathDigest" || typeof value.provenance.sourceAsset.assetId !== "string" || value.provenance.sourceAsset.assetId.length < 1 || value.provenance.sourceAsset.assetId.length > 200 || typeof value.provenance.sourceAsset.objectEtag !== "string" || value.provenance.sourceAsset.objectEtag.length < 1 || value.provenance.sourceAsset.objectEtag.length > 200 || !Number.isSafeInteger(value.provenance.sourceAsset.objectSize) || value.provenance.sourceAsset.objectSize < 1 || typeof value.provenance.sourceAsset.pathDigest !== "string" || !/^sha256:[0-9a-f]{64}$/.test(value.provenance.sourceAsset.pathDigest) || value.provenance.sourceAsset.contentType !== null && (typeof value.provenance.sourceAsset.contentType !== "string" || value.provenance.sourceAsset.contentType.length < 1 || value.provenance.sourceAsset.contentType.length > 160) || typeof value.provenance.sourceAsset.contentDigest !== "string" || !/^sha256:[0-9a-f]{64}$/.test(value.provenance.sourceAsset.contentDigest) || value.provenance.sourceAsset.analysisRevision !== null && (!Number.isSafeInteger(value.provenance.sourceAsset.analysisRevision) || value.provenance.sourceAsset.analysisRevision < 0) || value.provenance.sourceAsset.analysisDigest !== null && (typeof value.provenance.sourceAsset.analysisDigest !== "string" || !/^sha256:[0-9a-f]{64}$/.test(
4121
+ if (typeof value.kind !== "string" || !PROPOSAL_KINDS.includes(value.kind) || !record4(value.payload) || typeof value.rationale !== "string" || !Array.isArray(value.audience) || value.audience.length < 1 || value.audience.length > 100 || value.audience.some((id) => typeof id !== "string" || id.length < 1 || id.length > 200) || new Set(value.audience).size !== value.audience.length || typeof value.createdBy !== "string" || value.createdBy.length < 1 || value.createdBy.length > 200 || typeof value.createdAuthorityRef !== "string" || value.createdAuthorityRef.length < 1 || value.createdAuthorityRef.length > 200 || !Number.isSafeInteger(value.createdAt) || value.createdAt < 0 || typeof value.reviewDigest !== "string" || !/^sha256:[0-9a-f]{64}$/.test(value.reviewDigest)) throw new BrandInputError('"reviewedProposal" has invalid fields');
4122
+ if (!record4(value.provenance) || Object.keys(value.provenance).sort().join(",") !== "messageId,sourceAsset,sourceAssetId,taintLabels,turnId" || value.provenance.sourceAssetId !== null && (typeof value.provenance.sourceAssetId !== "string" || value.provenance.sourceAssetId.length < 1 || value.provenance.sourceAssetId.length > 200) || value.provenance.sourceAsset !== null && (!record4(value.provenance.sourceAsset) || Object.keys(value.provenance.sourceAsset).sort().join(",") !== "analysisDigest,analysisRevision,assetId,contentDigest,contentType,objectEtag,objectSize,pathDigest" || typeof value.provenance.sourceAsset.assetId !== "string" || value.provenance.sourceAsset.assetId.length < 1 || value.provenance.sourceAsset.assetId.length > 200 || typeof value.provenance.sourceAsset.objectEtag !== "string" || value.provenance.sourceAsset.objectEtag.length < 1 || value.provenance.sourceAsset.objectEtag.length > 200 || !Number.isSafeInteger(value.provenance.sourceAsset.objectSize) || value.provenance.sourceAsset.objectSize < 1 || typeof value.provenance.sourceAsset.pathDigest !== "string" || !/^sha256:[0-9a-f]{64}$/.test(value.provenance.sourceAsset.pathDigest) || value.provenance.sourceAsset.contentType !== null && (typeof value.provenance.sourceAsset.contentType !== "string" || value.provenance.sourceAsset.contentType.length < 1 || value.provenance.sourceAsset.contentType.length > 160) || typeof value.provenance.sourceAsset.contentDigest !== "string" || !/^sha256:[0-9a-f]{64}$/.test(value.provenance.sourceAsset.contentDigest) || value.provenance.sourceAsset.analysisRevision !== null && (!Number.isSafeInteger(value.provenance.sourceAsset.analysisRevision) || value.provenance.sourceAsset.analysisRevision < 0) || value.provenance.sourceAsset.analysisDigest !== null && (typeof value.provenance.sourceAsset.analysisDigest !== "string" || !/^sha256:[0-9a-f]{64}$/.test(
4088
4123
  value.provenance.sourceAsset.analysisDigest
4089
4124
  )) || value.provenance.sourceAsset.assetId !== value.provenance.sourceAssetId) || value.provenance.sourceAssetId === null !== (value.provenance.sourceAsset === null) || value.provenance.messageId !== null && (typeof value.provenance.messageId !== "string" || value.provenance.messageId.length < 1 || value.provenance.messageId.length > 200) || value.provenance.turnId !== null && (typeof value.provenance.turnId !== "string" || value.provenance.turnId.length < 1 || value.provenance.turnId.length > 200) || !Array.isArray(value.provenance.taintLabels) || value.provenance.taintLabels.length > 16 || value.provenance.taintLabels.some((label) => typeof label !== "string" || label.length < 1 || label.length > 120) || new Set(value.provenance.taintLabels).size !== value.provenance.taintLabels.length) throw new BrandInputError('"reviewedProposal.provenance" is invalid');
4090
4125
  return value;
@@ -4257,7 +4292,7 @@ async function proposalReceiptByMutation(ctx, mutationKey) {
4257
4292
  const row = (result[BRAND_NS.approvalReceipt] ?? [])[0];
4258
4293
  if (!row || !Array.isArray(row.book) || row.book.length !== 1 || row.book[0]?.id !== row.bookId) return void 0;
4259
4294
  const { book: _book, ...receipt2 } = row;
4260
- return receipt2;
4295
+ return receiptAsWritten(receipt2);
4261
4296
  }
4262
4297
  async function consumeProposalAuthority(ctx, req, bookId, proposalId, actionDigest, mutationKey) {
4263
4298
  const authority = await ctx.consumeHumanExact({
@@ -4291,7 +4326,8 @@ async function receipt(ctx, id, actionDigest, expected) {
4291
4326
  });
4292
4327
  const row = (result[BRAND_NS.approvalReceipt] ?? [])[0];
4293
4328
  if (!row || row.actionDigest !== actionDigest || row.resolution !== "accepted" || row.bookId !== expected.bookId || row.reviewedProposal.bookId !== expected.bookId || row.reviewedProposal.kind !== expected.kind || !Array.isArray(row.book) || row.book.length !== 1 || row.book[0]?.id !== expected.bookId || expected.paletteId !== void 0 && row.paletteId !== expected.paletteId || expected.proposalId !== void 0 && row.proposalId !== expected.proposalId || expected.content !== void 0 && canonicalBrandJson(row.reviewedProposal.payload.content) !== canonicalBrandJson(expected.content)) throw new BrandConflictError(`approval receipt ${id} is invalid`);
4294
- const { book: _book, ...unhydrated } = row;
4329
+ const { book: _book, ...stored } = row;
4330
+ const unhydrated = receiptAsWritten(stored);
4295
4331
  if (!await verifyBrandApprovalReceipt(unhydrated))
4296
4332
  throw new BrandConflictError(`approval receipt ${id} is invalid`);
4297
4333
  return unhydrated;
@@ -4529,9 +4565,9 @@ async function proposalDecisionState(ctx, req, book, proposal, resolution, recei
4529
4565
  }
4530
4566
 
4531
4567
  // src/routes/proposals.ts
4532
- var record4 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
4568
+ var record5 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
4533
4569
  var sameStrings2 = (left, right) => left.length === right.length && left.every((value, index) => value === right[index]);
4534
- var guardFailure2 = (error) => record4(error) && error.code === "transact_guard_failed";
4570
+ var guardFailure2 = (error) => record5(error) && error.code === "transact_guard_failed";
4535
4571
  async function handleProposalResolution(ctx, req, bookId, proposalId) {
4536
4572
  if (req.method !== "POST") return methodNotAllowed();
4537
4573
  const book = await loadMemberBook(ctx.db, bookId, ctx.actor.id);
@@ -4578,7 +4614,7 @@ async function handleProposalResolution(ctx, req, bookId, proposalId) {
4578
4614
  book: {}
4579
4615
  }
4580
4616
  });
4581
- const proposal = (result[BRAND_NS.proposal] ?? [])[0];
4617
+ const proposal = proposalAsWritten((result[BRAND_NS.proposal] ?? [])[0]);
4582
4618
  if (!proposal || proposal.bookId !== book.id || !Array.isArray(proposal.book) || proposal.book.length !== 1 || proposal.book[0]?.id !== book.id) throw new BrandNotFoundError(`proposal ${proposalId}`);
4583
4619
  const current = proposalReviewSnapshot(proposal);
4584
4620
  const { reviewDigest, ...unsignedReview } = current;
@@ -4711,7 +4747,7 @@ async function handleProposalResolution(ctx, req, bookId, proposalId) {
4711
4747
  }
4712
4748
 
4713
4749
  // src/routes/tokens.ts
4714
- var record5 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
4750
+ var record6 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
4715
4751
  var missing = (bookId) => {
4716
4752
  throw new BrandNotFoundError(`compiled tokens for brand book ${bookId}`);
4717
4753
  };
@@ -4724,13 +4760,14 @@ async function acceptedReceipt(db, bookId, receiptId, actionDigest) {
4724
4760
  });
4725
4761
  const receipt2 = (result[BRAND_NS.approvalReceipt] ?? [])[0];
4726
4762
  if (!receipt2 || receipt2.resolution !== "accepted" || receipt2.actionDigest !== actionDigest || !Array.isArray(receipt2.book) || receipt2.book.length !== 1 || receipt2.book[0]?.id !== bookId) return missing(bookId);
4727
- const { book: _book, ...unhydrated } = receipt2;
4763
+ const { book: _book, ...stored } = receipt2;
4764
+ const unhydrated = receiptAsWritten(stored);
4728
4765
  if (!await verifyBrandApprovalReceipt(unhydrated)) return missing(bookId);
4729
4766
  return unhydrated;
4730
4767
  }
4731
4768
  async function approvedCache(db, book) {
4732
4769
  const cache = book.tokens;
4733
- if (!cache || !record5(cache.light) || !record5(cache.dark) || !book.activePaletteId || cache.paletteId !== book.activePaletteId || !cache.paletteReceiptId || !cache.paletteActionDigest || !cache.sourceDigest) return missing(book.id);
4770
+ if (!cache || !record6(cache.light) || !record6(cache.dark) || !book.activePaletteId || cache.paletteId !== book.activePaletteId || !cache.paletteReceiptId || !cache.paletteActionDigest || !cache.sourceDigest) return missing(book.id);
4734
4771
  const paletteResult = await db.query({
4735
4772
  [BRAND_NS.palette]: {
4736
4773
  $: { where: { id: cache.paletteId, bookId: book.id } },