@odla-ai/brand 0.8.4 → 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/README.md +24 -0
- package/dist/{index-r6SGidYT.d.cts → index-BBjMVilU.d.cts} +1 -0
- package/dist/{index-r6SGidYT.d.ts → index-BBjMVilU.d.ts} +1 -0
- package/dist/index.cjs +592 -147
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +53 -11
- package/dist/index.d.ts +53 -11
- package/dist/index.js +594 -149
- package/dist/index.js.map +1 -1
- package/dist/tokens/index.d.cts +1 -1
- package/dist/tokens/index.d.ts +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -61,6 +61,7 @@ var BRAND_NS = {
|
|
|
61
61
|
palette: "brand_palette",
|
|
62
62
|
proposal: "brand_proposal",
|
|
63
63
|
approvalReceipt: "brand_approval_receipt",
|
|
64
|
+
activationReceipt: "brand_activation_receipt",
|
|
64
65
|
asset: "brand_asset"
|
|
65
66
|
};
|
|
66
67
|
var BOOK_STATUSES = ["draft", "active", "archived"];
|
|
@@ -185,6 +186,8 @@ var BRAND_SCHEMA = {
|
|
|
185
186
|
activationRevision: a("number"),
|
|
186
187
|
tokens: opt("json"),
|
|
187
188
|
// maps + complete palette/typography receipt provenance
|
|
189
|
+
activationReceiptId: opt("string"),
|
|
190
|
+
activationActionDigest: opt("string"),
|
|
188
191
|
summary: opt("string"),
|
|
189
192
|
createdAt: idx("date"),
|
|
190
193
|
updatedAt: idx("date")
|
|
@@ -279,6 +282,23 @@ var BRAND_SCHEMA = {
|
|
|
279
282
|
receiptDigest: idx("string")
|
|
280
283
|
}
|
|
281
284
|
},
|
|
285
|
+
[BRAND_NS.activationReceipt]: {
|
|
286
|
+
attrs: {
|
|
287
|
+
id: uniq("string"),
|
|
288
|
+
version: a("number"),
|
|
289
|
+
mutationKey: uniq("string"),
|
|
290
|
+
bookId: idx("string"),
|
|
291
|
+
reviewedBook: a("json"),
|
|
292
|
+
actionDigest: idx("string"),
|
|
293
|
+
approvedBy: idx("string"),
|
|
294
|
+
approvedByKind: a("string"),
|
|
295
|
+
authorityRef: idx("string"),
|
|
296
|
+
authorityCapability: a("string"),
|
|
297
|
+
authorityConsumption: a("json"),
|
|
298
|
+
createdAt: idx("date"),
|
|
299
|
+
receiptDigest: idx("string")
|
|
300
|
+
}
|
|
301
|
+
},
|
|
282
302
|
[BRAND_NS.asset]: {
|
|
283
303
|
attrs: {
|
|
284
304
|
id: uniq("string"),
|
|
@@ -333,6 +353,10 @@ var BRAND_SCHEMA = {
|
|
|
333
353
|
forward: { on: BRAND_NS.approvalReceipt, has: "one", label: "book" },
|
|
334
354
|
reverse: { on: BRAND_NS.book, has: "many", label: "approvalReceipts" }
|
|
335
355
|
},
|
|
356
|
+
brandActivationReceiptBook: {
|
|
357
|
+
forward: { on: BRAND_NS.activationReceipt, has: "one", label: "book" },
|
|
358
|
+
reverse: { on: BRAND_NS.book, has: "many", label: "activationReceipts" }
|
|
359
|
+
},
|
|
336
360
|
brandAssetBook: {
|
|
337
361
|
forward: { on: BRAND_NS.asset, has: "one", label: "book" },
|
|
338
362
|
reverse: { on: BRAND_NS.book, has: "many", label: "assets" }
|
|
@@ -374,6 +398,15 @@ function receiptAsWritten(row) {
|
|
|
374
398
|
for (const key of systemAttrs) delete out[key];
|
|
375
399
|
return out;
|
|
376
400
|
}
|
|
401
|
+
function activationReceiptAsWritten(row) {
|
|
402
|
+
const normalized = rowAsWritten(BRAND_NS.activationReceipt, row);
|
|
403
|
+
if (!record(normalized)) return normalized;
|
|
404
|
+
const systemAttrs = Object.keys(normalized).filter((key) => key.startsWith("$"));
|
|
405
|
+
if (systemAttrs.length === 0) return normalized;
|
|
406
|
+
const out = { ...normalized };
|
|
407
|
+
for (const key of systemAttrs) delete out[key];
|
|
408
|
+
return out;
|
|
409
|
+
}
|
|
377
410
|
function proposalAsWritten(row) {
|
|
378
411
|
return rowAsWritten(BRAND_NS.proposal, row);
|
|
379
412
|
}
|
|
@@ -445,6 +478,12 @@ var BRAND_RULES = {
|
|
|
445
478
|
update: "false",
|
|
446
479
|
delete: "false"
|
|
447
480
|
},
|
|
481
|
+
[BRAND_NS.activationReceipt]: {
|
|
482
|
+
view: CURRENT_BOOK_MEMBER,
|
|
483
|
+
create: "false",
|
|
484
|
+
update: "false",
|
|
485
|
+
delete: "false"
|
|
486
|
+
},
|
|
448
487
|
[BRAND_NS.asset]: {
|
|
449
488
|
view: CURRENT_BOOK_MEMBER,
|
|
450
489
|
create: "false",
|
|
@@ -686,15 +725,106 @@ async function brandJsonDigest(value) {
|
|
|
686
725
|
return `sha256:${[...new Uint8Array(bytes)].map((byte) => byte.toString(16).padStart(2, "0")).join("")}`;
|
|
687
726
|
}
|
|
688
727
|
|
|
689
|
-
// src/review.ts
|
|
728
|
+
// src/activation-review.ts
|
|
690
729
|
var DIGEST = /^sha256:[0-9a-f]{64}$/;
|
|
691
|
-
var record3 = (value) => typeof value === "object" && value !== null && !Array.isArray(value)
|
|
692
|
-
var exactKeys = (value, required
|
|
693
|
-
const allowed =
|
|
730
|
+
var record3 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
731
|
+
var exactKeys = (value, required) => {
|
|
732
|
+
const allowed = new Set(required);
|
|
694
733
|
return required.every((key) => Object.hasOwn(value, key)) && Object.keys(value).every((key) => allowed.has(key));
|
|
695
734
|
};
|
|
696
735
|
var boundedString = (value, max = 200) => typeof value === "string" && value.length > 0 && value.length <= max;
|
|
697
736
|
var safeTime = (value) => Number.isSafeInteger(value) && value >= 0;
|
|
737
|
+
var SNAPSHOT_KEYS = [
|
|
738
|
+
"version",
|
|
739
|
+
"bookId",
|
|
740
|
+
"slug",
|
|
741
|
+
"status",
|
|
742
|
+
"bookVersion",
|
|
743
|
+
"activationRevision",
|
|
744
|
+
"activePaletteId",
|
|
745
|
+
"memberIdsDigest",
|
|
746
|
+
"tokensSourceDigest",
|
|
747
|
+
"paletteReceiptId",
|
|
748
|
+
"paletteActionDigest",
|
|
749
|
+
"typographyReceiptId",
|
|
750
|
+
"typographyActionDigest",
|
|
751
|
+
"approvedSectionKeys",
|
|
752
|
+
"promotionDigest"
|
|
753
|
+
];
|
|
754
|
+
function validSnapshot(value) {
|
|
755
|
+
if (!record3(value) || !exactKeys(value, SNAPSHOT_KEYS)) return false;
|
|
756
|
+
const sectionKeys = value.approvedSectionKeys;
|
|
757
|
+
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);
|
|
758
|
+
}
|
|
759
|
+
var CONSUMPTION_KEYS = [
|
|
760
|
+
"id",
|
|
761
|
+
"grantId",
|
|
762
|
+
"grantVersion",
|
|
763
|
+
"useNumber",
|
|
764
|
+
"actorPrincipalId",
|
|
765
|
+
"actorKind",
|
|
766
|
+
"credentialId",
|
|
767
|
+
"credentialKind",
|
|
768
|
+
"appId",
|
|
769
|
+
"appIncarnation",
|
|
770
|
+
"capability",
|
|
771
|
+
"projectCapability",
|
|
772
|
+
"effect",
|
|
773
|
+
"actionDigest",
|
|
774
|
+
"resourceDigest",
|
|
775
|
+
"constraintEvidence",
|
|
776
|
+
"consumptionIdempotencyKey",
|
|
777
|
+
"requestDigest",
|
|
778
|
+
"consumedAt"
|
|
779
|
+
];
|
|
780
|
+
function validConsumption(value) {
|
|
781
|
+
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);
|
|
782
|
+
}
|
|
783
|
+
var RECEIPT_KEYS = [
|
|
784
|
+
"version",
|
|
785
|
+
"id",
|
|
786
|
+
"mutationKey",
|
|
787
|
+
"bookId",
|
|
788
|
+
"reviewedBook",
|
|
789
|
+
"actionDigest",
|
|
790
|
+
"approvedBy",
|
|
791
|
+
"approvedByKind",
|
|
792
|
+
"authorityRef",
|
|
793
|
+
"authorityCapability",
|
|
794
|
+
"authorityConsumption",
|
|
795
|
+
"createdAt",
|
|
796
|
+
"receiptDigest"
|
|
797
|
+
];
|
|
798
|
+
async function verifyBrandActivationReceipt(receipt2) {
|
|
799
|
+
try {
|
|
800
|
+
if (!isBoundedBrandJson(receipt2, { maxDepth: 12, maxNodes: 1e3, maxBytes: 32 * 1024 }) || !record3(receipt2) || !exactKeys(receipt2, RECEIPT_KEYS)) return false;
|
|
801
|
+
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;
|
|
802
|
+
const reviewed = receipt2.reviewedBook;
|
|
803
|
+
const authority = receipt2.authorityConsumption;
|
|
804
|
+
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;
|
|
805
|
+
const expectedAction = await brandJsonDigest({
|
|
806
|
+
version: 1,
|
|
807
|
+
reviewedBook: reviewed,
|
|
808
|
+
intendedStatus: "active",
|
|
809
|
+
activationReceiptId: receipt2.id
|
|
810
|
+
});
|
|
811
|
+
if (expectedAction !== receipt2.actionDigest) return false;
|
|
812
|
+
const { receiptDigest, ...immutable } = receipt2;
|
|
813
|
+
return await brandJsonDigest(immutable) === receiptDigest;
|
|
814
|
+
} catch {
|
|
815
|
+
return false;
|
|
816
|
+
}
|
|
817
|
+
}
|
|
818
|
+
|
|
819
|
+
// src/review.ts
|
|
820
|
+
var DIGEST2 = /^sha256:[0-9a-f]{64}$/;
|
|
821
|
+
var record4 = (value) => typeof value === "object" && value !== null && !Array.isArray(value) && (Object.getPrototypeOf(value) === Object.prototype || Object.getPrototypeOf(value) === null);
|
|
822
|
+
var exactKeys2 = (value, required, optional = []) => {
|
|
823
|
+
const allowed = /* @__PURE__ */ new Set([...required, ...optional]);
|
|
824
|
+
return required.every((key) => Object.hasOwn(value, key)) && Object.keys(value).every((key) => allowed.has(key));
|
|
825
|
+
};
|
|
826
|
+
var boundedString2 = (value, max = 200) => typeof value === "string" && value.length > 0 && value.length <= max;
|
|
827
|
+
var safeTime2 = (value) => Number.isSafeInteger(value) && value >= 0;
|
|
698
828
|
function brandAuthorityRef(consumption) {
|
|
699
829
|
return `authority:${consumption.grantId}:v${consumption.grantVersion}`;
|
|
700
830
|
}
|
|
@@ -713,15 +843,15 @@ var SNAPSHOT_REQUIRED = [
|
|
|
713
843
|
"createdAt"
|
|
714
844
|
];
|
|
715
845
|
function validSnapshotShape(value) {
|
|
716
|
-
if (!
|
|
846
|
+
if (!record4(value) || !exactKeys2(value, SNAPSHOT_REQUIRED)) return false;
|
|
717
847
|
const provenance2 = value.provenance;
|
|
718
|
-
return
|
|
848
|
+
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, [
|
|
719
849
|
"sourceAssetId",
|
|
720
850
|
"sourceAsset",
|
|
721
851
|
"messageId",
|
|
722
852
|
"turnId",
|
|
723
853
|
"taintLabels"
|
|
724
|
-
]) && (provenance2.sourceAssetId === null ||
|
|
854
|
+
]) && (provenance2.sourceAssetId === null || boundedString2(provenance2.sourceAssetId)) && (provenance2.sourceAsset === null || record4(provenance2.sourceAsset) && exactKeys2(provenance2.sourceAsset, [
|
|
725
855
|
"assetId",
|
|
726
856
|
"contentDigest",
|
|
727
857
|
"objectEtag",
|
|
@@ -730,7 +860,7 @@ function validSnapshotShape(value) {
|
|
|
730
860
|
"contentType",
|
|
731
861
|
"analysisRevision",
|
|
732
862
|
"analysisDigest"
|
|
733
|
-
]) &&
|
|
863
|
+
]) && 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);
|
|
734
864
|
}
|
|
735
865
|
var CONSUMPTION_REQUIRED = [
|
|
736
866
|
"id",
|
|
@@ -754,8 +884,8 @@ var CONSUMPTION_REQUIRED = [
|
|
|
754
884
|
"consumedAt"
|
|
755
885
|
];
|
|
756
886
|
function isBrandHumanAuthorityConsumption(value) {
|
|
757
|
-
if (!
|
|
758
|
-
return
|
|
887
|
+
if (!record4(value) || !exactKeys2(value, CONSUMPTION_REQUIRED)) return false;
|
|
888
|
+
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);
|
|
759
889
|
}
|
|
760
890
|
var RECEIPT_REQUIRED = [
|
|
761
891
|
"version",
|
|
@@ -781,10 +911,10 @@ async function verifyBrandApprovalReceipt(receipt2) {
|
|
|
781
911
|
try {
|
|
782
912
|
if (!isBoundedBrandJson(receipt2, { maxDepth: 14, maxNodes: 2500, maxBytes: 48 * 1024 }))
|
|
783
913
|
return false;
|
|
784
|
-
if (!
|
|
914
|
+
if (!record4(receipt2) || !exactKeys2(receipt2, RECEIPT_REQUIRED, ["paletteId", "resolutionNote"]))
|
|
785
915
|
return false;
|
|
786
916
|
const binding = receipt2.decisionBinding;
|
|
787
|
-
if (receipt2.version !== 1 || !
|
|
917
|
+
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, [
|
|
788
918
|
"version",
|
|
789
919
|
"bookVersion",
|
|
790
920
|
"activationRevision",
|
|
@@ -794,10 +924,10 @@ async function verifyBrandApprovalReceipt(receipt2) {
|
|
|
794
924
|
"typographyDigest",
|
|
795
925
|
"sourceAssetDigest",
|
|
796
926
|
"effectDigest"
|
|
797
|
-
]) || binding.version !== 1 || !Number.isSafeInteger(binding.bookVersion) || binding.bookVersion < 1 || !Number.isSafeInteger(binding.activationRevision) || binding.activationRevision < 0 || binding.activePaletteId !== null && !
|
|
927
|
+
]) || 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;
|
|
798
928
|
const reviewed = receipt2.reviewedProposal;
|
|
799
929
|
const authority = receipt2.authorityConsumption;
|
|
800
|
-
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;
|
|
930
|
+
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;
|
|
801
931
|
const { reviewDigest, ...unsignedReview } = reviewed;
|
|
802
932
|
if (await brandJsonDigest(unsignedReview) !== reviewDigest) return false;
|
|
803
933
|
const expectedAction = await brandJsonDigest({
|
|
@@ -2685,14 +2815,14 @@ async function uploadAsset(ctx, req, bookId) {
|
|
|
2685
2815
|
"internal",
|
|
2686
2816
|
book.id
|
|
2687
2817
|
);
|
|
2688
|
-
const
|
|
2818
|
+
const record9 = await ctx.db.storage.upload(
|
|
2689
2819
|
path,
|
|
2690
2820
|
file,
|
|
2691
2821
|
contentType,
|
|
2692
2822
|
{ private: true }
|
|
2693
2823
|
);
|
|
2694
|
-
if (
|
|
2695
|
-
await ctx.db.storage.delete(
|
|
2824
|
+
if (record9.path !== path) {
|
|
2825
|
+
await ctx.db.storage.delete(record9.path);
|
|
2696
2826
|
throw new BrandConflictError(
|
|
2697
2827
|
"private storage returned an unexpected asset path"
|
|
2698
2828
|
);
|
|
@@ -2711,11 +2841,11 @@ async function uploadAsset(ctx, req, bookId) {
|
|
|
2711
2841
|
id,
|
|
2712
2842
|
bookId: book.id,
|
|
2713
2843
|
kind,
|
|
2714
|
-
path:
|
|
2715
|
-
storageObjectId:
|
|
2844
|
+
path: record9.path,
|
|
2845
|
+
storageObjectId: record9.id,
|
|
2716
2846
|
contentDigest: await contentDigest(file),
|
|
2717
2847
|
contentType,
|
|
2718
|
-
size:
|
|
2848
|
+
size: record9.size,
|
|
2719
2849
|
uploadedBy: ctx.actor.id,
|
|
2720
2850
|
uploadedAuthorityRef: authority.authorityRef,
|
|
2721
2851
|
audience: book.memberIds,
|
|
@@ -2742,7 +2872,7 @@ async function uploadAsset(ctx, req, bookId) {
|
|
|
2742
2872
|
asPrincipalKind: "human"
|
|
2743
2873
|
});
|
|
2744
2874
|
} catch (error) {
|
|
2745
|
-
await ctx.db.storage.delete(
|
|
2875
|
+
await ctx.db.storage.delete(record9.path);
|
|
2746
2876
|
throw error;
|
|
2747
2877
|
}
|
|
2748
2878
|
return json(await linkedAsset(ctx, book, id), 201);
|
|
@@ -3009,7 +3139,7 @@ async function handleDesignDigest(ctx, req, bookId, assetId) {
|
|
|
3009
3139
|
}
|
|
3010
3140
|
|
|
3011
3141
|
// src/routes/proposal-input.ts
|
|
3012
|
-
var
|
|
3142
|
+
var SNAPSHOT_KEYS2 = [
|
|
3013
3143
|
"audience",
|
|
3014
3144
|
"bookId",
|
|
3015
3145
|
"createdAt",
|
|
@@ -3023,7 +3153,7 @@ var SNAPSHOT_KEYS = [
|
|
|
3023
3153
|
"reviewDigest",
|
|
3024
3154
|
"status"
|
|
3025
3155
|
];
|
|
3026
|
-
var
|
|
3156
|
+
var record5 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
3027
3157
|
function parseProposalResolutionRequest(body, bookId, proposalId) {
|
|
3028
3158
|
const allowed = /* @__PURE__ */ new Set([
|
|
3029
3159
|
"mutationId",
|
|
@@ -3067,13 +3197,13 @@ function proposalReviewSnapshot(proposal) {
|
|
|
3067
3197
|
function parseReviewedProposal(value, bookId, proposalId) {
|
|
3068
3198
|
if (!isBoundedBrandJson(value, { maxDepth: 12, maxNodes: 1500, maxBytes: 32 * 1024 }))
|
|
3069
3199
|
throw new BrandInputError('"reviewedProposal" is too deeply nested, complex, or large');
|
|
3070
|
-
if (!
|
|
3200
|
+
if (!record5(value)) throw new BrandInputError('"reviewedProposal" must be an object');
|
|
3071
3201
|
const keys = Object.keys(value).sort();
|
|
3072
|
-
if (keys.length !==
|
|
3202
|
+
if (keys.length !== SNAPSHOT_KEYS2.length || !SNAPSHOT_KEYS2.every((key, index) => key === keys[index])) throw new BrandInputError('"reviewedProposal" has an invalid shape');
|
|
3073
3203
|
if (value.id !== proposalId || value.bookId !== bookId || value.status !== "open")
|
|
3074
3204
|
throw new BrandInputError('"reviewedProposal" must identify this open proposal');
|
|
3075
|
-
if (typeof value.kind !== "string" || !PROPOSAL_KINDS.includes(value.kind) || !
|
|
3076
|
-
if (!
|
|
3205
|
+
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');
|
|
3206
|
+
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(
|
|
3077
3207
|
value.provenance.sourceAsset.analysisDigest
|
|
3078
3208
|
)) || 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');
|
|
3079
3209
|
return value;
|
|
@@ -3150,11 +3280,11 @@ async function proposalDecisionOps(input) {
|
|
|
3150
3280
|
input.approvedTypography?.approvalReceiptId,
|
|
3151
3281
|
input.approvedTypography?.approvalActionDigest
|
|
3152
3282
|
);
|
|
3153
|
-
const
|
|
3283
|
+
const bookOp2 = ops.find(
|
|
3154
3284
|
(op) => op.t === "update" && op.ns === BRAND_NS.book && op.id === input.book.id
|
|
3155
3285
|
);
|
|
3156
|
-
if (!
|
|
3157
|
-
Object.assign(
|
|
3286
|
+
if (!bookOp2 || bookOp2.t !== "update") throw new Error("book activation op missing");
|
|
3287
|
+
Object.assign(bookOp2.attrs, {
|
|
3158
3288
|
version: input.book.version + 1,
|
|
3159
3289
|
activationRevision: input.book.activationRevision + 1,
|
|
3160
3290
|
tokens
|
|
@@ -3206,13 +3336,13 @@ async function proposalDecisionOps(input) {
|
|
|
3206
3336
|
}
|
|
3207
3337
|
];
|
|
3208
3338
|
if (input.proposal.kind === "typography" && input.priorPalette) {
|
|
3209
|
-
const
|
|
3339
|
+
const bookOp2 = ops.find(
|
|
3210
3340
|
(op) => op.t === "update" && op.ns === BRAND_NS.book && op.id === input.book.id
|
|
3211
3341
|
);
|
|
3212
|
-
if (
|
|
3342
|
+
if (bookOp2?.t === "update") {
|
|
3213
3343
|
if (!input.priorPalette.approvalReceiptId || !input.priorPalette.approvalActionDigest)
|
|
3214
3344
|
throw new BrandInputError("active palette is missing approval provenance");
|
|
3215
|
-
|
|
3345
|
+
bookOp2.attrs.tokens = await tokenSnapshot(
|
|
3216
3346
|
input.priorPalette.swatches,
|
|
3217
3347
|
content,
|
|
3218
3348
|
input,
|
|
@@ -3225,6 +3355,19 @@ async function proposalDecisionOps(input) {
|
|
|
3225
3355
|
}
|
|
3226
3356
|
}
|
|
3227
3357
|
}
|
|
3358
|
+
const bookOp = ops.find(
|
|
3359
|
+
(candidate) => candidate.t === "update" && candidate.ns === BRAND_NS.book && candidate.id === input.book.id
|
|
3360
|
+
);
|
|
3361
|
+
if (!bookOp || bookOp.t !== "update") throw new Error("book approval op missing");
|
|
3362
|
+
bookOp.attrs.status = "draft";
|
|
3363
|
+
if (input.book.activationReceiptId || input.book.activationActionDigest) {
|
|
3364
|
+
ops.push({
|
|
3365
|
+
t: "retract",
|
|
3366
|
+
ns: BRAND_NS.book,
|
|
3367
|
+
id: input.book.id,
|
|
3368
|
+
attrs: ["activationReceiptId", "activationActionDigest"]
|
|
3369
|
+
});
|
|
3370
|
+
}
|
|
3228
3371
|
stampProposal(ops, input);
|
|
3229
3372
|
return ops;
|
|
3230
3373
|
}
|
|
@@ -3264,7 +3407,7 @@ async function consumeProposalAuthority(ctx, req, bookId, proposalId, actionDige
|
|
|
3264
3407
|
consumptionIdempotencyKey: mutationKey
|
|
3265
3408
|
});
|
|
3266
3409
|
const resourceDigest = await brandJsonDigest({ bookId, proposalId });
|
|
3267
|
-
if (!isBrandHumanAuthorityConsumption(authority) || authority.actorPrincipalId !== ctx.actor.id || authority.appId !== ctx.appId || authority.actionDigest !== actionDigest || authority.resourceDigest !== resourceDigest || authority.consumptionIdempotencyKey !== mutationKey) throw new BrandForbiddenError(
|
|
3410
|
+
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(
|
|
3268
3411
|
"a verified human-exact brand approval is required"
|
|
3269
3412
|
);
|
|
3270
3413
|
return authority;
|
|
@@ -3528,9 +3671,9 @@ async function proposalDecisionState(ctx, req, book, proposal, resolution, recei
|
|
|
3528
3671
|
}
|
|
3529
3672
|
|
|
3530
3673
|
// src/routes/proposals.ts
|
|
3531
|
-
var
|
|
3674
|
+
var record6 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
3532
3675
|
var sameStrings2 = (left, right) => left.length === right.length && left.every((value, index) => value === right[index]);
|
|
3533
|
-
var guardFailure2 = (error) =>
|
|
3676
|
+
var guardFailure2 = (error) => record6(error) && error.code === "transact_guard_failed";
|
|
3534
3677
|
async function handleProposalResolution(ctx, req, bookId, proposalId) {
|
|
3535
3678
|
if (req.method !== "POST") return methodNotAllowed();
|
|
3536
3679
|
const book = await loadMemberBook(ctx.db, bookId, ctx.actor.id);
|
|
@@ -3710,7 +3853,7 @@ async function handleProposalResolution(ctx, req, bookId, proposalId) {
|
|
|
3710
3853
|
}
|
|
3711
3854
|
|
|
3712
3855
|
// src/routes/tokens.ts
|
|
3713
|
-
var
|
|
3856
|
+
var record7 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
3714
3857
|
var missing = (bookId) => {
|
|
3715
3858
|
throw new BrandNotFoundError(`compiled tokens for brand book ${bookId}`);
|
|
3716
3859
|
};
|
|
@@ -3730,7 +3873,7 @@ async function acceptedReceipt(db, bookId, receiptId, actionDigest) {
|
|
|
3730
3873
|
}
|
|
3731
3874
|
async function approvedCache(db, book) {
|
|
3732
3875
|
const cache = book.tokens;
|
|
3733
|
-
if (!cache || !
|
|
3876
|
+
if (!cache || !record7(cache.light) || !record7(cache.dark) || !book.activePaletteId || cache.paletteId !== book.activePaletteId || !cache.paletteReceiptId || !cache.paletteActionDigest || !cache.sourceDigest) return missing(book.id);
|
|
3734
3877
|
const paletteResult = await db.query({
|
|
3735
3878
|
[BRAND_NS.palette]: {
|
|
3736
3879
|
$: { where: { id: cache.paletteId, bookId: book.id } },
|
|
@@ -4075,6 +4218,413 @@ async function handleBrandDiscussionReferences(ctx, req, url) {
|
|
|
4075
4218
|
});
|
|
4076
4219
|
}
|
|
4077
4220
|
|
|
4221
|
+
// src/promotion.ts
|
|
4222
|
+
import {
|
|
4223
|
+
canonicalPromotionValue
|
|
4224
|
+
} from "@odla-ai/promotion";
|
|
4225
|
+
var BRAND_PROMOTION_ADAPTER = "@odla-ai/brand/v1";
|
|
4226
|
+
var provenance = (book, sourceIds, receiptDigests = []) => ({
|
|
4227
|
+
adapter: BRAND_PROMOTION_ADAPTER,
|
|
4228
|
+
sourceIds: [
|
|
4229
|
+
...sourceIds,
|
|
4230
|
+
...book.activationReceiptId ? [book.activationReceiptId] : []
|
|
4231
|
+
].sort(),
|
|
4232
|
+
receiptDigests: [
|
|
4233
|
+
...receiptDigests,
|
|
4234
|
+
...book.activationActionDigest ? [book.activationActionDigest] : []
|
|
4235
|
+
].sort()
|
|
4236
|
+
});
|
|
4237
|
+
var exclusions = (book) => [
|
|
4238
|
+
{
|
|
4239
|
+
kind: "brand.runtime",
|
|
4240
|
+
id: book.slug,
|
|
4241
|
+
reason: "environment_local",
|
|
4242
|
+
paths: [`${BRAND_NS.book}.ownerId`, `${BRAND_NS.book}.memberIds`, `${BRAND_NS.book}.channelId`, `${BRAND_NS.book}.*At`],
|
|
4243
|
+
detail: "Ownership, roster, channel, and timestamps remain local to the target environment."
|
|
4244
|
+
},
|
|
4245
|
+
{
|
|
4246
|
+
kind: "brand.drafts",
|
|
4247
|
+
id: book.slug,
|
|
4248
|
+
reason: "draft_state",
|
|
4249
|
+
paths: [`${BRAND_NS.proposal}.*`, `${BRAND_NS.section}[status=draft]`],
|
|
4250
|
+
detail: "Open proposals and unapproved sections are review state, not published brand content."
|
|
4251
|
+
},
|
|
4252
|
+
{
|
|
4253
|
+
kind: "brand.assets",
|
|
4254
|
+
id: book.slug,
|
|
4255
|
+
reason: "provider_identity",
|
|
4256
|
+
paths: [`${BRAND_NS.asset}.storageObjectId`, `${BRAND_NS.asset}.path`, `${BRAND_NS.asset}.*AuthorityRef`],
|
|
4257
|
+
detail: "Stored files and provider object identities require a separate asset-transfer contract."
|
|
4258
|
+
},
|
|
4259
|
+
{
|
|
4260
|
+
kind: "brand.authority",
|
|
4261
|
+
id: book.slug,
|
|
4262
|
+
reason: "authority_history",
|
|
4263
|
+
paths: [
|
|
4264
|
+
`${BRAND_NS.approvalReceipt}.*`,
|
|
4265
|
+
`${BRAND_NS.activationReceipt}.*`,
|
|
4266
|
+
`${BRAND_NS.palette}.audience`
|
|
4267
|
+
],
|
|
4268
|
+
detail: "Authority rows stay immutable in their source environment; artifacts retain only receipt digests and ids."
|
|
4269
|
+
}
|
|
4270
|
+
];
|
|
4271
|
+
function buildBrandPromotion(input) {
|
|
4272
|
+
const { book } = input;
|
|
4273
|
+
const revision = `brand:${book.slug}:${book.activationRevision}`;
|
|
4274
|
+
const artifacts = [];
|
|
4275
|
+
if (book.tokens) {
|
|
4276
|
+
const {
|
|
4277
|
+
compiledAt: _compiledAt,
|
|
4278
|
+
paletteReceiptId,
|
|
4279
|
+
paletteActionDigest,
|
|
4280
|
+
typographyReceiptId,
|
|
4281
|
+
typographyActionDigest,
|
|
4282
|
+
sourceDigest,
|
|
4283
|
+
...stableTokens
|
|
4284
|
+
} = book.tokens;
|
|
4285
|
+
artifacts.push({
|
|
4286
|
+
kind: "brand.tokens",
|
|
4287
|
+
id: book.slug,
|
|
4288
|
+
sourceRevision: revision,
|
|
4289
|
+
provenance: provenance(
|
|
4290
|
+
book,
|
|
4291
|
+
[book.id, book.tokens.paletteId, paletteReceiptId, typographyReceiptId].filter((value) => Boolean(value)),
|
|
4292
|
+
[sourceDigest, paletteActionDigest, typographyActionDigest].filter((value) => Boolean(value))
|
|
4293
|
+
),
|
|
4294
|
+
content: canonicalPromotionValue({ bookSlug: book.slug, tokens: stableTokens })
|
|
4295
|
+
});
|
|
4296
|
+
}
|
|
4297
|
+
for (const section of input.sections.filter((row) => row.bookId === book.id && row.status === "approved")) {
|
|
4298
|
+
if (!section.approvalReceiptId || !section.approvalActionDigest) {
|
|
4299
|
+
throw new TypeError(`approved brand section ${section.key} lacks approval provenance`);
|
|
4300
|
+
}
|
|
4301
|
+
artifacts.push({
|
|
4302
|
+
kind: "brand.section",
|
|
4303
|
+
id: `${book.slug}/${section.kind}`,
|
|
4304
|
+
sourceRevision: revision,
|
|
4305
|
+
provenance: provenance(book, [book.id, section.id, section.approvalReceiptId], [section.approvalActionDigest]),
|
|
4306
|
+
content: canonicalPromotionValue({ bookSlug: book.slug, kind: section.kind, content: section.content })
|
|
4307
|
+
});
|
|
4308
|
+
}
|
|
4309
|
+
const activePalette2 = input.palettes.find((row) => row.bookId === book.id && row.id === book.activePaletteId && row.status === "active");
|
|
4310
|
+
if (book.activePaletteId && !activePalette2) throw new TypeError(`active palette ${book.activePaletteId} is unavailable`);
|
|
4311
|
+
if (activePalette2) {
|
|
4312
|
+
artifacts.push({
|
|
4313
|
+
kind: "brand.palette",
|
|
4314
|
+
id: `${book.slug}/${activePalette2.id}`,
|
|
4315
|
+
sourceRevision: revision,
|
|
4316
|
+
provenance: provenance(book, [book.id, activePalette2.id, activePalette2.proposalId, activePalette2.approvalReceiptId], [activePalette2.approvalActionDigest]),
|
|
4317
|
+
content: canonicalPromotionValue({
|
|
4318
|
+
bookSlug: book.slug,
|
|
4319
|
+
name: activePalette2.name,
|
|
4320
|
+
swatches: activePalette2.swatches,
|
|
4321
|
+
source: activePalette2.source,
|
|
4322
|
+
...activePalette2.seedHex ? { seedHex: activePalette2.seedHex } : {},
|
|
4323
|
+
...activePalette2.rationale ? { rationale: activePalette2.rationale } : {}
|
|
4324
|
+
})
|
|
4325
|
+
});
|
|
4326
|
+
}
|
|
4327
|
+
return {
|
|
4328
|
+
adapter: BRAND_PROMOTION_ADAPTER,
|
|
4329
|
+
artifacts,
|
|
4330
|
+
exclusions: exclusions(book),
|
|
4331
|
+
preconditions: [{
|
|
4332
|
+
code: "brand.schema",
|
|
4333
|
+
path: "services.brand.schemaVersion",
|
|
4334
|
+
expected: 1,
|
|
4335
|
+
detail: "The target must expose the Brand v1 approved-content schema."
|
|
4336
|
+
}]
|
|
4337
|
+
};
|
|
4338
|
+
}
|
|
4339
|
+
function exportBrandPromotionCandidate(input) {
|
|
4340
|
+
if (input.book.status !== "draft")
|
|
4341
|
+
throw new TypeError(`brand book ${input.book.slug} is not draft`);
|
|
4342
|
+
return buildBrandPromotion({
|
|
4343
|
+
...input,
|
|
4344
|
+
book: { ...input.book, status: "active" }
|
|
4345
|
+
});
|
|
4346
|
+
}
|
|
4347
|
+
function exportBrandPromotion(input) {
|
|
4348
|
+
const { book } = input;
|
|
4349
|
+
if (book.status !== "active") throw new TypeError(`brand book ${book.slug} is not active`);
|
|
4350
|
+
if (!book.activationReceiptId || !book.activationActionDigest)
|
|
4351
|
+
throw new TypeError(`active brand book ${book.slug} lacks activation provenance`);
|
|
4352
|
+
return buildBrandPromotion(input);
|
|
4353
|
+
}
|
|
4354
|
+
|
|
4355
|
+
// src/routes/activation-state.ts
|
|
4356
|
+
async function activationReceiptByMutation(ctx, mutationKey) {
|
|
4357
|
+
const result = await ctx.db.query({
|
|
4358
|
+
[BRAND_NS.activationReceipt]: {
|
|
4359
|
+
$: { where: { mutationKey } },
|
|
4360
|
+
book: {}
|
|
4361
|
+
}
|
|
4362
|
+
});
|
|
4363
|
+
const row = (result[BRAND_NS.activationReceipt] ?? [])[0];
|
|
4364
|
+
if (!row || !hasExactOneLink(row.book, row.bookId)) return void 0;
|
|
4365
|
+
const { book: _book, ...receipt2 } = row;
|
|
4366
|
+
return activationReceiptAsWritten(receipt2);
|
|
4367
|
+
}
|
|
4368
|
+
async function approvalReceipt(ctx, bookId, receiptId) {
|
|
4369
|
+
const result = await ctx.db.query({
|
|
4370
|
+
[BRAND_NS.approvalReceipt]: {
|
|
4371
|
+
$: { where: { id: receiptId, bookId } },
|
|
4372
|
+
book: {}
|
|
4373
|
+
}
|
|
4374
|
+
});
|
|
4375
|
+
const row = (result[BRAND_NS.approvalReceipt] ?? [])[0];
|
|
4376
|
+
if (!row || !hasExactOneLink(row.book, bookId))
|
|
4377
|
+
throw new BrandInputError(`approval receipt ${receiptId} is unavailable`);
|
|
4378
|
+
const { book: _book, ...stored } = row;
|
|
4379
|
+
const receipt2 = receiptAsWritten(stored);
|
|
4380
|
+
if (!await verifyBrandApprovalReceipt(receipt2))
|
|
4381
|
+
throw new BrandInputError(`approval receipt ${receiptId} is invalid`);
|
|
4382
|
+
return receipt2;
|
|
4383
|
+
}
|
|
4384
|
+
async function activationState(ctx, book) {
|
|
4385
|
+
if (book.status !== "draft")
|
|
4386
|
+
throw new BrandConflictError(`brand book ${book.slug} is already ${book.status}`);
|
|
4387
|
+
const tokens = book.tokens;
|
|
4388
|
+
if (!book.activePaletteId || !tokens || tokens.paletteId !== book.activePaletteId || !tokens.paletteReceiptId || !tokens.paletteActionDigest || !tokens.typographyReceiptId || !tokens.typographyActionDigest) throw new BrandInputError(
|
|
4389
|
+
"brand book activation requires approved palette and typography tokens"
|
|
4390
|
+
);
|
|
4391
|
+
const result = await ctx.db.query({
|
|
4392
|
+
[BRAND_NS.section]: { $: { where: { bookId: book.id } }, book: {} },
|
|
4393
|
+
[BRAND_NS.palette]: { $: { where: { bookId: book.id } }, book: {} }
|
|
4394
|
+
});
|
|
4395
|
+
const sections = (result[BRAND_NS.section] ?? []).map((row) => rowAsWritten(BRAND_NS.section, row));
|
|
4396
|
+
const palettes = (result[BRAND_NS.palette] ?? []).map((row) => rowAsWritten(BRAND_NS.palette, row));
|
|
4397
|
+
const activePalette2 = palettes.find((row) => row.id === book.activePaletteId && row.status === "active");
|
|
4398
|
+
const typography = sections.find((row) => row.kind === "typography" && row.status === "approved");
|
|
4399
|
+
if (!activePalette2 || activePalette2.approvalReceiptId !== tokens.paletteReceiptId || activePalette2.approvalActionDigest !== tokens.paletteActionDigest) throw new BrandInputError("active palette does not match compiled token provenance");
|
|
4400
|
+
if (!typography || typography.approvalReceiptId !== tokens.typographyReceiptId || typography.approvalActionDigest !== tokens.typographyActionDigest) throw new BrandInputError("approved typography does not match compiled token provenance");
|
|
4401
|
+
const [paletteReceipt, typographyReceipt] = await Promise.all([
|
|
4402
|
+
approvalReceipt(ctx, book.id, tokens.paletteReceiptId),
|
|
4403
|
+
approvalReceipt(ctx, book.id, tokens.typographyReceiptId)
|
|
4404
|
+
]);
|
|
4405
|
+
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");
|
|
4406
|
+
const candidateBook = { ...book, status: "draft" };
|
|
4407
|
+
delete candidateBook.activationReceiptId;
|
|
4408
|
+
delete candidateBook.activationActionDigest;
|
|
4409
|
+
const promotion = exportBrandPromotionCandidate({ book: candidateBook, sections, palettes });
|
|
4410
|
+
const approved = sections.filter((row) => row.status === "approved").sort((left, right) => left.key.localeCompare(right.key));
|
|
4411
|
+
const reviewed = {
|
|
4412
|
+
version: 1,
|
|
4413
|
+
bookId: book.id,
|
|
4414
|
+
slug: book.slug,
|
|
4415
|
+
status: "draft",
|
|
4416
|
+
bookVersion: book.version,
|
|
4417
|
+
activationRevision: book.activationRevision,
|
|
4418
|
+
activePaletteId: book.activePaletteId,
|
|
4419
|
+
memberIdsDigest: await brandJsonDigest(book.memberIds),
|
|
4420
|
+
tokensSourceDigest: tokens.sourceDigest,
|
|
4421
|
+
paletteReceiptId: tokens.paletteReceiptId,
|
|
4422
|
+
paletteActionDigest: tokens.paletteActionDigest,
|
|
4423
|
+
typographyReceiptId: tokens.typographyReceiptId,
|
|
4424
|
+
typographyActionDigest: tokens.typographyActionDigest,
|
|
4425
|
+
approvedSectionKeys: approved.map((row) => row.key),
|
|
4426
|
+
promotionDigest: await brandJsonDigest(promotion)
|
|
4427
|
+
};
|
|
4428
|
+
return {
|
|
4429
|
+
reviewed,
|
|
4430
|
+
sections,
|
|
4431
|
+
palettes,
|
|
4432
|
+
guards: [
|
|
4433
|
+
{
|
|
4434
|
+
ns: BRAND_NS.book,
|
|
4435
|
+
id: book.id,
|
|
4436
|
+
exists: true,
|
|
4437
|
+
equals: {
|
|
4438
|
+
version: book.version,
|
|
4439
|
+
status: "draft",
|
|
4440
|
+
activationRevision: book.activationRevision,
|
|
4441
|
+
activePaletteId: book.activePaletteId,
|
|
4442
|
+
memberIds: book.memberIds,
|
|
4443
|
+
tokens: book.tokens
|
|
4444
|
+
}
|
|
4445
|
+
},
|
|
4446
|
+
{
|
|
4447
|
+
ns: BRAND_NS.palette,
|
|
4448
|
+
id: activePalette2.id,
|
|
4449
|
+
exists: true,
|
|
4450
|
+
equals: {
|
|
4451
|
+
status: "active",
|
|
4452
|
+
approvalReceiptId: activePalette2.approvalReceiptId,
|
|
4453
|
+
approvalActionDigest: activePalette2.approvalActionDigest,
|
|
4454
|
+
swatches: activePalette2.swatches
|
|
4455
|
+
}
|
|
4456
|
+
},
|
|
4457
|
+
...approved.map((row) => ({
|
|
4458
|
+
ns: BRAND_NS.section,
|
|
4459
|
+
id: row.id,
|
|
4460
|
+
exists: true,
|
|
4461
|
+
equals: {
|
|
4462
|
+
status: "approved",
|
|
4463
|
+
content: row.content,
|
|
4464
|
+
approvalReceiptId: row.approvalReceiptId,
|
|
4465
|
+
approvalActionDigest: row.approvalActionDigest
|
|
4466
|
+
}
|
|
4467
|
+
})),
|
|
4468
|
+
...[paletteReceipt, typographyReceipt].map((row) => ({
|
|
4469
|
+
ns: BRAND_NS.approvalReceipt,
|
|
4470
|
+
id: row.id,
|
|
4471
|
+
exists: true,
|
|
4472
|
+
equals: { receiptDigest: row.receiptDigest }
|
|
4473
|
+
}))
|
|
4474
|
+
]
|
|
4475
|
+
};
|
|
4476
|
+
}
|
|
4477
|
+
|
|
4478
|
+
// src/routes/activation.ts
|
|
4479
|
+
var record8 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
4480
|
+
var guardFailure3 = (error) => record8(error) && error.code === "transact_guard_failed";
|
|
4481
|
+
async function activationMutationKey(bookId, mutationId) {
|
|
4482
|
+
const digest2 = await brandJsonDigest({ version: 1, bookId, mutationId });
|
|
4483
|
+
return `brand:book-activation:v1:${digest2.slice("sha256:".length)}`;
|
|
4484
|
+
}
|
|
4485
|
+
async function consumeActivationAuthority(ctx, req, bookId, actionDigest, mutationKey) {
|
|
4486
|
+
const authority = await ctx.consumeHumanExact({
|
|
4487
|
+
req,
|
|
4488
|
+
actor: ctx.actor,
|
|
4489
|
+
appId: ctx.appId,
|
|
4490
|
+
capability: "brand.book.activate",
|
|
4491
|
+
projectCapability: "brand.approve",
|
|
4492
|
+
effect: "external",
|
|
4493
|
+
resource: { bookId },
|
|
4494
|
+
actionDigest,
|
|
4495
|
+
consumptionIdempotencyKey: mutationKey
|
|
4496
|
+
});
|
|
4497
|
+
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");
|
|
4498
|
+
return authority;
|
|
4499
|
+
}
|
|
4500
|
+
async function handleBookActivation(ctx, req, bookId) {
|
|
4501
|
+
const book = await loadMemberBook(ctx.db, bookId, ctx.actor.id);
|
|
4502
|
+
if (ctx.actor.kind !== "human")
|
|
4503
|
+
throw new BrandForbiddenError("only a directly authenticated human can activate a brand book");
|
|
4504
|
+
if (req.method === "GET") {
|
|
4505
|
+
if (book.status === "active") return json({ status: "active", book });
|
|
4506
|
+
const state2 = await activationState(ctx, book);
|
|
4507
|
+
return json({ status: "draft", reviewedBook: state2.reviewed });
|
|
4508
|
+
}
|
|
4509
|
+
if (req.method !== "POST") return methodNotAllowed();
|
|
4510
|
+
const body = await readJson(req);
|
|
4511
|
+
if (!record8(body.reviewedBook))
|
|
4512
|
+
throw new BrandInputError('"reviewedBook" is required');
|
|
4513
|
+
const reviewed = body.reviewedBook;
|
|
4514
|
+
const mutationId = capString(str(body, "mutationId"), "mutationId", 200);
|
|
4515
|
+
const mutationKey = await activationMutationKey(bookId, mutationId);
|
|
4516
|
+
const stable = (await brandJsonDigest({ mutationKey })).slice(7);
|
|
4517
|
+
const receiptId = `brand-activation-${stable.slice(0, 32)}`;
|
|
4518
|
+
const existing = await activationReceiptByMutation(ctx, mutationKey);
|
|
4519
|
+
if (existing) {
|
|
4520
|
+
if (!await verifyBrandActivationReceipt(existing) || canonicalBrandJson(existing.reviewedBook) !== canonicalBrandJson(reviewed)) throw new BrandConflictError("mutationId was used for another activation");
|
|
4521
|
+
const replayed = await consumeActivationAuthority(
|
|
4522
|
+
ctx,
|
|
4523
|
+
req,
|
|
4524
|
+
bookId,
|
|
4525
|
+
existing.actionDigest,
|
|
4526
|
+
mutationKey
|
|
4527
|
+
);
|
|
4528
|
+
if (canonicalBrandJson(replayed) !== canonicalBrandJson(existing.authorityConsumption))
|
|
4529
|
+
throw new BrandConflictError("central activation receipt changed");
|
|
4530
|
+
if (book.status !== "active" || book.activationReceiptId !== existing.id || book.activationActionDigest !== existing.actionDigest) throw new BrandConflictError("activated Brand revision is no longer current");
|
|
4531
|
+
const result = await ctx.db.query({
|
|
4532
|
+
[BRAND_NS.section]: { $: { where: { bookId } } },
|
|
4533
|
+
[BRAND_NS.palette]: { $: { where: { bookId } } }
|
|
4534
|
+
});
|
|
4535
|
+
const promotion2 = exportBrandPromotion({
|
|
4536
|
+
book,
|
|
4537
|
+
sections: result[BRAND_NS.section] ?? [],
|
|
4538
|
+
palettes: result[BRAND_NS.palette] ?? []
|
|
4539
|
+
});
|
|
4540
|
+
return json({ receipt: existing, promotion: promotion2, promotionDigest: await brandJsonDigest(promotion2), duplicate: true });
|
|
4541
|
+
}
|
|
4542
|
+
const state = await activationState(ctx, book);
|
|
4543
|
+
if (canonicalBrandJson(state.reviewed) !== canonicalBrandJson(reviewed))
|
|
4544
|
+
throw new BrandConflictError("brand book changed since activation review");
|
|
4545
|
+
const actionDigest = await brandJsonDigest({
|
|
4546
|
+
version: 1,
|
|
4547
|
+
reviewedBook: state.reviewed,
|
|
4548
|
+
intendedStatus: "active",
|
|
4549
|
+
activationReceiptId: receiptId
|
|
4550
|
+
});
|
|
4551
|
+
const authority = await consumeActivationAuthority(
|
|
4552
|
+
ctx,
|
|
4553
|
+
req,
|
|
4554
|
+
bookId,
|
|
4555
|
+
actionDigest,
|
|
4556
|
+
mutationKey
|
|
4557
|
+
);
|
|
4558
|
+
const receiptBase = {
|
|
4559
|
+
version: 1,
|
|
4560
|
+
id: receiptId,
|
|
4561
|
+
mutationKey,
|
|
4562
|
+
bookId,
|
|
4563
|
+
reviewedBook: state.reviewed,
|
|
4564
|
+
actionDigest,
|
|
4565
|
+
approvedBy: ctx.actor.id,
|
|
4566
|
+
approvedByKind: "human",
|
|
4567
|
+
authorityRef: brandAuthorityRef(authority),
|
|
4568
|
+
authorityCapability: "brand.approve",
|
|
4569
|
+
authorityConsumption: authority,
|
|
4570
|
+
createdAt: authority.consumedAt
|
|
4571
|
+
};
|
|
4572
|
+
const receipt2 = {
|
|
4573
|
+
...receiptBase,
|
|
4574
|
+
receiptDigest: await brandJsonDigest(receiptBase)
|
|
4575
|
+
};
|
|
4576
|
+
try {
|
|
4577
|
+
const tx = await ctx.db.transact([
|
|
4578
|
+
{
|
|
4579
|
+
t: "update",
|
|
4580
|
+
ns: BRAND_NS.book,
|
|
4581
|
+
id: bookId,
|
|
4582
|
+
attrs: {
|
|
4583
|
+
status: "active",
|
|
4584
|
+
version: book.version + 1,
|
|
4585
|
+
activationReceiptId: receipt2.id,
|
|
4586
|
+
activationActionDigest: receipt2.actionDigest,
|
|
4587
|
+
updatedAt: authority.consumedAt
|
|
4588
|
+
}
|
|
4589
|
+
},
|
|
4590
|
+
{ t: "update", ns: BRAND_NS.activationReceipt, id: receipt2.id, attrs: receipt2 },
|
|
4591
|
+
{ t: "link", ns: BRAND_NS.activationReceipt, id: receipt2.id, label: "book", target: bookId }
|
|
4592
|
+
], {
|
|
4593
|
+
mutationId: mutationKey,
|
|
4594
|
+
guards: [
|
|
4595
|
+
...state.guards,
|
|
4596
|
+
{ ns: BRAND_NS.activationReceipt, id: receipt2.id, exists: false }
|
|
4597
|
+
],
|
|
4598
|
+
asUser: ctx.actor.id,
|
|
4599
|
+
...ctx.actor.email ? { asEmail: ctx.actor.email } : {},
|
|
4600
|
+
asPrincipalKind: "human"
|
|
4601
|
+
});
|
|
4602
|
+
if (tx.duplicate) throw new BrandConflictError("activation replay was not observable");
|
|
4603
|
+
} catch (error) {
|
|
4604
|
+
if (guardFailure3(error)) throw new BrandReviewStateChangedError();
|
|
4605
|
+
throw error;
|
|
4606
|
+
}
|
|
4607
|
+
const activeBook = {
|
|
4608
|
+
...book,
|
|
4609
|
+
status: "active",
|
|
4610
|
+
version: book.version + 1,
|
|
4611
|
+
activationReceiptId: receipt2.id,
|
|
4612
|
+
activationActionDigest: receipt2.actionDigest,
|
|
4613
|
+
updatedAt: authority.consumedAt
|
|
4614
|
+
};
|
|
4615
|
+
const promotion = exportBrandPromotion({
|
|
4616
|
+
book: activeBook,
|
|
4617
|
+
sections: state.sections,
|
|
4618
|
+
palettes: state.palettes
|
|
4619
|
+
});
|
|
4620
|
+
return json({
|
|
4621
|
+
receipt: receipt2,
|
|
4622
|
+
promotion,
|
|
4623
|
+
promotionDigest: await brandJsonDigest(promotion),
|
|
4624
|
+
duplicate: false
|
|
4625
|
+
});
|
|
4626
|
+
}
|
|
4627
|
+
|
|
4078
4628
|
// src/routes/index.ts
|
|
4079
4629
|
var tokensFile = (seg) => seg.length === 3 && seg[0] === "books" && (seg[2] === "tokens.css" || seg[2] === "tokens.json") ? seg[2] : null;
|
|
4080
4630
|
async function route(ctx, req, url, seg) {
|
|
@@ -4085,6 +4635,7 @@ async function route(ctx, req, url, seg) {
|
|
|
4085
4635
|
if (seg.length === 1) return handleBooksRoot(ctx, req);
|
|
4086
4636
|
if (seg.length === 2) return handleBookItem(ctx, req, id);
|
|
4087
4637
|
if (seg.length === 3 && sub === "members") return handleBookMembers(ctx, req, id);
|
|
4638
|
+
if (seg.length === 3 && sub === "activate") return handleBookActivation(ctx, req, id);
|
|
4088
4639
|
if (sub === "assets") {
|
|
4089
4640
|
if (seg.length === 3) return handleAssetsRoot(ctx, req, id);
|
|
4090
4641
|
if (seg.length === 4) return handleAssetItem(ctx, req, id, subId);
|
|
@@ -4344,7 +4895,7 @@ var brandIntegration = {
|
|
|
4344
4895
|
agentProfile: BRAND_AGENT_PROFILE,
|
|
4345
4896
|
provision: {
|
|
4346
4897
|
human: [
|
|
4347
|
-
"Configure direct Clerk human approval through authorizeCapability + consumeHumanExact; delegated, machine, and agent principals must not resolve proposals.",
|
|
4898
|
+
"Configure direct Clerk human approval through authorizeCapability + consumeHumanExact; delegated, machine, and agent principals must not resolve proposals or activate books.",
|
|
4348
4899
|
'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).',
|
|
4349
4900
|
"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.",
|
|
4350
4901
|
"Decide whether compiled tokens are public (publicTokens serves tokens.css/tokens.json unauthenticated)."
|
|
@@ -4358,9 +4909,10 @@ var brandIntegration = {
|
|
|
4358
4909
|
"Provision private file signing and verifySourceAssetSnapshot; never expose an admin credential or persistent asset URL to the agent."
|
|
4359
4910
|
],
|
|
4360
4911
|
doctor: [
|
|
4361
|
-
"All
|
|
4912
|
+
"All seven brand_* namespaces have rules installed; raw browser/agent writes are denied and child reads use current linked-book membership.",
|
|
4362
4913
|
"The agent runtime matches BRAND_AGENT_PROFILE: brand.read/edit only, semantic operations only, no raw brand_* or file access.",
|
|
4363
4914
|
"Proposal decisions consume a direct-human exact brand.approve authority use and persist one guarded brand_approval_receipt.",
|
|
4915
|
+
"Book activation consumes a direct-human exact external brand.approve authority use and persists one guarded brand_activation_receipt before promotion export opens.",
|
|
4364
4916
|
"Private source assets are signed briefly and verifySourceAssetSnapshot revalidates path, ETag, size, content type, and digest before approval.",
|
|
4365
4917
|
"brand_book.id/slug, brand_section.key, and the other mirrored id attrs are unique.",
|
|
4366
4918
|
"Each registered trigger's agent id is present in its target books' memberIds rosters.",
|
|
@@ -4384,115 +4936,6 @@ function createBrandIntegration(options = {}) {
|
|
|
4384
4936
|
probes: [{ path: `${basePath}/books`, expectedStatus: 401 }]
|
|
4385
4937
|
};
|
|
4386
4938
|
}
|
|
4387
|
-
|
|
4388
|
-
// src/promotion.ts
|
|
4389
|
-
import {
|
|
4390
|
-
canonicalPromotionValue
|
|
4391
|
-
} from "@odla-ai/promotion";
|
|
4392
|
-
var BRAND_PROMOTION_ADAPTER = "@odla-ai/brand/v1";
|
|
4393
|
-
var provenance = (sourceIds, receiptDigests = []) => ({
|
|
4394
|
-
adapter: BRAND_PROMOTION_ADAPTER,
|
|
4395
|
-
sourceIds: [...sourceIds].sort(),
|
|
4396
|
-
receiptDigests: [...receiptDigests].sort()
|
|
4397
|
-
});
|
|
4398
|
-
var exclusions = (book) => [
|
|
4399
|
-
{
|
|
4400
|
-
kind: "brand.runtime",
|
|
4401
|
-
id: book.slug,
|
|
4402
|
-
reason: "environment_local",
|
|
4403
|
-
paths: [`${BRAND_NS.book}.ownerId`, `${BRAND_NS.book}.memberIds`, `${BRAND_NS.book}.channelId`, `${BRAND_NS.book}.*At`],
|
|
4404
|
-
detail: "Ownership, roster, channel, and timestamps remain local to the target environment."
|
|
4405
|
-
},
|
|
4406
|
-
{
|
|
4407
|
-
kind: "brand.drafts",
|
|
4408
|
-
id: book.slug,
|
|
4409
|
-
reason: "draft_state",
|
|
4410
|
-
paths: [`${BRAND_NS.proposal}.*`, `${BRAND_NS.section}[status=draft]`],
|
|
4411
|
-
detail: "Open proposals and unapproved sections are review state, not published brand content."
|
|
4412
|
-
},
|
|
4413
|
-
{
|
|
4414
|
-
kind: "brand.assets",
|
|
4415
|
-
id: book.slug,
|
|
4416
|
-
reason: "provider_identity",
|
|
4417
|
-
paths: [`${BRAND_NS.asset}.storageObjectId`, `${BRAND_NS.asset}.path`, `${BRAND_NS.asset}.*AuthorityRef`],
|
|
4418
|
-
detail: "Stored files and provider object identities require a separate asset-transfer contract."
|
|
4419
|
-
},
|
|
4420
|
-
{
|
|
4421
|
-
kind: "brand.authority",
|
|
4422
|
-
id: book.slug,
|
|
4423
|
-
reason: "authority_history",
|
|
4424
|
-
paths: [`${BRAND_NS.approvalReceipt}.*`, `${BRAND_NS.palette}.audience`],
|
|
4425
|
-
detail: "Authority rows stay immutable in their source environment; artifacts retain only receipt digests and ids."
|
|
4426
|
-
}
|
|
4427
|
-
];
|
|
4428
|
-
function exportBrandPromotion(input) {
|
|
4429
|
-
const { book } = input;
|
|
4430
|
-
if (book.status !== "active") throw new TypeError(`brand book ${book.slug} is not active`);
|
|
4431
|
-
const revision = `brand:${book.slug}:${book.activationRevision}`;
|
|
4432
|
-
const artifacts = [];
|
|
4433
|
-
if (book.tokens) {
|
|
4434
|
-
const {
|
|
4435
|
-
compiledAt: _compiledAt,
|
|
4436
|
-
paletteReceiptId,
|
|
4437
|
-
paletteActionDigest,
|
|
4438
|
-
typographyReceiptId,
|
|
4439
|
-
typographyActionDigest,
|
|
4440
|
-
sourceDigest,
|
|
4441
|
-
...stableTokens
|
|
4442
|
-
} = book.tokens;
|
|
4443
|
-
artifacts.push({
|
|
4444
|
-
kind: "brand.tokens",
|
|
4445
|
-
id: book.slug,
|
|
4446
|
-
sourceRevision: revision,
|
|
4447
|
-
provenance: provenance(
|
|
4448
|
-
[book.id, book.tokens.paletteId, paletteReceiptId, typographyReceiptId].filter((value) => Boolean(value)),
|
|
4449
|
-
[sourceDigest, paletteActionDigest, typographyActionDigest].filter((value) => Boolean(value))
|
|
4450
|
-
),
|
|
4451
|
-
content: canonicalPromotionValue({ bookSlug: book.slug, tokens: stableTokens })
|
|
4452
|
-
});
|
|
4453
|
-
}
|
|
4454
|
-
for (const section of input.sections.filter((row) => row.bookId === book.id && row.status === "approved")) {
|
|
4455
|
-
if (!section.approvalReceiptId || !section.approvalActionDigest) {
|
|
4456
|
-
throw new TypeError(`approved brand section ${section.key} lacks approval provenance`);
|
|
4457
|
-
}
|
|
4458
|
-
artifacts.push({
|
|
4459
|
-
kind: "brand.section",
|
|
4460
|
-
id: `${book.slug}/${section.kind}`,
|
|
4461
|
-
sourceRevision: revision,
|
|
4462
|
-
provenance: provenance([book.id, section.id, section.approvalReceiptId], [section.approvalActionDigest]),
|
|
4463
|
-
content: canonicalPromotionValue({ bookSlug: book.slug, kind: section.kind, content: section.content })
|
|
4464
|
-
});
|
|
4465
|
-
}
|
|
4466
|
-
const activePalette2 = input.palettes.find((row) => row.bookId === book.id && row.id === book.activePaletteId && row.status === "active");
|
|
4467
|
-
if (book.activePaletteId && !activePalette2) throw new TypeError(`active palette ${book.activePaletteId} is unavailable`);
|
|
4468
|
-
if (activePalette2) {
|
|
4469
|
-
artifacts.push({
|
|
4470
|
-
kind: "brand.palette",
|
|
4471
|
-
id: `${book.slug}/${activePalette2.id}`,
|
|
4472
|
-
sourceRevision: revision,
|
|
4473
|
-
provenance: provenance([book.id, activePalette2.id, activePalette2.proposalId, activePalette2.approvalReceiptId], [activePalette2.approvalActionDigest]),
|
|
4474
|
-
content: canonicalPromotionValue({
|
|
4475
|
-
bookSlug: book.slug,
|
|
4476
|
-
name: activePalette2.name,
|
|
4477
|
-
swatches: activePalette2.swatches,
|
|
4478
|
-
source: activePalette2.source,
|
|
4479
|
-
...activePalette2.seedHex ? { seedHex: activePalette2.seedHex } : {},
|
|
4480
|
-
...activePalette2.rationale ? { rationale: activePalette2.rationale } : {}
|
|
4481
|
-
})
|
|
4482
|
-
});
|
|
4483
|
-
}
|
|
4484
|
-
return {
|
|
4485
|
-
adapter: BRAND_PROMOTION_ADAPTER,
|
|
4486
|
-
artifacts,
|
|
4487
|
-
exclusions: exclusions(book),
|
|
4488
|
-
preconditions: [{
|
|
4489
|
-
code: "brand.schema",
|
|
4490
|
-
path: "services.brand.schemaVersion",
|
|
4491
|
-
expected: 1,
|
|
4492
|
-
detail: "The target must expose the Brand v1 approved-content schema."
|
|
4493
|
-
}]
|
|
4494
|
-
};
|
|
4495
|
-
}
|
|
4496
4939
|
export {
|
|
4497
4940
|
ASSET_CONTENT_TYPES,
|
|
4498
4941
|
ASSET_KINDS,
|
|
@@ -4545,6 +4988,7 @@ export {
|
|
|
4545
4988
|
SECTION_STATUSES,
|
|
4546
4989
|
SWATCH_ROLES,
|
|
4547
4990
|
acceptProposalOps,
|
|
4991
|
+
activationReceiptAsWritten,
|
|
4548
4992
|
adjustLightnessUntil,
|
|
4549
4993
|
analogous,
|
|
4550
4994
|
assertAnalysis,
|
|
@@ -4661,6 +5105,7 @@ export {
|
|
|
4661
5105
|
triadic,
|
|
4662
5106
|
updateBookOps,
|
|
4663
5107
|
upsertSectionOps,
|
|
5108
|
+
verifyBrandActivationReceipt,
|
|
4664
5109
|
verifyBrandApprovalReceipt
|
|
4665
5110
|
};
|
|
4666
5111
|
//# sourceMappingURL=index.js.map
|