@gradial/aci 0.1.52-rc.3 → 0.1.52-rc.34-e2e.1

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.
Files changed (276) hide show
  1. package/CHANGELOG.md +80 -0
  2. package/README.md +86 -5
  3. package/dist/astro/error-page.js +1 -0
  4. package/dist/astro/index.js +4 -0
  5. package/dist/astro/page.d.ts +5 -2
  6. package/dist/astro/page.js +35 -23
  7. package/dist/astro/preview.js +2 -17
  8. package/dist/audience-data-models.d.ts +308 -194
  9. package/dist/audience-data-models.js +314 -51
  10. package/dist/cli/compile-registry.d.mts +5 -1
  11. package/dist/cli/compile-registry.mjs +31 -46
  12. package/dist/cli/mintlify-render.mjs +8 -0
  13. package/dist/cli/validate-content.d.mts +31 -1
  14. package/dist/cli/validate-content.mjs +50 -12
  15. package/dist/collect/aci.js +1 -1
  16. package/dist/collect/aci.js.sha256 +1 -1
  17. package/dist/collect/api.d.ts +6 -7
  18. package/dist/collect/api.js +21 -8
  19. package/dist/collect/assignments.d.ts +7 -9
  20. package/dist/collect/assignments.js +23 -32
  21. package/dist/collect/batcher.d.ts +9 -7
  22. package/dist/collect/batcher.js +6 -5
  23. package/dist/collect/consent/envelope.d.ts +7 -1
  24. package/dist/collect/consent/envelope.js +3 -1
  25. package/dist/collect/consent/mapping.d.ts +2 -0
  26. package/dist/collect/consent/mapping.js +1 -0
  27. package/dist/collect/consenthold.d.ts +38 -0
  28. package/dist/collect/consenthold.js +82 -0
  29. package/dist/collect/declarative.js +7 -5
  30. package/dist/collect/gapbuffer.d.ts +13 -4
  31. package/dist/collect/gapbuffer.js +21 -10
  32. package/dist/collect/index.js +67 -20
  33. package/dist/collect/pageviews.d.ts +11 -11
  34. package/dist/collect/pageviews.js +27 -22
  35. package/dist/consent-banner/index.d.ts +17 -0
  36. package/dist/consent-banner/index.js +23 -0
  37. package/dist/content/index.d.ts +2 -1
  38. package/dist/content/index.js +4 -1
  39. package/dist/content/provider.d.ts +16 -2
  40. package/dist/content/provider.js +5 -1
  41. package/dist/content/resolve-slots.d.ts +33 -4
  42. package/dist/content/resolve-slots.js +127 -19
  43. package/dist/content/types.d.ts +7 -1
  44. package/dist/edge/expression-capabilities.v1.d.ts +106 -0
  45. package/dist/edge/expression-capabilities.v1.js +165 -0
  46. package/dist/index.d.ts +1 -2
  47. package/dist/index.js +0 -1
  48. package/dist/internal/domain/edge/compose.d.ts +24 -0
  49. package/dist/internal/domain/edge/compose.js +89 -0
  50. package/dist/internal/domain/edge/identity.d.ts +5 -0
  51. package/dist/internal/domain/edge/identity.js +50 -14
  52. package/dist/internal/domain/edge/index.d.ts +2 -0
  53. package/dist/internal/domain/edge/index.js +2 -0
  54. package/dist/internal/domain/edge/mimic_token.d.ts +14 -0
  55. package/dist/internal/domain/edge/mimic_token.js +125 -0
  56. package/dist/internal/domain/edge/personalization-core.d.ts +61 -22
  57. package/dist/internal/domain/edge/personalization-core.js +789 -198
  58. package/dist/internal/domain/edge/personalization_index.d.ts +17 -0
  59. package/dist/internal/domain/edge/personalization_index.js +56 -0
  60. package/dist/internal/domain/edge/record/codec.d.ts +6 -32
  61. package/dist/internal/domain/edge/record/codec.js +86 -168
  62. package/dist/internal/domain/edge/resolve.js +76 -39
  63. package/dist/internal/domain/edge/testdata/flat_only_validator_v1.d.ts +10 -0
  64. package/dist/internal/domain/edge/testdata/flat_only_validator_v1.js +253 -0
  65. package/dist/internal/domain/edge/types.d.ts +93 -41
  66. package/dist/internal/domain/edge/types.js +3 -3
  67. package/dist/internal/domain/edge/variant_key.d.ts +3 -0
  68. package/dist/internal/domain/edge/variant_key.js +44 -0
  69. package/dist/internal/platform/collectionstore/errors.d.ts +24 -0
  70. package/dist/internal/platform/collectionstore/errors.js +48 -0
  71. package/dist/mimic/core.d.ts +2 -0
  72. package/dist/mimic/core.js +31 -0
  73. package/dist/mintlify/blocks.d.ts +6 -0
  74. package/dist/mintlify/blocks.js +201 -0
  75. package/dist/mintlify/config.d.ts +3 -0
  76. package/dist/mintlify/config.js +77 -0
  77. package/dist/mintlify/index.d.ts +3 -0
  78. package/dist/mintlify/index.js +2 -0
  79. package/dist/mintlify/json.d.ts +8 -0
  80. package/dist/mintlify/json.js +37 -0
  81. package/dist/mintlify/materialize.d.ts +14 -0
  82. package/dist/mintlify/materialize.js +254 -0
  83. package/dist/mintlify/types.d.ts +74 -0
  84. package/dist/mintlify/types.js +1 -0
  85. package/dist/mintlify/validate.d.ts +7 -0
  86. package/dist/mintlify/validate.js +256 -0
  87. package/dist/next/assignments-navigation.d.ts +4 -0
  88. package/dist/next/assignments-navigation.js +18 -0
  89. package/dist/next/assignments-script.d.ts +3 -2
  90. package/dist/next/assignments-script.js +5 -5
  91. package/dist/next/error-pages.d.ts +4 -0
  92. package/dist/next/error-pages.js +4 -0
  93. package/dist/next/error-seat-payload.d.ts +2 -1
  94. package/dist/next/error-seat-payload.js +14 -24
  95. package/dist/next/error-seat.js +7 -2
  96. package/dist/next/hosted-page-target.d.ts +7 -0
  97. package/dist/next/hosted-page-target.js +39 -0
  98. package/dist/next/index.d.ts +1 -1
  99. package/dist/next/index.js +3 -1
  100. package/dist/next/page.d.ts +29 -6
  101. package/dist/next/page.js +47 -25
  102. package/dist/next/release-context.d.ts +29 -0
  103. package/dist/next/release-context.js +55 -0
  104. package/dist/next/render.d.ts +1 -1
  105. package/dist/next/render.js +15 -5
  106. package/dist/next/server.d.ts +8 -0
  107. package/dist/next/server.js +44 -3
  108. package/dist/next/vercel-edge-projection-v1.d.ts +0 -9
  109. package/dist/next/vercel-edge-projection-v1.js +9 -539
  110. package/dist/preview/core.d.ts +1 -0
  111. package/dist/preview/core.js +27 -0
  112. package/dist/react/VideoPlayer.d.ts +3 -3
  113. package/dist/react/VideoPlayer.js +2 -2
  114. package/dist/react/index.d.ts +4 -4
  115. package/dist/react/index.js +2 -3
  116. package/dist/registry.d.ts +4 -2
  117. package/dist/testing/conformance/checks.d.ts +2 -2
  118. package/dist/testing/conformance/checks.js +2 -0
  119. package/dist/testing/conformance/index.d.ts +1 -0
  120. package/dist/testing/conformance/index.js +1 -0
  121. package/dist/testing/conformance/next-stubs.d.ts +6 -0
  122. package/dist/testing/conformance/next-stubs.js +9 -0
  123. package/dist/testing/conformance/preset.d.ts +3 -2
  124. package/dist/testing/conformance/preset.js +15 -8
  125. package/dist/testing/conformance/root-identity.d.ts +31 -0
  126. package/dist/testing/conformance/root-identity.js +361 -0
  127. package/dist/testing/conformance/stubs/next-image.d.ts +7 -0
  128. package/dist/testing/conformance/stubs/next-image.js +5 -0
  129. package/dist/testing/conformance/stubs/next-link.d.ts +7 -0
  130. package/dist/testing/conformance/stubs/next-link.js +4 -0
  131. package/dist/testing/conformance/types.d.ts +5 -2
  132. package/dist/testing/index.d.ts +1 -1
  133. package/dist/testing/index.js +4 -3
  134. package/dist/types/block-ref.d.ts +2 -0
  135. package/dist/types/block-ref.js +3 -2
  136. package/dist/types/component.d.ts +9 -0
  137. package/dist/types/config.d.ts +0 -1
  138. package/dist/types/image.d.ts +54 -17
  139. package/dist/types/image.js +102 -18
  140. package/dist/types/index.d.ts +0 -1
  141. package/dist/types/index.js +0 -1
  142. package/dist/types/media.d.ts +14 -6
  143. package/dist/types/page.d.ts +0 -1
  144. package/dist/types/video.d.ts +7 -3
  145. package/migrations/conformance-registry.md +22 -0
  146. package/package.json +21 -7
  147. package/src/cli/compile-registry.mjs +30 -42
  148. package/src/cli/mintlify-render.mjs +9 -0
  149. package/src/cli/validate-content.mjs +52 -13
  150. package/src/types/block-ref.test.ts +60 -0
  151. package/src/types/block-ref.ts +4 -2
  152. package/src/types/component.ts +10 -0
  153. package/src/types/config.ts +0 -1
  154. package/src/types/image.ts +133 -29
  155. package/src/types/index.ts +0 -1
  156. package/src/types/page.ts +0 -1
  157. package/src/types/video.ts +1 -1
  158. package/templates/astro/starter-content/src/components/chrome/Footer.astro +5 -3
  159. package/templates/astro/starter-content/src/components/chrome/Header.astro +5 -3
  160. package/templates/astro/starter-content/src/components/elements/Button.astro +5 -2
  161. package/templates/astro/starter-content/src/components/elements/ButtonBlock.astro +5 -3
  162. package/templates/astro/starter-content/src/components/sections/ContainerSection.astro +4 -3
  163. package/templates/astro/starter-content/src/components/sections/FeatureCard.astro +4 -3
  164. package/templates/astro/starter-content/src/components/sections/FeatureGrid.astro +4 -3
  165. package/templates/astro/starter-content/src/components/sections/HomeHero.astro +5 -3
  166. package/templates/astro/starter-content/src/components/sections/PersonalizationChoice.astro +4 -3
  167. package/templates/astro/starter-content/src/components/sections/StatsBar.astro +4 -3
  168. package/templates/astro/starter-content/src/fixtures/atomicBlocks.ts +7 -6
  169. package/templates/astro/starter-content/src/fixtures/composition.ts +3 -2
  170. package/templates/astro/starter-content/src/fixtures/sections.ts +3 -3
  171. package/templates/astro/starter-content/src/stories/CompositionStoryFrame.astro +7 -2
  172. package/templates/astro/template/.aci.yaml.tmpl +0 -1
  173. package/templates/astro/template/package.json.tmpl +5 -5
  174. package/templates/astro/template/src/layouts/SiteLayout.astro +2 -2
  175. package/templates/common/.agents/skills/aci/SKILL.md +58 -1
  176. package/templates/common/.agents/skills/authoring/SKILL.md +203 -43
  177. package/templates/common/.agents/skills/authoring/references/block-registry.md +19 -3
  178. package/templates/common/.agents/skills/authoring/references/block-slots.md +3 -0
  179. package/templates/common/.agents/skills/authoring/references/contracts.md +9 -30
  180. package/templates/common/.agents/skills/authoring/references/repo-structure.md +5 -4
  181. package/templates/common/.agents/skills/authoring/references/sdk-server-functions.md +3 -3
  182. package/templates/common/.agents/skills/dam-delivery/SKILL.md +217 -0
  183. package/templates/common/.agents/skills/dam-delivery/references/permalink-grammar.md +167 -0
  184. package/templates/common/.agents/skills/migration/references/canon-migration.md +18 -67
  185. package/templates/common/.agents/skills/migration/references/selector-map.md +1 -1
  186. package/templates/common/.content/fragments/navbar/_index.json +24 -0
  187. package/templates/common/.content/fragments/site-footer/_index.json +61 -0
  188. package/templates/common/.content/pages/404/_index.json +4 -2
  189. package/templates/common/.content/pages/{home/_index.json → _index.json} +10 -1
  190. package/templates/common/.content/pages/error/_index.json +4 -2
  191. package/templates/common/.content/schemas/site.schema.json +11 -1
  192. package/templates/common/.content/templates/error.json +24 -0
  193. package/templates/common/.content/templates/home.json +28 -0
  194. package/templates/common/.content/templates/site.json +8 -0
  195. package/templates/common/.content/templates/standard.json +24 -0
  196. package/templates/common/.nvmrc +1 -1
  197. package/templates/common/starter-content/.content/.rules-baseline.json +8 -0
  198. package/templates/common/starter-content/.content/config/site.json +1 -1
  199. package/templates/common/starter-content/.content/content-rules/no-public-resource-paths.json +6 -0
  200. package/templates/common/starter-content/.content/dam/README.md +74 -0
  201. package/templates/common/starter-content/.content/dam/folders/seed/{ast_site_home_hero → 000000000000000000000000101}/asset.json +1 -1
  202. package/templates/common/starter-content/.content/dam/folders/seed/{ast_site_about_hero → 000000000000000000000000101}/governance.json +2 -1
  203. package/templates/common/starter-content/.content/dam/folders/seed/{ast_site_about_hero → 000000000000000000000000102}/asset.json +1 -1
  204. package/templates/common/starter-content/.content/dam/folders/seed/{ast_site_home_hero → 000000000000000000000000102}/governance.json +2 -1
  205. package/templates/common/starter-content/.content/fragments/navbar/_index.json +23 -20
  206. package/templates/common/starter-content/.content/fragments/site-footer/_index.json +59 -56
  207. package/templates/common/starter-content/.content/pages/404/_index.json +2 -2
  208. package/templates/common/starter-content/.content/pages/{home/_index.json → _index.json} +23 -3
  209. package/templates/common/starter-content/.content/pages/about/_index.json +5 -2
  210. package/templates/common/starter-content/.content/pages/error/_index.json +2 -2
  211. package/templates/common/starter-content/.content/patterns/components/button.json +55 -0
  212. package/templates/common/starter-content/.content/patterns/components/container.json +67 -0
  213. package/templates/common/starter-content/.content/patterns/components/feature_card.json +30 -0
  214. package/templates/common/starter-content/.content/patterns/components/feature_grid.json +59 -0
  215. package/templates/common/starter-content/.content/patterns/components/home_hero.json +151 -0
  216. package/templates/common/starter-content/.content/patterns/components/personalization_choice.json +42 -0
  217. package/templates/common/starter-content/.content/patterns/components/site-footer.json +203 -0
  218. package/templates/common/starter-content/.content/patterns/components/site-navigation.json +115 -0
  219. package/templates/common/starter-content/.content/patterns/components/stats_bar.json +45 -0
  220. package/templates/common/starter-content/.content/schemas/site.schema.json +11 -1
  221. package/templates/nextjs/starter-content/src/components/chrome/Footer.tsx +4 -3
  222. package/templates/nextjs/starter-content/src/components/chrome/Header.tsx +4 -3
  223. package/templates/nextjs/starter-content/src/components/elements/Button.tsx +4 -3
  224. package/templates/nextjs/starter-content/src/components/elements/ButtonBlock.tsx +4 -3
  225. package/templates/nextjs/starter-content/src/components/sections/ContainerSection.tsx +4 -2
  226. package/templates/nextjs/starter-content/src/components/sections/FeatureCard.tsx +4 -3
  227. package/templates/nextjs/starter-content/src/components/sections/FeatureGrid.tsx +4 -2
  228. package/templates/nextjs/starter-content/src/components/sections/HomeHero.tsx +4 -3
  229. package/templates/nextjs/starter-content/src/components/sections/PersonalizationChoice.tsx +4 -2
  230. package/templates/nextjs/starter-content/src/components/sections/StatsBar.tsx +4 -3
  231. package/templates/nextjs/template/.aci.yaml +9 -0
  232. package/templates/nextjs/template/.aci.yaml.tmpl +0 -1
  233. package/templates/nextjs/template/package.json.tmpl +4 -3
  234. package/templates/nextjs/template/src/app/_components/c15t-bridge.ts +30 -0
  235. package/templates/nextjs/template/src/app/_components/c15t-consent-ui.tsx +37 -0
  236. package/templates/nextjs/template/src/app/_components/consent-manager.css +63 -0
  237. package/templates/nextjs/template/src/app/_components/consent-manager.tsx +76 -0
  238. package/templates/nextjs/template/src/app/fonts/inter/OFL.txt +93 -0
  239. package/templates/nextjs/template/src/app/fonts/inter/inter-latin.woff2 +0 -0
  240. package/templates/nextjs/template/src/app/fonts/space-grotesk/OFL.txt +93 -0
  241. package/templates/nextjs/template/src/app/fonts/space-grotesk/space-grotesk-latin.woff2 +0 -0
  242. package/templates/nextjs/template/src/app/layout.tsx +12 -7
  243. package/templates/nextjs/template/tests/conformance.test.ts.tmpl +1 -0
  244. package/dist/define-layout.d.ts +0 -6
  245. package/dist/define-layout.js +0 -14
  246. package/dist/edge/personalization-core.d.ts +0 -11
  247. package/dist/edge/personalization-core.js +0 -269
  248. package/dist/next/collect-route.d.ts +0 -19
  249. package/dist/next/collect-route.js +0 -459
  250. package/dist/next/envelope-digest.d.ts +0 -21
  251. package/dist/next/envelope-digest.js +0 -58
  252. package/dist/next/middleware.d.ts +0 -40
  253. package/dist/next/middleware.js +0 -475
  254. package/dist/next/personalization-host.d.ts +0 -134
  255. package/dist/next/personalization-host.js +0 -270
  256. package/dist/next/root-layout.d.ts +0 -4
  257. package/dist/next/root-layout.js +0 -5
  258. package/dist/next/vercel-collect-sdk-route.d.ts +0 -27
  259. package/dist/next/vercel-collect-sdk-route.js +0 -181
  260. package/dist/next/vercel-personalization.d.ts +0 -4
  261. package/dist/next/vercel-personalization.js +0 -4
  262. package/dist/next/vercel-s3-personalization.d.ts +0 -79
  263. package/dist/next/vercel-s3-personalization.js +0 -570
  264. package/dist/types/layout.d.ts +0 -24
  265. package/src/types/layout.ts +0 -29
  266. package/templates/astro/starter-content/src/render/RenderPage.astro +0 -67
  267. package/templates/astro/template/src/cms/contracts/layouts/index.ts +0 -18
  268. package/templates/nextjs/starter-content/src/components/elements/AtomicBlock.tsx +0 -13
  269. package/templates/nextjs/template/src/cms/contracts/layouts/index.ts +0 -16
  270. /package/dist/{types/layout.js → cli/mintlify-render.d.mts} +0 -0
  271. /package/templates/common/.content/overlays/audience/preference-blue/pages/{home/_index.json → _index.json} +0 -0
  272. /package/templates/common/.content/overlays/audience/preference-red/pages/{home/_index.json → _index.json} +0 -0
  273. /package/templates/common/starter-content/.content/dam/folders/seed/{ast_site_home_hero → 000000000000000000000000101}/editorial.json +0 -0
  274. /package/templates/common/starter-content/.content/dam/folders/seed/{ast_site_home_hero → 000000000000000000000000101}/versions/v0001.json +0 -0
  275. /package/templates/common/starter-content/.content/dam/folders/seed/{ast_site_about_hero → 000000000000000000000000102}/editorial.json +0 -0
  276. /package/templates/common/starter-content/.content/dam/folders/seed/{ast_site_about_hero → 000000000000000000000000102}/versions/v0001.json +0 -0
