@gradial/aci 0.1.52-rc.34 → 0.1.52-rc.40

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/CHANGELOG.md CHANGED
@@ -1,5 +1,46 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.1.52-rc.40
4
+
5
+ ### Fixed
6
+
7
+ - Mimic pages retain Next hydration and native 404 rendering while the hosting
8
+ platform owns their private response and session banner. Browser measurement
9
+ stays disabled for the lifetime of each mimic document, including after its
10
+ cookie expires or another tab exits the session. Rebuild and redeploy sites
11
+ with this SDK and the matching hosting platform to pick up the fix.
12
+
13
+ ## 0.1.52-rc.38
14
+
15
+ ### Migration required
16
+
17
+ - Link secondary experiment goals with `goalIds` instead of the retired
18
+ `goalRefs` field. Remove `goalRefs` from authored documents and authoring
19
+ integrations; `primaryGoalRef` remains supported. See the
20
+ [secondary-goal migration](./migrations/experiment-goal-ids.md).
21
+
22
+ ## 0.1.52-rc.37
23
+
24
+ ### Removed
25
+
26
+ - Unused edge identity helpers and types. The shared runtime retains the
27
+ production identity and routing behavior.
28
+
29
+ ## 0.1.52-rc.36
30
+
31
+ ### Removed
32
+
33
+ - The unused compact route-record codec and static compose terminal target.
34
+ Static delivery selects complete HTML through the shared edge runtime.
35
+
36
+ ## 0.1.52-rc.35
37
+
38
+ ### Fixed
39
+
40
+ - Next returns HTTP 404 for missing native assets and API endpoints that reach
41
+ the CMS catch-all without a routed release, including `/favicon.ico`, instead
42
+ of failing metadata and page rendering with a release-selection error.
43
+
3
44
  ## 0.1.52-rc.34
4
45
 
5
46
  ### Changed
package/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # Agentic Content Infrastructure SDK
2
2
 
3
- `@gradial/aci` `0.1.52-rc.34` is a pre-1.0 public-surface reset for the ACI
3
+ `@gradial/aci` `0.1.52-rc.40` is a pre-1.0 public-surface reset for the ACI
4
4
  client SDK. Breaking changes from the old `0.1.x` accidental surface are
5
5
  intentional: the root import is framework-neutral, framework runtime behavior is
6
6
  behind explicit subpaths, and the first required customer path is React/Next.
@@ -8,7 +8,7 @@ behind explicit subpaths, and the first required customer path is React/Next.
8
8
  ## Install
9
9
 
10
10
  ```bash
11
- npm install @gradial/aci@0.1.52-rc.34 zod@^4
11
+ npm install @gradial/aci@0.1.52-rc.40 zod@^4
12
12
  ```
13
13
 
14
14
  ## Root Import
@@ -137,13 +137,13 @@ host bundles select and serve immutable release artifacts.
137
137
  The first RC scaffold path is Next:
138
138
 
139
139
  ```bash
140
- npx @gradial/aci@0.1.52-rc.34 init my-site --template next-starter
140
+ npx @gradial/aci@0.1.52-rc.40 init my-site --template next-starter
141
141
  ```
142
142
 
143
143
  List available templates:
144
144
 
145
145
  ```bash
146
- npx @gradial/aci@0.1.52-rc.34 init --list
146
+ npx @gradial/aci@0.1.52-rc.40 init --list
147
147
  ```
148
148
 
149
149
  ## Personalization: The One-Tag Install
@@ -428,7 +428,6 @@ export declare const experimentDataModel: import("./index.js").DataModelContract
428
428
  }, z.core.$strict>>;
429
429
  primaryGoalRef: z.ZodOptional<z.ZodString>;
430
430
  primaryGoalId: z.ZodOptional<z.ZodString>;
431
- goalRefs: z.ZodOptional<z.ZodArray<z.ZodString>>;
432
431
  goalIds: z.ZodOptional<z.ZodArray<z.ZodString>>;
433
432
  promotedArmId: z.ZodOptional<z.ZodString>;
434
433
  promotedAt: z.ZodOptional<z.ZodString>;
@@ -490,7 +489,6 @@ export declare const experimentationDataModels: (import("./index.js").DataModelC
490
489
  }, z.core.$strict>>;
491
490
  primaryGoalRef: z.ZodOptional<z.ZodString>;
492
491
  primaryGoalId: z.ZodOptional<z.ZodString>;
493
- goalRefs: z.ZodOptional<z.ZodArray<z.ZodString>>;
494
492
  goalIds: z.ZodOptional<z.ZodArray<z.ZodString>>;
495
493
  promotedArmId: z.ZodOptional<z.ZodString>;
496
494
  promotedAt: z.ZodOptional<z.ZodString>;
@@ -923,7 +921,6 @@ export declare const audienceDataModels: (import("./index.js").DataModelContract
923
921
  }, z.core.$strict>>;
924
922
  primaryGoalRef: z.ZodOptional<z.ZodString>;
925
923
  primaryGoalId: z.ZodOptional<z.ZodString>;
926
- goalRefs: z.ZodOptional<z.ZodArray<z.ZodString>>;
927
924
  goalIds: z.ZodOptional<z.ZodArray<z.ZodString>>;
928
925
  promotedArmId: z.ZodOptional<z.ZodString>;
929
926
  promotedAt: z.ZodOptional<z.ZodString>;
@@ -385,7 +385,6 @@ export const experimentDataModel = defineDataModel({
385
385
  }).strict().optional(),
386
386
  primaryGoalRef: z.string().optional(),
387
387
  primaryGoalId: z.string().optional(),
388
- goalRefs: z.array(z.string().regex(/\S/, 'entries must be non-blank')).optional(),
389
388
  goalIds: z.array(z.string().regex(/\S/, 'entries must be non-blank')).optional(),
390
389
  promotedArmId: z.string().optional(),
391
390
  promotedAt: z.string().optional(),
