@odla-ai/brand 0.5.0 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.cts CHANGED
@@ -627,6 +627,33 @@ declare function assertAssetContentType(value: unknown, kind?: string): string;
627
627
  */
628
628
  declare function assertAnalysis(value: unknown): AssetAnalysis;
629
629
 
630
+ /**
631
+ * A stored row in the shape it was WRITTEN, ready to verify.
632
+ *
633
+ * The engine stores a `date` as the epoch ms it was given and hands a reader
634
+ * ISO-8601 text back. Brand digests rows that CONTAIN dates — a proposal's
635
+ * `reviewDigest` covers its `createdAt`, and an approval receipt's
636
+ * `receiptDigest` covers its own — so a row read straight out of the store can
637
+ * never match the digest stored beside it. Two production symptoms, both 409:
638
+ * resolving any proposal ("proposal changed since review") and reading a book's
639
+ * tokens after approval.
640
+ *
641
+ * Normalising at the READ is the fix, not tolerating both shapes in the
642
+ * verifier. Tolerance would repair a `safeTime` check and leave the digest
643
+ * failing, because the digest was taken over the number and nothing can change
644
+ * that after the fact.
645
+ *
646
+ * Anything that is not date-shaped text passes through untouched, so a row that
647
+ * never round-tripped verifies exactly as before. `json` attrs are left alone:
648
+ * their contents round-trip as written, which is why an `authorityConsumption`
649
+ * and the `createdAt` beside it disagreed at all.
650
+ */
651
+ declare function rowAsWritten<T>(ns: string, row: T): T;
652
+ /** A stored approval receipt in the shape it was written. */
653
+ declare function receiptAsWritten<T>(row: T): T;
654
+ /** A stored proposal in the shape it was written, so `reviewDigest` matches. */
655
+ declare function proposalAsWritten<T>(row: T): T;
656
+
630
657
  /** Stay materially below the server's whole-guards parser budget. */