@@ -0,0 +1,253 @@
1
+ // FROZEN FIXTURE — the request-expression validation path as it stood before
2
+ // ADR 0040 added nested boolean expression trees, vendored from
3
+ // `internal/domain/edge/personalization-core.ts` at commit 1b785e4ac^ (the
4
+ // parent of the ADR 0040 landing commit). Do NOT update this file to track the
5
+ // live validator: its whole value is that it is stale.
6
+ //
7
+ // It exists so one test can reproduce the incident ADR 0040 predicts and says
8
+ // it could not previously debug — a lane running a flat-only evaluator meeting
9
+ // a record carrying a valid nested rule. That evaluator has no concept of a
10
+ // group node, so it classifies `{all:[...]}` as a leaf condition and reports
11
+ // the leaf vocabulary as unsupported. The resulting diagnostics are what the
12
+ // host recorder actually writes during real version skew, and they are NOT the
13
+ // same as those produced by a current, nesting-aware validator refusing a
14
+ // malformed rule.
15
+ //
16
+ // What is vendored: the COMPLETE validation path — `validateRequestExpression`
17
+ // and `validateRequestCondition` with every branch (per-field operator
18
+ // restrictions for device.class / time.now / audience.memberships, and every
19
+ // value check), plus each helper those functions reach
20
+ // (`expressionNumberInDomain`, `parseExpressionTimestamp`,
21
+ // `expressionDaysInMonth`, `canonicalScalar`, `validExpressionRange`,
22
+ // `homogeneousExpressionMembers`, and the DEVICE_CLASSES / MAX_IN_VALUES /
23
+ // grammar constants). Evaluation, matching, and the fact vocabulary are
24
+ // irrelevant to the record's shape and are deliberately absent.
25
+ //
26
+ // Adaptations from the source commit (everything not listed here is verbatim):
27
+ // 1. Entry-point signature. The original `validateRequestExpression` was
28
+ // 3-argument — its callers pre-extracted `all`/`any` with `rule.all || []`.
29
+ // The frozen export is 1-argument and derives them itself with
30
+ // `Array.isArray` guards (strictly stronger than the original `|| []`) so
31
+ // the fixture is safe to call directly on untrusted record data.
32
+ // 2. The diagnostic type is renamed `FrozenExpressionDiagnostic` (from
33
+ // `ExpressionDiagnostic`) so a frozen diagnostic can never be confused
34
+ // with a live one at a call site.
35
+ // 3. Export keywords were removed from the vendored internals; only the
36
+ // entry point and its diagnostic type are exported.
37
+ const MAX_EXPRESSION_CONDITIONS = 64;
38
+ const EXPRESSION_OPERATORS = [
39
+ 'equals', 'contains', 'startsWith', 'exists',
40
+ 'greaterThan', 'greaterThanOrEqual', 'lessThan', 'lessThanOrEqual',
41
+ 'before', 'after', 'between', 'in',
42
+ ];
43
+ const MAX_IN_VALUES = 32;
44
+ // Observed values are gated on the published grammars before any host parser
45
+ // runs (ADR 0038): Number() and Date.parse accept supersets (hex, empty
46
+ // string, vendor date formats) that would let the same fact match here and
47
+ // not in Go. Keep these literals identical to the capability artifact — the
48
+ // drift test compares them byte-for-byte.
49
+ const EXPRESSION_NUMBER_GRAMMAR = /^-?(0|[1-9][0-9]*)(\.[0-9]+)?([eE][+-]?[0-9]+)?$/;
50
+ const EXPRESSION_TIMESTAMP_GRAMMAR = /^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2})(\.\d{1,9})?(Z|[+-]\d{2}:\d{2})$/;
51
+ const DEVICE_CLASSES = ['desktop', 'mobile', 'tablet', 'unknown'];
52
+ // Adaptation 1 (see header): the original was 3-argument with caller-extracted
53
+ // `all`/`any`; this 1-argument form derives them with Array.isArray guards.
54
+ // The body below this line is verbatim from the source commit.
55
+ export function frozenValidateRequestExpression(rule) {
56
+ const all = (Array.isArray(rule.all) ? rule.all : []);
57
+ const any = (Array.isArray(rule.any) ? rule.any : []);
58
+ const diagnostics = [];
59
+ if (all.length + any.length === 0)
60
+ diagnostics.push({ code: 'expression.empty', path: 'expression', scope: 'request', message: 'expression must include at least one all or any condition' });
61
+ if (all.length + any.length > MAX_EXPRESSION_CONDITIONS)
62
+ diagnostics.push({ code: 'expression.conditions.too_many', path: 'expression', scope: 'request', message: `expression must contain at most ${MAX_EXPRESSION_CONDITIONS} conditions` });
63
+ Object.keys(rule).filter((group) => group !== 'all' && group !== 'any').sort().forEach((group) => {
64
+ diagnostics.push({ code: 'expression.group.unsupported', path: `expression.${group}`, scope: 'request', message: `expression.${group} is not a supported expression group` });
65
+ });
66
+ for (const [group, conditions] of [['all', all], ['any', any]]) {
67
+ conditions.forEach((condition, index) => frozenValidateRequestCondition(condition, `expression.${group}[${index}]`, diagnostics));
68
+ }
69
+ return diagnostics;
70
+ }
71
+ // Verbatim from the source commit: the full flat-era condition validator,
72
+ // including the per-field operator restrictions and every value check.
73
+ function frozenValidateRequestCondition(condition, path, diagnostics) {
74
+ Object.keys(condition).filter((property) => !['field', 'operator', 'value'].includes(property)).sort().forEach((property) => {
75
+ diagnostics.push({ code: 'expression.property.unsupported', path: `${path}.${property}`, scope: 'request', message: `${path}.${property} is not a supported condition property` });
76
+ });
77
+ const field = String(condition.field || '');
78
+ if (field !== 'referrer' && field !== 'device.class' && field !== 'time.now' && field !== 'audience.memberships' && !['query.', 'header.', 'cookie.', 'geo.', 'utm.'].some((prefix) => field.startsWith(prefix) && field.length > prefix.length)) {
79
+ diagnostics.push({ code: 'expression.field.unsupported', path: `${path}.field`, scope: 'request', message: `${path}.field ${JSON.stringify(field)} is not supported` });
80
+ }
81
+ const operator = String(condition.operator || '');
82
+ if (!EXPRESSION_OPERATORS.includes(operator)) {
83
+ diagnostics.push({ code: 'expression.operator.unsupported', path: `${path}.operator`, scope: 'request', message: `${path}.operator ${JSON.stringify(operator)} is not supported` });
84
+ return;
85
+ }
86
+ // device.class compares against a closed four-value set, so substring
87
+ // operators express nothing the set cannot — and admitting them would
88
+ // re-create the user-agent-substring habit inside the safe field (ADR 0037).
89
+ if (field === 'device.class' && !['equals', 'in', 'exists'].includes(operator)) {
90
+ diagnostics.push({ code: 'expression.operator.unsupported', path: `${path}.operator`, scope: 'request', message: `${path}.operator ${JSON.stringify(operator)} is not supported for field ${JSON.stringify(field)}, which compares against a closed value set` });
91
+ return;
92
+ }
93
+ // time.now is a sub-second-churning clock: string operators are silently
94
+ // inert on it and the numeric family can never match an RFC 3339 string
95
+ // (ADR 0038).
96
+ if (field === 'time.now' && !['before', 'after', 'between', 'exists'].includes(operator)) {
97
+ diagnostics.push({ code: 'expression.operator.unsupported', path: `${path}.operator`, scope: 'request', message: `${path}.operator ${JSON.stringify(operator)} is not supported for field ${JSON.stringify(field)}, which supports only before, after, between, exists` });
98
+ return;
99
+ }
100
+ // audience.memberships is an opaque-ID list: equals is already
101
+ // any-member-equals under the multi-valued match loop, so the substring
102
+ // family would only add silent overmatching of prefixed IDs, and the
103
+ // numeric/instant families can never match an ID (ADR 0039).
104
+ if (field === 'audience.memberships' && !['equals', 'in', 'exists'].includes(operator)) {
105
+ diagnostics.push({ code: 'expression.operator.unsupported', path: `${path}.operator`, scope: 'request', message: `${path}.operator ${JSON.stringify(operator)} is not supported for field ${JSON.stringify(field)}, which supports only equals, in, exists` });
106
+ return;
107
+ }
108
+ const value = condition.value;
109
+ if (operator === 'exists') {
110
+ if (value !== undefined && value !== null && typeof value !== 'boolean') {
111
+ diagnostics.push({ code: 'expression.value.invalid', path: `${path}.value`, scope: 'request', message: `${path}.value must be a boolean for operator exists` });
112
+ }
113
+ return;
114
+ }
115
+ if (value === undefined || value === null) {
116
+ diagnostics.push({ code: 'expression.value.required', path: `${path}.value`, scope: 'request', message: `${path}.value is required for operator ${operator}` });
117
+ return;
118
+ }
119
+ if (['equals', 'contains', 'startsWith'].includes(operator)) {
120
+ if (!['string', 'number', 'boolean'].includes(typeof value)
121
+ || (typeof value === 'number' && !expressionNumberInDomain(value))) {
122
+ diagnostics.push({ code: 'expression.value.invalid', path: `${path}.value`, scope: 'request', message: `${path}.value must be a string, boolean, or finite number using only safe integers` });
123
+ }
124
+ else if (field === 'device.class' && DEVICE_CLASSES.indexOf(canonicalScalar(value)) < 0) {
125
+ diagnostics.push({ code: 'expression.value.invalid', path: `${path}.value`, scope: 'request', message: `${path}.value must be one of ${DEVICE_CLASSES.join(', ')} for field ${JSON.stringify(field)}` });
126
+ }
127
+ return;
128
+ }
129
+ if (['greaterThan', 'greaterThanOrEqual', 'lessThan', 'lessThanOrEqual'].includes(operator)) {
130
+ if (typeof value !== 'number' || !expressionNumberInDomain(value)) {
131
+ diagnostics.push({ code: 'expression.value.invalid', path: `${path}.value`, scope: 'request', message: `${path}.value must be a finite JSON number using only safe integers for operator ${operator}` });
132
+ }
133
+ return;
134
+ }
135
+ if (operator === 'before' || operator === 'after') {
136
+ if (typeof value !== 'string' || parseExpressionTimestamp(value) === null) {
137
+ diagnostics.push({ code: 'expression.value.invalid', path: `${path}.value`, scope: 'request', message: `${path}.value must be an RFC 3339 timestamp with an explicit UTC offset for operator ${operator}` });
138
+ }
139
+ return;
140
+ }
141
+ if (operator === 'between') {
142
+ if (!validExpressionRange(value)) {
143
+ diagnostics.push({ code: 'expression.value.invalid', path: `${path}.value`, scope: 'request', message: `${path}.value must be a min/max object whose bounds are both numbers or both timestamps with min not above max` });
144
+ }
145
+ else if (field === 'time.now' && typeof value.min === 'number') {
146
+ // An instant field's observed values are instants: a numeric range on
147
+ // it would validate and then sit silently inert at execution.
148
+ diagnostics.push({ code: 'expression.value.invalid', path: `${path}.value`, scope: 'request', message: `${path}.value bounds must be timestamps for field ${JSON.stringify(field)}` });
149
+ }
150
+ return;
151
+ }
152
+ // in: bounded, non-empty, every member one canonicalizable scalar, all
153
+ // members one JSON type — a mixed list is almost always an authoring bug.
154
+ const members = homogeneousExpressionMembers(value);
155
+ if (members === null) {
156
+ diagnostics.push({ code: 'expression.value.invalid', path: `${path}.value`, scope: 'request', message: `${path}.value must be an array of 1 to ${MAX_IN_VALUES} scalars sharing one type` });
157
+ return;
158
+ }
159
+ if (field === 'device.class') {
160
+ for (const member of members) {
161
+ if (DEVICE_CLASSES.indexOf(member) < 0) {
162
+ diagnostics.push({ code: 'expression.value.invalid', path: `${path}.value`, scope: 'request', message: `${path}.value must be one of ${DEVICE_CLASSES.join(', ')} for field ${JSON.stringify(field)}` });
163
+ return;
164
+ }
165
+ }
166
+ }
167
+ }
168
+ // ── Validation helpers, verbatim from the source commit ──────────────────
169
+ function expressionNumberInDomain(value) {
170
+ if (!Number.isFinite(value))
171
+ return false;
172
+ if (Number.isInteger(value) && !Number.isSafeInteger(value))
173
+ return false;
174
+ return true;
175
+ }
176
+ // Explicit calendar validation runs before Date arithmetic because Date.UTC
177
+ // (like Go's time.Date) silently normalizes impossible dates. Instants
178
+ // compare as epoch milliseconds; fractional seconds truncate to milliseconds.
179
+ function parseExpressionTimestamp(value) {
180
+ const match = EXPRESSION_TIMESTAMP_GRAMMAR.exec(value);
181
+ if (!match)
182
+ return null;
183
+ const year = Number(match[1]), month = Number(match[2]), day = Number(match[3]);
184
+ const hour = Number(match[4]), minute = Number(match[5]), second = Number(match[6]);
185
+ if (month < 1 || month > 12 || day < 1 || day > expressionDaysInMonth(year, month))
186
+ return null;
187
+ if (hour > 23 || minute > 59 || second > 59)
188
+ return null;
189
+ let offsetMinutes = 0;
190
+ if (match[8] !== 'Z') {
191
+ const offsetHour = Number(match[8].slice(1, 3)), offsetMinute = Number(match[8].slice(4, 6));
192
+ if (offsetHour > 23 || offsetMinute > 59)
193
+ return null;
194
+ offsetMinutes = offsetHour * 60 + offsetMinute;
195
+ if (match[8][0] === '-')
196
+ offsetMinutes = -offsetMinutes;
197
+ }
198
+ const fractionMillis = match[7] ? Number((match[7].slice(1) + '00').slice(0, 3)) : 0;
199
+ // setUTCFullYear, not Date.UTC: Date.UTC maps two-digit years to 1900+.
200
+ const date = new Date(0);
201
+ date.setUTCFullYear(year, month - 1, day);
202
+ date.setUTCHours(hour, minute, second, 0);
203
+ return date.getTime() - offsetMinutes * 60000 + fractionMillis;
204
+ }
205
+ function expressionDaysInMonth(year, month) {
206
+ if (month === 4 || month === 6 || month === 9 || month === 11)
207
+ return 30;
208
+ if (month === 2)
209
+ return year % 4 === 0 && (year % 100 !== 0 || year % 400 === 0) ? 29 : 28;
210
+ return 31;
211
+ }
212
+ function canonicalScalar(value) {
213
+ if (typeof value === 'number')
214
+ return Object.is(value, -0) ? '0' : JSON.stringify(value);
215
+ return String(value);
216
+ }
217
+ function validExpressionRange(value) {
218
+ if (typeof value !== 'object' || value === null || Array.isArray(value))
219
+ return false;
220
+ const record = value;
221
+ if (Object.keys(record).some((key) => key !== 'min' && key !== 'max'))
222
+ return false;
223
+ const min = record.min, max = record.max;
224
+ if (min === undefined || min === null || max === undefined || max === null)
225
+ return false;
226
+ if (typeof min === 'number' && expressionNumberInDomain(min)) {
227
+ return typeof max === 'number' && expressionNumberInDomain(max) && min <= max;
228
+ }
229
+ const minMillis = typeof min === 'string' ? parseExpressionTimestamp(min) : null;
230
+ if (minMillis === null)
231
+ return false;
232
+ const maxMillis = typeof max === 'string' ? parseExpressionTimestamp(max) : null;
233
+ return maxMillis !== null && minMillis <= maxMillis;
234
+ }
235
+ function homogeneousExpressionMembers(value) {
236
+ if (!Array.isArray(value) || value.length === 0 || value.length > MAX_IN_VALUES)
237
+ return null;
238
+ let memberKind = '';
239
+ const members = [];
240
+ for (const member of value) {
241
+ const kind = typeof member;
242
+ if (kind !== 'string' && kind !== 'number' && kind !== 'boolean')
243
+ return null;
244
+ if (kind === 'number' && !expressionNumberInDomain(member))
245
+ return null;
246
+ if (!memberKind)
247
+ memberKind = kind;
248
+ else if (memberKind !== kind)
249
+ return null;
250
+ members.push(canonicalScalar(member));
251
+ }
252
+ return members;
253
+ }
@@ -4,10 +4,17 @@ export interface EdgeDispatch {
4
4
  /** Release-relative immutable HTML artifact selected for this route. */
5
5
  artifactPath: string;
6
6
  }
