@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.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)
|
|
829
|
-
var exactKeys = (value, required
|
|
830
|
-
const allowed =
|
|
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 (!
|
|
985
|
+
if (!record4(value) || !exactKeys2(value, SNAPSHOT_REQUIRED)) return false;
|
|
854
986
|
const provenance2 = value.provenance;
|
|
855
|
-
return
|
|
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 ||
|
|
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
|
-
]) &&
|
|
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 (!
|
|
895
|
-
return
|
|
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 (!
|
|
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 || !
|
|
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 && !
|
|
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
|
|
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 (
|
|
3787
|
-
await ctx.db.storage.delete(
|
|
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:
|
|
3807
|
-
storageObjectId:
|
|
3938
|
+
path: record9.path,
|
|
3939
|
+
storageObjectId: record9.id,
|
|
3808
3940
|
contentDigest: await contentDigest(file),
|
|
3809
3941
|
contentType,
|
|
3810
|
-
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(
|
|
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
|
|
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
|
|
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 (!
|
|
4294
|
+
if (!record5(value)) throw new BrandInputError('"reviewedProposal" must be an object');
|
|
4163
4295
|
const keys = Object.keys(value).sort();
|
|
4164
|
-
if (keys.length !==
|
|
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) || !
|
|
4168
|
-
if (!
|
|
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
|
|
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 (!
|
|
4249
|
-
Object.assign(
|
|
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
|
|
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 (
|
|
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
|
-
|
|
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;
|
|
@@ -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
|
|
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) =>
|
|
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
|
|
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 || !
|
|
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
|
|
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
|