631
658
  declare function isBoundedBrandJson(root: unknown, limits?: {
632
659
  maxDepth?: number;
@@ -1874,4 +1901,4 @@ interface CreateBrandIntegrationOptions {
1874
1901
  */
1875
1902
  declare function createBrandIntegration(options?: CreateBrandIntegrationOptions): BrandIntegrationDescriptor;
1876
1903
 
1877
- export { ASSET_CONTENT_TYPES, type AcceptProposalInput, AssetAnalysis, AssetKind, type AttrType, type AudienceChildren, BRAND_AGENT_PROFILE, BRAND_DISCUSSION_REFERENCE_KINDS, BRAND_INSTRUCTIONS, BRAND_RULES, BRAND_SCHEMA, BookStatus, type BrandActor, type BrandAgentBridge, type BrandAgentProfile, type BrandApprovalReceipt, type BrandAsset, type BrandAttrs, type BrandBook, type BrandBotTriggerOpts, type BrandCapabilityAuthority, BrandConflictError, type BrandDb, type BrandDecisionBinding, type BrandDeps, type BrandDiscussionReference, type BrandDiscussionReferenceKind, type BrandDiscussionReferenceTarget, type BrandDiscussionSwatch, type BrandDispatchBody, type BrandDispatchDeps, type BrandEntityRef, type BrandFetchedBytes, type BrandFileRecord, BrandForbiddenError, BrandGoneError, type BrandHumanAuthorityConsumption, BrandInputError, type BrandIntegrationDescriptor, type BrandIntegrationProbe, type BrandLookup, BrandNotFoundError, type BrandOp, type BrandPalette, type BrandPrincipalProjection, type BrandProposal, type BrandProposalProvenance, type BrandProposalReviewSnapshot, type BrandResult, BrandReviewStateChangedError, type BrandRouteCtx, type BrandRouteOpts, type BrandRow, type BrandRule, type BrandRules, type BrandScalar, type BrandSection, type BrandSkillOpts, type BrandSkillSelf, type BrandSourceAssetSnapshot, type BrandStorage, BrandTokensSnapshot, type BrandToolCtx, type BrandTransactGuard, type BrandTransactOptions, type BrandTrigger, type BrandUploadBody, type BrandVisionCapabilities, type BrandVisionSpec, CHART_DELTA_MIN, CHAT_MESSAGE_NS, CSS_NAMED_COLORS, type ContrastKind, type CreateAssetInput, type CreateBookInput, type CreateBrandIntegrationOptions, type CreateBrandPersonaOpts, DEFAULT_BRAND_SYSTEM, DESIGN_CONTENT_TYPES, type DeriveChartOptions, type DerivePaletteOptions, type DesignAssetGroup, type DesignBundle, type DesignBundleAsset, type DesignColorUse, type DesignDecompilation, type DesignDigest, type DesignManifestEntry, type DesignOutlineEntry, type DesignProp, type DesignTokenSets, type DesignTokenSkip, type Hsl, type IntegrationProvision, type IntegrationSecret, type IntegrationSetting, type LightnessDirection, MAX_COLORS, MAX_EXTERNALS, MAX_FONTS, MAX_HEADING_CHARS, MAX_OUTLINE_ENTRIES, MAX_PROPS, MAX_SWATCHES, MAX_THUMBNAIL_CHARS, MAX_VIEW_BYTES, type NamedColor, type NearestNamedColor, type Oklab, type Oklch, PICK_TEXT_DEFAULT_CANDIDATES, PaletteSource, PaletteStatus, ProposalKind, ProposalStatus, type ProposePaletteInput, type ProposeSectionInput, RAMP_L_MAX, RAMP_L_MIN, type RejectProposalInput, type ResolvedBrandDeps, type Rgb, type ScannedDeclaration, SectionKind, SectionStatus, type SerializedAttr, type SerializedEntity, type SerializedLink, type SerializedLinkEnd, type SerializedSchema, Swatch, SwatchRole, type UpsertSectionInput, acceptProposalOps, adjustLightnessUntil, analogous, assertAnalysis, assertAssetContentType, assertHex, assertSectionContent, assertSwatches, assetTools, attachedBrandAssetIds, audienceFanoutOps, base64ByteLength, base64FromBytes, beginAssetDeleteOps, bookForChannel, bookTools, brandAuthorityRef, brandBotTrigger, brandDiscussionReferenceHref, brandDiscussionReferenceId, brandInputFor, brandIntegration, brandJsonDigest, brandRules, brandSkill, canonicalBrandJson, capString, capStringArray, clamp01, clampToGamut, collapseWhitespace, complementary, contrastRatio, contrastReport, createAssetOps, createBookOps, createBrandIntegration, createBrandPersona, createBrandRoutes, cssColorToHex, decodeEntities, deltaEOK, deltaEOKLab, deriveChartColors, derivePalette, digestDesignBundle, digestDesignHtml, dispatchBrandTurn, extractColors, extractDesignTokens, extractFonts, extractOutline, extractProps, extractThumbnailSvg, extractTitle, finishAssetDeleteOps, formatBrandDiscussionReference, groupAssets, hexToOklch, hslToRgb, htmlToText, inSrgbGamut, isBoundedBrandJson, isBrandHumanAuthorityConsumption, isDesignBundle, linearToSrgb, meetsAA, meetsAAA, monochrome, nearestNamedColor, normalizeHex, oklabToOklch, oklabToRgb, oklchToHex, oklchToOklab, paletteTools, parseBrandDiscussionReference, parseBrandDispatch, parseDesignBundle, parseHex, pickTextOn, proposalReviewSnapshot, proposePaletteOps, proposeSectionOps, readDesignManifest, readTools, recordAnalysisOps, rejectProposalOps, relativeLuminance, resolveDeps, resolveVarRefs, rgbToHsl, rgbToOklab, rotateHue, safeFileName, scanCustomProperties, sectionKey, splitComplementary, srgbToLinear, stripCssComments, styleSheetText, supportsBrandVision, swatchesFromDesignTokens, tetradic, tintShadeRamp, toHex, triadic, updateBookOps, upsertSectionOps, verifyBrandApprovalReceipt };
1904
+ export { ASSET_CONTENT_TYPES, type AcceptProposalInput, AssetAnalysis, AssetKind, type AttrType, type AudienceChildren, BRAND_AGENT_PROFILE, BRAND_DISCUSSION_REFERENCE_KINDS, BRAND_INSTRUCTIONS, BRAND_RULES, BRAND_SCHEMA, BookStatus, type BrandActor, type BrandAgentBridge, type BrandAgentProfile, type BrandApprovalReceipt, type BrandAsset, type BrandAttrs, type BrandBook, type BrandBotTriggerOpts, type BrandCapabilityAuthority, BrandConflictError, type BrandDb, type BrandDecisionBinding, type BrandDeps, type BrandDiscussionReference, type BrandDiscussionReferenceKind, type BrandDiscussionReferenceTarget, type BrandDiscussionSwatch, type BrandDispatchBody, type BrandDispatchDeps, type BrandEntityRef, type BrandFetchedBytes, type BrandFileRecord, BrandForbiddenError, BrandGoneError, type BrandHumanAuthorityConsumption, BrandInputError, type BrandIntegrationDescriptor, type BrandIntegrationProbe, type BrandLookup, BrandNotFoundError, type BrandOp, type BrandPalette, type BrandPrincipalProjection, type BrandProposal, type BrandProposalProvenance, type BrandProposalReviewSnapshot, type BrandResult, BrandReviewStateChangedError, type BrandRouteCtx, type BrandRouteOpts, type BrandRow, type BrandRule, type BrandRules, type BrandScalar, type BrandSection, type BrandSkillOpts, type BrandSkillSelf, type BrandSourceAssetSnapshot, type BrandStorage, BrandTokensSnapshot, type BrandToolCtx, type BrandTransactGuard, type BrandTransactOptions, type BrandTrigger, type BrandUploadBody, type BrandVisionCapabilities, type BrandVisionSpec, CHART_DELTA_MIN, CHAT_MESSAGE_NS, CSS_NAMED_COLORS, type ContrastKind, type CreateAssetInput, type CreateBookInput, type CreateBrandIntegrationOptions, type CreateBrandPersonaOpts, DEFAULT_BRAND_SYSTEM, DESIGN_CONTENT_TYPES, type DeriveChartOptions, type DerivePaletteOptions, type DesignAssetGroup, type DesignBundle, type DesignBundleAsset, type DesignColorUse, type DesignDecompilation, type DesignDigest, type DesignManifestEntry, type DesignOutlineEntry, type DesignProp, type DesignTokenSets, type DesignTokenSkip, type Hsl, type IntegrationProvision, type IntegrationSecret, type IntegrationSetting, type LightnessDirection, MAX_COLORS, MAX_EXTERNALS, MAX_FONTS, MAX_HEADING_CHARS, MAX_OUTLINE_ENTRIES, MAX_PROPS, MAX_SWATCHES, MAX_THUMBNAIL_CHARS, MAX_VIEW_BYTES, type NamedColor, type NearestNamedColor, type Oklab, type Oklch, PICK_TEXT_DEFAULT_CANDIDATES, PaletteSource, PaletteStatus, ProposalKind, ProposalStatus, type ProposePaletteInput, type ProposeSectionInput, RAMP_L_MAX, RAMP_L_MIN, type RejectProposalInput, type ResolvedBrandDeps, type Rgb, type ScannedDeclaration, SectionKind, SectionStatus, type SerializedAttr, type SerializedEntity, type SerializedLink, type SerializedLinkEnd, type SerializedSchema, Swatch, SwatchRole, type UpsertSectionInput, acceptProposalOps, adjustLightnessUntil, analogous, assertAnalysis, assertAssetContentType, assertHex, assertSectionContent, assertSwatches, assetTools, attachedBrandAssetIds, audienceFanoutOps, base64ByteLength, base64FromBytes, beginAssetDeleteOps, bookForChannel, bookTools, brandAuthorityRef, brandBotTrigger, brandDiscussionReferenceHref, brandDiscussionReferenceId, brandInputFor, brandIntegration, brandJsonDigest, brandRules, brandSkill, canonicalBrandJson, capString, capStringArray, clamp01, clampToGamut, collapseWhitespace, complementary, contrastRatio, contrastReport, createAssetOps, createBookOps, createBrandIntegration, createBrandPersona, createBrandRoutes, cssColorToHex, decodeEntities, deltaEOK, deltaEOKLab, deriveChartColors, derivePalette, digestDesignBundle, digestDesignHtml, dispatchBrandTurn, extractColors, extractDesignTokens, extractFonts, extractOutline, extractProps, extractThumbnailSvg, extractTitle, finishAssetDeleteOps, formatBrandDiscussionReference, groupAssets, hexToOklch, hslToRgb, htmlToText, inSrgbGamut, isBoundedBrandJson, isBrandHumanAuthorityConsumption, isDesignBundle, linearToSrgb, meetsAA, meetsAAA, monochrome, nearestNamedColor, normalizeHex, oklabToOklch, oklabToRgb, oklchToHex, oklchToOklab, paletteTools, parseBrandDiscussionReference, parseBrandDispatch, parseDesignBundle, parseHex, pickTextOn, proposalAsWritten, proposalReviewSnapshot, proposePaletteOps, proposeSectionOps, readDesignManifest, readTools, receiptAsWritten, recordAnalysisOps, rejectProposalOps, relativeLuminance, resolveDeps, resolveVarRefs, rgbToHsl, rgbToOklab, rotateHue, rowAsWritten, safeFileName, scanCustomProperties, sectionKey, splitComplementary, srgbToLinear, stripCssComments, styleSheetText, supportsBrandVision, swatchesFromDesignTokens, tetradic, tintShadeRamp, toHex, triadic, updateBookOps, upsertSectionOps, verifyBrandApprovalReceipt };
package/dist/index.d.ts CHANGED
@@ -627,6 +627,33 @@ declare function assertAssetContentType(value: unknown, kind?: string): string;
627
627
  */
628
628
  declare function assertAnalysis(value: unknown): AssetAnalysis;
629
629
 
630
+ /**
631
+ * A stored row in the shape it was WRITTEN, ready to verify.
632
+ *
633
+ * The engine stores a `date` as the epoch ms it was given and hands a reader
634
+ * ISO-8601 text back. Brand digests rows that CONTAIN dates — a proposal's
635
+ * `reviewDigest` covers its `createdAt`, and an approval receipt's
636
+ * `receiptDigest` covers its own — so a row read straight out of the store can
637
+ * never match the digest stored beside it. Two production symptoms, both 409:
638
+ * resolving any proposal ("proposal changed since review") and reading a book's
639
+ * tokens after approval.
640
+ *
641
+ * Normalising at the READ is the fix, not tolerating both shapes in the
642
+ * verifier. Tolerance would repair a `safeTime` check and leave the digest
643
+ * failing, because the digest was taken over the number and nothing can change
644
+ * that after the fact.
645
+ *
646
+ * Anything that is not date-shaped text passes through untouched, so a row that
647
+ * never round-tripped verifies exactly as before. `json` attrs are left alone:
648
+ * their contents round-trip as written, which is why an `authorityConsumption`
649
+ * and the `createdAt` beside it disagreed at all.
650
+ */
651
+ declare function rowAsWritten<T>(ns: string, row: T): T;
652
+ /** A stored approval receipt in the shape it was written. */
653
+ declare function receiptAsWritten<T>(row: T): T;
654
+ /** A stored proposal in the shape it was written, so `reviewDigest` matches. */
655
+ declare function proposalAsWritten<T>(row: T): T;
656
+
630
657
  /** Stay materially below the server's whole-guards parser budget. */
631
658
  declare function isBoundedBrandJson(root: unknown, limits?: {
632
659
  maxDepth?: number;
@@ -1874,4 +1901,4 @@ interface CreateBrandIntegrationOptions {
1874
1901
  */
1875
1902
  declare function createBrandIntegration(options?: CreateBrandIntegrationOptions): BrandIntegrationDescriptor;
1876
1903
 
1877
- export { ASSET_CONTENT_TYPES, type AcceptProposalInput, AssetAnalysis, AssetKind, type AttrType, type AudienceChildren, BRAND_AGENT_PROFILE, BRAND_DISCUSSION_REFERENCE_KINDS, BRAND_INSTRUCTIONS, BRAND_RULES, BRAND_SCHEMA, BookStatus, type BrandActor, type BrandAgentBridge, type BrandAgentProfile, type BrandApprovalReceipt, type BrandAsset, type BrandAttrs, type BrandBook, type BrandBotTriggerOpts, type BrandCapabilityAuthority, BrandConflictError, type BrandDb, type BrandDecisionBinding, type BrandDeps, type BrandDiscussionReference, type BrandDiscussionReferenceKind, type BrandDiscussionReferenceTarget, type BrandDiscussionSwatch, type BrandDispatchBody, type BrandDispatchDeps, type BrandEntityRef, type BrandFetchedBytes, type BrandFileRecord, BrandForbiddenError, BrandGoneError, type BrandHumanAuthorityConsumption, BrandInputError, type BrandIntegrationDescriptor, type BrandIntegrationProbe, type BrandLookup, BrandNotFoundError, type BrandOp, type BrandPalette, type BrandPrincipalProjection, type BrandProposal, type BrandProposalProvenance, type BrandProposalReviewSnapshot, type BrandResult, BrandReviewStateChangedError, type BrandRouteCtx, type BrandRouteOpts, type BrandRow, type BrandRule, type BrandRules, type BrandScalar, type BrandSection, type BrandSkillOpts, type BrandSkillSelf, type BrandSourceAssetSnapshot, type BrandStorage, BrandTokensSnapshot, type BrandToolCtx, type BrandTransactGuard, type BrandTransactOptions, type BrandTrigger, type BrandUploadBody, type BrandVisionCapabilities, type BrandVisionSpec, CHART_DELTA_MIN, CHAT_MESSAGE_NS, CSS_NAMED_COLORS, type ContrastKind, type CreateAssetInput, type CreateBookInput, type CreateBrandIntegrationOptions, type CreateBrandPersonaOpts, DEFAULT_BRAND_SYSTEM, DESIGN_CONTENT_TYPES, type DeriveChartOptions, type DerivePaletteOptions, type DesignAssetGroup, type DesignBundle, type DesignBundleAsset, type DesignColorUse, type DesignDecompilation, type DesignDigest, type DesignManifestEntry, type DesignOutlineEntry, type DesignProp, type DesignTokenSets, type DesignTokenSkip, type Hsl, type IntegrationProvision, type IntegrationSecret, type IntegrationSetting, type LightnessDirection, MAX_COLORS, MAX_EXTERNALS, MAX_FONTS, MAX_HEADING_CHARS, MAX_OUTLINE_ENTRIES, MAX_PROPS, MAX_SWATCHES, MAX_THUMBNAIL_CHARS, MAX_VIEW_BYTES, type NamedColor, type NearestNamedColor, type Oklab, type Oklch, PICK_TEXT_DEFAULT_CANDIDATES, PaletteSource, PaletteStatus, ProposalKind, ProposalStatus, type ProposePaletteInput, type ProposeSectionInput, RAMP_L_MAX, RAMP_L_MIN, type RejectProposalInput, type ResolvedBrandDeps, type Rgb, type ScannedDeclaration, SectionKind, SectionStatus, type SerializedAttr, type SerializedEntity, type SerializedLink, type SerializedLinkEnd, type SerializedSchema, Swatch, SwatchRole, type UpsertSectionInput, acceptProposalOps, adjustLightnessUntil, analogous, assertAnalysis, assertAssetContentType, assertHex, assertSectionContent, assertSwatches, assetTools, attachedBrandAssetIds, audienceFanoutOps, base64ByteLength, base64FromBytes, beginAssetDeleteOps, bookForChannel, bookTools, brandAuthorityRef, brandBotTrigger, brandDiscussionReferenceHref, brandDiscussionReferenceId, brandInputFor, brandIntegration, brandJsonDigest, brandRules, brandSkill, canonicalBrandJson, capString, capStringArray, clamp01, clampToGamut, collapseWhitespace, complementary, contrastRatio, contrastReport, createAssetOps, createBookOps, createBrandIntegration, createBrandPersona, createBrandRoutes, cssColorToHex, decodeEntities, deltaEOK, deltaEOKLab, deriveChartColors, derivePalette, digestDesignBundle, digestDesignHtml, dispatchBrandTurn, extractColors, extractDesignTokens, extractFonts, extractOutline, extractProps, extractThumbnailSvg, extractTitle, finishAssetDeleteOps, formatBrandDiscussionReference, groupAssets, hexToOklch, hslToRgb, htmlToText, inSrgbGamut, isBoundedBrandJson, isBrandHumanAuthorityConsumption, isDesignBundle, linearToSrgb, meetsAA, meetsAAA, monochrome, nearestNamedColor, normalizeHex, oklabToOklch, oklabToRgb, oklchToHex, oklchToOklab, paletteTools, parseBrandDiscussionReference, parseBrandDispatch, parseDesignBundle, parseHex, pickTextOn, proposalReviewSnapshot, proposePaletteOps, proposeSectionOps, readDesignManifest, readTools, recordAnalysisOps, rejectProposalOps, relativeLuminance, resolveDeps, resolveVarRefs, rgbToHsl, rgbToOklab, rotateHue, safeFileName, scanCustomProperties, sectionKey, splitComplementary, srgbToLinear, stripCssComments, styleSheetText, supportsBrandVision, swatchesFromDesignTokens, tetradic, tintShadeRamp, toHex, triadic, updateBookOps, upsertSectionOps, verifyBrandApprovalReceipt };
1904
+ export { ASSET_CONTENT_TYPES, type AcceptProposalInput, AssetAnalysis, AssetKind, type AttrType, type AudienceChildren, BRAND_AGENT_PROFILE, BRAND_DISCUSSION_REFERENCE_KINDS, BRAND_INSTRUCTIONS, BRAND_RULES, BRAND_SCHEMA, BookStatus, type BrandActor, type BrandAgentBridge, type BrandAgentProfile, type BrandApprovalReceipt, type BrandAsset, type BrandAttrs, type BrandBook, type BrandBotTriggerOpts, type BrandCapabilityAuthority, BrandConflictError, type BrandDb, type BrandDecisionBinding, type BrandDeps, type BrandDiscussionReference, type BrandDiscussionReferenceKind, type BrandDiscussionReferenceTarget, type BrandDiscussionSwatch, type BrandDispatchBody, type BrandDispatchDeps, type BrandEntityRef, type BrandFetchedBytes, type BrandFileRecord, BrandForbiddenError, BrandGoneError, type BrandHumanAuthorityConsumption, BrandInputError, type BrandIntegrationDescriptor, type BrandIntegrationProbe, type BrandLookup, BrandNotFoundError, type BrandOp, type BrandPalette, type BrandPrincipalProjection, type BrandProposal, type BrandProposalProvenance, type BrandProposalReviewSnapshot, type BrandResult, BrandReviewStateChangedError, type BrandRouteCtx, type BrandRouteOpts, type BrandRow, type BrandRule, type BrandRules, type BrandScalar, type BrandSection, type BrandSkillOpts, type BrandSkillSelf, type BrandSourceAssetSnapshot, type BrandStorage, BrandTokensSnapshot, type BrandToolCtx, type BrandTransactGuard, type BrandTransactOptions, type BrandTrigger, type BrandUploadBody, type BrandVisionCapabilities, type BrandVisionSpec, CHART_DELTA_MIN, CHAT_MESSAGE_NS, CSS_NAMED_COLORS, type ContrastKind, type CreateAssetInput, type CreateBookInput, type CreateBrandIntegrationOptions, type CreateBrandPersonaOpts, DEFAULT_BRAND_SYSTEM, DESIGN_CONTENT_TYPES, type DeriveChartOptions, type DerivePaletteOptions, type DesignAssetGroup, type DesignBundle, type DesignBundleAsset, type DesignColorUse, type DesignDecompilation, type DesignDigest, type DesignManifestEntry, type DesignOutlineEntry, type DesignProp, type DesignTokenSets, type DesignTokenSkip, type Hsl, type IntegrationProvision, type IntegrationSecret, type IntegrationSetting, type LightnessDirection, MAX_COLORS, MAX_EXTERNALS, MAX_FONTS, MAX_HEADING_CHARS, MAX_OUTLINE_ENTRIES, MAX_PROPS, MAX_SWATCHES, MAX_THUMBNAIL_CHARS, MAX_VIEW_BYTES, type NamedColor, type NearestNamedColor, type Oklab, type Oklch, PICK_TEXT_DEFAULT_CANDIDATES, PaletteSource, PaletteStatus, ProposalKind, ProposalStatus, type ProposePaletteInput, type ProposeSectionInput, RAMP_L_MAX, RAMP_L_MIN, type RejectProposalInput, type ResolvedBrandDeps, type Rgb, type ScannedDeclaration, SectionKind, SectionStatus, type SerializedAttr, type SerializedEntity, type SerializedLink, type SerializedLinkEnd, type SerializedSchema, Swatch, SwatchRole, type UpsertSectionInput, acceptProposalOps, adjustLightnessUntil, analogous, assertAnalysis, assertAssetContentType, assertHex, assertSectionContent, assertSwatches, assetTools, attachedBrandAssetIds, audienceFanoutOps, base64ByteLength, base64FromBytes, beginAssetDeleteOps, bookForChannel, bookTools, brandAuthorityRef, brandBotTrigger, brandDiscussionReferenceHref, brandDiscussionReferenceId, brandInputFor, brandIntegration, brandJsonDigest, brandRules, brandSkill, canonicalBrandJson, capString, capStringArray, clamp01, clampToGamut, collapseWhitespace, complementary, contrastRatio, contrastReport, createAssetOps, createBookOps, createBrandIntegration, createBrandPersona, createBrandRoutes, cssColorToHex, decodeEntities, deltaEOK, deltaEOKLab, deriveChartColors, derivePalette, digestDesignBundle, digestDesignHtml, dispatchBrandTurn, extractColors, extractDesignTokens, extractFonts, extractOutline, extractProps, extractThumbnailSvg, extractTitle, finishAssetDeleteOps, formatBrandDiscussionReference, groupAssets, hexToOklch, hslToRgb, htmlToText, inSrgbGamut, isBoundedBrandJson, isBrandHumanAuthorityConsumption, isDesignBundle, linearToSrgb, meetsAA, meetsAAA, monochrome, nearestNamedColor, normalizeHex, oklabToOklch, oklabToRgb, oklchToHex, oklchToOklab, paletteTools, parseBrandDiscussionReference, parseBrandDispatch, parseDesignBundle, parseHex, pickTextOn, proposalAsWritten, proposalReviewSnapshot, proposePaletteOps, proposeSectionOps, readDesignManifest, readTools, receiptAsWritten, recordAnalysisOps, rejectProposalOps, relativeLuminance, resolveDeps, resolveVarRefs, rgbToHsl, rgbToOklab, rotateHue, rowAsWritten, safeFileName, scanCustomProperties, sectionKey, splitComplementary, srgbToLinear, stripCssComments, styleSheetText, supportsBrandVision, swatchesFromDesignTokens, tetradic, tintShadeRamp, toHex, triadic, updateBookOps, upsertSectionOps, verifyBrandApprovalReceipt };
package/dist/index.js CHANGED
@@ -558,8 +558,40 @@ function assertAnalysis(value) {
558
558
  };
559
559
  }
560
560
 
561
+ // src/read-shape.ts
562
+ var record = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
563
+ var DATE_FIELDS = /* @__PURE__ */ new Map();
564
+ for (const [ns, entity] of Object.entries(BRAND_SCHEMA.entities))
565
+ DATE_FIELDS.set(
566
+ ns,
567
+ new Set(
568
+ Object.entries(entity.attrs).filter(([, attr]) => attr.type === "date").map(([label]) => label)
569
+ )
570
+ );
571
+ function rowAsWritten(ns, row) {
572
+ const dates = DATE_FIELDS.get(ns);
573
+ if (!dates?.size || !record(row)) return row;
574
+ let changed = false;
575
+ const out = { ...row };
576
+ for (const field of dates) {
577
+ const value = out[field];
578
+ if (typeof value !== "string") continue;
579
+ const parsed = Date.parse(value);
580
+ if (!Number.isSafeInteger(parsed) || parsed < 0) continue;
581
+ out[field] = parsed;
582
+ changed = true;
583
+ }
584
+ return changed ? out : row;
585
+ }
586
+ function receiptAsWritten(row) {
587
+ return rowAsWritten(BRAND_NS.approvalReceipt, row);
588
+ }
589
+ function proposalAsWritten(row) {
590
+ return rowAsWritten(BRAND_NS.proposal, row);
591
+ }
592
+
561
593
  // src/review-json.ts
562
- var record = (value) => typeof value === "object" && value !== null && !Array.isArray(value) && (Object.getPrototypeOf(value) === Object.prototype || Object.getPrototypeOf(value) === null);
594
+ var record2 = (value) => typeof value === "object" && value !== null && !Array.isArray(value) && (Object.getPrototypeOf(value) === Object.prototype || Object.getPrototypeOf(value) === null);
563
595
  function isBoundedBrandJson(root, limits = {}) {
564
596
  const maxDepth = limits.maxDepth ?? 12;
565
597
  const maxNodes = limits.maxNodes ?? 2048;
@@ -583,7 +615,7 @@ function isBoundedBrandJson(root, limits = {}) {
583
615
  if (Array.isArray(value)) {
584
616
  for (const child of value)
585
617
  stack.push({ value: child, depth: depth + 1 });
586
- } else if (record(value)) {
618
+ } else if (record2(value)) {
587
619
  for (const child of Object.values(value))
588
620
  stack.push({ value: child, depth: depth + 1 });
589
621
  } else {
@@ -620,7 +652,7 @@ async function brandJsonDigest(value) {
620
652
 
621
653
  // src/review.ts
622
654
  var DIGEST = /^sha256:[0-9a-f]{64}$/;
623
- var record2 = (value) => typeof value === "object" && value !== null && !Array.isArray(value) && (Object.getPrototypeOf(value) === Object.prototype || Object.getPrototypeOf(value) === null);
655
+ var record3 = (value) => typeof value === "object" && value !== null && !Array.isArray(value) && (Object.getPrototypeOf(value) === Object.prototype || Object.getPrototypeOf(value) === null);
624
656
  var exactKeys = (value, required, optional = []) => {
625
657
  const allowed = /* @__PURE__ */ new Set([...required, ...optional]);
626
658
  return required.every((key) => Object.hasOwn(value, key)) && Object.keys(value).every((key) => allowed.has(key));
@@ -645,15 +677,15 @@ var SNAPSHOT_REQUIRED = [
645
677
  "createdAt"
646
678
  ];
647
679
  function validSnapshotShape(value) {
648
- if (!record2(value) || !exactKeys(value, SNAPSHOT_REQUIRED)) return false;
680
+ if (!record3(value) || !exactKeys(value, SNAPSHOT_REQUIRED)) return false;
649
681
  const provenance = value.provenance;
650
- return boundedString(value.id) && boundedString(value.bookId) && typeof value.kind === "string" && PROPOSAL_KINDS.includes(value.kind) && value.status === "open" && record2(value.payload) && boundedString(value.rationale, 2e3) && record2(provenance) && exactKeys(provenance, [
682
+ return boundedString(value.id) && boundedString(value.bookId) && typeof value.kind === "string" && PROPOSAL_KINDS.includes(value.kind) && value.status === "open" && record3(value.payload) && boundedString(value.rationale, 2e3) && record3(provenance) && exactKeys(provenance, [
651
683
  "sourceAssetId",
652
684
  "sourceAsset",
653
685
  "messageId",
654
686
  "turnId",
655
687
  "taintLabels"
656
- ]) && (provenance.sourceAssetId === null || boundedString(provenance.sourceAssetId)) && (provenance.sourceAsset === null || record2(provenance.sourceAsset) && exactKeys(provenance.sourceAsset, [
688
+ ]) && (provenance.sourceAssetId === null || boundedString(provenance.sourceAssetId)) && (provenance.sourceAsset === null || record3(provenance.sourceAsset) && exactKeys(provenance.sourceAsset, [
657
689
  "assetId",
658
690
  "contentDigest",
659
691
  "objectEtag",
@@ -686,8 +718,8 @@ var CONSUMPTION_REQUIRED = [
686
718
  "consumedAt"
687
719
  ];
688
720
  function isBrandHumanAuthorityConsumption(value) {
689
- if (!record2(value) || !exactKeys(value, CONSUMPTION_REQUIRED)) return false;
690
- return boundedString(value.id) && boundedString(value.grantId) && Number.isSafeInteger(value.grantVersion) && value.grantVersion > 0 && Number.isSafeInteger(value.useNumber) && value.useNumber > 0 && boundedString(value.actorPrincipalId) && value.actorKind === "human" && boundedString(value.credentialId) && value.credentialKind === "clerk" && boundedString(value.appId) && typeof value.appIncarnation === "string" && /^[a-f0-9]{32}$/.test(value.appIncarnation) && value.capability === "brand.proposal.resolve" && value.projectCapability === "brand.approve" && value.effect === "internal" && typeof value.actionDigest === "string" && DIGEST.test(value.actionDigest) && typeof value.resourceDigest === "string" && DIGEST.test(value.resourceDigest) && record2(value.constraintEvidence) && boundedString(value.consumptionIdempotencyKey) && typeof value.requestDigest === "string" && DIGEST.test(value.requestDigest) && safeTime(value.consumedAt);
721
+ if (!record3(value) || !exactKeys(value, CONSUMPTION_REQUIRED)) return false;
722
+ return boundedString(value.id) && boundedString(value.grantId) && Number.isSafeInteger(value.grantVersion) && value.grantVersion > 0 && Number.isSafeInteger(value.useNumber) && value.useNumber > 0 && boundedString(value.actorPrincipalId) && value.actorKind === "human" && boundedString(value.credentialId) && value.credentialKind === "clerk" && boundedString(value.appId) && typeof value.appIncarnation === "string" && /^[a-f0-9]{32}$/.test(value.appIncarnation) && value.capability === "brand.proposal.resolve" && value.projectCapability === "brand.approve" && value.effect === "internal" && typeof value.actionDigest === "string" && DIGEST.test(value.actionDigest) && typeof value.resourceDigest === "string" && DIGEST.test(value.resourceDigest) && record3(value.constraintEvidence) && boundedString(value.consumptionIdempotencyKey) && typeof value.requestDigest === "string" && DIGEST.test(value.requestDigest) && safeTime(value.consumedAt);
691
723
  }
692
724
  var RECEIPT_REQUIRED = [
693
725
  "version",
@@ -713,10 +745,10 @@ async function verifyBrandApprovalReceipt(receipt2) {
713
745
  try {
714
746
  if (!isBoundedBrandJson(receipt2, { maxDepth: 14, maxNodes: 2500, maxBytes: 48 * 1024 }))
715
747
  return false;
716
- if (!record2(receipt2) || !exactKeys(receipt2, RECEIPT_REQUIRED, ["paletteId", "resolutionNote"]))
748
+ if (!record3(receipt2) || !exactKeys(receipt2, RECEIPT_REQUIRED, ["paletteId", "resolutionNote"]))
717
749
  return false;
718
750
  const binding = receipt2.decisionBinding;
719
- if (receipt2.version !== 1 || !boundedString(receipt2.id) || !boundedString(receipt2.mutationKey) || !boundedString(receipt2.bookId) || !boundedString(receipt2.proposalId) || receipt2.resolution !== "accepted" && receipt2.resolution !== "rejected" || !validSnapshotShape(receipt2.reviewedProposal) || typeof receipt2.actionDigest !== "string" || !DIGEST.test(receipt2.actionDigest) || !record2(binding) || !exactKeys(binding, [
751
+ if (receipt2.version !== 1 || !boundedString(receipt2.id) || !boundedString(receipt2.mutationKey) || !boundedString(receipt2.bookId) || !boundedString(receipt2.proposalId) || receipt2.resolution !== "accepted" && receipt2.resolution !== "rejected" || !validSnapshotShape(receipt2.reviewedProposal) || typeof receipt2.actionDigest !== "string" || !DIGEST.test(receipt2.actionDigest) || !record3(binding) || !exactKeys(binding, [
720
752
  "version",
721
753
  "bookVersion",
722
754
  "activationRevision",
@@ -2481,7 +2513,7 @@ async function linkedAsset(ctx, book, assetId, allowDeleting = false) {
2481
2513
  book: {}
2482
2514
  }
2483
2515
  });
2484
- const row = (result[BRAND_NS.asset] ?? [])[0];
2516
+ const row = rowAsWritten(BRAND_NS.asset, (result[BRAND_NS.asset] ?? [])[0]);
2485
2517
  const links = row?.book;
2486
2518
  if (!row || row.bookId !== book.id || !Array.isArray(links) || links.length !== 1 || links[0]?.id !== book.id || row.status !== "live" && !(allowDeleting && row.status === "deleting")) throw new BrandNotFoundError(`asset ${assetId}`);
2487
2519
  return row;
@@ -2555,7 +2587,7 @@ async function loadBook(db, bookId) {
2555
2587
  const res = await db.query({ [BRAND_NS.book]: { $: { where: { id: bookId } } } });
2556
2588
  const row = (res[BRAND_NS.book] ?? [])[0];
2557
2589
  if (!row) throw new BrandNotFoundError(`brand book ${bookId}`);
2558
- return row;
2590
+ return rowAsWritten(BRAND_NS.book, row);
2559
2591
  }
2560
2592
  async function loadMemberBook(db, bookId, actorId) {
2561
2593
  const book = await loadBook(db, bookId);
@@ -2610,14 +2642,14 @@ async function uploadAsset(ctx, req, bookId) {
2610
2642
  "internal",
2611
2643
  book.id
2612
2644
  );
2613
- const record6 = await ctx.db.storage.upload(
2645
+ const record7 = await ctx.db.storage.upload(
2614
2646
  path,
2615
2647
  file,
2616
2648
  contentType,
2617
2649
  { private: true }
2618
2650
  );
2619
- if (record6.path !== path) {
2620
- await ctx.db.storage.delete(record6.path);
2651
+ if (record7.path !== path) {
2652
+ await ctx.db.storage.delete(record7.path);
2621
2653
  throw new BrandConflictError(
2622
2654
  "private storage returned an unexpected asset path"
2623
2655
  );
@@ -2636,11 +2668,11 @@ async function uploadAsset(ctx, req, bookId) {
2636
2668
  id,
2637
2669
  bookId: book.id,
2638
2670
  kind,
2639
- path: record6.path,
2640
- storageObjectId: record6.id,
2671
+ path: record7.path,
2672
+ storageObjectId: record7.id,
2641
2673
  contentDigest: await contentDigest(file),
2642
2674
  contentType,
2643
- size: record6.size,
2675
+ size: record7.size,
2644
2676
  uploadedBy: ctx.actor.id,
2645
2677
  uploadedAuthorityRef: authority.authorityRef,
2646
2678
  audience: book.memberIds,
@@ -2667,7 +2699,7 @@ async function uploadAsset(ctx, req, bookId) {
2667
2699
  asPrincipalKind: "human"
2668
2700
  });
2669
2701
  } catch (error) {
2670
- await ctx.db.storage.delete(record6.path);
2702
+ await ctx.db.storage.delete(record7.path);
2671
2703
  throw error;
2672
2704
  }
2673
2705
  return json(await linkedAsset(ctx, book, id), 201);
@@ -2782,7 +2814,7 @@ async function handleAssetItem(ctx, req, bookId, assetId) {
2782
2814
  async function handleBooksRoot(ctx, req) {
2783
2815
  if (req.method === "GET") {
2784
2816
  const res = await ctx.db.query({ [BRAND_NS.book]: { $: { order: { createdAt: "asc" } } } });
2785
- const books = (res[BRAND_NS.book] ?? []).filter(
2817
+ const books = (res[BRAND_NS.book] ?? []).map((b) => rowAsWritten(BRAND_NS.book, b)).filter(
2786
2818
  (b) => isMember(b, ctx.actor.id)
2787
2819
  );
2788
2820
  return json({ books });
@@ -2947,7 +2979,7 @@ var SNAPSHOT_KEYS = [
2947
2979
  "reviewDigest",
2948
2980
  "status"
2949
2981
  ];
2950
- var record3 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
2982
+ var record4 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
2951
2983
  function parseProposalResolutionRequest(body, bookId, proposalId) {
2952
2984
  const allowed = /* @__PURE__ */ new Set([
2953
2985
  "mutationId",
@@ -2991,13 +3023,13 @@ function proposalReviewSnapshot(proposal) {
2991
3023
  function parseReviewedProposal(value, bookId, proposalId) {
2992
3024
  if (!isBoundedBrandJson(value, { maxDepth: 12, maxNodes: 1500, maxBytes: 32 * 1024 }))
2993
3025
  throw new BrandInputError('"reviewedProposal" is too deeply nested, complex, or large');
2994
- if (!record3(value)) throw new BrandInputError('"reviewedProposal" must be an object');
3026
+ if (!record4(value)) throw new BrandInputError('"reviewedProposal" must be an object');
2995
3027
  const keys = Object.keys(value).sort();
2996
3028
  if (keys.length !== SNAPSHOT_KEYS.length || !SNAPSHOT_KEYS.every((key, index) => key === keys[index])) throw new BrandInputError('"reviewedProposal" has an invalid shape');
2997
3029
  if (value.id !== proposalId || value.bookId !== bookId || value.status !== "open")
2998
3030
  throw new BrandInputError('"reviewedProposal" must identify this open proposal');
2999
- if (typeof value.kind !== "string" || !PROPOSAL_KINDS.includes(value.kind) || !record3(value.payload) || typeof value.rationale !== "string" || !Array.isArray(value.audience) || value.audience.length < 1 || value.audience.length > 100 || value.audience.some((id) => typeof id !== "string" || id.length < 1 || id.length > 200) || new Set(value.audience).size !== value.audience.length || typeof value.createdBy !== "string" || value.createdBy.length < 1 || value.createdBy.length > 200 || typeof value.createdAuthorityRef !== "string" || value.createdAuthorityRef.length < 1 || value.createdAuthorityRef.length > 200 || !Number.isSafeInteger(value.createdAt) || value.createdAt < 0 || typeof value.reviewDigest !== "string" || !/^sha256:[0-9a-f]{64}$/.test(value.reviewDigest)) throw new BrandInputError('"reviewedProposal" has invalid fields');
3000
- if (!record3(value.provenance) || Object.keys(value.provenance).sort().join(",") !== "messageId,sourceAsset,sourceAssetId,taintLabels,turnId" || value.provenance.sourceAssetId !== null && (typeof value.provenance.sourceAssetId !== "string" || value.provenance.sourceAssetId.length < 1 || value.provenance.sourceAssetId.length > 200) || value.provenance.sourceAsset !== null && (!record3(value.provenance.sourceAsset) || Object.keys(value.provenance.sourceAsset).sort().join(",") !== "analysisDigest,analysisRevision,assetId,contentDigest,contentType,objectEtag,objectSize,pathDigest" || typeof value.provenance.sourceAsset.assetId !== "string" || value.provenance.sourceAsset.assetId.length < 1 || value.provenance.sourceAsset.assetId.length > 200 || typeof value.provenance.sourceAsset.objectEtag !== "string" || value.provenance.sourceAsset.objectEtag.length < 1 || value.provenance.sourceAsset.objectEtag.length > 200 || !Number.isSafeInteger(value.provenance.sourceAsset.objectSize) || value.provenance.sourceAsset.objectSize < 1 || typeof value.provenance.sourceAsset.pathDigest !== "string" || !/^sha256:[0-9a-f]{64}$/.test(value.provenance.sourceAsset.pathDigest) || value.provenance.sourceAsset.contentType !== null && (typeof value.provenance.sourceAsset.contentType !== "string" || value.provenance.sourceAsset.contentType.length < 1 || value.provenance.sourceAsset.contentType.length > 160) || typeof value.provenance.sourceAsset.contentDigest !== "string" || !/^sha256:[0-9a-f]{64}$/.test(value.provenance.sourceAsset.contentDigest) || value.provenance.sourceAsset.analysisRevision !== null && (!Number.isSafeInteger(value.provenance.sourceAsset.analysisRevision) || value.provenance.sourceAsset.analysisRevision < 0) || value.provenance.sourceAsset.analysisDigest !== null && (typeof value.provenance.sourceAsset.analysisDigest !== "string" || !/^sha256:[0-9a-f]{64}$/.test(
3031
+ if (typeof value.kind !== "string" || !PROPOSAL_KINDS.includes(value.kind) || !record4(value.payload) || typeof value.rationale !== "string" || !Array.isArray(value.audience) || value.audience.length < 1 || value.audience.length > 100 || value.audience.some((id) => typeof id !== "string" || id.length < 1 || id.length > 200) || new Set(value.audience).size !== value.audience.length || typeof value.createdBy !== "string" || value.createdBy.length < 1 || value.createdBy.length > 200 || typeof value.createdAuthorityRef !== "string" || value.createdAuthorityRef.length < 1 || value.createdAuthorityRef.length > 200 || !Number.isSafeInteger(value.createdAt) || value.createdAt < 0 || typeof value.reviewDigest !== "string" || !/^sha256:[0-9a-f]{64}$/.test(value.reviewDigest)) throw new BrandInputError('"reviewedProposal" has invalid fields');
3032
+ if (!record4(value.provenance) || Object.keys(value.provenance).sort().join(",") !== "messageId,sourceAsset,sourceAssetId,taintLabels,turnId" || value.provenance.sourceAssetId !== null && (typeof value.provenance.sourceAssetId !== "string" || value.provenance.sourceAssetId.length < 1 || value.provenance.sourceAssetId.length > 200) || value.provenance.sourceAsset !== null && (!record4(value.provenance.sourceAsset) || Object.keys(value.provenance.sourceAsset).sort().join(",") !== "analysisDigest,analysisRevision,assetId,contentDigest,contentType,objectEtag,objectSize,pathDigest" || typeof value.provenance.sourceAsset.assetId !== "string" || value.provenance.sourceAsset.assetId.length < 1 || value.provenance.sourceAsset.assetId.length > 200 || typeof value.provenance.sourceAsset.objectEtag !== "string" || value.provenance.sourceAsset.objectEtag.length < 1 || value.provenance.sourceAsset.objectEtag.length > 200 || !Number.isSafeInteger(value.provenance.sourceAsset.objectSize) || value.provenance.sourceAsset.objectSize < 1 || typeof value.provenance.sourceAsset.pathDigest !== "string" || !/^sha256:[0-9a-f]{64}$/.test(value.provenance.sourceAsset.pathDigest) || value.provenance.sourceAsset.contentType !== null && (typeof value.provenance.sourceAsset.contentType !== "string" || value.provenance.sourceAsset.contentType.length < 1 || value.provenance.sourceAsset.contentType.length > 160) || typeof value.provenance.sourceAsset.contentDigest !== "string" || !/^sha256:[0-9a-f]{64}$/.test(value.provenance.sourceAsset.contentDigest) || value.provenance.sourceAsset.analysisRevision !== null && (!Number.isSafeInteger(value.provenance.sourceAsset.analysisRevision) || value.provenance.sourceAsset.analysisRevision < 0) || value.provenance.sourceAsset.analysisDigest !== null && (typeof value.provenance.sourceAsset.analysisDigest !== "string" || !/^sha256:[0-9a-f]{64}$/.test(
3001
3033
  value.provenance.sourceAsset.analysisDigest
3002
3034
  )) || 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');
3003
3035
  return value;
@@ -3170,7 +3202,7 @@ async function proposalReceiptByMutation(ctx, mutationKey) {
3170
3202
  const row = (result[BRAND_NS.approvalReceipt] ?? [])[0];
3171
3203
  if (!row || !Array.isArray(row.book) || row.book.length !== 1 || row.book[0]?.id !== row.bookId) return void 0;
3172
3204
  const { book: _book, ...receipt2 } = row;
3173
- return receipt2;
3205
+ return receiptAsWritten(receipt2);
3174
3206
  }
3175
3207
  async function consumeProposalAuthority(ctx, req, bookId, proposalId, actionDigest, mutationKey) {
3176
3208
  const authority = await ctx.consumeHumanExact({
@@ -3204,7 +3236,8 @@ async function receipt(ctx, id, actionDigest, expected) {
3204
3236
  });
3205
3237
  const row = (result[BRAND_NS.approvalReceipt] ?? [])[0];
3206
3238
  if (!row || row.actionDigest !== actionDigest || row.resolution !== "accepted" || row.bookId !== expected.bookId || row.reviewedProposal.bookId !== expected.bookId || row.reviewedProposal.kind !== expected.kind || !Array.isArray(row.book) || row.book.length !== 1 || row.book[0]?.id !== expected.bookId || expected.paletteId !== void 0 && row.paletteId !== expected.paletteId || expected.proposalId !== void 0 && row.proposalId !== expected.proposalId || expected.content !== void 0 && canonicalBrandJson(row.reviewedProposal.payload.content) !== canonicalBrandJson(expected.content)) throw new BrandConflictError(`approval receipt ${id} is invalid`);
3207
- const { book: _book, ...unhydrated } = row;
3239
+ const { book: _book, ...stored } = row;
3240
+ const unhydrated = receiptAsWritten(stored);
3208
3241
  if (!await verifyBrandApprovalReceipt(unhydrated))
3209
3242
  throw new BrandConflictError(`approval receipt ${id} is invalid`);
3210
3243
  return unhydrated;
@@ -3442,9 +3475,9 @@ async function proposalDecisionState(ctx, req, book, proposal, resolution, recei
3442
3475
  }
3443
3476
 
3444
3477
  // src/routes/proposals.ts
3445
- var record4 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
3478
+ var record5 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
3446
3479
  var sameStrings2 = (left, right) => left.length === right.length && left.every((value, index) => value === right[index]);
3447
- var guardFailure2 = (error) => record4(error) && error.code === "transact_guard_failed";
3480
+ var guardFailure2 = (error) => record5(error) && error.code === "transact_guard_failed";
3448
3481
  async function handleProposalResolution(ctx, req, bookId, proposalId) {
3449
3482
  if (req.method !== "POST") return methodNotAllowed();
3450
3483
  const book = await loadMemberBook(ctx.db, bookId, ctx.actor.id);
@@ -3491,7 +3524,7 @@ async function handleProposalResolution(ctx, req, bookId, proposalId) {
3491
3524
  book: {}
3492
3525
  }
3493
3526
  });
3494
- const proposal = (result[BRAND_NS.proposal] ?? [])[0];
3527
+ const proposal = proposalAsWritten((result[BRAND_NS.proposal] ?? [])[0]);
3495
3528
  if (!proposal || proposal.bookId !== book.id || !Array.isArray(proposal.book) || proposal.book.length !== 1 || proposal.book[0]?.id !== book.id) throw new BrandNotFoundError(`proposal ${proposalId}`);
3496
3529
  const current = proposalReviewSnapshot(proposal);
3497
3530
  const { reviewDigest, ...unsignedReview } = current;
@@ -3624,7 +3657,7 @@ async function handleProposalResolution(ctx, req, bookId, proposalId) {
3624
3657
  }
3625
3658
 
3626
3659
  // src/routes/tokens.ts
3627
- var record5 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
3660
+ var record6 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
3628
3661
  var missing = (bookId) => {
3629
3662
  throw new BrandNotFoundError(`compiled tokens for brand book ${bookId}`);
3630
3663
  };
@@ -3637,13 +3670,14 @@ async function acceptedReceipt(db, bookId, receiptId, actionDigest) {
3637
3670
  });
3638
3671
  const receipt2 = (result[BRAND_NS.approvalReceipt] ?? [])[0];
3639
3672
  if (!receipt2 || receipt2.resolution !== "accepted" || receipt2.actionDigest !== actionDigest || !Array.isArray(receipt2.book) || receipt2.book.length !== 1 || receipt2.book[0]?.id !== bookId) return missing(bookId);
3640
- const { book: _book, ...unhydrated } = receipt2;
3673
+ const { book: _book, ...stored } = receipt2;
3674
+ const unhydrated = receiptAsWritten(stored);
3641
3675
  if (!await verifyBrandApprovalReceipt(unhydrated)) return missing(bookId);
3642
3676
  return unhydrated;
3643
3677
  }
3644
3678
  async function approvedCache(db, book) {
3645
3679
  const cache = book.tokens;
3646
- if (!cache || !record5(cache.light) || !record5(cache.dark) || !book.activePaletteId || cache.paletteId !== book.activePaletteId || !cache.paletteReceiptId || !cache.paletteActionDigest || !cache.sourceDigest) return missing(book.id);
3680
+ if (!cache || !record6(cache.light) || !record6(cache.dark) || !book.activePaletteId || cache.paletteId !== book.activePaletteId || !cache.paletteReceiptId || !cache.paletteActionDigest || !cache.sourceDigest) return missing(book.id);
3647
3681
  const paletteResult = await db.query({
3648
3682
  [BRAND_NS.palette]: {
3649
3683
  $: { where: { id: cache.paletteId, bookId: book.id } },
@@ -4427,11 +4461,13 @@ export {
4427
4461
  parseDesignBundle,
4428
4462
  parseHex,
4429
4463
  pickTextOn,
4464
+ proposalAsWritten,
4430
4465
  proposalReviewSnapshot,
4431
4466
  proposePaletteOps,
4432
4467
  proposeSectionOps,
4433
4468
  readDesignManifest,
4434
4469
  readTools,
4470
+ receiptAsWritten,
4435
4471
  recordAnalysisOps,
4436
4472
  rejectProposalOps,
4437
4473
  relativeLuminance,
@@ -4441,6 +4477,7 @@ export {
4441
4477
  rgbToHsl,
4442
4478
  rgbToOklab,
4443
4479
  rotateHue,
4480
+ rowAsWritten,
4444
4481
  safeFileName,
4445
4482
  scanCustomProperties,
4446
4483
  sectionKey,