@odla-ai/brand 0.8.3 → 0.8.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.
package/dist/index.cjs CHANGED
@@ -71,6 +71,7 @@ __export(src_exports, {
71
71
  SECTION_STATUSES: () => SECTION_STATUSES,
72
72
  SWATCH_ROLES: () => SWATCH_ROLES,
73
73
  acceptProposalOps: () => acceptProposalOps,
74
+ activationReceiptAsWritten: () => activationReceiptAsWritten,
74
75
  adjustLightnessUntil: () => adjustLightnessUntil,
75
76
  analogous: () => analogous,
76
77
  assertAnalysis: () => assertAnalysis,
@@ -187,6 +188,7 @@ __export(src_exports, {
187
188
  triadic: () => triadic,
188
189
  updateBookOps: () => updateBookOps,
189
190
  upsertSectionOps: () => upsertSectionOps,
191
+ verifyBrandActivationReceipt: () => verifyBrandActivationReceipt,
190
192
  verifyBrandApprovalReceipt: () => verifyBrandApprovalReceipt
191
193
  });
192
194
  module.exports = __toCommonJS(src_exports);
@@ -198,6 +200,7 @@ var BRAND_NS = {
198
200
  palette: "brand_palette",
199
201
  proposal: "brand_proposal",
200
202
  approvalReceipt: "brand_approval_receipt",
203
+ activationReceipt: "brand_activation_receipt",
201
204
  asset: "brand_asset"
202
205
  };
203
206
  var BOOK_STATUSES = ["draft", "active", "archived"];
@@ -322,6 +325,8 @@ var BRAND_SCHEMA = {
322
325
  activationRevision: a("number"),
323
326
  tokens: opt("json"),
324
327
  // maps + complete palette/typography receipt provenance
328
+ activationReceiptId: opt("string"),
329
+ activationActionDigest: opt("string"),
325
330
  summary: opt("string"),
326
331
  createdAt: idx("date"),
327
332
  updatedAt: idx("date")
@@ -416,6 +421,23 @@ var BRAND_SCHEMA = {
416
421
  receiptDigest: idx("string")
417
422
  }
418
423
  },
424
+ [BRAND_NS.activationReceipt]: {
425
+ attrs: {
426
+ id: uniq("string"),
427
+ version: a("number"),
428
+ mutationKey: uniq("string"),
429
+ bookId: idx("string"),
430
+ reviewedBook: a("json"),
431
+ actionDigest: idx("string"),
432
+ approvedBy: idx("string"),
433
+ approvedByKind: a("string"),
434
+ authorityRef: idx("string"),
435
+ authorityCapability: a("string"),
436
+ authorityConsumption: a("json"),
437
+ createdAt: idx("date"),
438
+ receiptDigest: idx("string")
439
+ }
440
+ },
419
441
  [BRAND_NS.asset]: {
420
442
  attrs: {
421
443
  id: uniq("string"),
@@ -470,6 +492,10 @@ var BRAND_SCHEMA = {
470
492
  forward: { on: BRAND_NS.approvalReceipt, has: "one", label: "book" },
471
493
  reverse: { on: BRAND_NS.book, has: "many", label: "approvalReceipts" }
472
494
  },
495
+ brandActivationReceiptBook: {
496
+ forward: { on: BRAND_NS.activationReceipt, has: "one", label: "book" },
497
+ reverse: { on: BRAND_NS.book, has: "many", label: "activationReceipts" }
498
+ },
473
499
  brandAssetBook: {
474
500
  forward: { on: BRAND_NS.asset, has: "one", label: "book" },
475
501
  reverse: { on: BRAND_NS.book, has: "many", label: "assets" }
@@ -511,6 +537,15 @@ function receiptAsWritten(row) {
511
537
  for (const key of systemAttrs) delete out[key];
512
538
  return out;
513
539
  }
540
+ function activationReceiptAsWritten(row) {
541
+ const normalized = rowAsWritten(BRAND_NS.activationReceipt, row);
542
+ if (!record(normalized)) return normalized;
543
+ const systemAttrs = Object.keys(normalized).filter((key) => key.startsWith("$"));
544
+ if (systemAttrs.length === 0) return normalized;
545
+ const out = { ...normalized };
546
+ for (const key of systemAttrs) delete out[key];
547
+ return out;
548
+ }
514
549
  function proposalAsWritten(row) {
515
550
  return rowAsWritten(BRAND_NS.proposal, row);
516
551
  }
@@ -582,6 +617,12 @@ var BRAND_RULES = {
582
617
  update: "false",
583
618
  delete: "false"
584
619
  },
620
+ [BRAND_NS.activationReceipt]: {
621
+ view: CURRENT_BOOK_MEMBER,
622
+ create: "false",
623
+ update: "false",
624
+ delete: "false"
625
+ },
585
626
  [BRAND_NS.asset]: {
586
627
  view: CURRENT_BOOK_MEMBER,
587
628
  create: "false",
@@ -823,15 +864,106 @@ async function brandJsonDigest(value) {
823
864
  return `sha256:${[...new Uint8Array(bytes)].map((byte) => byte.toString(16).padStart(2, "0")).join("")}`;
824
865
  }
825
866
 
826
- // src/review.ts
867
+ // src/activation-review.ts
827
868
  var DIGEST = /^sha256:[0-9a-f]{64}$/;
828
- var record3 = (value) => typeof value === "object" && value !== null && !Array.isArray(value) && (Object.getPrototypeOf(value) === Object.prototype || Object.getPrototypeOf(value) === null);
829
- var exactKeys = (value, required, optional = []) => {
830
- const allowed = /* @__PURE__ */ new Set([...required, ...optional]);
869
+ var record3 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
870
+ var exactKeys = (value, required) => {
871
+ const allowed = new Set(required);
831
872
  return required.every((key) => Object.hasOwn(value, key)) && Object.keys(value).every((key) => allowed.has(key));
832
873
  };
833
874
  var boundedString = (value, max = 200) => typeof value === "string" && value.length > 0 && value.length <= max;
834
875
  var safeTime = (value) => Number.isSafeInteger(value) && value >= 0;
876
+ var SNAPSHOT_KEYS = [
877
+ "version",
878
+ "bookId",
879
+ "slug",
880
+ "status",
881
+ "bookVersion",
882
+ "activationRevision",
883
+ "activePaletteId",
884
+ "memberIdsDigest",
885
+ "tokensSourceDigest",
886
+ "paletteReceiptId",
887
+ "paletteActionDigest",
888
+ "typographyReceiptId",
889
+ "typographyActionDigest",
890
+ "approvedSectionKeys",
891
+ "promotionDigest"
892
+ ];
893
+ function validSnapshot(value) {
894
+ if (!record3(value) || !exactKeys(value, SNAPSHOT_KEYS)) return false;
895
+ const sectionKeys = value.approvedSectionKeys;
896
+ return value.version === 1 && boundedString(value.bookId) && boundedString(value.slug) && value.status === "draft" && Number.isSafeInteger(value.bookVersion) && value.bookVersion > 0 && Number.isSafeInteger(value.activationRevision) && value.activationRevision > 0 && boundedString(value.activePaletteId) && typeof value.memberIdsDigest === "string" && DIGEST.test(value.memberIdsDigest) && typeof value.tokensSourceDigest === "string" && DIGEST.test(value.tokensSourceDigest) && boundedString(value.paletteReceiptId) && typeof value.paletteActionDigest === "string" && DIGEST.test(value.paletteActionDigest) && boundedString(value.typographyReceiptId) && typeof value.typographyActionDigest === "string" && DIGEST.test(value.typographyActionDigest) && Array.isArray(sectionKeys) && sectionKeys.length > 0 && sectionKeys.length <= 5 && sectionKeys.every((key) => boundedString(key)) && new Set(sectionKeys).size === sectionKeys.length && [...sectionKeys].sort().every((key, index) => key === sectionKeys[index]) && typeof value.promotionDigest === "string" && DIGEST.test(value.promotionDigest);
897
+ }
898
+ var CONSUMPTION_KEYS = [
899
+ "id",
900
+ "grantId",
901
+ "grantVersion",
902
+ "useNumber",
903
+ "actorPrincipalId",
904
+ "actorKind",
905
+ "credentialId",
906
+ "credentialKind",
907
+ "appId",
908
+ "appIncarnation",
909
+ "capability",
910
+ "projectCapability",
911
+ "effect",
912
+ "actionDigest",
913
+ "resourceDigest",
914
+ "constraintEvidence",
915
+ "consumptionIdempotencyKey",
916
+ "requestDigest",
917
+ "consumedAt"
918
+ ];
919
+ function validConsumption(value) {
920
+ return record3(value) && exactKeys(value, CONSUMPTION_KEYS) && 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.book.activate" && value.projectCapability === "brand.approve" && value.effect === "external" && 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);
921
+ }
922
+ var RECEIPT_KEYS = [
923
+ "version",
924
+ "id",
925
+ "mutationKey",
926
+ "bookId",
927
+ "reviewedBook",
928
+ "actionDigest",
929
+ "approvedBy",
930
+ "approvedByKind",
931
+ "authorityRef",
932
+ "authorityCapability",
933
+ "authorityConsumption",
934
+ "createdAt",
935
+ "receiptDigest"
936
+ ];
937
+ async function verifyBrandActivationReceipt(receipt2) {
938
+ try {
939
+ if (!isBoundedBrandJson(receipt2, { maxDepth: 12, maxNodes: 1e3, maxBytes: 32 * 1024 }) || !record3(receipt2) || !exactKeys(receipt2, RECEIPT_KEYS)) return false;
940
+ if (receipt2.version !== 1 || !boundedString(receipt2.id) || !boundedString(receipt2.mutationKey) || !boundedString(receipt2.bookId) || !validSnapshot(receipt2.reviewedBook) || typeof receipt2.actionDigest !== "string" || !DIGEST.test(receipt2.actionDigest) || !boundedString(receipt2.approvedBy) || receipt2.approvedByKind !== "human" || !boundedString(receipt2.authorityRef) || receipt2.authorityCapability !== "brand.approve" || !validConsumption(receipt2.authorityConsumption) || !safeTime(receipt2.createdAt) || typeof receipt2.receiptDigest !== "string" || !DIGEST.test(receipt2.receiptDigest)) return false;
941
+ const reviewed = receipt2.reviewedBook;
942
+ const authority = receipt2.authorityConsumption;
943
+ if (reviewed.bookId !== receipt2.bookId || authority.actorPrincipalId !== receipt2.approvedBy || authority.actionDigest !== receipt2.actionDigest || authority.consumptionIdempotencyKey !== receipt2.mutationKey || receipt2.authorityRef !== `authority:${authority.grantId}:v${authority.grantVersion}` || authority.consumedAt > receipt2.createdAt || await brandJsonDigest({ bookId: receipt2.bookId }) !== authority.resourceDigest) return false;
944
+ const expectedAction = await brandJsonDigest({
945
+ version: 1,
946
+ reviewedBook: reviewed,
947
+ intendedStatus: "active",
948
+ activationReceiptId: receipt2.id
949
+ });
950
+ if (expectedAction !== receipt2.actionDigest) return false;
951
+ const { receiptDigest, ...immutable } = receipt2;
952
+ return await brandJsonDigest(immutable) === receiptDigest;
953
+ } catch {
954
+ return false;
955
+ }
956
+ }
957
+
958
+ // src/review.ts
959
+ var DIGEST2 = /^sha256:[0-9a-f]{64}$/;
960
+ var record4 = (value) => typeof value === "object" && value !== null && !Array.isArray(value) && (Object.getPrototypeOf(value) === Object.prototype || Object.getPrototypeOf(value) === null);
961
+ var exactKeys2 = (value, required, optional = []) => {
962
+ const allowed = /* @__PURE__ */ new Set([...required, ...optional]);
963
+ return required.every((key) => Object.hasOwn(value, key)) && Object.keys(value).every((key) => allowed.has(key));
964
+ };
965
+ var boundedString2 = (value, max = 200) => typeof value === "string" && value.length > 0 && value.length <= max;
966
+ var safeTime2 = (value) => Number.isSafeInteger(value) && value >= 0;
835
967
  function brandAuthorityRef(consumption) {
836
968
  return `authority:${consumption.grantId}:v${consumption.grantVersion}`;
837
969
  }
@@ -850,15 +982,15 @@ var SNAPSHOT_REQUIRED = [
850
982
  "createdAt"
851
983
  ];
852
984
  function validSnapshotShape(value) {
853
- if (!record3(value) || !exactKeys(value, SNAPSHOT_REQUIRED)) return false;
985
+ if (!record4(value) || !exactKeys2(value, SNAPSHOT_REQUIRED)) return false;
854
986
  const provenance2 = value.provenance;
855
- 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(provenance2) && exactKeys(provenance2, [
987
+ return boundedString2(value.id) && boundedString2(value.bookId) && typeof value.kind === "string" && PROPOSAL_KINDS.includes(value.kind) && value.status === "open" && record4(value.payload) && boundedString2(value.rationale, 2e3) && record4(provenance2) && exactKeys2(provenance2, [
856
988
  "sourceAssetId",
857
989
  "sourceAsset",
858
990
  "messageId",
859
991
  "turnId",
860
992
  "taintLabels"
861
- ]) && (provenance2.sourceAssetId === null || boundedString(provenance2.sourceAssetId)) && (provenance2.sourceAsset === null || record3(provenance2.sourceAsset) && exactKeys(provenance2.sourceAsset, [
993
+ ]) && (provenance2.sourceAssetId === null || boundedString2(provenance2.sourceAssetId)) && (provenance2.sourceAsset === null || record4(provenance2.sourceAsset) && exactKeys2(provenance2.sourceAsset, [
862
994
  "assetId",
863
995
  "contentDigest",
864
996
  "objectEtag",
@@ -867,7 +999,7 @@ function validSnapshotShape(value) {
867
999
  "contentType",
868
1000
  "analysisRevision",
869
1001
  "analysisDigest"
870
- ]) && boundedString(provenance2.sourceAsset.assetId) && typeof provenance2.sourceAsset.contentDigest === "string" && DIGEST.test(provenance2.sourceAsset.contentDigest) && boundedString(provenance2.sourceAsset.objectEtag) && Number.isSafeInteger(provenance2.sourceAsset.objectSize) && provenance2.sourceAsset.objectSize > 0 && typeof provenance2.sourceAsset.pathDigest === "string" && DIGEST.test(provenance2.sourceAsset.pathDigest) && (provenance2.sourceAsset.contentType === null || boundedString(provenance2.sourceAsset.contentType, 160)) && (provenance2.sourceAsset.analysisRevision === null || Number.isSafeInteger(provenance2.sourceAsset.analysisRevision) && provenance2.sourceAsset.analysisRevision >= 0) && (provenance2.sourceAsset.analysisDigest === null || typeof provenance2.sourceAsset.analysisDigest === "string" && DIGEST.test(provenance2.sourceAsset.analysisDigest))) && (provenance2.sourceAssetId === null && provenance2.sourceAsset === null || provenance2.sourceAssetId !== null && provenance2.sourceAsset !== null && provenance2.sourceAsset.assetId === provenance2.sourceAssetId) && (provenance2.messageId === null || boundedString(provenance2.messageId)) && (provenance2.turnId === null || boundedString(provenance2.turnId)) && Array.isArray(provenance2.taintLabels) && provenance2.taintLabels.length <= 16 && provenance2.taintLabels.every((label) => boundedString(label, 120)) && new Set(provenance2.taintLabels).size === provenance2.taintLabels.length && typeof value.reviewDigest === "string" && DIGEST.test(value.reviewDigest) && Array.isArray(value.audience) && value.audience.length > 0 && value.audience.length <= 100 && value.audience.every((id) => boundedString(id)) && new Set(value.audience).size === value.audience.length && boundedString(value.createdBy) && boundedString(value.createdAuthorityRef) && safeTime(value.createdAt);
1002
+ ]) && boundedString2(provenance2.sourceAsset.assetId) && typeof provenance2.sourceAsset.contentDigest === "string" && DIGEST2.test(provenance2.sourceAsset.contentDigest) && boundedString2(provenance2.sourceAsset.objectEtag) && Number.isSafeInteger(provenance2.sourceAsset.objectSize) && provenance2.sourceAsset.objectSize > 0 && typeof provenance2.sourceAsset.pathDigest === "string" && DIGEST2.test(provenance2.sourceAsset.pathDigest) && (provenance2.sourceAsset.contentType === null || boundedString2(provenance2.sourceAsset.contentType, 160)) && (provenance2.sourceAsset.analysisRevision === null || Number.isSafeInteger(provenance2.sourceAsset.analysisRevision) && provenance2.sourceAsset.analysisRevision >= 0) && (provenance2.sourceAsset.analysisDigest === null || typeof provenance2.sourceAsset.analysisDigest === "string" && DIGEST2.test(provenance2.sourceAsset.analysisDigest))) && (provenance2.sourceAssetId === null && provenance2.sourceAsset === null || provenance2.sourceAssetId !== null && provenance2.sourceAsset !== null && provenance2.sourceAsset.assetId === provenance2.sourceAssetId) && (provenance2.messageId === null || boundedString2(provenance2.messageId)) && (provenance2.turnId === null || boundedString2(provenance2.turnId)) && Array.isArray(provenance2.taintLabels) && provenance2.taintLabels.length <= 16 && provenance2.taintLabels.every((label) => boundedString2(label, 120)) && new Set(provenance2.taintLabels).size === provenance2.taintLabels.length && typeof value.reviewDigest === "string" && DIGEST2.test(value.reviewDigest) && Array.isArray(value.audience) && value.audience.length > 0 && value.audience.length <= 100 && value.audience.every((id) => boundedString2(id)) && new Set(value.audience).size === value.audience.length && boundedString2(value.createdBy) && boundedString2(value.createdAuthorityRef) && safeTime2(value.createdAt);
871
1003
  }
872
1004
  var CONSUMPTION_REQUIRED = [
873
1005
  "id",
@@ -891,8 +1023,8 @@ var CONSUMPTION_REQUIRED = [
891
1023
  "consumedAt"
892
1024
  ];
893
1025
  function isBrandHumanAuthorityConsumption(value) {
894
- if (!record3(value) || !exactKeys(value, CONSUMPTION_REQUIRED)) return false;
895
- 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);
1026
+ if (!record4(value) || !exactKeys2(value, CONSUMPTION_REQUIRED)) return false;
1027
+ return boundedString2(value.id) && boundedString2(value.grantId) && Number.isSafeInteger(value.grantVersion) && value.grantVersion > 0 && Number.isSafeInteger(value.useNumber) && value.useNumber > 0 && boundedString2(value.actorPrincipalId) && value.actorKind === "human" && boundedString2(value.credentialId) && value.credentialKind === "clerk" && boundedString2(value.appId) && typeof value.appIncarnation === "string" && /^[a-f0-9]{32}$/.test(value.appIncarnation) && (value.capability === "brand.proposal.resolve" || value.capability === "brand.book.activate") && value.projectCapability === "brand.approve" && (value.effect === "internal" || value.effect === "external") && typeof value.actionDigest === "string" && DIGEST2.test(value.actionDigest) && typeof value.resourceDigest === "string" && DIGEST2.test(value.resourceDigest) && record4(value.constraintEvidence) && boundedString2(value.consumptionIdempotencyKey) && typeof value.requestDigest === "string" && DIGEST2.test(value.requestDigest) && safeTime2(value.consumedAt);
896
1028
  }
897
1029
  var RECEIPT_REQUIRED = [
898
1030
  "version",
@@ -918,10 +1050,10 @@ async function verifyBrandApprovalReceipt(receipt2) {
918
1050
  try {
919
1051
  if (!isBoundedBrandJson(receipt2, { maxDepth: 14, maxNodes: 2500, maxBytes: 48 * 1024 }))
920
1052
  return false;
921
- if (!record3(receipt2) || !exactKeys(receipt2, RECEIPT_REQUIRED, ["paletteId", "resolutionNote"]))
1053
+ if (!record4(receipt2) || !exactKeys2(receipt2, RECEIPT_REQUIRED, ["paletteId", "resolutionNote"]))
922
1054
  return false;
923
1055
  const binding = receipt2.decisionBinding;
924
- 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, [
1056
+ if (receipt2.version !== 1 || !boundedString2(receipt2.id) || !boundedString2(receipt2.mutationKey) || !boundedString2(receipt2.bookId) || !boundedString2(receipt2.proposalId) || receipt2.resolution !== "accepted" && receipt2.resolution !== "rejected" || !validSnapshotShape(receipt2.reviewedProposal) || typeof receipt2.actionDigest !== "string" || !DIGEST2.test(receipt2.actionDigest) || !record4(binding) || !exactKeys2(binding, [
925
1057
  "version",
926
1058
  "bookVersion",
927
1059
  "activationRevision",
@@ -931,10 +1063,10 @@ async function verifyBrandApprovalReceipt(receipt2) {
931
1063
  "typographyDigest",
932
1064
  "sourceAssetDigest",
933
1065
  "effectDigest"
934
- ]) || binding.version !== 1 || !Number.isSafeInteger(binding.bookVersion) || binding.bookVersion < 1 || !Number.isSafeInteger(binding.activationRevision) || binding.activationRevision < 0 || binding.activePaletteId !== null && !boundedString(binding.activePaletteId) || typeof binding.memberIdsDigest !== "string" || !DIGEST.test(binding.memberIdsDigest) || binding.activePaletteDigest !== null && (typeof binding.activePaletteDigest !== "string" || !DIGEST.test(binding.activePaletteDigest)) || binding.typographyDigest !== null && (typeof binding.typographyDigest !== "string" || !DIGEST.test(binding.typographyDigest)) || binding.sourceAssetDigest !== null && (typeof binding.sourceAssetDigest !== "string" || !DIGEST.test(binding.sourceAssetDigest)) || typeof binding.effectDigest !== "string" || !DIGEST.test(binding.effectDigest) || !boundedString(receipt2.approvedBy) || receipt2.approvedByKind !== "human" || !boundedString(receipt2.appliedBy) || receipt2.appliedByKind !== "human" || !boundedString(receipt2.authorityRef) || receipt2.authorityCapability !== "brand.approve" || !isBrandHumanAuthorityConsumption(receipt2.authorityConsumption) || !safeTime(receipt2.createdAt) || typeof receipt2.receiptDigest !== "string" || !DIGEST.test(receipt2.receiptDigest) || receipt2.paletteId !== void 0 && !boundedString(receipt2.paletteId) || receipt2.resolutionNote !== void 0 && !boundedString(receipt2.resolutionNote, 1e3)) return false;
1066
+ ]) || binding.version !== 1 || !Number.isSafeInteger(binding.bookVersion) || binding.bookVersion < 1 || !Number.isSafeInteger(binding.activationRevision) || binding.activationRevision < 0 || binding.activePaletteId !== null && !boundedString2(binding.activePaletteId) || typeof binding.memberIdsDigest !== "string" || !DIGEST2.test(binding.memberIdsDigest) || binding.activePaletteDigest !== null && (typeof binding.activePaletteDigest !== "string" || !DIGEST2.test(binding.activePaletteDigest)) || binding.typographyDigest !== null && (typeof binding.typographyDigest !== "string" || !DIGEST2.test(binding.typographyDigest)) || binding.sourceAssetDigest !== null && (typeof binding.sourceAssetDigest !== "string" || !DIGEST2.test(binding.sourceAssetDigest)) || typeof binding.effectDigest !== "string" || !DIGEST2.test(binding.effectDigest) || !boundedString2(receipt2.approvedBy) || receipt2.approvedByKind !== "human" || !boundedString2(receipt2.appliedBy) || receipt2.appliedByKind !== "human" || !boundedString2(receipt2.authorityRef) || receipt2.authorityCapability !== "brand.approve" || !isBrandHumanAuthorityConsumption(receipt2.authorityConsumption) || !safeTime2(receipt2.createdAt) || typeof receipt2.receiptDigest !== "string" || !DIGEST2.test(receipt2.receiptDigest) || receipt2.paletteId !== void 0 && !boundedString2(receipt2.paletteId) || receipt2.resolutionNote !== void 0 && !boundedString2(receipt2.resolutionNote, 1e3)) return false;
935
1067
  const reviewed = receipt2.reviewedProposal;
936
1068
  const authority = receipt2.authorityConsumption;
937
- if (reviewed.id !== receipt2.proposalId || reviewed.bookId !== receipt2.bookId || receipt2.approvedBy !== receipt2.appliedBy || authority.actorPrincipalId !== receipt2.approvedBy || authority.actionDigest !== receipt2.actionDigest || authority.consumptionIdempotencyKey !== receipt2.mutationKey || receipt2.authorityRef !== brandAuthorityRef(authority) || authority.consumedAt > receipt2.createdAt || receipt2.resolution === "rejected" && receipt2.paletteId !== void 0 || receipt2.resolution === "accepted" && reviewed.kind === "palette" && !receipt2.paletteId || reviewed.kind !== "palette" && receipt2.paletteId !== void 0) return false;
1069
+ if (authority.capability !== "brand.proposal.resolve" || authority.effect !== "internal" || reviewed.id !== receipt2.proposalId || reviewed.bookId !== receipt2.bookId || receipt2.approvedBy !== receipt2.appliedBy || authority.actorPrincipalId !== receipt2.approvedBy || authority.actionDigest !== receipt2.actionDigest || authority.consumptionIdempotencyKey !== receipt2.mutationKey || receipt2.authorityRef !== brandAuthorityRef(authority) || authority.consumedAt > receipt2.createdAt || receipt2.resolution === "rejected" && receipt2.paletteId !== void 0 || receipt2.resolution === "accepted" && reviewed.kind === "palette" && !receipt2.paletteId || reviewed.kind !== "palette" && receipt2.paletteId !== void 0) return false;
938
1070
  const { reviewDigest, ...unsignedReview } = reviewed;
939
1071
  if (await brandJsonDigest(unsignedReview) !== reviewDigest) return false;
940
1072
  const expectedAction = await brandJsonDigest({
@@ -3777,14 +3909,14 @@ async function uploadAsset(ctx, req, bookId) {
3777
3909
  "internal",
3778
3910
  book.id
3779
3911
  );
3780
- const record7 = await ctx.db.storage.upload(
3912
+ const record9 = await ctx.db.storage.upload(
3781
3913
  path,
3782
3914
  file,
3783
3915
  contentType,
3784
3916
  { private: true }
3785
3917
  );
3786
- if (record7.path !== path) {
3787
- await ctx.db.storage.delete(record7.path);
3918
+ if (record9.path !== path) {
3919
+ await ctx.db.storage.delete(record9.path);
3788
3920
  throw new BrandConflictError(
3789
3921
  "private storage returned an unexpected asset path"
3790
3922
  );
@@ -3803,11 +3935,11 @@ async function uploadAsset(ctx, req, bookId) {
3803
3935
  id,
3804
3936
  bookId: book.id,
3805
3937
  kind,
3806
- path: record7.path,
3807
- storageObjectId: record7.id,
3938
+ path: record9.path,
3939
+ storageObjectId: record9.id,
3808
3940
  contentDigest: await contentDigest(file),
3809
3941
  contentType,
3810
- size: record7.size,
3942
+ size: record9.size,
3811
3943
  uploadedBy: ctx.actor.id,
3812
3944
  uploadedAuthorityRef: authority.authorityRef,
3813
3945
  audience: book.memberIds,
@@ -3834,7 +3966,7 @@ async function uploadAsset(ctx, req, bookId) {
3834
3966
  asPrincipalKind: "human"
3835
3967
  });
3836
3968
  } catch (error) {
3837
- await ctx.db.storage.delete(record7.path);
3969
+ await ctx.db.storage.delete(record9.path);
3838
3970
  throw error;
3839
3971
  }
3840
3972
  return json(await linkedAsset(ctx, book, id), 201);
@@ -4101,7 +4233,7 @@ async function handleDesignDigest(ctx, req, bookId, assetId) {
4101
4233
  }
4102
4234
 
4103
4235
  // src/routes/proposal-input.ts
4104
- var SNAPSHOT_KEYS = [
4236
+ var SNAPSHOT_KEYS2 = [
4105
4237
  "audience",
4106
4238
  "bookId",
4107
4239
  "createdAt",
@@ -4115,7 +4247,7 @@ var SNAPSHOT_KEYS = [
4115
4247
  "reviewDigest",
4116
4248
  "status"
4117
4249
  ];
4118
- var record4 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
4250
+ var record5 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
4119
4251
  function parseProposalResolutionRequest(body, bookId, proposalId) {
4120
4252
  const allowed = /* @__PURE__ */ new Set([
4121
4253
  "mutationId",
@@ -4159,13 +4291,13 @@ function proposalReviewSnapshot(proposal) {
4159
4291
  function parseReviewedProposal(value, bookId, proposalId) {
4160
4292
  if (!isBoundedBrandJson(value, { maxDepth: 12, maxNodes: 1500, maxBytes: 32 * 1024 }))
4161
4293
  throw new BrandInputError('"reviewedProposal" is too deeply nested, complex, or large');
4162
- if (!record4(value)) throw new BrandInputError('"reviewedProposal" must be an object');
4294
+ if (!record5(value)) throw new BrandInputError('"reviewedProposal" must be an object');
4163
4295
  const keys = Object.keys(value).sort();
4164
- if (keys.length !== SNAPSHOT_KEYS.length || !SNAPSHOT_KEYS.every((key, index) => key === keys[index])) throw new BrandInputError('"reviewedProposal" has an invalid shape');
4296
+ if (keys.length !== SNAPSHOT_KEYS2.length || !SNAPSHOT_KEYS2.every((key, index) => key === keys[index])) throw new BrandInputError('"reviewedProposal" has an invalid shape');
4165
4297
  if (value.id !== proposalId || value.bookId !== bookId || value.status !== "open")
4166
4298
  throw new BrandInputError('"reviewedProposal" must identify this open proposal');
4167
- 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');
4168
- 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(
4299
+ if (typeof value.kind !== "string" || !PROPOSAL_KINDS.includes(value.kind) || !record5(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');
4300
+ if (!record5(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 && (!record5(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(
4169
4301
  value.provenance.sourceAsset.analysisDigest
4170
4302
  )) || 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');
4171
4303
  return value;
@@ -4242,11 +4374,11 @@ async function proposalDecisionOps(input) {
4242
4374
  input.approvedTypography?.approvalReceiptId,
4243
4375
  input.approvedTypography?.approvalActionDigest
4244
4376
  );
4245
- const bookOp = ops.find(
4377
+ const bookOp2 = ops.find(
4246
4378
  (op) => op.t === "update" && op.ns === BRAND_NS.book && op.id === input.book.id
4247
4379
  );
4248
- if (!bookOp || bookOp.t !== "update") throw new Error("book activation op missing");
4249
- Object.assign(bookOp.attrs, {
4380
+ if (!bookOp2 || bookOp2.t !== "update") throw new Error("book activation op missing");
4381
+ Object.assign(bookOp2.attrs, {
4250
4382
  version: input.book.version + 1,
4251
4383
  activationRevision: input.book.activationRevision + 1,
4252
4384
  tokens
@@ -4298,13 +4430,13 @@ async function proposalDecisionOps(input) {
4298
4430
  }
4299
4431
  ];
4300
4432
  if (input.proposal.kind === "typography" && input.priorPalette) {
4301
- const bookOp = ops.find(
4433
+ const bookOp2 = ops.find(
4302
4434
  (op) => op.t === "update" && op.ns === BRAND_NS.book && op.id === input.book.id
4303
4435
  );
4304
- if (bookOp?.t === "update") {
4436
+ if (bookOp2?.t === "update") {
4305
4437
  if (!input.priorPalette.approvalReceiptId || !input.priorPalette.approvalActionDigest)
4306
4438
  throw new BrandInputError("active palette is missing approval provenance");
4307
- bookOp.attrs.tokens = await tokenSnapshot(
4439
+ bookOp2.attrs.tokens = await tokenSnapshot(
4308
4440
  input.priorPalette.swatches,
4309
4441
  content,
4310
4442
  input,
@@ -4317,6 +4449,19 @@ async function proposalDecisionOps(input) {
4317
4449
  }
4318
4450
  }
4319
4451
  }
4452
+ const bookOp = ops.find(
4453
+ (candidate) => candidate.t === "update" && candidate.ns === BRAND_NS.book && candidate.id === input.book.id
4454
+ );
4455
+ if (!bookOp || bookOp.t !== "update") throw new Error("book approval op missing");
4456
+ bookOp.attrs.status = "draft";
4457
+ if (input.book.activationReceiptId || input.book.activationActionDigest) {
4458
+ ops.push({
4459
+ t: "retract",
4460
+ ns: BRAND_NS.book,
4461
+ id: input.book.id,
4462
+ attrs: ["activationReceiptId", "activationActionDigest"]
4463
+ });
4464
+ }
4320
4465
  stampProposal(ops, input);
4321
4466
  return ops;
4322
4467
  }
@@ -4356,7 +4501,7 @@ async function consumeProposalAuthority(ctx, req, bookId, proposalId, actionDige
4356
4501
  consumptionIdempotencyKey: mutationKey
4357
4502
  });
4358
4503
  const resourceDigest = await brandJsonDigest({ bookId, proposalId });
4359
- if (!isBrandHumanAuthorityConsumption(authority) || authority.actorPrincipalId !== ctx.actor.id || authority.appId !== ctx.appId || authority.actionDigest !== actionDigest || authority.resourceDigest !== resourceDigest || authority.consumptionIdempotencyKey !== mutationKey) throw new BrandForbiddenError(
4504
+ if (!isBrandHumanAuthorityConsumption(authority) || authority.capability !== "brand.proposal.resolve" || authority.projectCapability !== "brand.approve" || authority.effect !== "internal" || authority.actorPrincipalId !== ctx.actor.id || authority.appId !== ctx.appId || authority.actionDigest !== actionDigest || authority.resourceDigest !== resourceDigest || authority.consumptionIdempotencyKey !== mutationKey) throw new BrandForbiddenError(
4360
4505
  "a verified human-exact brand approval is required"
4361
4506
  );
4362
4507
  return authority;
@@ -4526,7 +4671,7 @@ async function proposalDecisionState(ctx, req, book, proposal, resolution, recei
4526
4671
  status: "active",
4527
4672
  name: paletteDep.palette.name,
4528
4673
  swatches: paletteDep.palette.swatches,
4529
- seedHex: paletteDep.palette.seedHex ?? null,
4674
+ ...paletteDep.palette.seedHex ? { seedHex: paletteDep.palette.seedHex } : {},
4530
4675
  source: paletteDep.palette.source,
4531
4676
  rationale: paletteDep.palette.rationale,
4532
4677
  proposalId: paletteDep.palette.proposalId,
@@ -4620,9 +4765,9 @@ async function proposalDecisionState(ctx, req, book, proposal, resolution, recei
4620
4765
  }
4621
4766
 
4622
4767
  // src/routes/proposals.ts
4623
- var record5 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
4768
+ var record6 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
4624
4769
  var sameStrings2 = (left, right) => left.length === right.length && left.every((value, index) => value === right[index]);
4625
- var guardFailure2 = (error) => record5(error) && error.code === "transact_guard_failed";
4770
+ var guardFailure2 = (error) => record6(error) && error.code === "transact_guard_failed";
4626
4771
  async function handleProposalResolution(ctx, req, bookId, proposalId) {
4627
4772
  if (req.method !== "POST") return methodNotAllowed();
4628
4773
  const book = await loadMemberBook(ctx.db, bookId, ctx.actor.id);
@@ -4802,7 +4947,7 @@ async function handleProposalResolution(ctx, req, bookId, proposalId) {
4802
4947
  }
4803
4948
 
4804
4949
  // src/routes/tokens.ts
4805
- var record6 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
4950
+ var record7 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
4806
4951
  var missing = (bookId) => {
4807
4952
  throw new BrandNotFoundError(`compiled tokens for brand book ${bookId}`);
4808
4953
  };
@@ -4822,7 +4967,7 @@ async function acceptedReceipt(db, bookId, receiptId, actionDigest) {
4822
4967
  }
4823
4968
  async function approvedCache(db, book) {
4824
4969
  const cache = book.tokens;
4825
- if (!cache || !record6(cache.light) || !record6(cache.dark) || !book.activePaletteId || cache.paletteId !== book.activePaletteId || !cache.paletteReceiptId || !cache.paletteActionDigest || !cache.sourceDigest) return missing(book.id);
4970
+ if (!cache || !record7(cache.light) || !record7(cache.dark) || !book.activePaletteId || cache.paletteId !== book.activePaletteId || !cache.paletteReceiptId || !cache.paletteActionDigest || !cache.sourceDigest) return missing(book.id);
4826
4971
  const paletteResult = await db.query({
4827
4972
  [BRAND_NS.palette]: {
4828
4973
  $: { where: { id: cache.paletteId, bookId: book.id } },
@@ -5167,6 +5312,411 @@ async function handleBrandDiscussionReferences(ctx, req, url) {
5167
5312
  });
5168
5313
  }
5169
5314
 
5315
+ // src/promotion.ts
5316
+ var import_promotion = require("@odla-ai/promotion");
5317
+ var BRAND_PROMOTION_ADAPTER = "@odla-ai/brand/v1";
5318
+ var provenance = (book, sourceIds, receiptDigests = []) => ({
5319
+ adapter: BRAND_PROMOTION_ADAPTER,
5320
+ sourceIds: [
5321
+ ...sourceIds,
5322
+ ...book.activationReceiptId ? [book.activationReceiptId] : []
5323
+ ].sort(),
5324
+ receiptDigests: [
5325
+ ...receiptDigests,
5326
+ ...book.activationActionDigest ? [book.activationActionDigest] : []
5327
+ ].sort()
5328
+ });
5329
+ var exclusions = (book) => [
5330
+ {
5331
+ kind: "brand.runtime",
5332
+ id: book.slug,
5333
+ reason: "environment_local",
5334
+ paths: [`${BRAND_NS.book}.ownerId`, `${BRAND_NS.book}.memberIds`, `${BRAND_NS.book}.channelId`, `${BRAND_NS.book}.*At`],
5335
+ detail: "Ownership, roster, channel, and timestamps remain local to the target environment."
5336
+ },
5337
+ {
5338
+ kind: "brand.drafts",
5339
+ id: book.slug,
5340
+ reason: "draft_state",
5341
+ paths: [`${BRAND_NS.proposal}.*`, `${BRAND_NS.section}[status=draft]`],
5342
+ detail: "Open proposals and unapproved sections are review state, not published brand content."
5343
+ },
5344
+ {
5345
+ kind: "brand.assets",
5346
+ id: book.slug,
5347
+ reason: "provider_identity",
5348
+ paths: [`${BRAND_NS.asset}.storageObjectId`, `${BRAND_NS.asset}.path`, `${BRAND_NS.asset}.*AuthorityRef`],
5349
+ detail: "Stored files and provider object identities require a separate asset-transfer contract."
5350
+ },
5351
+ {
5352
+ kind: "brand.authority",
5353
+ id: book.slug,
5354
+ reason: "authority_history",
5355
+ paths: [
5356
+ `${BRAND_NS.approvalReceipt}.*`,
5357
+ `${BRAND_NS.activationReceipt}.*`,
5358
+ `${BRAND_NS.palette}.audience`
5359
+ ],
5360
+ detail: "Authority rows stay immutable in their source environment; artifacts retain only receipt digests and ids."
5361
+ }
5362
+ ];
5363
+ function buildBrandPromotion(input) {
5364
+ const { book } = input;
5365
+ const revision = `brand:${book.slug}:${book.activationRevision}`;
5366
+ const artifacts = [];
5367
+ if (book.tokens) {
5368
+ const {
5369
+ compiledAt: _compiledAt,
5370
+ paletteReceiptId,
5371
+ paletteActionDigest,
5372
+ typographyReceiptId,
5373
+ typographyActionDigest,
5374
+ sourceDigest,
5375
+ ...stableTokens
5376
+ } = book.tokens;
5377
+ artifacts.push({
5378
+ kind: "brand.tokens",
5379
+ id: book.slug,
5380
+ sourceRevision: revision,
5381
+ provenance: provenance(
5382
+ book,
5383
+ [book.id, book.tokens.paletteId, paletteReceiptId, typographyReceiptId].filter((value) => Boolean(value)),
5384
+ [sourceDigest, paletteActionDigest, typographyActionDigest].filter((value) => Boolean(value))
5385
+ ),
5386
+ content: (0, import_promotion.canonicalPromotionValue)({ bookSlug: book.slug, tokens: stableTokens })
5387
+ });
5388
+ }
5389
+ for (const section of input.sections.filter((row) => row.bookId === book.id && row.status === "approved")) {
5390
+ if (!section.approvalReceiptId || !section.approvalActionDigest) {
5391
+ throw new TypeError(`approved brand section ${section.key} lacks approval provenance`);
5392
+ }
5393
+ artifacts.push({
5394
+ kind: "brand.section",
5395
+ id: `${book.slug}/${section.kind}`,
5396
+ sourceRevision: revision,
5397
+ provenance: provenance(book, [book.id, section.id, section.approvalReceiptId], [section.approvalActionDigest]),
5398
+ content: (0, import_promotion.canonicalPromotionValue)({ bookSlug: book.slug, kind: section.kind, content: section.content })
5399
+ });
5400
+ }
5401
+ const activePalette2 = input.palettes.find((row) => row.bookId === book.id && row.id === book.activePaletteId && row.status === "active");
5402
+ if (book.activePaletteId && !activePalette2) throw new TypeError(`active palette ${book.activePaletteId} is unavailable`);
5403
+ if (activePalette2) {
5404
+ artifacts.push({
5405
+ kind: "brand.palette",
5406
+ id: `${book.slug}/${activePalette2.id}`,
5407
+ sourceRevision: revision,
5408
+ provenance: provenance(book, [book.id, activePalette2.id, activePalette2.proposalId, activePalette2.approvalReceiptId], [activePalette2.approvalActionDigest]),
5409
+ content: (0, import_promotion.canonicalPromotionValue)({
5410
+ bookSlug: book.slug,
5411
+ name: activePalette2.name,
5412
+ swatches: activePalette2.swatches,
5413
+ source: activePalette2.source,
5414
+ ...activePalette2.seedHex ? { seedHex: activePalette2.seedHex } : {},
5415
+ ...activePalette2.rationale ? { rationale: activePalette2.rationale } : {}
5416
+ })
5417
+ });
5418
+ }
5419
+ return {
5420
+ adapter: BRAND_PROMOTION_ADAPTER,
5421
+ artifacts,
5422
+ exclusions: exclusions(book),
5423
+ preconditions: [{
5424
+ code: "brand.schema",
5425
+ path: "services.brand.schemaVersion",
5426
+ expected: 1,
5427
+ detail: "The target must expose the Brand v1 approved-content schema."
5428
+ }]
5429
+ };
5430
+ }
5431
+ function exportBrandPromotionCandidate(input) {
5432
+ if (input.book.status !== "draft")
5433
+ throw new TypeError(`brand book ${input.book.slug} is not draft`);
5434
+ return buildBrandPromotion({
5435
+ ...input,
5436
+ book: { ...input.book, status: "active" }
5437
+ });
5438
+ }
5439
+ function exportBrandPromotion(input) {
5440
+ const { book } = input;
5441
+ if (book.status !== "active") throw new TypeError(`brand book ${book.slug} is not active`);
5442
+ if (!book.activationReceiptId || !book.activationActionDigest)
5443
+ throw new TypeError(`active brand book ${book.slug} lacks activation provenance`);
5444
+ return buildBrandPromotion(input);
5445
+ }
5446
+
5447
+ // src/routes/activation-state.ts
5448
+ async function activationReceiptByMutation(ctx, mutationKey) {
5449
+ const result = await ctx.db.query({
5450
+ [BRAND_NS.activationReceipt]: {
5451
+ $: { where: { mutationKey } },
5452
+ book: {}
5453
+ }
5454
+ });
5455
+ const row = (result[BRAND_NS.activationReceipt] ?? [])[0];
5456
+ if (!row || !hasExactOneLink(row.book, row.bookId)) return void 0;
5457
+ const { book: _book, ...receipt2 } = row;
5458
+ return activationReceiptAsWritten(receipt2);
5459
+ }
5460
+ async function approvalReceipt(ctx, bookId, receiptId) {
5461
+ const result = await ctx.db.query({
5462
+ [BRAND_NS.approvalReceipt]: {
5463
+ $: { where: { id: receiptId, bookId } },
5464
+ book: {}
5465
+ }
5466
+ });
5467
+ const row = (result[BRAND_NS.approvalReceipt] ?? [])[0];
5468
+ if (!row || !hasExactOneLink(row.book, bookId))
5469
+ throw new BrandInputError(`approval receipt ${receiptId} is unavailable`);
5470
+ const { book: _book, ...stored } = row;
5471
+ const receipt2 = receiptAsWritten(stored);
5472
+ if (!await verifyBrandApprovalReceipt(receipt2))
5473
+ throw new BrandInputError(`approval receipt ${receiptId} is invalid`);
5474
+ return receipt2;
5475
+ }
5476
+ async function activationState(ctx, book) {
5477
+ if (book.status !== "draft")
5478
+ throw new BrandConflictError(`brand book ${book.slug} is already ${book.status}`);
5479
+ const tokens = book.tokens;
5480
+ if (!book.activePaletteId || !tokens || tokens.paletteId !== book.activePaletteId || !tokens.paletteReceiptId || !tokens.paletteActionDigest || !tokens.typographyReceiptId || !tokens.typographyActionDigest) throw new BrandInputError(
5481
+ "brand book activation requires approved palette and typography tokens"
5482
+ );
5483
+ const result = await ctx.db.query({
5484
+ [BRAND_NS.section]: { $: { where: { bookId: book.id } }, book: {} },
5485
+ [BRAND_NS.palette]: { $: { where: { bookId: book.id } }, book: {} }
5486
+ });
5487
+ const sections = (result[BRAND_NS.section] ?? []).map((row) => rowAsWritten(BRAND_NS.section, row));
5488
+ const palettes = (result[BRAND_NS.palette] ?? []).map((row) => rowAsWritten(BRAND_NS.palette, row));
5489
+ const activePalette2 = palettes.find((row) => row.id === book.activePaletteId && row.status === "active");
5490
+ const typography = sections.find((row) => row.kind === "typography" && row.status === "approved");
5491
+ if (!activePalette2 || activePalette2.approvalReceiptId !== tokens.paletteReceiptId || activePalette2.approvalActionDigest !== tokens.paletteActionDigest) throw new BrandInputError("active palette does not match compiled token provenance");
5492
+ if (!typography || typography.approvalReceiptId !== tokens.typographyReceiptId || typography.approvalActionDigest !== tokens.typographyActionDigest) throw new BrandInputError("approved typography does not match compiled token provenance");
5493
+ const [paletteReceipt, typographyReceipt] = await Promise.all([
5494
+ approvalReceipt(ctx, book.id, tokens.paletteReceiptId),
5495
+ approvalReceipt(ctx, book.id, tokens.typographyReceiptId)
5496
+ ]);
5497
+ if (paletteReceipt.resolution !== "accepted" || paletteReceipt.actionDigest !== tokens.paletteActionDigest || typographyReceipt.resolution !== "accepted" || typographyReceipt.actionDigest !== tokens.typographyActionDigest) throw new BrandInputError("compiled tokens are not backed by accepted approvals");
5498
+ const candidateBook = { ...book, status: "draft" };
5499
+ delete candidateBook.activationReceiptId;
5500
+ delete candidateBook.activationActionDigest;
5501
+ const promotion = exportBrandPromotionCandidate({ book: candidateBook, sections, palettes });
5502
+ const approved = sections.filter((row) => row.status === "approved").sort((left, right) => left.key.localeCompare(right.key));
5503
+ const reviewed = {
5504
+ version: 1,
5505
+ bookId: book.id,
5506
+ slug: book.slug,
5507
+ status: "draft",
5508
+ bookVersion: book.version,
5509
+ activationRevision: book.activationRevision,
5510
+ activePaletteId: book.activePaletteId,
5511
+ memberIdsDigest: await brandJsonDigest(book.memberIds),
5512
+ tokensSourceDigest: tokens.sourceDigest,
5513
+ paletteReceiptId: tokens.paletteReceiptId,
5514
+ paletteActionDigest: tokens.paletteActionDigest,
5515
+ typographyReceiptId: tokens.typographyReceiptId,
5516
+ typographyActionDigest: tokens.typographyActionDigest,
5517
+ approvedSectionKeys: approved.map((row) => row.key),
5518
+ promotionDigest: await brandJsonDigest(promotion)
5519
+ };
5520
+ return {
5521
+ reviewed,
5522
+ sections,
5523
+ palettes,
5524
+ guards: [
5525
+ {
5526
+ ns: BRAND_NS.book,
5527
+ id: book.id,
5528
+ exists: true,
5529
+ equals: {
5530
+ version: book.version,
5531
+ status: "draft",
5532
+ activationRevision: book.activationRevision,
5533
+ activePaletteId: book.activePaletteId,
5534
+ memberIds: book.memberIds,
5535
+ tokens: book.tokens
5536
+ }
5537
+ },
5538
+ {
5539
+ ns: BRAND_NS.palette,
5540
+ id: activePalette2.id,
5541
+ exists: true,
5542
+ equals: {
5543
+ status: "active",
5544
+ approvalReceiptId: activePalette2.approvalReceiptId,
5545
+ approvalActionDigest: activePalette2.approvalActionDigest,
5546
+ swatches: activePalette2.swatches
5547
+ }
5548
+ },
5549
+ ...approved.map((row) => ({
5550
+ ns: BRAND_NS.section,
5551
+ id: row.id,
5552
+ exists: true,
5553
+ equals: {
5554
+ status: "approved",
5555
+ content: row.content,
5556
+ approvalReceiptId: row.approvalReceiptId,
5557
+ approvalActionDigest: row.approvalActionDigest
5558
+ }
5559
+ })),
5560
+ ...[paletteReceipt, typographyReceipt].map((row) => ({
5561
+ ns: BRAND_NS.approvalReceipt,
5562
+ id: row.id,
5563
+ exists: true,
5564
+ equals: { receiptDigest: row.receiptDigest }
5565
+ }))
5566
+ ]
5567
+ };
5568
+ }
5569
+
5570
+ // src/routes/activation.ts
5571
+ var record8 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
5572
+ var guardFailure3 = (error) => record8(error) && error.code === "transact_guard_failed";
5573
+ async function activationMutationKey(bookId, mutationId) {
5574
+ const digest2 = await brandJsonDigest({ version: 1, bookId, mutationId });
5575
+ return `brand:book-activation:v1:${digest2.slice("sha256:".length)}`;
5576
+ }
5577
+ async function consumeActivationAuthority(ctx, req, bookId, actionDigest, mutationKey) {
5578
+ const authority = await ctx.consumeHumanExact({
5579
+ req,
5580
+ actor: ctx.actor,
5581
+ appId: ctx.appId,
5582
+ capability: "brand.book.activate",
5583
+ projectCapability: "brand.approve",
5584
+ effect: "external",
5585
+ resource: { bookId },
5586
+ actionDigest,
5587
+ consumptionIdempotencyKey: mutationKey
5588
+ });
5589
+ if (!authority || authority.capability !== "brand.book.activate" || authority.projectCapability !== "brand.approve" || authority.effect !== "external" || authority.actorKind !== "human" || authority.credentialKind !== "clerk" || authority.actorPrincipalId !== ctx.actor.id || authority.appId !== ctx.appId || authority.actionDigest !== actionDigest || authority.resourceDigest !== await brandJsonDigest({ bookId }) || authority.consumptionIdempotencyKey !== mutationKey) throw new BrandForbiddenError("a verified human-exact Brand activation is required");
5590
+ return authority;
5591
+ }
5592
+ async function handleBookActivation(ctx, req, bookId) {
5593
+ const book = await loadMemberBook(ctx.db, bookId, ctx.actor.id);
5594
+ if (ctx.actor.kind !== "human")
5595
+ throw new BrandForbiddenError("only a directly authenticated human can activate a brand book");
5596
+ if (req.method === "GET") {
5597
+ if (book.status === "active") return json({ status: "active", book });
5598
+ const state2 = await activationState(ctx, book);
5599
+ return json({ status: "draft", reviewedBook: state2.reviewed });
5600
+ }
5601
+ if (req.method !== "POST") return methodNotAllowed();
5602
+ const body = await readJson(req);
5603
+ if (!record8(body.reviewedBook))
5604
+ throw new BrandInputError('"reviewedBook" is required');
5605
+ const reviewed = body.reviewedBook;
5606
+ const mutationId = capString(str(body, "mutationId"), "mutationId", 200);
5607
+ const mutationKey = await activationMutationKey(bookId, mutationId);
5608
+ const stable = (await brandJsonDigest({ mutationKey })).slice(7);
5609
+ const receiptId = `brand-activation-${stable.slice(0, 32)}`;
5610
+ const existing = await activationReceiptByMutation(ctx, mutationKey);
5611
+ if (existing) {
5612
+ if (!await verifyBrandActivationReceipt(existing) || canonicalBrandJson(existing.reviewedBook) !== canonicalBrandJson(reviewed)) throw new BrandConflictError("mutationId was used for another activation");
5613
+ const replayed = await consumeActivationAuthority(
5614
+ ctx,
5615
+ req,
5616
+ bookId,
5617
+ existing.actionDigest,
5618
+ mutationKey
5619
+ );
5620
+ if (canonicalBrandJson(replayed) !== canonicalBrandJson(existing.authorityConsumption))
5621
+ throw new BrandConflictError("central activation receipt changed");
5622
+ if (book.status !== "active" || book.activationReceiptId !== existing.id || book.activationActionDigest !== existing.actionDigest) throw new BrandConflictError("activated Brand revision is no longer current");
5623
+ const result = await ctx.db.query({
5624
+ [BRAND_NS.section]: { $: { where: { bookId } } },
5625
+ [BRAND_NS.palette]: { $: { where: { bookId } } }
5626
+ });
5627
+ const promotion2 = exportBrandPromotion({
5628
+ book,
5629
+ sections: result[BRAND_NS.section] ?? [],
5630
+ palettes: result[BRAND_NS.palette] ?? []
5631
+ });
5632
+ return json({ receipt: existing, promotion: promotion2, promotionDigest: await brandJsonDigest(promotion2), duplicate: true });
5633
+ }
5634
+ const state = await activationState(ctx, book);
5635
+ if (canonicalBrandJson(state.reviewed) !== canonicalBrandJson(reviewed))
5636
+ throw new BrandConflictError("brand book changed since activation review");
5637
+ const actionDigest = await brandJsonDigest({
5638
+ version: 1,
5639
+ reviewedBook: state.reviewed,
5640
+ intendedStatus: "active",
5641
+ activationReceiptId: receiptId
5642
+ });
5643
+ const authority = await consumeActivationAuthority(
5644
+ ctx,
5645
+ req,
5646
+ bookId,
5647
+ actionDigest,
5648
+ mutationKey
5649
+ );
5650
+ const receiptBase = {
5651
+ version: 1,
5652
+ id: receiptId,
5653
+ mutationKey,
5654
+ bookId,
5655
+ reviewedBook: state.reviewed,
5656
+ actionDigest,
5657
+ approvedBy: ctx.actor.id,
5658
+ approvedByKind: "human",
5659
+ authorityRef: brandAuthorityRef(authority),
5660
+ authorityCapability: "brand.approve",
5661
+ authorityConsumption: authority,
5662
+ createdAt: authority.consumedAt
5663
+ };
5664
+ const receipt2 = {
5665
+ ...receiptBase,
5666
+ receiptDigest: await brandJsonDigest(receiptBase)
5667
+ };
5668
+ try {
5669
+ const tx = await ctx.db.transact([
5670
+ {
5671
+ t: "update",
5672
+ ns: BRAND_NS.book,
5673
+ id: bookId,
5674
+ attrs: {
5675
+ status: "active",
5676
+ version: book.version + 1,
5677
+ activationReceiptId: receipt2.id,
5678
+ activationActionDigest: receipt2.actionDigest,
5679
+ updatedAt: authority.consumedAt
5680
+ }
5681
+ },
5682
+ { t: "update", ns: BRAND_NS.activationReceipt, id: receipt2.id, attrs: receipt2 },
5683
+ { t: "link", ns: BRAND_NS.activationReceipt, id: receipt2.id, label: "book", target: bookId }
5684
+ ], {
5685
+ mutationId: mutationKey,
5686
+ guards: [
5687
+ ...state.guards,
5688
+ { ns: BRAND_NS.activationReceipt, id: receipt2.id, exists: false }
5689
+ ],
5690
+ asUser: ctx.actor.id,
5691
+ ...ctx.actor.email ? { asEmail: ctx.actor.email } : {},
5692
+ asPrincipalKind: "human"
5693
+ });
5694
+ if (tx.duplicate) throw new BrandConflictError("activation replay was not observable");
5695
+ } catch (error) {
5696
+ if (guardFailure3(error)) throw new BrandReviewStateChangedError();
5697
+ throw error;
5698
+ }
5699
+ const activeBook = {
5700
+ ...book,
5701
+ status: "active",
5702
+ version: book.version + 1,
5703
+ activationReceiptId: receipt2.id,
5704
+ activationActionDigest: receipt2.actionDigest,
5705
+ updatedAt: authority.consumedAt
5706
+ };
5707
+ const promotion = exportBrandPromotion({
5708
+ book: activeBook,
5709
+ sections: state.sections,
5710
+ palettes: state.palettes
5711
+ });
5712
+ return json({
5713
+ receipt: receipt2,
5714
+ promotion,
5715
+ promotionDigest: await brandJsonDigest(promotion),
5716
+ duplicate: false
5717
+ });
5718
+ }
5719
+
5170
5720
  // src/routes/index.ts
5171
5721
  var tokensFile = (seg) => seg.length === 3 && seg[0] === "books" && (seg[2] === "tokens.css" || seg[2] === "tokens.json") ? seg[2] : null;
5172
5722
  async function route(ctx, req, url, seg) {
@@ -5177,6 +5727,7 @@ async function route(ctx, req, url, seg) {
5177
5727
  if (seg.length === 1) return handleBooksRoot(ctx, req);
5178
5728
  if (seg.length === 2) return handleBookItem(ctx, req, id);
5179
5729
  if (seg.length === 3 && sub === "members") return handleBookMembers(ctx, req, id);
5730
+ if (seg.length === 3 && sub === "activate") return handleBookActivation(ctx, req, id);
5180
5731
  if (sub === "assets") {
5181
5732
  if (seg.length === 3) return handleAssetsRoot(ctx, req, id);
5182
5733
  if (seg.length === 4) return handleAssetItem(ctx, req, id, subId);
@@ -5436,7 +5987,7 @@ var brandIntegration = {
5436
5987
  agentProfile: BRAND_AGENT_PROFILE,
5437
5988
  provision: {
5438
5989
  human: [
5439
- "Configure direct Clerk human approval through authorizeCapability + consumeHumanExact; delegated, machine, and agent principals must not resolve proposals.",
5990
+ "Configure direct Clerk human approval through authorizeCapability + consumeHumanExact; delegated, machine, and agent principals must not resolve proposals or activate books.",
5440
5991
  'Choose the trigger mode: one global mention-gated bot (brandBotTrigger({ mention: "@brand" }), no channels) or per-book channel-scoped bots (channels: [book.channelId], no mention).',
5441
5992
  "Pick a model where supportsBrandVision(catalog[model]) is true for in-conversation asset viewing; secure dispatch never falls back to signed or persistent asset URLs.",
5442
5993
  "Decide whether compiled tokens are public (publicTokens serves tokens.css/tokens.json unauthenticated)."
@@ -5450,9 +6001,10 @@ var brandIntegration = {
5450
6001
  "Provision private file signing and verifySourceAssetSnapshot; never expose an admin credential or persistent asset URL to the agent."
5451
6002
  ],
5452
6003
  doctor: [
5453
- "All six brand_* namespaces have rules installed; raw browser/agent writes are denied and child reads use current linked-book membership.",
6004
+ "All seven brand_* namespaces have rules installed; raw browser/agent writes are denied and child reads use current linked-book membership.",
5454
6005
  "The agent runtime matches BRAND_AGENT_PROFILE: brand.read/edit only, semantic operations only, no raw brand_* or file access.",
5455
6006
  "Proposal decisions consume a direct-human exact brand.approve authority use and persist one guarded brand_approval_receipt.",
6007
+ "Book activation consumes a direct-human exact external brand.approve authority use and persists one guarded brand_activation_receipt before promotion export opens.",
5456
6008
  "Private source assets are signed briefly and verifySourceAssetSnapshot revalidates path, ETag, size, content type, and digest before approval.",
5457
6009
  "brand_book.id/slug, brand_section.key, and the other mirrored id attrs are unique.",
5458
6010
  "Each registered trigger's agent id is present in its target books' memberIds rosters.",
@@ -5476,111 +6028,4 @@ function createBrandIntegration(options = {}) {
5476
6028
  probes: [{ path: `${basePath}/books`, expectedStatus: 401 }]
5477
6029
  };
5478
6030
  }
5479
-
5480
- // src/promotion.ts
5481
- var import_promotion = require("@odla-ai/promotion");
5482
- var BRAND_PROMOTION_ADAPTER = "@odla-ai/brand/v1";
5483
- var provenance = (sourceIds, receiptDigests = []) => ({
5484
- adapter: BRAND_PROMOTION_ADAPTER,
5485
- sourceIds: [...sourceIds].sort(),
5486
- receiptDigests: [...receiptDigests].sort()
5487
- });
5488
- var exclusions = (book) => [
5489
- {
5490
- kind: "brand.runtime",
5491
- id: book.slug,
5492
- reason: "environment_local",
5493
- paths: [`${BRAND_NS.book}.ownerId`, `${BRAND_NS.book}.memberIds`, `${BRAND_NS.book}.channelId`, `${BRAND_NS.book}.*At`],
5494
- detail: "Ownership, roster, channel, and timestamps remain local to the target environment."
5495
- },
5496
- {
5497
- kind: "brand.drafts",
5498
- id: book.slug,
5499
- reason: "draft_state",
5500
- paths: [`${BRAND_NS.proposal}.*`, `${BRAND_NS.section}[status=draft]`],
5501
- detail: "Open proposals and unapproved sections are review state, not published brand content."
5502
- },
5503
- {
5504
- kind: "brand.assets",
5505
- id: book.slug,
5506
- reason: "provider_identity",
5507
- paths: [`${BRAND_NS.asset}.storageObjectId`, `${BRAND_NS.asset}.path`, `${BRAND_NS.asset}.*AuthorityRef`],
5508
- detail: "Stored files and provider object identities require a separate asset-transfer contract."
5509
- },
5510
- {
5511
- kind: "brand.authority",
5512
- id: book.slug,
5513
- reason: "authority_history",
5514
- paths: [`${BRAND_NS.approvalReceipt}.*`, `${BRAND_NS.palette}.audience`],
5515
- detail: "Authority rows stay immutable in their source environment; artifacts retain only receipt digests and ids."
5516
- }
5517
- ];
5518
- function exportBrandPromotion(input) {
5519
- const { book } = input;
5520
- if (book.status !== "active") throw new TypeError(`brand book ${book.slug} is not active`);
5521
- const revision = `brand:${book.slug}:${book.activationRevision}`;
5522
- const artifacts = [];
5523
- if (book.tokens) {
5524
- const {
5525
- compiledAt: _compiledAt,
5526
- paletteReceiptId,
5527
- paletteActionDigest,
5528
- typographyReceiptId,
5529
- typographyActionDigest,
5530
- sourceDigest,
5531
- ...stableTokens
5532
- } = book.tokens;
5533
- artifacts.push({
5534
- kind: "brand.tokens",
5535
- id: book.slug,
5536
- sourceRevision: revision,
5537
- provenance: provenance(
5538
- [book.id, book.tokens.paletteId, paletteReceiptId, typographyReceiptId].filter((value) => Boolean(value)),
5539
- [sourceDigest, paletteActionDigest, typographyActionDigest].filter((value) => Boolean(value))
5540
- ),
5541
- content: (0, import_promotion.canonicalPromotionValue)({ bookSlug: book.slug, tokens: stableTokens })
5542
- });
5543
- }
5544
- for (const section of input.sections.filter((row) => row.bookId === book.id && row.status === "approved")) {
5545
- if (!section.approvalReceiptId || !section.approvalActionDigest) {
5546
- throw new TypeError(`approved brand section ${section.key} lacks approval provenance`);
5547
- }
5548
- artifacts.push({
5549
- kind: "brand.section",
5550
- id: `${book.slug}/${section.kind}`,
5551
- sourceRevision: revision,
5552
- provenance: provenance([book.id, section.id, section.approvalReceiptId], [section.approvalActionDigest]),
5553
- content: (0, import_promotion.canonicalPromotionValue)({ bookSlug: book.slug, kind: section.kind, content: section.content })
5554
- });
5555
- }
5556
- const activePalette2 = input.palettes.find((row) => row.bookId === book.id && row.id === book.activePaletteId && row.status === "active");
5557
- if (book.activePaletteId && !activePalette2) throw new TypeError(`active palette ${book.activePaletteId} is unavailable`);
5558
- if (activePalette2) {
5559
- artifacts.push({
5560
- kind: "brand.palette",
5561
- id: `${book.slug}/${activePalette2.id}`,
5562
- sourceRevision: revision,
5563
- provenance: provenance([book.id, activePalette2.id, activePalette2.proposalId, activePalette2.approvalReceiptId], [activePalette2.approvalActionDigest]),
5564
- content: (0, import_promotion.canonicalPromotionValue)({
5565
- bookSlug: book.slug,
5566
- name: activePalette2.name,
5567
- swatches: activePalette2.swatches,
5568
- source: activePalette2.source,
5569
- ...activePalette2.seedHex ? { seedHex: activePalette2.seedHex } : {},
5570
- ...activePalette2.rationale ? { rationale: activePalette2.rationale } : {}
5571
- })
5572
- });
5573
- }
5574
- return {
5575
- adapter: BRAND_PROMOTION_ADAPTER,
5576
- artifacts,
5577
- exclusions: exclusions(book),
5578
- preconditions: [{
5579
- code: "brand.schema",
5580
- path: "services.brand.schemaVersion",
5581
- expected: 1,
5582
- detail: "The target must expose the Brand v1 approved-content schema."
5583
- }]
5584
- };
5585
- }
5586
6031
  //# sourceMappingURL=index.cjs.map