@@ -1 +1 @@
1
- "use strict";(()=>{var Me=Object.defineProperty;var je=(t,e,n)=>e in t?Me(t,e,{enumerable:!0,configurable:!0,writable:!0,value:n}):t[e]=n;var b=(t,e,n)=>je(t,typeof e!="symbol"?e+"":e,n);var We="experiment.exposure",Ge='script[type="application/json"][data-aci-assignments]';function H(t){if(typeof t!="object"||t===null)return null;let e=t,n=e.experimentId,r=e.armId,o=e.allocationVersion;return typeof n!="string"||n===""||typeof r!="string"||r===""||typeof o!="string"?null:{experimentId:n,armId:r,allocationVersion:o}}function M(t,e){try{let n=t.querySelector(Ge);if(!n)return[];let r=n.textContent;if(r==null||r.trim()==="")return[];let o=JSON.parse(r);if(typeof o!="object"||o===null)return e==null||e("capture",{reason:"assignments_malformed"}),[];let i=o;if(i.v!==1||!Array.isArray(i.experiments))return e==null||e("capture",{reason:"assignments_malformed"}),[];let s=[];for(let a of i.experiments){let c=H(a);c&&!s.some(u=>u.experimentId===c.experimentId)&&s.push(c)}return s}catch(n){return e==null||e("capture",{reason:"assignments_parse_failed",error:String(n)}),[]}}function ne(t,e){if(!e||t.experiments!==void 0)return t;let n=e.getActiveAssignments();return n.length===0?t:{...t,experiments:n}}function te(t){let e=[],n=new Set,r,o=i=>{e=(i!=null?i:M(t.document,t.debug)).map(s=>({...s}));for(let s of e)n.has(s.experimentId)||(n.add(s.experimentId),r==null||r({eventName:We,url:t.window.location.href,experiments:[{experimentId:s.experimentId,armId:s.armId,allocationVersion:s.allocationVersion}]}))};return{getActiveAssignments:()=>e.map(i=>({...i})),start(i){r=i,o()},clear(){e=[]},rearm(i){n.clear(),o(i)}}}var L="data-aci-event",ze="data-aci-on",re="data-aci-prop-",qe="data-aci-refresh",oe="custom.";function j(t){if(typeof t!="string")return;let e=t.trim().toLowerCase();if(!(e===""||/[\s/]/.test(e)))return e.startsWith(oe)?e:oe+e}function $e(t){let e;for(let n of Array.from(t.attributes)){if(!n.name.startsWith(re))continue;let r=n.name.slice(re.length);r!==""&&((e!=null?e:e={})[r]=n.value)}return e}function ie(t,e,n){let r=j(t.getAttribute(L));if(r===void 0){n==null||n("capture",{reason:"malformed_declaration",attr:L});return}return{eventName:r,url:e,properties:$e(t)}}function se(t){var e,n;return(n=(e=t.getAttribute(ze))==null?void 0:e.trim().toLowerCase())!=null?n:"click"}function ae(t,e,n){var d;let{document:r,debug:o}=e,i=(d=r.defaultView)==null?void 0:d.location;r.addEventListener("click",p=>{var f,g;try{let m=p.target;if((f=m==null?void 0:m.closest)==null?void 0:f.call(m,`[${qe}]`)){n==null||n();return}let l=(g=m==null?void 0:m.closest)==null?void 0:g.call(m,`[${L}]`);if(!l||se(l)!=="click")return;let h=ie(l,i==null?void 0:i.href,o);h&&t(h)}catch(m){o==null||o("capture",{reason:"click_handler_failed",error:String(m)})}});let s=e.intersectionObserver;if(typeof s!="function")return;let a=Array.from(r.querySelectorAll(`[${L}]`)).filter(p=>se(p)==="visible");if(a.length===0)return;let c=new WeakSet,u=new s((p,f)=>{try{for(let g of p){if(!g.isIntersecting||g.intersectionRatio<.5||c.has(g.target))continue;c.add(g.target),f.unobserve(g.target);let m=ie(g.target,i==null?void 0:i.href,o);m&&t(m)}}catch(g){o==null||o("capture",{reason:"visible_handler_failed",error:String(g)})}},{threshold:.5});for(let p of a)u.observe(p)}function W(t){return typeof t=="object"&&t!==null&&!Array.isArray(t)}function ce(t,e,n){let r=(...s)=>{try{let[a,...c]=s;if(a==="navigation-complete"){let u=c[0];if(!W(u)||typeof u.url!="string"||!/^https?:\/\//.test(u.url)||!Array.isArray(u.assignments)||u.assignments.length>16){n==null||n("capture",{reason:"malformed_navigation_completion"});return}let d=[];for(let p of u.assignments){let f=H(p);if(!f||d.some(g=>g.experimentId===f.experimentId)){n==null||n("capture",{reason:"malformed_navigation_completion"});return}d.push(f)}e.navigationComplete({url:u.url,assignments:d});return}if(a==="track"){let u=j(c[0]);if(u===void 0){n==null||n("capture",{reason:"malformed_track_name",name:c[0]});return}let d=c[1];if(d!==void 0&&!W(d)){n==null||n("capture",{reason:"malformed_track_properties",name:u});return}e.track(u,d);return}if(a==="consent"){let u=c[0];if(!W(u)){n==null||n("consent",{reason:"malformed_consent_envelope"});return}e.consent({p:u.p,src:u.src});return}n==null||n("capture",{reason:"unknown_command",command:a})}catch(a){n==null||n("capture",{reason:"command_failed",error:String(a)})}},o=t.aci,i=o==null?void 0:o.q;if(t.aci=r,i!=null)for(let s of Array.from(i)){let a=Array.from(s);r(...a)}}var ue="0123456789ABCDEFGHJKMNPQRSTVWXYZ";function Ue(t){globalThis.crypto.getRandomValues(t)}function A(t,e){let n=t!=null?t:Date.now(),r="";for(let c=0;c<10;c++)r=ue[n%32]+r,n=Math.floor(n/32);let o=new Uint8Array(10);(e!=null?e:Ue)(o);let i="",s=0,a=0;for(let c of o)for(s=s<<8|c,a+=8;a>=5;)i+=ue[s>>>a-5&31],a-=5;return r+i}var Xe=60*1024,Ke=100,Ye=5e3,le='{"envelopeId":"',de='","events":[',fe="]}",Je=26,I=le.length+Je+de.length+fe.length,Ze=new TextEncoder,Qe=t=>Ze.encode(t).length,O=class{constructor(e){b(this,"options");b(this,"maxBytes");b(this,"queue",[]);b(this,"queuedBytes",0);b(this,"flushing",!1);b(this,"intervalId");var n;this.options=e,this.maxBytes=(n=e.maxBatchBytes)!=null?n:Xe}get pending(){return this.queue.length}push(e){let n=JSON.stringify(e),r=Qe(n);this.queue.push({json:n,bytes:r,eventId:e.eventId}),this.queuedBytes+=r,!this.flushing&&this.envelopeBytes()>=this.maxBytes&&this.flush()}flush(){var e,n;if(!this.flushing){this.flushing=!0;try{(n=(e=this.options).onTick)==null||n.call(e),this.drain()}finally{this.flushing=!1}}}start(){var o,i,s,a;let e=(o=this.options.timers)!=null?o:{setInterval:(c,u)=>setInterval(c,u),clearInterval:c=>clearInterval(c)};this.intervalId=e.setInterval(()=>this.flush(),(i=this.options.flushIntervalMs)!=null?i:Ye);let n=(s=this.options.documentTarget)!=null?s:typeof document!="undefined"?document:void 0;n==null||n.addEventListener("visibilitychange",()=>{n.visibilityState==="hidden"&&this.flush()});let r=(a=this.options.windowTarget)!=null?a:typeof window!="undefined"?window:void 0;r==null||r.addEventListener("pagehide",()=>this.flush()),this.flush()}stop(){if(this.intervalId!==void 0){let e=this.options.timers;e?e.clearInterval(this.intervalId):clearInterval(this.intervalId),this.intervalId=void 0}}envelopeBytes(){let e=this.queue.length>1?this.queue.length-1:0;return I+this.queuedBytes+e}drain(){var e,n,r;for(;this.queue.length>0;){let o=this.queue[0];if(I+o.bytes>this.maxBytes){this.queue.shift(),this.queuedBytes-=o.bytes,(n=(e=this.options).debug)==null||n.call(e,"flush",{reason:"oversize_event",eventId:o.eventId,bytes:o.bytes});continue}let i=[],s=I;for(;this.queue.length>0;){let c=this.queue[0];if(I+c.bytes>this.maxBytes||i.length>=Ke)break;let u=s+c.bytes+(i.length>0?1:0);if(u>this.maxBytes)break;i.push(c.json),s=u,this.queue.shift(),this.queuedBytes-=c.bytes}if(i.length===0)continue;let a=((r=this.options.createEnvelopeId)!=null?r:A)();this.options.send(le+a+de+i.join(",")+fe)}}};var G=new TextEncoder,en=2048,nn=32,tn=128,rn=512,on=2147483647,sn=/^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2})(?:\.(\d{1,9}))?(Z|[+-]\d{2}:\d{2})$/,an=/^[\t\n\v\f\r ]+|[\t\n\v\f\r ]+$/g,cn=new Set(["true","all","granted","yes","personalization","personalized"]);function x(t,e=!0){let n=S(t);if(!n)return null;if(e)try{n=decodeURIComponent(n)}catch{}if(n=S(n),!n||G.encode(n).byteLength>en)return null;if(cn.has(n.toLowerCase()))return{version:0,purposes:["personalization"],grantedAt:""};try{let r=JSON.parse(n),o=typeof r.t=="string"?r.t:"";return!Number.isInteger(r.v)||r.v<0||r.v>on||!Array.isArray(r.p)||r.p.length>nn||!r.p.every(i=>typeof i=="string")||r.p.some(i=>G.encode(i).byteLength>tn)||r.t!==void 0&&r.t!==null&&(typeof r.t!="string"||r.t===""||!un(r.t))||r.src!==void 0&&r.src!==null&&typeof r.src!="string"||typeof r.src=="string"&&G.encode(r.src).byteLength>rn?null:{version:r.v,purposes:r.p,grantedAt:o,source:typeof r.src=="string"?r.src:void 0}}catch{return null}}function z(t){return(t==null?void 0:t.purposes.some(e=>S(e).toLowerCase()==="personalization"))||!1}function S(t){return t.replace(an,"")}function un(t){let e=sn.exec(t);if(!e||Number.isNaN(Date.parse(t)))return!1;let n=Number(e[1]),r=Number(e[2]),o=Number(e[3]),i=Number(e[4]),s=Number(e[5]),a=Number(e[6]);if(r<1||r>12||o<1||o>ln(n,r)||i>23||s>59||a>59)return!1;let c=e[8];return c==="Z"||Number(c.slice(1,3))<=23&&Number(c.slice(4,6))<=59}function ln(t,e){return e===2?t%4===0&&(t%100!==0||t%400===0)?29:28:[4,6,9,11].includes(e)?30:31}var $="personalization",q="aci_consent",U=1,dn=365*24*60*60;function fn(t,e){var r;let n=`${q}=${t.encoded}; Path=/; Max-Age=${dn}; SameSite=Lax`;((r=e.document.location)==null?void 0:r.protocol)==="https:"&&(n+="; Secure"),e.document.cookie=n}function N(t,e,n){var o,i;let r=pn(t,e,n.now);return r===null?!1:(z(r.snapshot)||(o=n.gapBuffer)==null||o.purge(),fn(r,n),(i=n.onDecision)==null||i.call(n),!0)}function P(t){for(let e of t.cookie.split(";")){let n=S(e);if(n.startsWith(q+"="))return n.slice(q.length+1)}}function pe(t){return t===void 0||t===""?!1:z(x(t,!0))}function me(t){var r;if(!Array.isArray(t))return;let e=[];for(let o of t){if(typeof o!="string")return;e.push(S(o))}let n=JSON.stringify({v:U,p:e});return(r=x(n,!1))==null?void 0:r.purposes}function ge(t){var o;if(t!==void 0&&typeof t!="string")return;let e=typeof t=="string"?S(t):"",r=JSON.stringify({v:U,p:[],src:e===""?"manual":e});return(o=x(r,!1))==null?void 0:o.source}function pn(t,e,n){try{let r=JSON.stringify({v:U,p:t,t:new Date((n!=null?n:Date.now)()).toISOString(),src:e}),o=x(r,!1);return o===null?null:{encoded:encodeURIComponent(r),snapshot:o}}catch{return null}}var mn="data-aci-consent-banner";var gn="data-aci-consent-banner-mount",ve="aci:consent-banner-decided";function vn(t){return P(t)!==void 0}function he(t){return vn(t)?!1:t.querySelector(`[${gn}], [${mn}]`)!==null}var hn=new Set(["c15t","onetrust","cookiebot","trustarc","manual"]);function D(t,e){var o;let n=(o=t==null?void 0:t.getAttribute)==null?void 0:o.call(t,e);if(n==null)return;let r=n.trim();return r===""?void 0:r}function be(t){var r;let e=(r=D(t,"data-aci-consent"))==null?void 0:r.toLowerCase(),n=e!==void 0&&hn.has(e)?e:"manual";return{site:D(t,"data-aci-site"),preset:n,categories:D(t,"data-aci-consent-categories"),debug:D(t,"data-aci-debug")==="1"}}function Ee(t,e,n){if(!t)return()=>{};let r=e!=null?e:(i=>console.debug("[aci]",i)),o=n!=null?n:Date.now;return(i,s)=>{try{r({t:o(),kind:i,detail:s})}catch{}}}var R=class{constructor(e){b(this,"events",[]);b(this,"now");b(this,"debug");var n;this.now=(n=e==null?void 0:e.now)!=null?n:Date.now,this.debug=e==null?void 0:e.debug}push(e){var n;if(this.expire(),this.events.length>=50){(n=this.debug)==null||n.call(this,"buffer",{reason:"consent_hold_cap_drop_newest",eventId:e.eventId});return}this.events.push({event:e,heldAt:this.now()})}size(){return this.events.length}release(){this.expire();let e=this.events.map(n=>n.event);return this.events=[],e}discard(e){var n;this.events.length>0&&((n=this.debug)==null||n.call(this,"buffer",{reason:`consent_hold_discarded_${e}`,count:this.events.length})),this.events=[]}expire(){var r;let e=this.now()-18e5,n=0;for(;n<this.events.length&&this.events[n].heldAt<e;)n+=1;n>0&&(this.events.splice(0,n),(r=this.debug)==null||r.call(this,"buffer",{reason:"consent_hold_expired",count:n}))}};function ye(t,e){var r;if(t.size()===0||!e.decided())return;if(!e.grants()){t.discard("denied");return}let n=t.release();for(let o of n)e.batcher.push(o);(r=e.debug)==null||r.call(e,"buffer",{reason:"consent_hold_released",count:n.length})}var bn="cmp-",En=/^[a-z0-9._-]+$/,yn={c15t:"measurement,marketing",onetrust:"c0002,c0004",cookiebot:"statistics,marketing",trustarc:"functional,advertising"};function X(t,e){let n=e!=null?e:yn[t];if(n===void 0)return null;let r=n.split(",").map(o=>o.trim().toLowerCase()).filter(o=>o!=="");if(r.length===0)return null;for(let o of r)if(!En.test(o))return null;return r}function Cn(t,e){return e.filter(r=>r in t.categories).some(r=>t.categories[r]===!0)?[$]:[]}function _(t,e,n){var r,o,i,s;try{let a=X(e,n.categories);if(a===null){(r=n.debug)==null||r.call(n,"consent",{reason:"unparseable_mapping",preset:e});return}if(a.filter(d=>d in t.categories).length===0){(o=n.debug)==null||o.call(n,"consent",{reason:"unmappable_cmp_state",preset:e});return}let u=Cn(t,a);N(u,bn+e,{document:n.document,gapBuffer:n.gapBuffer,now:n.now,onDecision:n.onDecision}),(i=n.debug)==null||i.call(n,"consent",{reason:"written",preset:e,purposes:u})}catch(a){(s=n.debug)==null||s.call(n,"consent",{reason:"consent_bridge_failed",preset:e,error:String(a)})}}var Ce=250,kn=1e4;function w(t,e,n){var s;let r=(s=n==null?void 0:n.scheduler)!=null?s:{setTimeout:(a,c)=>setTimeout(a,c),clearTimeout:a=>clearTimeout(a)},o=Math.floor(kn/Ce),i=()=>{var c,u;let a;try{a=t()}catch(d){(c=n==null?void 0:n.debug)==null||c.call(n,"consent",{reason:"cmp_read_failed",error:String(d)});return}if(a!==void 0){e(a);return}if(o-=1,o<=0){(u=n==null?void 0:n.debug)==null||u.call(n,"consent",{reason:"cmp_not_found"});return}r.setTimeout(i,Ce)};i()}function y(t,e){try{e()}catch(n){t==null||t("consent",{reason:"adapter_failed",error:String(n)})}}var _n="c15t";function wn(t){if(typeof t!="object"||t===null)return;let e={};for(let[n,r]of Object.entries(t))typeof r=="boolean"&&(e[n.toLowerCase()]=r);return{categories:e}}function Sn(t,e){return typeof t=="object"&&t!==null?"consents"in t?t.consents:t:e.consents}function ke(t){w(()=>{let e=t.window.c15t;return typeof e=="object"&&e!==null?e:void 0},e=>{y(t.debug,()=>{var r;let n=o=>{let i=wn(o);i&&_(i,_n,t)};n(e.consents),(r=e.onConsentChange)==null||r.call(e,o=>y(t.debug,()=>n(Sn(o,e))))})},t)}var An="cookiebot",xn=["CookiebotOnConsentReady","CookiebotOnAccept","CookiebotOnDecline"];function _e(t){let e=t.Cookiebot;return typeof e=="object"&&e!==null?e:void 0}function Nn(t){if(typeof t!="object"||t===null)return;let e={};for(let[n,r]of Object.entries(t))typeof r=="boolean"&&(e[n.toLowerCase()]=r);return{categories:e}}function we(t){let e=()=>{let r=_e(t.window);if(!r||r.hasResponse!==!0)return;let o=Nn(r.consent);o&&_(o,An,t)},n=t.window;if(typeof n.addEventListener=="function")for(let r of xn)n.addEventListener(r,()=>y(t.debug,e));w(()=>_e(t.window),()=>y(t.debug,e),t)}var Se="onetrust",Pn=/^c\d{4}$/;function Tn(t,e){let n={};for(let r of t.split(",")){let o=r.trim().toLowerCase();o!==""&&(n[o]=!0)}for(let r of e!=null?e:[])!(r in n)&&Pn.test(r)&&(n[r]=!1);return{categories:n}}function Ae(t){let e=t.window,n=()=>{let o=e.OnetrustActiveGroups;if(typeof o!="string")return;let i=X(Se,t.categories);_(Tn(o,i),Se,t)},r=e.OptanonWrapper;e.OptanonWrapper=()=>{if(typeof r=="function")try{r()}catch{}y(t.debug,n)},w(()=>{let o=e.OnetrustActiveGroups;return typeof o=="string"?o:void 0},()=>y(t.debug,n),t)}var Ln="trustarc",In={required:1,functional:2,advertising:3},xe="PrivacyManagerAPI";function Ne(t){let e=t.truste,n=e==null?void 0:e.cma,r=n==null?void 0:n.callApi;return typeof r=="function"?r.bind(n):void 0}function On(t){if(typeof t!="object"||t===null)return;let e=t.consentDecision;if(typeof e!="number"||!Number.isFinite(e)||e<1)return;let n={};for(let[r,o]of Object.entries(In))n[r]=e>=o;return{categories:n}}function Dn(t){return typeof t=="string"?t.includes(xe):typeof t=="object"&&t!==null&&xe in t}function Rn(t){let e=t.location;return typeof(e==null?void 0:e.hostname)=="string"?e.hostname:void 0}function Pe(t){let e=()=>{let r=Ne(t.window);if(!r)return;let o=On(r("getConsentDecision",Rn(t.window)));o&&_(o,Ln,t)},n=t.window;typeof n.addEventListener=="function"&&n.addEventListener("message",r=>y(t.debug,()=>{Dn(r==null?void 0:r.data)&&e()})),w(()=>Ne(t.window),()=>y(t.debug,e),t)}var B="aci:buf";var Te="aci_m",F=class{constructor(e){b(this,"events",[]);b(this,"storage");b(this,"debug");this.debug=e==null?void 0:e.debug,this.storage=e==null?void 0:e.storage}push(e){var n;this.events=this.restore(),this.events.push(e),this.events.length>50&&(this.events.splice(0,this.events.length-50),(n=this.debug)==null||n.call(this,"buffer",{reason:"cap_drop_oldest"})),this.persist()}drain(){let e=this.restore();return this.events=[],this.persist(),e}purge(){var e;this.events=this.restore(),this.events.length>0&&((e=this.debug)==null||e.call(this,"buffer",{reason:"purged",count:this.events.length})),this.events=[],this.persist()}size(){return this.restore().length}restore(){var r;let e=this.storage;if(!e)return this.events;let n;try{n=e.getItem(B)}catch(o){return this.storage=void 0,(r=this.debug)==null||r.call(this,"buffer",{reason:"storage_unavailable",error:String(o)}),[]}if(n==null)return[];try{let o=JSON.parse(n);if(Array.isArray(o))return o}catch{}try{e.removeItem(B)}catch{this.storage=void 0}return[]}persist(){var n;let e=this.storage;if(e)try{this.events.length===0?e.removeItem(B):e.setItem(B,JSON.stringify(this.events)),this.events=[]}catch(r){this.storage=void 0,(n=this.debug)==null||n.call(this,"buffer",{reason:"storage_unavailable",error:String(r)})}}};function Le(t){for(let e of t.cookie.split(";")){let n=e.trim();if(n.startsWith(Te+"="))return n.slice(Te.length+1)!==""}return!1}function Ie(t,e){var n;if(e.consentHold&&((n=e.consentPending)!=null&&n.call(e))){e.consentHold.push(t);return}if(e.grants()&&!e.marker()){e.gapBuffer.push(t);return}e.batcher.push(t)}function K(t,e,n,r){if(t.size()===0||!n())return;let o=t.drain();for(let i of o)e.push(i);r==null||r("buffer",{reason:"flushed",count:o.length})}var Bn="rum.link_click";function Oe(t,e){var i;let{document:n,debug:r}=e,o=(i=n.defaultView)==null?void 0:i.location;n.addEventListener("click",s=>{var a,c,u;try{let d=s.target,p=(a=d==null?void 0:d.closest)==null?void 0:a.call(d,"a[href]");if(!p)return;let f=p;t({eventName:Bn,url:o==null?void 0:o.href,properties:{linkText:(u=(c=f.textContent)==null?void 0:c.trim())!=null?u:"",linkHref:f.href}})}catch(d){r==null||r("capture",{reason:"link_click_failed",error:String(d)})}},{capture:!0})}var Fn="rum.page_view";function V(t){let e=t.indexOf("#");return e===-1?t:t.slice(0,e)}function De(t,e,n=()=>{}){let r=V(t.location.href),o=r,i=!1,s=()=>{let c=V(t.location.href);c!==r&&(r=c,i=!0,n())},a=t.history;for(let c of["pushState","replaceState"]){let u=a[c].bind(a);a[c]=(...d)=>{let p=u(...d);return s(),p}}return t.addEventListener("popstate",s),{complete({url:c,assignments:u}){let d=V(t.location.href);if(V(c)===d&&(s(),!(!i&&o===d))){i=!1,o=d;for(let p of e)p(u)}}}}function Re(t,e){let n=()=>t({eventName:Fn,url:e.location.href,referrer:e.document.referrer});return n(),n}var Y="/collect";function Be(t,e){let n=e==null?void 0:e.debug,r=e!==void 0&&"navigator"in e?e.navigator:globalThis.navigator,o=e!==void 0&&"fetch"in e?e.fetch:globalThis.fetch,i;try{i=new Blob([t],{type:"text/plain"})}catch{n==null||n("flush",{reason:"no_blob"});return}if(r!=null&&r.sendBeacon)try{if(r.sendBeacon(Y,i))return}catch{}if(!o){n==null||n("flush",{reason:"no_transport"});return}try{o(Y,{method:"POST",body:i,keepalive:!0}).catch(s=>{n==null||n("flush",{reason:"fetch_failed",error:String(s)})})}catch(s){n==null||n("flush",{reason:"fetch_failed",error:String(s)})}}function Fe(t){var n,r;let e=t!==void 0&&"fetch"in t?t.fetch:globalThis.fetch;if(!e)return Promise.resolve(!1);try{let o=((n=t==null?void 0:t.createEnvelopeId)!=null?n:A)(),i=new Blob([`{"envelopeId":"${o}","events":[]}`],{type:"text/plain"});return e(Y,{method:"POST",body:i,keepalive:!0,credentials:"same-origin"}).then(s=>{var a;return typeof s=="object"&&s!==null&&"ok"in s&&s.ok===!1?((a=t==null?void 0:t.debug)==null||a.call(t,"flush",{reason:"refresh_failed",status:"not_ok"}),!1):!0}).catch(s=>{var a;return(a=t==null?void 0:t.debug)==null||a.call(t,"flush",{reason:"refresh_failed",error:String(s)}),!1})}catch(o){return(r=t==null?void 0:t.debug)==null||r.call(t,"flush",{reason:"refresh_failed",error:String(o)}),Promise.resolve(!1)}}var Vn="rum.variation_view",J="data-aci-variation";function Ve(t,e){var u,d,p;let{document:n,debug:r}=e,o=e.MutationObserver;if(typeof o!="function")return;let i=(u=n.defaultView)==null?void 0:u.location,s=new WeakSet,a=f=>{let g=f.getAttribute(J);g!=null&&(s.has(f)||(s.add(f),t({eventName:Vn,url:i==null?void 0:i.href,properties:{variantKey:g}})))},c=f=>{try{f.hasAttribute(J)&&a(f);for(let g of Array.from(f.querySelectorAll(`[${J}]`)))a(g)}catch(g){r==null||r("capture",{reason:"variation_scan_failed",error:String(g)})}};try{let f=(d=n.body)!=null?d:n.documentElement;f&&c(f)}catch(f){r==null||r("capture",{reason:"variation_initial_scan_failed",error:String(f)})}try{let f=(p=n.body)!=null?p:n.documentElement;if(!f)return;new o(m=>{try{for(let E of m)for(let l=0;l<E.addedNodes.length;l++){let h=E.addedNodes[l];if((h==null?void 0:h.nodeType)!==1)continue;let C=h.element;C&&c(C)}}catch(E){r==null||r("capture",{reason:"variation_observer_failed",error:String(E)})}}).observe(f,{subtree:!0,childList:!0})}catch(f){r==null||r("capture",{reason:"variation_observe_failed",error:String(f)})}}var Hn="rum.web_vital";function He(t,e){var g,m,E;let n=e.PerformanceObserver;if(typeof n!="function")return;let{performance:r,debug:o}=e,i=(m=(g=e.window)==null?void 0:g.location)==null?void 0:m.href,s=e.document,a=new Set,c=(l,h)=>{a.has(l)||(a.add(l),t({eventName:Hn,url:i,properties:{vitalName:l,vitalValue:h}}))};try{let l=r==null?void 0:r.getEntriesByType("navigation")[0];l&&typeof l.responseStart=="number"&&typeof l.requestStart=="number"&&c("TTFB",l.responseStart-l.requestStart)}catch(l){o==null||o("capture",{reason:"ttfb_failed",error:String(l)})}let u=0,d=0,p=(l,h)=>{try{new n(T=>{try{for(let v of T.getEntries())h(v)}catch(v){o==null||o("capture",{reason:`${l}_handler_failed`,error:String(v)})}}).observe({type:l,buffered:!0})}catch(C){o==null||o("capture",{reason:`${l}_observe_failed`,error:String(C)})}};p("largest-contentful-paint",l=>{c("LCP",l.startTime)}),p("first-input",l=>{c("FID",l.startTime)}),p("layout-shift",l=>{var h;l.hadRecentInput||(u+=(h=l.value)!=null?h:0)}),p("event",l=>{var C;let h=(C=l.value)!=null?C:0;h>d&&(d=h)}),p("paint",l=>{l.name==="first-contentful-paint"&&c("FCP",l.startTime)});let f=()=>{try{a.has("CLS")||c("CLS",u),a.has("INP")||c("INP",d)}catch(l){o==null||o("capture",{reason:"visibility_handler_failed",error:String(l)})}};s&&s.addEventListener("visibilitychange",()=>{s.visibilityState==="hidden"&&f()}),(E=e.window)==null||E.addEventListener("pagehide",f)}var Z={c15t:ke,onetrust:Ae,cookiebot:we,trustarc:Pe};function Mn(t){var e,n;return typeof((e=t.navigator)==null?void 0:e.sendBeacon)=="function"&&typeof t.IntersectionObserver=="function"&&typeof((n=t.crypto)==null?void 0:n.getRandomValues)=="function"}function jn(t){try{return t.sessionStorage}catch{return}}function Wn(t){var o;let e=t!=null?t:typeof window!="undefined"?window:void 0;if(!e||e.__aci_loaded||(e.__aci_loaded=!0,!Mn(e)))return;let n=be(e.document.currentScript),r=Ee(n.debug);try{let i=e.document,s=new F({storage:jn(e),debug:r}),a=()=>{try{return pe(P(i))}catch{return!1}},c=()=>{try{return Le(i)}catch{return!1}},u=()=>{try{return P(i)!==void 0}catch{return!1}},d=n.preset==="manual"?()=>{try{return he(i)}catch{return!1}}:()=>!0,p=new R({debug:r}),f=()=>!u()&&!c()&&d(),g={navigator:e.navigator,fetch:typeof e.fetch=="function"?e.fetch.bind(e):void 0,debug:r},m;m=new O({send:v=>Be(v,g),debug:r,onTick:()=>{ye(p,{decided:u,grants:a,batcher:m,debug:r}),K(s,m,c,r)},timers:{setInterval:(v,k)=>e.setInterval(v,k),clearInterval:v=>e.clearInterval(v)},documentTarget:i,windowTarget:e});let E=te({document:i,window:e,debug:r}),l=v=>{Ie({...ne(v,E),eventId:A(void 0,k=>e.crypto.getRandomValues(k))},{grants:a,marker:c,gapBuffer:s,batcher:m,consentHold:p,consentPending:f})},h=()=>m.flush();i.addEventListener(ve,h),E.start(l);let C=()=>{},T=De(e,[v=>E.rearm(v),()=>C()],()=>E.clear());i.addEventListener("astro:page-load",()=>T.complete({url:e.location.href,assignments:M(i,r)})),ce(e,{navigationComplete:v=>T.complete(v),track:(v,k)=>l({eventName:v,properties:k,url:e.location.href}),consent:({p:v,src:k})=>{let Q=me(v);if(Q===void 0){r("consent",{reason:"malformed_purposes"});return}let ee=ge(k);if(ee===void 0){r("consent",{reason:"malformed_source"});return}N(Q,ee,{document:i,gapBuffer:s,onDecision:h})||r("consent",{reason:"oversized_envelope"})}},r),C=Re(l,e),ae(l,{document:i,intersectionObserver:e.IntersectionObserver,debug:r},()=>{Fe(g).then(v=>{v&&e.location.reload()})}),Oe(l,{document:i,debug:r}),He(l,{performance:e.performance,PerformanceObserver:e.PerformanceObserver,window:e,document:i,debug:r}),Ve(l,{document:i,MutationObserver:e.MutationObserver,debug:r}),n.preset!=="manual"&&((o=Z[n.preset])==null||o.call(Z,{window:e,document:i,categories:n.categories,gapBuffer:s,onDecision:h,debug:r,scheduler:{setTimeout:(v,k)=>e.setTimeout(v,k),clearTimeout:v=>e.clearTimeout(v)}})),m.start(),e.addEventListener("pagehide",()=>p.discard("unload")),K(s,m,c,r),r("init",{config:n})}catch(i){r("init",{error:String(i)})}}Wn();})();
1
+ "use strict";(()=>{var je=Object.defineProperty;var We=(t,e,n)=>e in t?je(t,e,{enumerable:!0,configurable:!0,writable:!0,value:n}):t[e]=n;var E=(t,e,n)=>We(t,typeof e!="symbol"?e+"":e,n);var Ge="experiment.exposure",ze='script[type="application/json"][data-aci-assignments]';function M(t){if(typeof t!="object"||t===null)return null;let e=t,n=e.experimentId,r=e.armId,o=e.allocationVersion;return typeof n!="string"||n===""||typeof r!="string"||r===""||typeof o!="string"?null:{experimentId:n,armId:r,allocationVersion:o}}function j(t,e){try{let n=t.querySelector(ze);if(!n)return[];let r=n.textContent;if(r==null||r.trim()==="")return[];let o=JSON.parse(r);if(typeof o!="object"||o===null)return e==null||e("capture",{reason:"assignments_malformed"}),[];let i=o;if(i.v!==1||!Array.isArray(i.experiments))return e==null||e("capture",{reason:"assignments_malformed"}),[];let s=[];for(let a of i.experiments){let c=M(a);c&&!s.some(u=>u.experimentId===c.experimentId)&&s.push(c)}return s}catch(n){return e==null||e("capture",{reason:"assignments_parse_failed",error:String(n)}),[]}}function te(t,e){if(!e||t.experiments!==void 0)return t;let n=e.getActiveAssignments();return n.length===0?t:{...t,experiments:n}}function re(t){let e=[],n=new Set,r,o=i=>{e=(i!=null?i:j(t.document,t.debug)).map(s=>({...s}));for(let s of e)n.has(s.experimentId)||(n.add(s.experimentId),r==null||r({eventName:Ge,url:t.window.location.href,experiments:[{experimentId:s.experimentId,armId:s.armId,allocationVersion:s.allocationVersion}]}))};return{getActiveAssignments:()=>e.map(i=>({...i})),start(i){r=i,o()},clear(){e=[]},rearm(i){n.clear(),o(i)}}}var I="data-aci-event",qe="data-aci-on",oe="data-aci-prop-",$e="data-aci-refresh",ie="custom.";function W(t){if(typeof t!="string")return;let e=t.trim().toLowerCase();if(!(e===""||/[\s/]/.test(e)))return e.startsWith(ie)?e:ie+e}function Ue(t){let e;for(let n of Array.from(t.attributes)){if(!n.name.startsWith(oe))continue;let r=n.name.slice(oe.length);r!==""&&((e!=null?e:e={})[r]=n.value)}return e}function se(t,e,n){let r=W(t.getAttribute(I));if(r===void 0){n==null||n("capture",{reason:"malformed_declaration",attr:I});return}return{eventName:r,url:e,properties:Ue(t)}}function ae(t){var e,n;return(n=(e=t.getAttribute(qe))==null?void 0:e.trim().toLowerCase())!=null?n:"click"}function ce(t,e,n){var d;let{document:r,debug:o}=e,i=(d=r.defaultView)==null?void 0:d.location;r.addEventListener("click",p=>{var f,m;try{let v=p.target;if((f=v==null?void 0:v.closest)==null?void 0:f.call(v,`[${$e}]`)){n==null||n();return}let l=(m=v==null?void 0:v.closest)==null?void 0:m.call(v,`[${I}]`);if(!l||ae(l)!=="click")return;let g=se(l,i==null?void 0:i.href,o);g&&t(g)}catch(v){o==null||o("capture",{reason:"click_handler_failed",error:String(v)})}});let s=e.intersectionObserver;if(typeof s!="function")return;let a=Array.from(r.querySelectorAll(`[${I}]`)).filter(p=>ae(p)==="visible");if(a.length===0)return;let c=new WeakSet,u=new s((p,f)=>{try{for(let m of p){if(!m.isIntersecting||m.intersectionRatio<.5||c.has(m.target))continue;c.add(m.target),f.unobserve(m.target);let v=se(m.target,i==null?void 0:i.href,o);v&&t(v)}}catch(m){o==null||o("capture",{reason:"visible_handler_failed",error:String(m)})}},{threshold:.5});for(let p of a)u.observe(p)}function G(t){return typeof t=="object"&&t!==null&&!Array.isArray(t)}function ue(t,e,n){let r=(...s)=>{try{let[a,...c]=s;if(a==="navigation-complete"){let u=c[0];if(!G(u)||typeof u.url!="string"||!/^https?:\/\//.test(u.url)||!Array.isArray(u.assignments)||u.assignments.length>16){n==null||n("capture",{reason:"malformed_navigation_completion"});return}let d=[];for(let p of u.assignments){let f=M(p);if(!f||d.some(m=>m.experimentId===f.experimentId)){n==null||n("capture",{reason:"malformed_navigation_completion"});return}d.push(f)}e.navigationComplete({url:u.url,assignments:d});return}if(a==="track"){let u=W(c[0]);if(u===void 0){n==null||n("capture",{reason:"malformed_track_name",name:c[0]});return}let d=c[1];if(d!==void 0&&!G(d)){n==null||n("capture",{reason:"malformed_track_properties",name:u});return}e.track(u,d);return}if(a==="consent"){let u=c[0];if(!G(u)){n==null||n("consent",{reason:"malformed_consent_envelope"});return}e.consent({p:u.p,src:u.src});return}n==null||n("capture",{reason:"unknown_command",command:a})}catch(a){n==null||n("capture",{reason:"command_failed",error:String(a)})}},o=t.aci,i=o==null?void 0:o.q;if(t.aci=r,i!=null)for(let s of Array.from(i)){let a=Array.from(s);r(...a)}}var le="0123456789ABCDEFGHJKMNPQRSTVWXYZ";function Xe(t){globalThis.crypto.getRandomValues(t)}function x(t,e){let n=t!=null?t:Date.now(),r="";for(let c=0;c<10;c++)r=le[n%32]+r,n=Math.floor(n/32);let o=new Uint8Array(10);(e!=null?e:Xe)(o);let i="",s=0,a=0;for(let c of o)for(s=s<<8|c,a+=8;a>=5;)i+=le[s>>>a-5&31],a-=5;return r+i}var Ke=60*1024,Ye=100,Je=5e3,de='{"envelopeId":"',fe='","events":[',pe="]}",Ze=26,O=de.length+Ze+fe.length+pe.length,Qe=new TextEncoder,en=t=>Qe.encode(t).length,D=class{constructor(e){E(this,"options");E(this,"maxBytes");E(this,"queue",[]);E(this,"queuedBytes",0);E(this,"flushing",!1);E(this,"intervalId");var n;this.options=e,this.maxBytes=(n=e.maxBatchBytes)!=null?n:Ke}get pending(){return this.queue.length}push(e){let n=JSON.stringify(e),r=en(n);this.queue.push({json:n,bytes:r,eventId:e.eventId}),this.queuedBytes+=r,!this.flushing&&this.envelopeBytes()>=this.maxBytes&&this.flush()}flush(){var e,n;if(!this.flushing){this.flushing=!0;try{(n=(e=this.options).onTick)==null||n.call(e),this.drain()}finally{this.flushing=!1}}}start(){var o,i,s,a;let e=(o=this.options.timers)!=null?o:{setInterval:(c,u)=>setInterval(c,u),clearInterval:c=>clearInterval(c)};this.intervalId=e.setInterval(()=>this.flush(),(i=this.options.flushIntervalMs)!=null?i:Je);let n=(s=this.options.documentTarget)!=null?s:typeof document!="undefined"?document:void 0;n==null||n.addEventListener("visibilitychange",()=>{n.visibilityState==="hidden"&&this.flush()});let r=(a=this.options.windowTarget)!=null?a:typeof window!="undefined"?window:void 0;r==null||r.addEventListener("pagehide",()=>this.flush()),this.flush()}stop(){if(this.intervalId!==void 0){let e=this.options.timers;e?e.clearInterval(this.intervalId):clearInterval(this.intervalId),this.intervalId=void 0}}envelopeBytes(){let e=this.queue.length>1?this.queue.length-1:0;return O+this.queuedBytes+e}drain(){var e,n,r;for(;this.queue.length>0;){let o=this.queue[0];if(O+o.bytes>this.maxBytes){this.queue.shift(),this.queuedBytes-=o.bytes,(n=(e=this.options).debug)==null||n.call(e,"flush",{reason:"oversize_event",eventId:o.eventId,bytes:o.bytes});continue}let i=[],s=O;for(;this.queue.length>0;){let c=this.queue[0];if(O+c.bytes>this.maxBytes||i.length>=Ye)break;let u=s+c.bytes+(i.length>0?1:0);if(u>this.maxBytes)break;i.push(c.json),s=u,this.queue.shift(),this.queuedBytes-=c.bytes}if(i.length===0)continue;let a=((r=this.options.createEnvelopeId)!=null?r:x)();this.options.send(de+a+fe+i.join(",")+pe)}}};var z=new TextEncoder,nn=2048,tn=32,rn=128,on=512,sn=2147483647,an=/^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2})(?:\.(\d{1,9}))?(Z|[+-]\d{2}:\d{2})$/,cn=/^[\t\n\v\f\r ]+|[\t\n\v\f\r ]+$/g,un=new Set(["true","all","granted","yes","personalization","personalized"]);function N(t,e=!0){let n=S(t);if(!n)return null;if(e)try{n=decodeURIComponent(n)}catch{}if(n=S(n),!n||z.encode(n).byteLength>nn)return null;if(un.has(n.toLowerCase()))return{version:0,purposes:["personalization"],grantedAt:""};try{let r=JSON.parse(n),o=typeof r.t=="string"?r.t:"";return!Number.isInteger(r.v)||r.v<0||r.v>sn||!Array.isArray(r.p)||r.p.length>tn||!r.p.every(i=>typeof i=="string")||r.p.some(i=>z.encode(i).byteLength>rn)||r.t!==void 0&&r.t!==null&&(typeof r.t!="string"||r.t===""||!ln(r.t))||r.src!==void 0&&r.src!==null&&typeof r.src!="string"||typeof r.src=="string"&&z.encode(r.src).byteLength>on?null:{version:r.v,purposes:r.p,grantedAt:o,source:typeof r.src=="string"?r.src:void 0}}catch{return null}}function q(t){return(t==null?void 0:t.purposes.some(e=>S(e).toLowerCase()==="personalization"))||!1}function S(t){return t.replace(cn,"")}function ln(t){let e=an.exec(t);if(!e||Number.isNaN(Date.parse(t)))return!1;let n=Number(e[1]),r=Number(e[2]),o=Number(e[3]),i=Number(e[4]),s=Number(e[5]),a=Number(e[6]);if(r<1||r>12||o<1||o>dn(n,r)||i>23||s>59||a>59)return!1;let c=e[8];return c==="Z"||Number(c.slice(1,3))<=23&&Number(c.slice(4,6))<=59}function dn(t,e){return e===2?t%4===0&&(t%100!==0||t%400===0)?29:28:[4,6,9,11].includes(e)?30:31}var U="personalization",$="aci_consent",X=1,fn=365*24*60*60;function pn(t,e){var r;let n=`${$}=${t.encoded}; Path=/; Max-Age=${fn}; SameSite=Lax`;((r=e.document.location)==null?void 0:r.protocol)==="https:"&&(n+="; Secure"),e.document.cookie=n}function P(t,e,n){var o,i;let r=mn(t,e,n.now);return r===null?!1:(q(r.snapshot)||(o=n.gapBuffer)==null||o.purge(),pn(r,n),(i=n.onDecision)==null||i.call(n),!0)}function T(t){for(let e of t.cookie.split(";")){let n=S(e);if(n.startsWith($+"="))return n.slice($.length+1)}}function me(t){return t===void 0||t===""?!1:q(N(t,!0))}function ge(t){var r;if(!Array.isArray(t))return;let e=[];for(let o of t){if(typeof o!="string")return;e.push(S(o))}let n=JSON.stringify({v:X,p:e});return(r=N(n,!1))==null?void 0:r.purposes}function ve(t){var o;if(t!==void 0&&typeof t!="string")return;let e=typeof t=="string"?S(t):"",r=JSON.stringify({v:X,p:[],src:e===""?"manual":e});return(o=N(r,!1))==null?void 0:o.source}function mn(t,e,n){try{let r=JSON.stringify({v:X,p:t,t:new Date((n!=null?n:Date.now)()).toISOString(),src:e}),o=N(r,!1);return o===null?null:{encoded:encodeURIComponent(r),snapshot:o}}catch{return null}}var gn="data-aci-consent-banner";var vn="data-aci-consent-banner-mount",he="aci:consent-banner-decided";function hn(t){return T(t)!==void 0}function be(t){return hn(t)?!1:t.querySelector(`[${vn}], [${gn}]`)!==null}var bn=new Set(["c15t","onetrust","cookiebot","trustarc","manual"]);function R(t,e){var o;let n=(o=t==null?void 0:t.getAttribute)==null?void 0:o.call(t,e);if(n==null)return;let r=n.trim();return r===""?void 0:r}function Ee(t){var r;let e=(r=R(t,"data-aci-consent"))==null?void 0:r.toLowerCase(),n=e!==void 0&&bn.has(e)?e:"manual";return{site:R(t,"data-aci-site"),preset:n,categories:R(t,"data-aci-consent-categories"),debug:R(t,"data-aci-debug")==="1"}}function ye(t,e,n){if(!t)return()=>{};let r=e!=null?e:(i=>console.debug("[aci]",i)),o=n!=null?n:Date.now;return(i,s)=>{try{r({t:o(),kind:i,detail:s})}catch{}}}var B=class{constructor(e){E(this,"events",[]);E(this,"now");E(this,"debug");var n;this.now=(n=e==null?void 0:e.now)!=null?n:Date.now,this.debug=e==null?void 0:e.debug}push(e){var n;if(this.expire(),this.events.length>=50){(n=this.debug)==null||n.call(this,"buffer",{reason:"consent_hold_cap_drop_newest",eventId:e.eventId});return}this.events.push({event:e,heldAt:this.now()})}size(){return this.events.length}release(){this.expire();let e=this.events.map(n=>n.event);return this.events=[],e}discard(e){var n;this.events.length>0&&((n=this.debug)==null||n.call(this,"buffer",{reason:`consent_hold_discarded_${e}`,count:this.events.length})),this.events=[]}expire(){var r;let e=this.now()-18e5,n=0;for(;n<this.events.length&&this.events[n].heldAt<e;)n+=1;n>0&&(this.events.splice(0,n),(r=this.debug)==null||r.call(this,"buffer",{reason:"consent_hold_expired",count:n}))}};function Ce(t,e){var r;if(t.size()===0||!e.decided())return;if(!e.grants()){t.discard("denied");return}let n=t.release();for(let o of n)e.batcher.push(o);(r=e.debug)==null||r.call(e,"buffer",{reason:"consent_hold_released",count:n.length})}var En="cmp-",yn=/^[a-z0-9._-]+$/,Cn={c15t:"measurement,marketing",onetrust:"c0002,c0004",cookiebot:"statistics,marketing",trustarc:"functional,advertising"};function K(t,e){let n=e!=null?e:Cn[t];if(n===void 0)return null;let r=n.split(",").map(o=>o.trim().toLowerCase()).filter(o=>o!=="");if(r.length===0)return null;for(let o of r)if(!yn.test(o))return null;return r}function kn(t,e){return e.filter(r=>r in t.categories).some(r=>t.categories[r]===!0)?[U]:[]}function _(t,e,n){var r,o,i,s;try{let a=K(e,n.categories);if(a===null){(r=n.debug)==null||r.call(n,"consent",{reason:"unparseable_mapping",preset:e});return}if(a.filter(d=>d in t.categories).length===0){(o=n.debug)==null||o.call(n,"consent",{reason:"unmappable_cmp_state",preset:e});return}let u=kn(t,a);P(u,En+e,{document:n.document,gapBuffer:n.gapBuffer,now:n.now,onDecision:n.onDecision}),(i=n.debug)==null||i.call(n,"consent",{reason:"written",preset:e,purposes:u})}catch(a){(s=n.debug)==null||s.call(n,"consent",{reason:"consent_bridge_failed",preset:e,error:String(a)})}}var ke=250,_n=1e4;function w(t,e,n){var s;let r=(s=n==null?void 0:n.scheduler)!=null?s:{setTimeout:(a,c)=>setTimeout(a,c),clearTimeout:a=>clearTimeout(a)},o=Math.floor(_n/ke),i=()=>{var c,u;let a;try{a=t()}catch(d){(c=n==null?void 0:n.debug)==null||c.call(n,"consent",{reason:"cmp_read_failed",error:String(d)});return}if(a!==void 0){e(a);return}if(o-=1,o<=0){(u=n==null?void 0:n.debug)==null||u.call(n,"consent",{reason:"cmp_not_found"});return}r.setTimeout(i,ke)};i()}function C(t,e){try{e()}catch(n){t==null||t("consent",{reason:"adapter_failed",error:String(n)})}}var wn="c15t";function Sn(t){if(typeof t!="object"||t===null)return;let e={};for(let[n,r]of Object.entries(t))typeof r=="boolean"&&(e[n.toLowerCase()]=r);return{categories:e}}function An(t,e){return typeof t=="object"&&t!==null?"consents"in t?t.consents:t:e.consents}function _e(t){w(()=>{let e=t.window.c15t;return typeof e=="object"&&e!==null?e:void 0},e=>{C(t.debug,()=>{var r;let n=o=>{let i=Sn(o);i&&_(i,wn,t)};n(e.consents),(r=e.onConsentChange)==null||r.call(e,o=>C(t.debug,()=>n(An(o,e))))})},t)}var xn="cookiebot",Nn=["CookiebotOnConsentReady","CookiebotOnAccept","CookiebotOnDecline"];function we(t){let e=t.Cookiebot;return typeof e=="object"&&e!==null?e:void 0}function Pn(t){if(typeof t!="object"||t===null)return;let e={};for(let[n,r]of Object.entries(t))typeof r=="boolean"&&(e[n.toLowerCase()]=r);return{categories:e}}function Se(t){let e=()=>{let r=we(t.window);if(!r||r.hasResponse!==!0)return;let o=Pn(r.consent);o&&_(o,xn,t)},n=t.window;if(typeof n.addEventListener=="function")for(let r of Nn)n.addEventListener(r,()=>C(t.debug,e));w(()=>we(t.window),()=>C(t.debug,e),t)}var Ae="onetrust",Tn=/^c\d{4}$/;function Ln(t,e){let n={};for(let r of t.split(",")){let o=r.trim().toLowerCase();o!==""&&(n[o]=!0)}for(let r of e!=null?e:[])!(r in n)&&Tn.test(r)&&(n[r]=!1);return{categories:n}}function xe(t){let e=t.window,n=()=>{let o=e.OnetrustActiveGroups;if(typeof o!="string")return;let i=K(Ae,t.categories);_(Ln(o,i),Ae,t)},r=e.OptanonWrapper;e.OptanonWrapper=()=>{if(typeof r=="function")try{r()}catch{}C(t.debug,n)},w(()=>{let o=e.OnetrustActiveGroups;return typeof o=="string"?o:void 0},()=>C(t.debug,n),t)}var In="trustarc",On={required:1,functional:2,advertising:3},Ne="PrivacyManagerAPI";function Pe(t){let e=t.truste,n=e==null?void 0:e.cma,r=n==null?void 0:n.callApi;return typeof r=="function"?r.bind(n):void 0}function Dn(t){if(typeof t!="object"||t===null)return;let e=t.consentDecision;if(typeof e!="number"||!Number.isFinite(e)||e<1)return;let n={};for(let[r,o]of Object.entries(On))n[r]=e>=o;return{categories:n}}function Rn(t){return typeof t=="string"?t.includes(Ne):typeof t=="object"&&t!==null&&Ne in t}function Bn(t){let e=t.location;return typeof(e==null?void 0:e.hostname)=="string"?e.hostname:void 0}function Te(t){let e=()=>{let r=Pe(t.window);if(!r)return;let o=Dn(r("getConsentDecision",Bn(t.window)));o&&_(o,In,t)},n=t.window;typeof n.addEventListener=="function"&&n.addEventListener("message",r=>C(t.debug,()=>{Rn(r==null?void 0:r.data)&&e()})),w(()=>Pe(t.window),()=>C(t.debug,e),t)}var F="aci:buf";var Le="aci_m",V=class{constructor(e){E(this,"events",[]);E(this,"storage");E(this,"debug");this.debug=e==null?void 0:e.debug,this.storage=e==null?void 0:e.storage}push(e){var n;this.events=this.restore(),this.events.push(e),this.events.length>50&&(this.events.splice(0,this.events.length-50),(n=this.debug)==null||n.call(this,"buffer",{reason:"cap_drop_oldest"})),this.persist()}drain(){let e=this.restore();return this.events=[],this.persist(),e}purge(){var e;this.events=this.restore(),this.events.length>0&&((e=this.debug)==null||e.call(this,"buffer",{reason:"purged",count:this.events.length})),this.events=[],this.persist()}size(){return this.restore().length}restore(){var r;let e=this.storage;if(!e)return this.events;let n;try{n=e.getItem(F)}catch(o){return this.storage=void 0,(r=this.debug)==null||r.call(this,"buffer",{reason:"storage_unavailable",error:String(o)}),[]}if(n==null)return[];try{let o=JSON.parse(n);if(Array.isArray(o))return o}catch{}try{e.removeItem(F)}catch{this.storage=void 0}return[]}persist(){var n;let e=this.storage;if(e)try{this.events.length===0?e.removeItem(F):e.setItem(F,JSON.stringify(this.events)),this.events=[]}catch(r){this.storage=void 0,(n=this.debug)==null||n.call(this,"buffer",{reason:"storage_unavailable",error:String(r)})}}};function Ie(t){for(let e of t.cookie.split(";")){let n=e.trim();if(n.startsWith(Le+"="))return n.slice(Le.length+1)!==""}return!1}function Oe(t,e){var n;if(e.consentHold&&((n=e.consentPending)!=null&&n.call(e))){e.consentHold.push(t);return}if(e.grants()&&!e.marker()){e.gapBuffer.push(t);return}e.batcher.push(t)}function Y(t,e,n,r){if(t.size()===0||!n())return;let o=t.drain();for(let i of o)e.push(i);r==null||r("buffer",{reason:"flushed",count:o.length})}var Fn="rum.link_click";function De(t,e){var i;let{document:n,debug:r}=e,o=(i=n.defaultView)==null?void 0:i.location;n.addEventListener("click",s=>{var a,c,u;try{let d=s.target,p=(a=d==null?void 0:d.closest)==null?void 0:a.call(d,"a[href]");if(!p)return;let f=p;t({eventName:Fn,url:o==null?void 0:o.href,properties:{linkText:(u=(c=f.textContent)==null?void 0:c.trim())!=null?u:"",linkHref:f.href}})}catch(d){r==null||r("capture",{reason:"link_click_failed",error:String(d)})}},{capture:!0})}var Vn="rum.page_view";function H(t){let e=t.indexOf("#");return e===-1?t:t.slice(0,e)}function Re(t,e,n=()=>{}){let r=H(t.location.href),o=r,i=!1,s=()=>{let c=H(t.location.href);c!==r&&(r=c,i=!0,n())},a=t.history;for(let c of["pushState","replaceState"]){let u=a[c].bind(a);a[c]=(...d)=>{let p=u(...d);return s(),p}}return t.addEventListener("popstate",s),{complete({url:c,assignments:u}){let d=H(t.location.href);if(H(c)===d&&(s(),!(!i&&o===d))){i=!1,o=d;for(let p of e)p(u)}}}}function Be(t,e){let n=()=>t({eventName:Vn,url:e.location.href,referrer:e.document.referrer});return n(),n}var J="/collect";function Fe(t,e){let n=e==null?void 0:e.debug,r=e!==void 0&&"navigator"in e?e.navigator:globalThis.navigator,o=e!==void 0&&"fetch"in e?e.fetch:globalThis.fetch,i;try{i=new Blob([t],{type:"text/plain"})}catch{n==null||n("flush",{reason:"no_blob"});return}if(r!=null&&r.sendBeacon)try{if(r.sendBeacon(J,i))return}catch{}if(!o){n==null||n("flush",{reason:"no_transport"});return}try{o(J,{method:"POST",body:i,keepalive:!0}).catch(s=>{n==null||n("flush",{reason:"fetch_failed",error:String(s)})})}catch(s){n==null||n("flush",{reason:"fetch_failed",error:String(s)})}}function Ve(t){var n,r;let e=t!==void 0&&"fetch"in t?t.fetch:globalThis.fetch;if(!e)return Promise.resolve(!1);try{let o=((n=t==null?void 0:t.createEnvelopeId)!=null?n:x)(),i=new Blob([`{"envelopeId":"${o}","events":[]}`],{type:"text/plain"});return e(J,{method:"POST",body:i,keepalive:!0,credentials:"same-origin"}).then(s=>{var a;return typeof s=="object"&&s!==null&&"ok"in s&&s.ok===!1?((a=t==null?void 0:t.debug)==null||a.call(t,"flush",{reason:"refresh_failed",status:"not_ok"}),!1):!0}).catch(s=>{var a;return(a=t==null?void 0:t.debug)==null||a.call(t,"flush",{reason:"refresh_failed",error:String(s)}),!1})}catch(o){return(r=t==null?void 0:t.debug)==null||r.call(t,"flush",{reason:"refresh_failed",error:String(o)}),Promise.resolve(!1)}}var Hn="rum.variation_view",Z="data-aci-variation";function He(t,e){var u,d,p;let{document:n,debug:r}=e,o=e.MutationObserver;if(typeof o!="function")return;let i=(u=n.defaultView)==null?void 0:u.location,s=new WeakSet,a=f=>{let m=f.getAttribute(Z);m!=null&&(s.has(f)||(s.add(f),t({eventName:Hn,url:i==null?void 0:i.href,properties:{variantKey:m}})))},c=f=>{try{f.hasAttribute(Z)&&a(f);for(let m of Array.from(f.querySelectorAll(`[${Z}]`)))a(m)}catch(m){r==null||r("capture",{reason:"variation_scan_failed",error:String(m)})}};try{let f=(d=n.body)!=null?d:n.documentElement;f&&c(f)}catch(f){r==null||r("capture",{reason:"variation_initial_scan_failed",error:String(f)})}try{let f=(p=n.body)!=null?p:n.documentElement;if(!f)return;new o(v=>{try{for(let b of v)for(let l=0;l<b.addedNodes.length;l++){let g=b.addedNodes[l];if((g==null?void 0:g.nodeType)!==1)continue;let y=g.element;y&&c(y)}}catch(b){r==null||r("capture",{reason:"variation_observer_failed",error:String(b)})}}).observe(f,{subtree:!0,childList:!0})}catch(f){r==null||r("capture",{reason:"variation_observe_failed",error:String(f)})}}var Mn="rum.web_vital";function Me(t,e){var m,v,b;let n=e.PerformanceObserver;if(typeof n!="function")return;let{performance:r,debug:o}=e,i=(v=(m=e.window)==null?void 0:m.location)==null?void 0:v.href,s=e.document,a=new Set,c=(l,g)=>{a.has(l)||(a.add(l),t({eventName:Mn,url:i,properties:{vitalName:l,vitalValue:g}}))};try{let l=r==null?void 0:r.getEntriesByType("navigation")[0];l&&typeof l.responseStart=="number"&&typeof l.requestStart=="number"&&c("TTFB",l.responseStart-l.requestStart)}catch(l){o==null||o("capture",{reason:"ttfb_failed",error:String(l)})}let u=0,d=0,p=(l,g)=>{try{new n(L=>{try{for(let A of L.getEntries())g(A)}catch(A){o==null||o("capture",{reason:`${l}_handler_failed`,error:String(A)})}}).observe({type:l,buffered:!0})}catch(y){o==null||o("capture",{reason:`${l}_observe_failed`,error:String(y)})}};p("largest-contentful-paint",l=>{c("LCP",l.startTime)}),p("first-input",l=>{c("FID",l.startTime)}),p("layout-shift",l=>{var g;l.hadRecentInput||(u+=(g=l.value)!=null?g:0)}),p("event",l=>{var y;let g=(y=l.value)!=null?y:0;g>d&&(d=g)}),p("paint",l=>{l.name==="first-contentful-paint"&&c("FCP",l.startTime)});let f=()=>{try{a.has("CLS")||c("CLS",u),a.has("INP")||c("INP",d)}catch(l){o==null||o("capture",{reason:"visibility_handler_failed",error:String(l)})}};s&&s.addEventListener("visibilitychange",()=>{s.visibilityState==="hidden"&&f()}),(b=e.window)==null||b.addEventListener("pagehide",f)}var Q={c15t:_e,onetrust:xe,cookiebot:Se,trustarc:Te};function jn(t){var e,n;return typeof((e=t.navigator)==null?void 0:e.sendBeacon)=="function"&&typeof t.IntersectionObserver=="function"&&typeof((n=t.crypto)==null?void 0:n.getRandomValues)=="function"}function Wn(t){try{return t.sessionStorage}catch{return}}function Gn(t){var i;let e=t!=null?t:typeof window!="undefined"?window:void 0;if(!e||e.__aci_loaded||(e.__aci_loaded=!0,!jn(e)))return;let n=e.__aci_measurement_disabled===!0,r=Ee(e.document.currentScript),o=ye(r.debug);try{let s=e.document,a=new V({storage:Wn(e),debug:o}),c=()=>{try{return me(T(s))}catch{return!1}},u=()=>{try{return Ie(s)}catch{return!1}},d=()=>{try{return T(s)!==void 0}catch{return!1}},p=r.preset==="manual"?()=>{try{return be(s)}catch{return!1}}:()=>!0,f=new B({debug:o}),m=()=>!d()&&!u()&&p(),v={navigator:e.navigator,fetch:typeof e.fetch=="function"?e.fetch.bind(e):void 0,debug:o},b;b=new D({send:h=>Fe(h,v),debug:o,onTick:()=>{n||(Ce(f,{decided:d,grants:c,batcher:b,debug:o}),Y(a,b,u,o))},timers:{setInterval:(h,k)=>e.setInterval(h,k),clearInterval:h=>e.clearInterval(h)},documentTarget:s,windowTarget:e});let l=re({document:s,window:e,debug:o}),g=h=>{n||Oe({...te(h,l),eventId:x(void 0,k=>e.crypto.getRandomValues(k))},{grants:c,marker:u,gapBuffer:a,batcher:b,consentHold:f,consentPending:m})},y=()=>b.flush();s.addEventListener(he,y),l.start(g);let L=()=>{},A=Re(e,[h=>l.rearm(h),()=>L()],()=>l.clear());s.addEventListener("astro:page-load",()=>A.complete({url:e.location.href,assignments:j(s,o)})),ue(e,{navigationComplete:h=>A.complete(h),track:(h,k)=>g({eventName:h,properties:k,url:e.location.href}),consent:({p:h,src:k})=>{let ee=ge(h);if(ee===void 0){o("consent",{reason:"malformed_purposes"});return}let ne=ve(k);if(ne===void 0){o("consent",{reason:"malformed_source"});return}P(ee,ne,{document:s,gapBuffer:a,onDecision:y})||o("consent",{reason:"oversized_envelope"})}},o),L=Be(g,e),ce(g,{document:s,intersectionObserver:e.IntersectionObserver,debug:o},()=>{n||Ve(v).then(h=>{h&&e.location.reload()})}),De(g,{document:s,debug:o}),Me(g,{performance:e.performance,PerformanceObserver:e.PerformanceObserver,window:e,document:s,debug:o}),He(g,{document:s,MutationObserver:e.MutationObserver,debug:o}),r.preset!=="manual"&&((i=Q[r.preset])==null||i.call(Q,{window:e,document:s,categories:r.categories,gapBuffer:a,onDecision:y,debug:o,scheduler:{setTimeout:(h,k)=>e.setTimeout(h,k),clearTimeout:h=>e.clearTimeout(h)}})),b.start(),e.addEventListener("pagehide",()=>f.discard("unload")),n||Y(a,b,u,o),o("init",{config:r})}catch(s){o("init",{error:String(s)})}}Gn();})();
@@ -1 +1 @@
1
- f9c19e96f671a17edb4cd0a767da4233a1b7d859947092eaf84f4c3386773fd0
1
+ c330eb304d6a7aa01375646c97c67be0f68ce9137974da03aa339696e0f01de6
@@ -55,6 +55,9 @@ export function init(win) {
55
55
  w.__aci_loaded = true;
56
56
  if (!hasBaseline(w))
57
57
  return;
58
+ // Snapshot before exposures or queued commands. Cookie expiry, hydration
59
+ // and BFCache never turn this synthetic document into a genuine visit.
60
+ const measurementDisabled = w.__aci_measurement_disabled === true;
58
61
  const cfg = readTagConfig(w.document.currentScript);
59
62
  const debug = createDebug(cfg.debug);
60
63
  try {
@@ -119,6 +122,8 @@ export function init(win) {
119
122
  // and then the marker (so a mint by another tab or navigation is
120
123
  // noticed). Held events are older than gap-buffered ones: release first.
121
124
  onTick: () => {
125
+ if (measurementDisabled)
126
+ return;
122
127
  resolveConsentHold(consentHold, { decided, grants, batcher, debug });
123
128
  maybeFlushGapBuffer(gapBuffer, batcher, marker, debug);
124
129
  },
@@ -135,6 +140,8 @@ export function init(win) {
135
140
  // exists before emit can run; exposures flow at start() below.
136
141
  const assignments = createAssignments({ document: doc, window: w, debug });
137
142
  const emit = (event) => {
143
+ if (measurementDisabled)
144
+ return;
138
145
  routeEvent({
139
146
  ...withAssignmentContext(event, assignments),
140
147
  eventId: ulid(undefined, (bytes) => w.crypto.getRandomValues(bytes)),
@@ -187,6 +194,8 @@ export function init(win) {
187
194
  intersectionObserver: w.IntersectionObserver,
188
195
  debug,
189
196
  }, () => {
197
+ if (measurementDisabled)
198
+ return;
190
199
  void refreshClaims(transportEnv).then((refreshed) => {
191
200
  if (refreshed)
192
201
  w.location.reload();
@@ -224,7 +233,8 @@ export function init(win) {
224
233
  // has already released a hold whose decision exists; whatever remains is
225
234
  // undecided and never leaves the page.
226
235
  w.addEventListener("pagehide", () => consentHold.discard("unload"));
227
- maybeFlushGapBuffer(gapBuffer, batcher, marker, debug);
236
+ if (!measurementDisabled)
237
+ maybeFlushGapBuffer(gapBuffer, batcher, marker, debug);
228
238
  debug("init", { config: cfg });
229
239
  }
230
240
  catch (err) {
@@ -1,4 +1,4 @@
1
- import type { IdentitySigningKeys, SigningKey, CookieMutation, ConsentSnapshot, ConsentSignals } from './types.js';
1
+ import type { IdentitySigningKeys, SigningKey, ConsentSnapshot, ConsentSignals } from './types.js';
2
2
  export declare const DEFAULT_IDENTITY_TTL_SECONDS: number;
3
3
  export declare function parseCookieHeader(value: string | null): Map<string, string>;
4
4
  export declare function consentSignals(request: Request, cookies: Map<string, string>): ConsentSignals;
@@ -9,10 +9,7 @@ export declare function verifyIdentityCookie(value: string, keys: IdentitySignin
9
9
  export declare function signIdentity(rawID: string, key: SigningKey): Promise<string>;
10
10
  export declare function randomIdentity(random?: (length: number) => Uint8Array): string;
11
11
  export declare function identityHashHex(namespace: string, value: string): Promise<string>;
12
- export declare function sha256Hex(value: string): Promise<string>;
13
12
  export declare function verifyClaims(value: string, keys: Record<string, string>, now: Date): Promise<Record<string, unknown> | null>;
14
- export declare function cookieMutation(name: string, value: string, maxAge: number, now: Date, secure: boolean, httpOnly: boolean): CookieMutation;
15
- export declare function expireCookie(name: string, secure: boolean): CookieMutation;
16
13
  export declare function verifyHMAC(value: string, signature: string, secret: string): Promise<boolean>;
17
14
  export declare function hmacSHA256(value: string, secret: string): Promise<string>;
18
15
  export declare function base64URLEncode(bytes: Uint8Array): string;
@@ -5,7 +5,6 @@
5
5
  // - Consent signal extraction (GPC/DNT + portable consent envelope)
6
6
  // - Identity cookie verification and minting (aci_id)
7
7
  // - Claims cookie verification (aci_claims)
8
- // - Cookie mutation helpers
9
8
  //
10
9
  // Shared by every static edge adapter; this logic has no SDK host wrapper.
11
10
  // internal/domain/audience/runtime/collect.ts imports from here as well.
@@ -143,10 +142,6 @@ export async function identityHashHex(namespace, value) {
143
142
  const digest = await crypto.subtle.digest('SHA-256', encoder.encode(`${namespace.trim().toLowerCase()}\x00${value.trim()}`));
144
143
  return bytesToHex(new Uint8Array(digest));
145
144
  }
146
- export async function sha256Hex(value) {
147
- const digest = await crypto.subtle.digest('SHA-256', encoder.encode(value));
148
- return bytesToHex(new Uint8Array(digest));
149
- }
150
145
  function bytesToHex(bytes) {
151
146
  let value = '';
152
147
  for (const byte of bytes)
@@ -174,17 +169,6 @@ export async function verifyClaims(value, keys, now) {
174
169
  return null;
175
170
  }
176
171
  }
177
- // ── Cookie mutation helpers ──
178
- export function cookieMutation(name, value, maxAge, now, secure, httpOnly) {
179
- return {
180
- name,
181
- value,
182
- options: { path: '/', httpOnly, secure, sameSite: 'lax', maxAge, expires: new Date(now.getTime() + maxAge * 1000) },
183
- };
184
- }
185
- export function expireCookie(name, secure) {
186
- return { name, value: '', options: { path: '/', httpOnly: true, secure, sameSite: 'lax', maxAge: 0, expires: new Date(0) } };
187
- }
188
172
  // ── Internal crypto helpers ──
189
173
  // verifyHMAC returns a verdict about the signature, which is the only visitor
190
174
  // input here: a malformed or wrong signature is `false`. A fault importing the
@@ -2,5 +2,4 @@ export * from './types.js';
2
2
  export * from './personalization-core.js';
3
3
  export * from './identity.js';
4
4
  export * from './mimic_token.js';
5
- export * from './compose.js';
6
5
  export { resolvePersonalizationRequest, } from './resolve.js';
@@ -4,5 +4,4 @@ export * from './types.js';
4
4
  export * from './personalization-core.js';
5
5
  export * from './identity.js';
6
6
  export * from './mimic_token.js';
7
- export * from './compose.js';
8
7
  export { resolvePersonalizationRequest, } from './resolve.js';
@@ -4,8 +4,7 @@
4
4
  //
5
5
  // This is the pure decision algorithm — no I/O, no cookies, no identity
6
6
  // minting. The caller resolves identity and claims, then calls this.
7
- import { variantValuesFromKey } from './record/codec.js';
8
- import { canonicalVariantKey } from './variant_key.js';
7
+ import { canonicalVariantKey, variantValuesFromKey } from './variant_key.js';
9
8
  import { personalizationSelection } from './personalization_index.js';
10
9
  export { preparePersonalizationRoute } from './personalization_index.js';
11
10
  export class PersonalizationResolutionError extends Error {
@@ -171,8 +170,8 @@ function experimentEligible(item, claims, selectedLegAudienceId) {
171
170
  // contentbuild planner's leg factors, candidates, and Lookup. A
172
171
  // selected-mode experiment with an unresolved eligibility audience is a
173
172
  // factor of no leg, base included. Both inputs are canonical by variant-key
174
- // construction (the record codec and the strict document decode pre-trim
175
- // them), so this comparison needs no normalization — the Go twin's TrimSpace
173
+ // construction (the variant-key reader and the strict document decode
174
+ // pre-trim them), so this comparison needs no normalization — the Go twin's TrimSpace
176
175
  // is defensive only and never changes the outcome.
177
176
  function selectedModeFactorOf(item, legAudienceId) {
178
177
  const audienceID = String(item.eligibilityAudienceId || '').trim();
@@ -385,7 +384,7 @@ function validateExpressionMembers(path, group, members, depth, diagnostics) {
385
384
  // topLevel carries nesting's one asymmetry: the top envelope always records
386
385
  // both group rows because they express the scope's empty-envelope policy,
387
386
  // while a nested node records only for a list it carries members in — which
388
- // also keeps the trace stable across a codec that drops empty lists.
387
+ // also keeps the trace stable across a serialization that drops empty lists.
389
388
  function qualifyRequestExpression(all, any, resolve, trace, path = 'expression', topLevel = true) {
390
389
  let qualified = true;
391
390
  for (const [group, members] of [['all', all], ['any', any]]) {
@@ -1,5 +1,4 @@
1
- import { variantValuesFromKey } from './record/codec.js';
2
- import { compareUTF8 } from './variant_key.js';
1
+ import { compareUTF8, variantValuesFromKey } from './variant_key.js';
3
2
  const preparedSelection = Symbol('prepared personalization selection');
4
3
  // Call once when an immutable partition is decoded. The derived index is a
5
4
  // non-enumerable symbol property, so it cannot alter any serialized record.
@@ -33,20 +33,6 @@ export interface EdgeRouteEntry {
33
33
  dispatch: EdgeDispatch;
34
34
  variants?: Record<string, EdgeRouteVariant>;
35
35
  }
36
- export interface CollectSDKEntry {
37
- hash: string;
38
- path: string;
39
- }
40
- export interface EdgeRouteManifest {
41
- manifestVersion: string;
42
- releaseId: string;
43
- siteId: string;
44
- routes: Record<string, EdgeRouteEntry>;
45
- experiments?: Record<string, PersonalizationExperiment>;
46
- collectSdk?: CollectSDKEntry;
47
- notFoundPage?: string;
48
- errorPage?: string;
49
- }
50
36
  export interface PersonalizationRequestContext {
51
37
  path: string;
52
38
  query: Record<string, string[]>;
@@ -141,18 +127,6 @@ export interface IdentitySigningKeys {
141
127
  current: SigningKey;
142
128
  previous?: SigningKey;
143
129
  }
144
- export interface CookieMutation {
145
- name: string;
146
- value: string;
147
- options: {
148
- path: '/';
149
- httpOnly?: boolean;
150
- secure?: boolean;
151
- sameSite: 'lax';
152
- maxAge: number;
153
- expires?: Date;
154
- };
155
- }
156
130
  export interface ConsentSnapshot {
157
131
  version: number;
158
132
  purposes: string[];
@@ -219,15 +193,4 @@ export interface PersonalizationResolution {
219
193
  forced?: boolean;
220
194
  }>;
221
195
  }
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;
232
- }
233
196
  export { IdentityErasureFencedError } from '../../platform/collectionstore/errors.js';
@@ -1,3 +1,4 @@
1
1
  export declare function compareUTF8(left: string, right: string): number;
2
2
  export declare function canonicalVariantKey(values: Record<string, string>): string;
3
3
  export declare function parseVariantKey(key: string): Record<string, string>;
4
+ export declare function variantValuesFromKey(key: string): Record<string, string>;
@@ -39,6 +39,18 @@ export function parseVariantKey(key) {
39
39
  throw new Error('variant key is not canonically ordered');
40
40
  return values;
41
41
  }
42
+ // Lenient reader for a route record's variant key when the record carries no
43
+ // variantValues. Unlike parseVariantKey it never throws and also accepts `&`
44
+ // as a separator, so it is not interchangeable with the canonical parser.
45
+ export function variantValuesFromKey(key) {
46
+ const values = {};
47
+ for (const part of String(key || '').replace(/&/g, ';').split(';')) {
48
+ const sep = part.indexOf('=');
49
+ if (sep > 0)
50
+ values[part.slice(0, sep).trim()] = part.slice(sep + 1).trim();
51
+ }
52
+ return values;
53
+ }
42
54
  function validToken(value) {
43
55
  return value !== '' && value !== '.' && value !== '..' && !/[\p{White_Space};=/]/u.test(value);
44
56
  }
@@ -34,7 +34,8 @@ export interface CreatePageOptions {
34
34
  * to `/__r/{releaseId}/{targetRef}`. The target ref selects the exact immutable
35
35
  * base or variant page while its prefix identifies the logical public route.
36
36
  * createPage passes both identities to the content loader, giving each target
37
- * a distinct CDN cache key without sending selection headers into Next.
37
+ * a distinct CDN cache key without sending selection headers into Next. The
38
+ * mimic response is decorated by the hosting platform after rendering.
38
39
  *
39
40
  * Dev pass-through (ACI_CONTENT_PROVIDER=file): the middleware passes
40
41
  * requests through unrewritten, so `/` arrives with an empty/undefined
package/dist/next/page.js CHANGED
@@ -1,9 +1,7 @@
1
1
  import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { notFound } from 'next/navigation.js';
3
- import { headers } from 'next/headers.js';
4
3
  import { Fragment } from 'react';
5
4
  import { PageNotFoundError, getRenderInput, getLayout, getFragment, } from './server.js';
6
- import { MIMIC_BANNER_HEADER, decodeMimicBanner } from '../mimic/core.js';
7
5
  import { routeMetadataForContent, resolveSlots, } from '../content/index.js';
8
6
  import { renderBlocks } from './render.js';
9
7
  import { AssignmentsScript } from './assignments-script.js';
@@ -34,7 +32,8 @@ import { parseHostedPageTarget } from './hosted-page-target.js';
34
32
  * to `/__r/{releaseId}/{targetRef}`. The target ref selects the exact immutable
35
33
  * base or variant page while its prefix identifies the logical public route.
36
34
  * createPage passes both identities to the content loader, giving each target
37
- * a distinct CDN cache key without sending selection headers into Next.
35
+ * a distinct CDN cache key without sending selection headers into Next. The
36
+ * mimic response is decorated by the hosting platform after rendering.
38
37
  *
39
38
  * Dev pass-through (ACI_CONTENT_PROVIDER=file): the middleware passes
40
39
  * requests through unrewritten, so `/` arrives with an empty/undefined
@@ -86,7 +85,7 @@ export function createPage(registry, options) {
86
85
  }
87
86
  return (_jsx(Fragment, { children: renderedBlocks }, slot.name));
88
87
  }));
89
- return (_jsxs(_Fragment, { children: [renderedSlots.filter(Boolean), _jsx(AssignmentsScript, { page: input.page }), _jsx(CollectScript, { siteConfig: input.siteConfig }), _jsx(MimicBanner, { html: await readMimicBannerHTML() }), renderProofContent
88
+ return (_jsxs(_Fragment, { children: [renderedSlots.filter(Boolean), _jsx(AssignmentsScript, { page: input.page }), _jsx(CollectScript, { siteConfig: input.siteConfig }), renderProofContent
90
89
  ? _jsx("meta", { name: "aci-render-proof", content: renderProofContent })
91
90
  : null] }));
92
91
  }
@@ -102,6 +101,8 @@ export function createPage(registry, options) {
102
101
  default: CatchAllPage,
103
102
  };
104
103
  }
104
+ // Immutable render identity, shared by ordinary and platform-fetched HTML.
105
+ const SHARED_RENDER_SEGMENT = '__r';
105
106
  /**
106
107
  * Resolves the logical route and releaseId from Next.js catch-all params.
107
108
  *
@@ -121,7 +122,7 @@ export function createPage(registry, options) {
121
122
  export async function resolveRouteAndRelease(params) {
122
123
  const resolved = await params;
123
124
  const slug = resolved.slug ?? [];
124
- if (slug.length >= 2 && slug[0] === '__r') {
125
+ if (slug.length >= 2 && slug[0] === SHARED_RENDER_SEGMENT) {
125
126
  const releaseId = decodeURIComponent(slug[1]);
126
127
  if (!releaseId || releaseId.includes('/') || releaseId === '.' || releaseId === '..') {
127
128
  return { route: '/' };
@@ -135,6 +136,14 @@ export async function resolveRouteAndRelease(params) {
135
136
  pageRef: target.pageRef,
136
137
  };
137
138
  }
139
+ // These namespaces belong to native Next/static/API handlers. A missing
140
+ // file can fall through to this catch-all without the hosting router's
141
+ // release selection; it is a native 404, not a CMS content request. The
142
+ // shared resolver guards both page rendering and generateMetadata.
143
+ if (slug[0] === 'favicon.ico'
144
+ || ['assets', '_astro', '_next', 'api'].includes(slug[0])) {
145
+ notFound();
146
+ }
138
147
  const route = routeFromSlug(slug);
139
148
  return { route };
140
149
  }
@@ -143,23 +152,3 @@ function routeFromSlug(slug) {
143
152
  return '/';
144
153
  return `/${slug.join('/')}/`;
145
154
  }
146
- // The middleware emits the banner HTML in a reserved request header only
147
- // after validating a signed mimic token; viewer-forged copies are stripped at
148
- // the dispatch gate (specs/features/audience-mimic REQ-4). The header read
149
- // happens here (the page already awaits request-scoped data) so the banner
150
- // itself stays a synchronous leaf.
151
- async function readMimicBannerHTML() {
152
- try {
153
- const current = await headers();
154
- return decodeMimicBanner(current.get(MIMIC_BANNER_HEADER) || '');
155
- }
156
- catch {
157
- // headers() has no request context in static generation and unit tests.
158
- return '';
159
- }
160
- }
161
- function MimicBanner({ html }) {
162
- if (!html)
163
- return null;
164
- return _jsx("div", { dangerouslySetInnerHTML: { __html: html } });
165
- }
@@ -1,6 +1,7 @@
1
1
  /**
2
2
  * The release identity of a request the hosting router rewrote to
3
- * `/__r/{releaseId}/…`. The catch-all reads the release from that path; Next
3
+ * `/__r/{releaseId}/…`. The
4
+ * catch-all reads the release from that path; Next
4
5
  * code that receives no route params (the not-found seat, release-less SDK
5
6
  * reads such as a secondary `getPage('/blog')`) reads it from this header.
6
7
  *
@@ -2,7 +2,8 @@ import { createHmac, timingSafeEqual } from 'node:crypto';
2
2
  import { headers } from 'next/headers.js';
3
3
  /**
4
4
  * The release identity of a request the hosting router rewrote to
5
- * `/__r/{releaseId}/…`. The catch-all reads the release from that path; Next
5
+ * `/__r/{releaseId}/…`. The
6
+ * catch-all reads the release from that path; Next
6
7
  * code that receives no route params (the not-found seat, release-less SDK
7
8
  * reads such as a secondary `getPage('/blog')`) reads it from this header.
8
9
  *
@@ -0,0 +1,43 @@
1
+ # Secondary experiment goals use goalIds
2
+
3
+ `goalIds` is the supported field for secondary experiment goal links. The
4
+ API rejects an experiment document containing `goalRefs`, including an empty
5
+ array or `null`, rather than silently discarding its saved goal links. The
6
+ SDK authoring model no longer declares `goalRefs`.
7
+
8
+ ## Convert the field
9
+
10
+ In experiment `props`, replace reference strings with the corresponding bare
11
+ goal IDs and remove `goalRefs` entirely.
12
+
13
+ Before:
14
+
15
+ ```json
16
+ {
17
+ "goalRefs": ["$ref:data/conversion-goals/request-demo"]
18
+ }
19
+ ```
20
+
21
+ After:
22
+
23
+ ```json
24
+ {
25
+ "goalIds": ["request-demo"]
26
+ }
27
+ ```
28
+
29
+ Preserve the linked goal set and the primary goal. `primaryGoalId` and
30
+ `primaryGoalRef` remain supported; this migration concerns only the secondary
31
+ list.
32
+
33
+ ## Upgrade existing content
34
+
35
+ Check experiment documents, seeds, templates, and authoring integrations for
36
+ `goalRefs`. If any existing content uses it, submit the spelling change
37
+ through the normal content API while the preceding API version still accepts
38
+ both spellings, and validate the resulting experiment before upgrading the
39
+ API image that retires `goalRefs`. Existing horizon and settled-goal rules
40
+ continue to apply.
41
+
42
+ If no document or authoring integration uses `goalRefs`, no content migration
43
+ is needed. Use `goalIds` for new secondary-goal links.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gradial/aci",
3
- "version": "0.1.52-rc.34",
3
+ "version": "0.1.52-rc.40",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "https://github.com/gradial/bare-metal-go.git",
@@ -160,11 +160,11 @@
160
160
  "zod": "^4.0.0"
161
161
  },
162
162
  "optionalDependencies": {
163
- "@gradial/aci-cli-darwin-arm64": "0.1.52-rc.34",
164
- "@gradial/aci-cli-darwin-x64": "0.1.52-rc.34",
165
- "@gradial/aci-cli-linux-arm64": "0.1.52-rc.34",
166
- "@gradial/aci-cli-linux-x64": "0.1.52-rc.34",
167
- "@gradial/aci-cli-win32-arm64": "0.1.52-rc.34",
168
- "@gradial/aci-cli-win32-x64": "0.1.52-rc.34"
163
+ "@gradial/aci-cli-darwin-arm64": "0.1.52-rc.40",
164
+ "@gradial/aci-cli-darwin-x64": "0.1.52-rc.40",
165
+ "@gradial/aci-cli-linux-arm64": "0.1.52-rc.40",
166
+ "@gradial/aci-cli-linux-x64": "0.1.52-rc.40",
167
+ "@gradial/aci-cli-win32-arm64": "0.1.52-rc.40",
168
+ "@gradial/aci-cli-win32-x64": "0.1.52-rc.40"
169
169
  }
170
170
  }
@@ -1,24 +0,0 @@
1
- export interface ObjectReader {
2
- readObject(path: string): Promise<Uint8Array>;
3
- }
4
- export interface ComposeObjectRef {
5
- path: string;
6
- digest: string;
7
- }
8
- export interface ComposeSlot {
9
- name: string;
10
- required: boolean;
11
- placeholder: string;
12
- parts: ComposeObjectRef[];
13
- }
14
- export interface ComposeEnvelope {
15
- schema: 'gradial.compose-envelope.v1';
16
- algorithm: 'aci-html-compose-v1';
17
- shell: ComposeObjectRef;
18
- slots: ComposeSlot[];
19
- }
20
- export declare function composeEnvelope(reader: ObjectReader, envelopePath: string, envelopeDigest: string): Promise<{
21
- html: string;
22
- contentType: string;
23
- }>;
24
- export declare function validateEnvelope(envelope: unknown): ComposeEnvelope;
@@ -1,89 +0,0 @@
1
- const decoder = new TextDecoder('utf-8', { fatal: true });
2
- export async function composeEnvelope(reader, envelopePath, envelopeDigest) {
3
- const envelopeBytes = await reader.readObject(envelopePath);
4
- await requireDigest(envelopeBytes, envelopeDigest, 'compose envelope');
5
- let parsed;
6
- try {
7
- parsed = JSON.parse(decoder.decode(envelopeBytes));
8
- }
9
- catch {
10
- throw new Error('compose envelope is malformed');
11
- }
12
- const envelope = validateEnvelope(parsed);
13
- const shellBytes = await reader.readObject(envelope.shell.path);
14
- await requireDigest(shellBytes, envelope.shell.digest, 'compose shell');
15
- let html = decoder.decode(shellBytes);
16
- for (const slot of envelope.slots) {
17
- if (occurrences(html, slot.placeholder) !== 1) {
18
- throw new Error(`compose slot ${JSON.stringify(slot.name)} placeholder must occur exactly once`);
19
- }
20
- if (slot.required && slot.parts.length === 0) {
21
- throw new Error(`compose slot ${JSON.stringify(slot.name)} is required`);
22
- }
23
- const parts = [];
24
- for (const part of slot.parts) {
25
- const bytes = await reader.readObject(part.path);
26
- await requireDigest(bytes, part.digest, `compose slot ${slot.name}`);
27
- parts.push(decoder.decode(bytes));
28
- }
29
- html = html.replace(slot.placeholder, parts.join(''));
30
- }
31
- if (html.includes('<!--ACI_SLOT:')) {
32
- throw new Error('compose shell has unresolved slots');
33
- }
34
- return { html, contentType: 'text/html; charset=utf-8' };
35
- }
36
- export function validateEnvelope(envelope) {
37
- if (!envelope || typeof envelope !== 'object') {
38
- throw new Error('compose envelope is malformed');
39
- }
40
- const obj = envelope;
41
- if (obj.schema !== 'gradial.compose-envelope.v1'
42
- || obj.algorithm !== 'aci-html-compose-v1'
43
- || !Array.isArray(obj.slots)) {
44
- throw new Error('compose envelope is malformed');
45
- }
46
- const env = envelope;
47
- validateRef(env.shell);
48
- const names = new Set();
49
- for (const slot of env.slots) {
50
- if (!slot || !/^[A-Za-z][A-Za-z0-9_-]*$/.test(slot.name) || names.has(slot.name)) {
51
- throw new Error('compose slot is malformed');
52
- }
53
- names.add(slot.name);
54
- if (slot.placeholder !== `<!--ACI_SLOT:${slot.name}-->` || !Array.isArray(slot.parts)) {
55
- throw new Error(`compose slot ${JSON.stringify(slot.name)} is malformed`);
56
- }
57
- for (const part of slot.parts)
58
- validateRef(part);
59
- }
60
- return env;
61
- }
62
- function validateRef(ref) {
63
- const path = String(ref?.path || '');
64
- if (!path || path.includes('\\') || path.split('/').some((part) => !part || part === '.' || part === '..')) {
65
- throw new Error('compose object path is invalid');
66
- }
67
- if (!/^[0-9a-f]{64}$/.test(String(ref.digest || '').toLowerCase())) {
68
- throw new Error('compose object digest is invalid');
69
- }
70
- }
71
- async function requireDigest(bytes, want, label) {
72
- const ab = new ArrayBuffer(bytes.byteLength);
73
- new Uint8Array(ab).set(bytes);
74
- const actual = Array.from(new Uint8Array(await crypto.subtle.digest('SHA-256', ab)), (byte) => byte.toString(16).padStart(2, '0')).join('');
75
- if (actual !== want.toLowerCase())
76
- throw new Error(`${label} digest mismatch`);
77
- }
78
- function occurrences(value, needle) {
79
- let count = 0;
80
- let offset = 0;
81
- while (true) {
82
- offset = value.indexOf(needle, offset);
83
- if (offset < 0)
84
- break;
85
- count += 1;
86
- offset += needle.length;
87
- }
88
- return count;
89
- }
@@ -1,45 +0,0 @@
1
- import type { EdgeRouteEntry } from '../types.js';
2
- /**
3
- * Unique sentinel object returned by expand functions on failure.
4
- * Callers check `result === INVALID`. Never throws.
5
- */
6
- export declare const INVALID: unique symbol;
7
- export type Invalid = typeof INVALID;
8
- interface CompactDispatch {
9
- k: 's';
10
- ap: string;
11
- }
12
- interface CompactCondition {
13
- f?: string;
14
- o?: string;
15
- w?: unknown;
16
- a?: CompactCondition[];
17
- n?: CompactCondition[];
18
- }
19
- interface CompactRule {
20
- a?: CompactCondition[];
21
- n?: CompactCondition[];
22
- }
23
- export declare const COMPACT_MAX_DEPTH = 32;
24
- interface CompactVariant {
25
- pr?: number;
26
- r?: CompactRule;
27
- d?: CompactDispatch;
28
- vk?: string;
29
- vv?: Record<string, string>;
30
- }
31
- export interface CompactRouteRecord {
32
- s?: number;
33
- p: string;
34
- d: CompactDispatch;
35
- v?: Record<string, CompactVariant>;
36
- x?: string[];
37
- }
38
- export declare function projectRouteRecord(entry: EdgeRouteEntry): CompactRouteRecord;
39
- export declare function expandRouteRecord(raw: unknown): EdgeRouteEntry | Invalid;
40
- export declare const MARKER = "{\"s\":1,\"e\":1}";
41
- export declare function isMarker(raw: string): boolean;
42
- export declare function canonicalSerialize(value: unknown): string;
43
- export declare function variantValuesFromKey(key: string): Record<string, string>;
44
- export declare function pathHash16(path: string): string;
45
- export {};
@@ -1,319 +0,0 @@
1
- // Compact JSON codec for edge-decisioning route records.
2
- //
3
- // project(): canonical → compact (short field names via alias table, omit defaults)
4
- // expand(): compact → canonical (reverse, never throws — returns INVALID sentinel on failure)
5
- //
6
- // Laws (property-tested):
7
- // expand(project(r)) ≡ normalize(r)
8
- // expand() never throws — malformed input → INVALID
9
- // Unknown field code on expand = invalid data (→ base + alarm)
10
- //
11
- // The alias table is the single source of truth (schema/alias-table.v1.json).
12
- // These codecs are hand-written from it; cross-language round-trip tests
13
- // enforce drift between the Go and TS implementations.
14
- //
15
- // Failure is represented by the exported INVALID sentinel (a unique object).
16
- // Expand functions return the value directly on success, or INVALID on failure.
17
- // Callers check `result === INVALID`. This avoids the {ok,value,reason}
18
- // wrapper in the byte-constrained runtime.
19
- // ── INVALID sentinel ─────────────────────────────────────────────────
20
- /**
21
- * Unique sentinel object returned by expand functions on failure.
22
- * Callers check `result === INVALID`. Never throws.
23
- */
24
- export const INVALID = Symbol();
25
- const te = new TextEncoder();
26
- // ── Alias table (hand-mirrored from schema/alias-table.v1.json) ──────
27
- const FIELD_ALIASES = {
28
- schema: 's', path: 'p', dispatch: 'd', variants: 'v', experiments: 'x',
29
- priority: 'pr', kind: 'k', rule: 'r', all: 'a', any: 'n', field: 'f',
30
- operator: 'o', value: 'w', audiences: 'au', segments: 'sg',
31
- classification: 'c', arms: 'ar', id: 'id', status: 'st',
32
- allocationVersion: 'av', salt: 'sl', allocationBasisPoints: 'ab',
33
- variantKey: 'vk', variantValues: 'vv', artifactPath: 'ap',
34
- displayName: 'dn', primaryGoalId: 'pg', goalIds: 'gl', role: 'ro',
35
- };
36
- const DISPATCH_KIND_TO_COMPACT = {
37
- 'static-direct': 's',
38
- };
39
- const DISPATCH_KIND_FROM_COMPACT = {
40
- s: 'static-direct',
41
- };
42
- const OPERATOR_TO_COMPACT = {
43
- equals: 'eq', contains: 'ct', startsWith: 'sw', exists: 'ex',
44
- greaterThan: 'gt', greaterThanOrEqual: 'ge', lessThan: 'lt', lessThanOrEqual: 'le',
45
- before: 'bf', after: 'af', between: 'bt', in: 'in',
46
- };
47
- const OPERATOR_FROM_COMPACT = {
48
- eq: 'equals', ct: 'contains', sw: 'startsWith', ex: 'exists',
49
- gt: 'greaterThan', ge: 'greaterThanOrEqual', lt: 'lessThan', le: 'lessThanOrEqual',
50
- bf: 'before', af: 'after', bt: 'between', in: 'in',
51
- };
52
- // Compact type guard — reduces minified bytes vs repeated typeof+null+isArray checks
53
- function isObj(v) {
54
- return typeof v === 'object' && v !== null && !Array.isArray(v);
55
- }
56
- function projectDispatch(dispatch) {
57
- if (dispatch.kind !== 'static-direct' || !dispatch.artifactPath) {
58
- throw new Error('edge dispatch must name one static artifact');
59
- }
60
- return { k: DISPATCH_KIND_TO_COMPACT[dispatch.kind], ap: dispatch.artifactPath };
61
- }
62
- function expandDispatch(raw) {
63
- if (raw == null)
64
- return undefined;
65
- if (!isObj(raw))
66
- return INVALID;
67
- const kind = DISPATCH_KIND_FROM_COMPACT[raw.k];
68
- const artifactPath = raw.ap;
69
- if (kind !== 'static-direct' || typeof artifactPath !== 'string' || artifactPath.length === 0)
70
- return INVALID;
71
- return { kind, artifactPath };
72
- }
73
- // Structural recursion guard, deliberately looser than the authored MaxDepth
74
- // (5) the validators enforce. The codec's job is to bound adversarial
75
- // recursion in a byte-constrained runtime; the authored contract is
76
- // validation's. Keeping it looser is what lets an over-depth expression
77
- // round-trip and be rejected by validation identically in every runtime,
78
- // rather than being erased by the codec first (ADR 0040).
79
- export const COMPACT_MAX_DEPTH = 32;
80
- // A member carrying all/any is an envelope, not a condition — the same
81
- // presence rule the evaluators apply, so the codec cannot disagree with them
82
- // about what a member is.
83
- function isGroupMember(cond) {
84
- return !!cond && (cond.all !== undefined || cond.any !== undefined);
85
- }
86
- function projectCondition(cond, depth) {
87
- // Projection runs at publish time in a trusted context behind write-time
88
- // validation: a malformed condition or an operator with no compact token is
89
- // vocabulary/codec drift, and silently dropping the condition would ship a
90
- // rule missing a leaf — MORE permissive than authored (ADR 0038). The same
91
- // reasoning covers a group node: dropping one publishes a rule the author
92
- // did not write. Expansion stays fail-closed for already-deployed bundles.
93
- if (!cond) {
94
- throw new Error('edge rule condition must name a field and operator');
95
- }
96
- if (isGroupMember(cond)) {
97
- if (depth > COMPACT_MAX_DEPTH) {
98
- throw new Error('edge rule nests deeper than the codec can project');
99
- }
100
- if (cond.field !== undefined || cond.operator !== undefined || cond.value !== undefined) {
101
- throw new Error('edge rule member mixes group and leaf keys');
102
- }
103
- const group = {};
104
- if (cond.all && cond.all.length > 0)
105
- group.a = cond.all.map((member) => projectCondition(member, depth + 1));
106
- if (cond.any && cond.any.length > 0)
107
- group.n = cond.any.map((member) => projectCondition(member, depth + 1));
108
- if (!group.a && !group.n) {
109
- throw new Error('edge rule group must include at least one member');
110
- }
111
- return group;
112
- }
113
- if (!cond.field || !cond.operator) {
114
- throw new Error('edge rule condition must name a field and operator');
115
- }
116
- const compactOp = OPERATOR_TO_COMPACT[cond.operator];
117
- if (!compactOp) {
118
- throw new Error(`edge rule operator has no compact token: ${cond.operator}`);
119
- }
120
- const result = { f: cond.field, o: compactOp };
121
- if (cond.value !== undefined)
122
- result.w = cond.value;
123
- return result;
124
- }
125
- function expandCondition(raw, depth) {
126
- if (!isObj(raw))
127
- return INVALID;
128
- if (raw.a !== undefined || raw.n !== undefined) {
129
- if (depth > COMPACT_MAX_DEPTH)
130
- return INVALID;
131
- // A member cannot be both: expanding a mixed record would hand the
132
- // evaluator something neither a group nor a condition.
133
- if (raw.f !== undefined || raw.o !== undefined || raw.w !== undefined)
134
- return INVALID;
135
- const group = {};
136
- for (const [compact, key] of [['a', 'all'], ['n', 'any']]) {
137
- if (raw[compact] === undefined)
138
- continue;
139
- if (!Array.isArray(raw[compact]))
140
- return INVALID;
141
- const members = [];
142
- for (const member of raw[compact]) {
143
- const expanded = expandCondition(member, depth + 1);
144
- if (expanded === INVALID)
145
- return INVALID;
146
- members.push(expanded);
147
- }
148
- group[key] = members;
149
- }
150
- // Length, not truthiness: [] is truthy, so a group whose only list is
151
- // empty would otherwise expand into a member with no predicate at all.
152
- if ((group.all?.length || 0) + (group.any?.length || 0) === 0)
153
- return INVALID;
154
- return group;
155
- }
156
- const f = raw.f;
157
- const co = raw.o;
158
- if (typeof f !== 'string')
159
- return INVALID;
160
- const op = OPERATOR_FROM_COMPACT[co];
161
- if (!op)
162
- return INVALID;
163
- const c = { field: f, operator: op };
164
- if (raw.w !== undefined)
165
- c.value = raw.w;
166
- return c;
167
- }
168
- function projectRule(rule) {
169
- if (!rule)
170
- return undefined;
171
- const result = {};
172
- if (rule.all && rule.all.length > 0)
173
- result.a = rule.all.map((cond) => projectCondition(cond, 1));
174
- if (rule.any && rule.any.length > 0)
175
- result.n = rule.any.map((cond) => projectCondition(cond, 1));
176
- if (!result.a && !result.n)
177
- return undefined;
178
- return result;
179
- }
180
- function expandRule(raw) {
181
- if (raw == null)
182
- return undefined;
183
- if (!isObj(raw))
184
- return INVALID;
185
- const rule = {};
186
- if (Array.isArray(raw.a))
187
- for (const c of raw.a) {
188
- const r = expandCondition(c, 1);
189
- if (r === INVALID)
190
- return INVALID;
191
- if (r)
192
- (rule.all = rule.all || []).push(r);
193
- }
194
- if (Array.isArray(raw.n))
195
- for (const c of raw.n) {
196
- const r = expandCondition(c, 1);
197
- if (r === INVALID)
198
- return INVALID;
199
- if (r)
200
- (rule.any = rule.any || []).push(r);
201
- }
202
- if (!rule.all && !rule.any)
203
- return undefined;
204
- return rule;
205
- }
206
- function projectVariant(variant) {
207
- const result = {};
208
- if (variant.priority)
209
- result.pr = variant.priority;
210
- if (variant.rule)
211
- result.r = projectRule(variant.rule);
212
- result.d = projectDispatch(variant.dispatch);
213
- if (variant.variantValues && Object.keys(variant.variantValues).length > 0)
214
- result.vv = variant.variantValues;
215
- return result;
216
- }
217
- function expandVariant(raw) {
218
- if (!isObj(raw))
219
- return INVALID;
220
- const obj = raw;
221
- if (obj.d === undefined)
222
- return INVALID;
223
- const dispatchResult = expandDispatch(obj.d);
224
- if (dispatchResult === INVALID || !dispatchResult)
225
- return INVALID;
226
- const variant = { variantKey: '', dispatch: dispatchResult };
227
- if (typeof obj.pr === 'number')
228
- variant.priority = obj.pr;
229
- if (obj.r !== undefined) {
230
- const ruleResult = expandRule(obj.r);
231
- if (ruleResult === INVALID)
232
- return INVALID;
233
- if (ruleResult)
234
- variant.rule = ruleResult;
235
- }
236
- if (isObj(obj.vv))
237
- variant.variantValues = obj.vv;
238
- return variant;
239
- }
240
- export function projectRouteRecord(entry) {
241
- const result = { p: entry.pattern, d: projectDispatch(entry.dispatch) };
242
- result.s = 1;
243
- if (entry.variants && Object.keys(entry.variants).length > 0) {
244
- result.v = {};
245
- for (const [key, variant] of Object.entries(entry.variants))
246
- result.v[key] = projectVariant(variant);
247
- }
248
- return result;
249
- }
250
- export function expandRouteRecord(raw) {
251
- if (!isObj(raw))
252
- return INVALID;
253
- const obj = raw;
254
- if (obj.s !== undefined && obj.s !== 1)
255
- return INVALID;
256
- const path = obj.p;
257
- if (typeof path !== 'string')
258
- return INVALID;
259
- if (obj.d === undefined)
260
- return INVALID;
261
- const dispatchResult = expandDispatch(obj.d);
262
- if (dispatchResult === INVALID || !dispatchResult)
263
- return INVALID;
264
- const entry = { pattern: path, priority: 0, dispatch: dispatchResult };
265
- if (obj.v !== undefined) {
266
- if (!isObj(obj.v))
267
- return INVALID;
268
- entry.variants = {};
269
- for (const [key, variantRaw] of Object.entries(obj.v)) {
270
- const variantResult = expandVariant(variantRaw);
271
- if (variantResult === INVALID)
272
- return INVALID;
273
- const variant = variantResult;
274
- variant.variantKey = key;
275
- if (!variant.variantValues)
276
- variant.variantValues = variantValuesFromKey(key);
277
- entry.variants[key] = variant;
278
- }
279
- }
280
- return entry;
281
- }
282
- // ── Marker (tri-state: record | marker | absent) ─────────────────────
283
- export const MARKER = '{"s":1,"e":1}';
284
- export function isMarker(raw) {
285
- return raw === MARKER;
286
- }
287
- // ── Serialization helpers ────────────────────────────────────────────
288
- export function canonicalSerialize(value) {
289
- return JSON.stringify(sortKeys(value));
290
- }
291
- function sortKeys(value) {
292
- if (value === null || typeof value !== 'object')
293
- return value;
294
- if (Array.isArray(value))
295
- return value.map(sortKeys);
296
- const sorted = {};
297
- for (const key of Object.keys(value).sort())
298
- sorted[key] = sortKeys(value[key]);
299
- return sorted;
300
- }
301
- // ── VariantKey parsing ───────────────────────────────────────────────
302
- export function variantValuesFromKey(key) {
303
- const values = {};
304
- for (const part of String(key || '').replace(/&/g, ';').split(';')) {
305
- const sep = part.indexOf('=');
306
- if (sep > 0)
307
- values[part.slice(0, sep).trim()] = part.slice(sep + 1).trim();
308
- }
309
- return values;
310
- }
311
- // ── Path hashing (FNV-1a-32, hex 16 chars) ───────────────────────────
312
- export function pathHash16(path) {
313
- let hash = 0x811c9dc5;
314
- for (const byte of te.encode(path)) {
315
- hash ^= byte;
316
- hash = Math.imul(hash, 0x01000193) >>> 0;
317
- }
318
- return hash.toString(16).padStart(8, '0') + '00000000';
319
- }
@@ -1,2 +0,0 @@
1
- export declare const MIMIC_BANNER_HEADER = "x-aci-mimic-banner";
2
- export declare function decodeMimicBanner(encoded: string): string;
@@ -1,31 +0,0 @@
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
- }