7
+ /**
8
+ * One member of an expression group: a leaf condition, or a nested envelope
9
+ * when it carries `all`/`any` (ADR 0040). Presence of a group key decides
10
+ * which it is, so flat records stay a byte-identical subset.
11
+ */
7
12
  export interface EdgeRuleCondition {
8
- field: string;
9
- operator: 'exists' | 'equals' | 'contains' | 'startsWith' | string;
13
+ field?: string;
14
+ operator?: 'exists' | 'equals' | 'contains' | 'startsWith' | 'greaterThan' | 'greaterThanOrEqual' | 'lessThan' | 'lessThanOrEqual' | 'before' | 'after' | 'between' | 'in' | string;
10
15
  value?: unknown;
16
+ all?: EdgeRuleCondition[];
17
+ any?: EdgeRuleCondition[];
11
18
  }
12
19
  export interface EdgeRuleExpression {
13
20
  all?: EdgeRuleCondition[];
@@ -48,9 +55,30 @@ export interface PersonalizationRequestContext {
48
55
  signals?: {
49
56
  geo?: Record<string, string>;
50
57
  };
58
+ /**
59
+ * Epoch milliseconds resolving the time.now clock fact (ADR 0038).
60
+ * Defaults to Date.now(); hosts may pin it once per request so every
61
+ * expression in one evaluation reads the same instant.
62
+ */
63
+ now?: number;
51
64
  identityHash?: string;
52
65
  identityNamespace?: string;
53
66
  siteId?: string;
67
+ /**
68
+ * Called when a variant's rule fails validation during serving (ADR 0040).
69
+ * Records pass the write-time gate before shipping, so this only fires on
70
+ * version skew or a projection bug — a precise signal that must not vanish.
71
+ * Receives routing facts and diagnostic codes only, never request data.
72
+ */
73
+ onRuleRejected?: (event: RuleRejectionEvent) => void;
74
+ }
75
+ export interface RuleRejectionEvent {
76
+ experimentId?: string;
77
+ armId?: string;
78
+ variantKey: string;
79
+ diagnosticCode: string;
80
+ diagnosticPath: string;
81
+ diagnosticCount: number;
54
82
  }
55
83
  export interface PersonalizationClaims {
56
84
  aud?: string[];
@@ -58,13 +86,7 @@ export interface PersonalizationClaims {
58
86
  iat: number;
59
87
  exp: number;
60
88
  }
61
- export interface PersonalizationVariant {
62
- variantKey: string;
63
- variantValues?: Record<string, string>;
64
- priority?: number;
65
- rule?: EdgeRuleExpression;
66
- dispatch: EdgeDispatch;
67
- }
89
+ export type PersonalizationVariant = EdgeRouteVariant;
68
90
  export interface PersonalizationRoute {
69
91
  variants?: Record<string, PersonalizationVariant>;
70
92
  }
@@ -75,10 +97,19 @@ export interface PersonalizationExperiment {
75
97
  priority?: number;
76
98
  allocationVersion?: string;
77
99
  salt?: string;
100
+ eligibilityAudienceId?: string;
101
+ /**
102
+ * ADR 0050 (experiment I-18): the projection carries this only when the
103
+ * experiment declares `eligibility.mode: "selected"`; absent means the
104
+ * claims gate (I-6) applies. The selected-leg predicate itself is slice
105
+ * A5 (edge-functions I-16) — A3 is projection plumbing only.
106
+ */
107
+ eligibilityMode?: 'selected';
78
108
  arms: Array<{
79
109
  id: string;
80
110
  allocationBasisPoints?: number;
81
- variantKey: string;
111
+ /** DEBT(edge-functions): decode old arm keys until every retained tenant release passes the ADR-0045 removal inventory. */
112
+ variantKey?: string;
82
113
  }>;
83
114
  }
84
115
  export interface PersonalizationIdentity {
@@ -93,24 +124,14 @@ export interface PersonalizationExperimentDecision {
93
124
  allocationVersion: string;
94
125
  variantKey: string;
95
126
  bucket: number;
127
+ /** audience-mimic design/09: the token's assertion replaced the dice. */
128
+ forced?: boolean;
96
129
  }
97
130
  export interface PersonalizationDecision {
131
+ /** ADR-0045: number of route experiments excluded as legacy pair-only entries. */
132
+ legacyExperimentExclusions?: number;
98
133
  variant: PersonalizationVariant | null;
99
- experiment: PersonalizationExperimentDecision | null;
100
- }
101
- export interface ClassificationData {
102
- audiences: Record<string, {
103
- rule: EdgeRuleExpression;
104
- }>;
105
- segments: Record<string, {
106
- rule: EdgeRuleExpression;
107
- }>;
108
- experiments: Record<string, PersonalizationExperiment>;
109
- }
110
- export interface Classification {
111
- matchedAudiences: string[];
112
- matchedSegments: string[];
113
- experimentArms: Record<string, PersonalizationExperimentDecision>;
134
+ experiments: PersonalizationExperimentDecision[];
114
135
  }
115
136
  export interface SigningKey {
116
137
  kid: string;
@@ -145,20 +166,6 @@ export interface ConsentSignals {
145
166
  allowsIdentityTier: boolean;
146
167
  privacyDenied: boolean;
147
168
  }
148
- export interface PersonalizationEnvelopeWrite {
149
- key: string;
150
- envelopeId: string;
151
- siteId: string;
152
- identityNamespace: string;
153
- identityHash: string;
154
- records: readonly unknown[];
155
- digest: string;
156
- }
157
- export interface PersonalizationCollectionStore {
158
- readClaims(siteId: string, namespace: string, identityHash: string, now?: Date): Promise<string | null>;
159
- appendEnvelope(write: PersonalizationEnvelopeWrite): Promise<void>;
160
- putSuppression(key: string, record: unknown): Promise<void>;
161
- }
162
169
  export interface PersonalizationHostConfig {
163
170
  siteId: string;
164
171
  route: EdgeRouteEntry | null;
@@ -169,13 +176,58 @@ export interface PersonalizationHostConfig {
169
176
  secureCookies?: boolean;
170
177
  now?: () => Date;
171
178
  randomBytes?: (length: number) => Uint8Array;
179
+ verifiedVisitor?: {
180
+ rawIdentity: string | null;
181
+ claims: ClaimsPayload | null;
182
+ };
183
+ requestGeo?: {
184
+ country?: string;
185
+ region?: string;
186
+ city?: string;
187
+ };
188
+ issueIdentity?: boolean;
189
+ mimicClaims?: {
190
+ aud?: string[];
191
+ seg?: string[];
192
+ arms?: Record<string, string>;
193
+ };
194
+ /**
195
+ * Passed straight through to the request context's rejection sink so a host
196
+ * can record a variant skipped for a rule it could not validate (ADR 0040).
197
+ * Unset means the skip stays silent, which is what made the skew incident
198
+ * the ADR predicts undebuggable.
199
+ */
200
+ onRuleRejected?: (event: RuleRejectionEvent) => void;
201
+ }
202
+ export interface ClaimsPayload {
203
+ aud?: string[];
204
+ seg?: string[];
205
+ iat: number;
206
+ exp: number;
172
207
  }
173
208
  export interface PersonalizationResolution {
209
+ /** ADR-0045: number of route experiments excluded as legacy pair-only entries. */
210
+ legacyExperimentExclusions?: number;
211
+ /** ADR 0048: the raw identity issued for this request, unsigned and not yet persisted. */
212
+ issuedIdentity?: string;
174
213
  variantKey: string;
175
- experiment?: {
214
+ experiments: Array<{
176
215
  id: string;
177
216
  arm: string;
178
217
  allocationVersion: string;
179
- };
218
+ /** audience-mimic design/09: the assignment came from the token, not the dice. */
219
+ forced?: boolean;
220
+ }>;
221
+ }
222
+ export interface CompositionDescriptor {
223
+ layoutArtifact: string;
224
+ fragments?: Record<string, string>;
225
+ }
226
+ export type SelectedTargetKind = 'direct' | 'compose' | 'render';
227
+ export interface SelectedTarget {
228
+ kind: SelectedTargetKind;
229
+ path: string;
230
+ digest: string;
231
+ composition?: CompositionDescriptor;
180
232
  }
181
233
  export { IdentityErasureFencedError } from '../../platform/collectionstore/errors.js';
@@ -1,7 +1,7 @@
1
1
  // Shared edge-decisioning types.
2
2
  //
3
3
  // These types are the canonical shape for the edge decisioning algorithm.
4
- // All host adapters (AWS, Cloudflare, Vercel, emulator) import from here.
5
- // The SDK's personalization-core types mirror these — the two are kept in
6
- // sync by cross-language round-trip tests (spec edge-decisioning R-conformance).
4
+ // All host adapters (AWS, Cloudflare, Vercel, emulator) import from here,
5
+ // and the SDK build compiles this module into its dist/internal tree — there
6
+ // is no SDK-side mirror (ADR 0041 retired the hand-ported copy).
7
7
  export { IdentityErasureFencedError } from '../../platform/collectionstore/errors.js';
@@ -0,0 +1,3 @@
1
+ export declare function compareUTF8(left: string, right: string): number;
2
+ export declare function canonicalVariantKey(values: Record<string, string>): string;
3
+ export declare function parseVariantKey(key: string): Record<string, string>;
@@ -0,0 +1,44 @@
1
+ // Canonical factor keys mirror audience.VariantKey/ParseVariantKey. Their
2
+ // shared vector lives in audience/testdata/variant-key.v1.json.
3
+ export function compareUTF8(left, right) {
4
+ const a = Array.from(left);
5
+ const b = Array.from(right);
6
+ for (let i = 0; i < Math.min(a.length, b.length); i++) {
7
+ const difference = a[i].codePointAt(0) - b[i].codePointAt(0);
8
+ if (difference)
9
+ return difference;
10
+ }
11
+ return a.length - b.length;
12
+ }
13
+ export function canonicalVariantKey(values) {
14
+ const fixed = ['locale', 'audience', 'segment'].filter((name) => values[name]?.trim());
15
+ const rest = Object.keys(values).filter((name) => values[name]?.trim() && !fixed.includes(name));
16
+ const generic = rest.filter((name) => !name.startsWith('experiment.')).sort(compareUTF8);
17
+ const experiments = rest.filter((name) => name.startsWith('experiment.')).sort(compareUTF8);
18
+ return [...fixed, ...generic, ...experiments].map((name) => `${name}=${values[name].trim()}`).join(';') || 'base';
19
+ }
20
+ export function parseVariantKey(key) {
21
+ const values = {};
22
+ if (key === 'base')
23
+ return values;
24
+ for (const part of key.split(';')) {
25
+ const separator = part.indexOf('=');
26
+ const name = part.slice(0, separator);
27
+ const value = part.slice(separator + 1);
28
+ if (separator < 1 || !validToken(name) || !validToken(value))
29
+ throw new Error('variant key has malformed factors');
30
+ if (name === 'experiment' || name === 'experimentArm')
31
+ throw new Error('pair-form experiment key is not canonical');
32
+ if (name.startsWith('experiment.') && (!/^[a-zA-Z0-9_-]+$/.test(name.slice('experiment.'.length)) || !/^[a-zA-Z0-9_-]+$/.test(value)))
33
+ throw new Error('variant key has malformed experiment factor');
34
+ if (Object.prototype.hasOwnProperty.call(values, name))
35
+ throw new Error('variant key repeats a dimension');
36
+ Object.defineProperty(values, name, { value, enumerable: true, writable: true, configurable: true });
37
+ }
38
+ if (canonicalVariantKey(values) !== key)
39
+ throw new Error('variant key is not canonically ordered');
40
+ return values;
41
+ }
42
+ function validToken(value) {
43
+ return value !== '' && value !== '.' && value !== '..' && !/[\p{White_Space};=/]/u.test(value);
44
+ }
@@ -3,3 +3,27 @@ export declare class IdentityErasureFencedError extends Error {
3
3
  readonly code = "IDENTITY_ERASURE_FENCED";
4
4
  constructor();
5
5
  }
6
+ export declare class CollectionGateBusyError extends Error {
7
+ readonly name = "CollectionGateBusyError";
8
+ readonly code = "COLLECTION_GATE_BUSY";
9
+ constructor(detail?: string);
10
+ }
11
+ export declare class EnvelopeIdCollisionError extends Error {
12
+ readonly name = "EnvelopeIdCollisionError";
13
+ readonly code = "ENVELOPE_ID_COLLISION";
14
+ readonly envelopeId: string;
15
+ readonly existingDigest: string;
16
+ readonly incomingDigest: string;
17
+ constructor(envelopeId: string, existingDigest: string, incomingDigest: string);
18
+ }
19
+ export declare class AdmissionConflictError extends Error {
20
+ readonly kind: 'envelope' | 'event';
21
+ readonly name = "AdmissionConflictError";
22
+ readonly code = "COLLECT_ADMISSION_CONFLICT";
23
+ constructor(kind: 'envelope' | 'event');
24
+ }
25
+ export declare class CollectionStoreError extends Error {
26
+ readonly code: string;
27
+ readonly name = "CollectionStoreError";
28
+ constructor(code: string, message: string, options?: ErrorOptions);
29
+ }
@@ -5,3 +5,51 @@ export class IdentityErasureFencedError extends Error {
5
5
  super('identity collection is fenced by erasure');
6
6
  }
7
7
  }
8
+ // Go parity: audience.ErrCollectionGateBusy. A live or unproven collecting
9
+ // generation owns the identity; the caller must fail loudly and retry later,
10
+ // never treat the append as stored or dropped.
11
+ export class CollectionGateBusyError extends Error {
12
+ name = 'CollectionGateBusyError';
13
+ code = 'COLLECTION_GATE_BUSY';
14
+ constructor(detail) {
15
+ super(detail
16
+ ? `audience: identity collection gate is busy: ${detail}`
17
+ : 'audience: identity collection gate is busy');
18
+ }
19
+ }
20
+ // Go parity: audience.EnvelopeIDCollisionError. The envelope ID already names
21
+ // an object whose stable-input digest differs (or is absent), so this request
22
+ // is not an identical retry and must not be acknowledged as stored.
23
+ export class EnvelopeIdCollisionError extends Error {
24
+ name = 'EnvelopeIdCollisionError';
25
+ code = 'ENVELOPE_ID_COLLISION';
26
+ envelopeId;
27
+ existingDigest;
28
+ incomingDigest;
29
+ constructor(envelopeId, existingDigest, incomingDigest) {
30
+ super(`audience: envelope ID collision: ${envelopeId}`);
31
+ this.envelopeId = envelopeId;
32
+ this.existingDigest = existingDigest;
33
+ this.incomingDigest = incomingDigest;
34
+ }
35
+ }
36
+ // Different site-scoped acceptance owner; no visitor identifiers in the error.
37
+ export class AdmissionConflictError extends Error {
38
+ kind;
39
+ name = 'AdmissionConflictError';
40
+ code = 'COLLECT_ADMISSION_CONFLICT';
41
+ constructor(kind) {
42
+ super(`collect ${kind} ID already belongs to another acceptance; retry only the original sealed envelope with its original identity and consent`);
43
+ this.kind = kind;
44
+ }
45
+ }
46
+ // Internal store failure with a stable bounded code. Collector diagnostics log
47
+ // the code and never the message, which may interpolate object keys.
48
+ export class CollectionStoreError extends Error {
49
+ code;
50
+ name = 'CollectionStoreError';
51
+ constructor(code, message, options) {
52
+ super(message, options);
53
+ this.code = code;
54
+ }
55
+ }
@@ -0,0 +1,2 @@
1
+ export declare const MIMIC_BANNER_HEADER = "x-aci-mimic-banner";
2
+ export declare function decodeMimicBanner(encoded: string): string;
@@ -0,0 +1,31 @@
1
+ // View-as-audience mimic banner contract (specs/features/audience-mimic).
2
+ // The platform edge router (internal/domain/hosting/runtime/mimic.ts) owns
3
+ // the mimic session — token validation, cookies, redirects, and banner
4
+ // creation. On body-less SSR rewrites it forwards the rendered banner to the
5
+ // framework host via MIMIC_BANNER_HEADER; this module is the framework-side
6
+ // leg that decodes it (the MimicBanner component in next/page.tsx). The
7
+ // middleware strips any inbound copy of the header and sets it only for a
8
+ // validated session, so the decoded HTML is trusted as authored.
9
+ //
10
+ // The SDK-side token validator that used to live here served the retired
11
+ // Next personalization-host lane (ADR 0041); the Go/TS validators in
12
+ // internal/domain/edge remain pinned by the shared vector file.
13
+ export const MIMIC_BANNER_HEADER = 'x-aci-mimic-banner';
14
+ export function decodeMimicBanner(encoded) {
15
+ try {
16
+ return new TextDecoder().decode(base64URLToBytes(encoded));
17
+ }
18
+ catch {
19
+ return '';
20
+ }
21
+ }
22
+ function base64URLToBytes(value) {
23
+ const base64 = value.replace(/-/g, '+').replace(/_/g, '/');
24
+ const padded = base64.padEnd(Math.ceil(base64.length / 4) * 4, '=');
25
+ const binary = atob(padded);
26
+ const bytes = new Uint8Array(binary.length);
27
+ for (let i = 0; i < binary.length; i++) {
28
+ bytes[i] = binary.charCodeAt(i);
29
+ }
30
+ return bytes;
31
+ }
@@ -0,0 +1,6 @@
1
+ import type { MintlifyBlock, MintlifyDocument, MintlifySnippet } from './types.js';
2
+ export declare function renderDocument(document: MintlifyDocument): string;
3
+ export declare function renderSnippet(snippet: MintlifySnippet): string;
4
+ export declare function validateBlock(value: unknown, label: string, ids: Set<string>): MintlifyBlock;
5
+ export declare function visitBlocks(blocks: MintlifyBlock[], visit: (block: MintlifyBlock) => void): void;
6
+ export declare function validateSnippetReferences(documents: MintlifyDocument[], snippets: MintlifySnippet[]): void;