@gradial/aci 0.1.50 → 0.1.51-ssr.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (114) hide show
  1. package/CHANGELOG.md +0 -21
  2. package/README.md +13 -20
  3. package/dist/astro/assets.d.ts +6 -6
  4. package/dist/astro/assets.js +6 -6
  5. package/dist/astro/index.d.ts +0 -2
  6. package/dist/astro/index.js +0 -4
  7. package/dist/cli/emit-error-pages.mjs +0 -1
  8. package/dist/cli/verify-next-cdn-routes.mjs +3 -4
  9. package/dist/collect/aci.js +1 -1
  10. package/dist/collect/aci.js.sha256 +1 -1
  11. package/dist/collect/batcher.d.ts +0 -6
  12. package/dist/collect/index.js +0 -9
  13. package/dist/collect/webvitals.d.ts +0 -3
  14. package/dist/collect/webvitals.js +11 -21
  15. package/dist/content/cache.d.ts +1 -9
  16. package/dist/content/cache.js +2 -37
  17. package/dist/content/error-pages.d.ts +1 -2
  18. package/dist/content/index.d.ts +0 -1
  19. package/dist/content/index.js +0 -1
  20. package/dist/index.d.ts +0 -1
  21. package/dist/internal/domain/edge/identity.d.ts +14 -0
  22. package/dist/internal/domain/edge/identity.js +214 -0
  23. package/dist/internal/domain/edge/index.d.ts +4 -0
  24. package/dist/internal/domain/edge/index.js +6 -0
  25. package/dist/internal/domain/edge/personalization-core.d.ts +24 -0
  26. package/dist/{edge → internal/domain/edge}/personalization-core.js +165 -64
  27. package/dist/internal/domain/edge/record/codec.d.ts +71 -0
  28. package/dist/internal/domain/edge/record/codec.js +401 -0
  29. package/dist/internal/domain/edge/resolve.d.ts +2 -0
  30. package/dist/internal/domain/edge/resolve.js +76 -0
  31. package/dist/{next/personalization-host.d.ts → internal/domain/edge/types.d.ts} +110 -63
  32. package/dist/internal/domain/edge/types.js +7 -0
  33. package/dist/internal/platform/collectionstore/errors.d.ts +5 -0
  34. package/dist/internal/platform/collectionstore/errors.js +7 -0
  35. package/dist/next/config.d.ts +0 -8
  36. package/dist/next/config.js +3 -78
  37. package/dist/next/error-pages.d.ts +2 -3
  38. package/dist/next/error-pages.js +5 -5
  39. package/dist/next/error-seat.d.ts +2 -3
  40. package/dist/next/index.d.ts +2 -2
  41. package/dist/next/index.js +1 -2
  42. package/dist/next/page.d.ts +3 -13
  43. package/dist/next/page.js +39 -51
  44. package/dist/next/server.d.ts +12 -32
  45. package/dist/next/server.js +66 -328
  46. package/dist/providers/object.d.ts +24 -0
  47. package/dist/providers/object.js +105 -0
  48. package/dist/providers/vercel-object-reader.d.ts +20 -0
  49. package/dist/providers/vercel-object-reader.js +45 -0
  50. package/dist/testing/conformance/checks.d.ts +1 -1
  51. package/dist/testing/conformance/checks.js +14 -152
  52. package/dist/testing/conformance/index.d.ts +1 -1
  53. package/dist/testing/conformance/index.js +1 -1
  54. package/dist/testing/conformance/preset.js +1 -1
  55. package/dist/testing/conformance/types.d.ts +1 -1
  56. package/dist/types/config.d.ts +0 -4
  57. package/dist/types/index.d.ts +0 -2
  58. package/dist/types/index.js +0 -2
  59. package/dist/types/page.d.ts +0 -10
  60. package/package.json +11 -35
  61. package/src/cli/emit-error-pages.mjs +0 -1
  62. package/src/cli/verify-next-cdn-routes.mjs +8 -11
  63. package/src/types/config.ts +0 -5
  64. package/src/types/index.ts +0 -2
  65. package/src/types/page.ts +0 -11
  66. package/templates/astro/template/.aci.yaml.tmpl +0 -11
  67. package/templates/common/.agents/skills/aci/SKILL.md +8 -9
  68. package/templates/common/.agents/skills/authoring/SKILL.md +3 -4
  69. package/templates/common/.agents/skills/authoring/references/block-registry.md +13 -36
  70. package/templates/common/.agents/skills/authoring/references/contracts.md +10 -17
  71. package/templates/common/.agents/skills/authoring/references/repo-structure.md +2 -2
  72. package/templates/common/.agents/skills/authoring/references/sdk-server-functions.md +45 -95
  73. package/templates/common/.agents/skills/migration/references/canon-migration.md +17 -37
  74. package/templates/nextjs/template/.aci.yaml.tmpl +0 -11
  75. package/templates/nextjs/template/README.md.tmpl +0 -1
  76. package/templates/nextjs/template/src/app/[[...slug]]/page.tsx +1 -1
  77. package/dist/collect/deviceclass.d.ts +0 -12
  78. package/dist/collect/deviceclass.js +0 -27
  79. package/dist/content/assets.d.ts +0 -18
  80. package/dist/content/assets.js +0 -66
  81. package/dist/edge/personalization-core.d.ts +0 -11
  82. package/dist/internal/render-proof.d.ts +0 -1
  83. package/dist/internal/render-proof.js +0 -197
  84. package/dist/next/asset-route.d.ts +0 -10
  85. package/dist/next/asset-route.js +0 -45
  86. package/dist/next/collect-route.d.ts +0 -19
  87. package/dist/next/collect-route.js +0 -459
  88. package/dist/next/edge-config.d.ts +0 -1
  89. package/dist/next/edge-config.js +0 -92
  90. package/dist/next/envelope-digest.d.ts +0 -21
  91. package/dist/next/envelope-digest.js +0 -58
  92. package/dist/next/middleware.d.ts +0 -40
  93. package/dist/next/middleware.js +0 -475
  94. package/dist/next/personalization-host.js +0 -270
  95. package/dist/next/vercel-collect-sdk-route.d.ts +0 -27
  96. package/dist/next/vercel-collect-sdk-route.js +0 -181
  97. package/dist/next/vercel-edge-projection-v1.d.ts +0 -24
  98. package/dist/next/vercel-edge-projection-v1.js +0 -880
  99. package/dist/next/vercel-personalization.d.ts +0 -4
  100. package/dist/next/vercel-personalization.js +0 -4
  101. package/dist/next/vercel-s3-personalization.d.ts +0 -79
  102. package/dist/next/vercel-s3-personalization.js +0 -570
  103. package/dist/providers/s3.d.ts +0 -47
  104. package/dist/providers/s3.js +0 -434
  105. package/dist/types/render-mode.d.ts +0 -2
  106. package/dist/types/render-mode.js +0 -1
  107. package/dist/types/renderer.d.ts +0 -71
  108. package/dist/types/renderer.js +0 -1
  109. package/src/types/render-mode.ts +0 -10
  110. package/src/types/renderer.ts +0 -83
  111. package/templates/nextjs/starter-content/src/middleware.ts +0 -49
  112. package/templates/nextjs/template/src/app/gradial/assets/[...path]/route.ts +0 -1
  113. package/templates/nextjs/template/src/middleware.ts +0 -49
  114. package/templates/nextjs/template/src/middleware.ts.tmpl +0 -40
@@ -0,0 +1,401 @@
1
+ // Compact JSON codec for edge-decisioning route records and classification data.
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
+ };
45
+ const OPERATOR_FROM_COMPACT = {
46
+ eq: 'equals', ct: 'contains', sw: 'startsWith', ex: 'exists',
47
+ };
48
+ const EXPERIMENT_STATUS_TO_COMPACT = {
49
+ running: 'r', paused: 'p', completed: 'd',
50
+ };
51
+ const EXPERIMENT_STATUS_FROM_COMPACT = {
52
+ r: 'running', p: 'paused', d: 'completed',
53
+ };
54
+ const EXPERIMENT_KIND_TO_COMPACT = {
55
+ ab: 'a', abn: 'n',
56
+ };
57
+ const EXPERIMENT_KIND_FROM_COMPACT = {
58
+ a: 'ab', n: 'abn',
59
+ };
60
+ // Compact type guard — reduces minified bytes vs repeated typeof+null+isArray checks
61
+ function isObj(v) {
62
+ return typeof v === 'object' && v !== null && !Array.isArray(v);
63
+ }
64
+ function projectDispatch(dispatch) {
65
+ if (dispatch.kind !== 'static-direct' || !dispatch.artifactPath) {
66
+ throw new Error('edge dispatch must name one static artifact');
67
+ }
68
+ return { k: DISPATCH_KIND_TO_COMPACT[dispatch.kind], ap: dispatch.artifactPath };
69
+ }
70
+ function expandDispatch(raw) {
71
+ if (raw == null)
72
+ return undefined;
73
+ if (!isObj(raw))
74
+ return INVALID;
75
+ const kind = DISPATCH_KIND_FROM_COMPACT[raw.k];
76
+ const artifactPath = raw.ap;
77
+ if (kind !== 'static-direct' || typeof artifactPath !== 'string' || artifactPath.length === 0)
78
+ return INVALID;
79
+ return { kind, artifactPath };
80
+ }
81
+ function projectCondition(cond) {
82
+ if (!cond || !cond.field || !cond.operator)
83
+ return null;
84
+ const compactOp = OPERATOR_TO_COMPACT[cond.operator];
85
+ if (!compactOp)
86
+ return null;
87
+ const result = { f: cond.field, o: compactOp };
88
+ if (cond.operator !== 'exists' && cond.value !== undefined)
89
+ result.w = cond.value;
90
+ return result;
91
+ }
92
+ function expandCondition(raw) {
93
+ if (!isObj(raw))
94
+ return INVALID;
95
+ const f = raw.f;
96
+ const co = raw.o;
97
+ if (typeof f !== 'string')
98
+ return INVALID;
99
+ const op = OPERATOR_FROM_COMPACT[co];
100
+ if (!op)
101
+ return INVALID;
102
+ const c = { field: f, operator: op };
103
+ if (op !== 'exists' && raw.w !== undefined)
104
+ c.value = raw.w;
105
+ return c;
106
+ }
107
+ function projectRule(rule) {
108
+ if (!rule)
109
+ return undefined;
110
+ const result = {};
111
+ if (rule.all && rule.all.length > 0)
112
+ result.a = rule.all.map(projectCondition).filter((c) => c !== null);
113
+ if (rule.any && rule.any.length > 0)
114
+ result.n = rule.any.map(projectCondition).filter((c) => c !== null);
115
+ if (!result.a && !result.n)
116
+ return undefined;
117
+ return result;
118
+ }
119
+ function expandRule(raw) {
120
+ if (raw == null)
121
+ return undefined;
122
+ if (!isObj(raw))
123
+ return INVALID;
124
+ const rule = {};
125
+ if (Array.isArray(raw.a))
126
+ for (const c of raw.a) {
127
+ const r = expandCondition(c);
128
+ if (r === INVALID)
129
+ return INVALID;
130
+ if (r)
131
+ (rule.all = rule.all || []).push(r);
132
+ }
133
+ if (Array.isArray(raw.n))
134
+ for (const c of raw.n) {
135
+ const r = expandCondition(c);
136
+ if (r === INVALID)
137
+ return INVALID;
138
+ if (r)
139
+ (rule.any = rule.any || []).push(r);
140
+ }
141
+ if (!rule.all && !rule.any)
142
+ return undefined;
143
+ return rule;
144
+ }
145
+ function projectVariant(variant) {
146
+ const result = {};
147
+ if (variant.priority)
148
+ result.pr = variant.priority;
149
+ if (variant.rule)
150
+ result.r = projectRule(variant.rule);
151
+ result.d = projectDispatch(variant.dispatch);
152
+ if (variant.variantValues && Object.keys(variant.variantValues).length > 0)
153
+ result.vv = variant.variantValues;
154
+ return result;
155
+ }
156
+ function expandVariant(raw) {
157
+ if (!isObj(raw))
158
+ return INVALID;
159
+ const obj = raw;
160
+ if (obj.d === undefined)
161
+ return INVALID;
162
+ const dispatchResult = expandDispatch(obj.d);
163
+ if (dispatchResult === INVALID || !dispatchResult)
164
+ return INVALID;
165
+ const variant = { variantKey: '', dispatch: dispatchResult };
166
+ if (typeof obj.pr === 'number')
167
+ variant.priority = obj.pr;
168
+ if (obj.r !== undefined) {
169
+ const ruleResult = expandRule(obj.r);
170
+ if (ruleResult === INVALID)
171
+ return INVALID;
172
+ if (ruleResult)
173
+ variant.rule = ruleResult;
174
+ }
175
+ if (isObj(obj.vv))
176
+ variant.variantValues = obj.vv;
177
+ return variant;
178
+ }
179
+ export function projectRouteRecord(entry) {
180
+ const result = { p: entry.pattern, d: projectDispatch(entry.dispatch) };
181
+ result.s = 1;
182
+ if (entry.variants && Object.keys(entry.variants).length > 0) {
183
+ result.v = {};
184
+ for (const [key, variant] of Object.entries(entry.variants))
185
+ result.v[key] = projectVariant(variant);
186
+ }
187
+ return result;
188
+ }
189
+ export function expandRouteRecord(raw) {
190
+ if (!isObj(raw))
191
+ return INVALID;
192
+ const obj = raw;
193
+ if (obj.s !== undefined && obj.s !== 1)
194
+ return INVALID;
195
+ const path = obj.p;
196
+ if (typeof path !== 'string')
197
+ return INVALID;
198
+ if (obj.d === undefined)
199
+ return INVALID;
200
+ const dispatchResult = expandDispatch(obj.d);
201
+ if (dispatchResult === INVALID || !dispatchResult)
202
+ return INVALID;
203
+ const entry = { pattern: path, priority: 0, dispatch: dispatchResult };
204
+ if (obj.v !== undefined) {
205
+ if (!isObj(obj.v))
206
+ return INVALID;
207
+ entry.variants = {};
208
+ for (const [key, variantRaw] of Object.entries(obj.v)) {
209
+ const variantResult = expandVariant(variantRaw);
210
+ if (variantResult === INVALID)
211
+ return INVALID;
212
+ const variant = variantResult;
213
+ variant.variantKey = key;
214
+ if (!variant.variantValues)
215
+ variant.variantValues = variantValuesFromKey(key);
216
+ entry.variants[key] = variant;
217
+ }
218
+ }
219
+ return entry;
220
+ }
221
+ export function projectClassificationData(data) {
222
+ const result = {};
223
+ if (data.audiences && Object.keys(data.audiences).length > 0) {
224
+ result.au = {};
225
+ for (const [key, def] of Object.entries(data.audiences)) {
226
+ const entry = {};
227
+ const rule = projectRule(def.rule);
228
+ if (rule)
229
+ entry.r = rule;
230
+ result.au[key] = entry;
231
+ }
232
+ }
233
+ if (data.segments && Object.keys(data.segments).length > 0) {
234
+ result.sg = {};
235
+ for (const [key, def] of Object.entries(data.segments)) {
236
+ const entry = {};
237
+ const rule = projectRule(def.rule);
238
+ if (rule)
239
+ entry.r = rule;
240
+ result.sg[key] = entry;
241
+ }
242
+ }
243
+ if (data.experiments && Object.keys(data.experiments).length > 0) {
244
+ result.x = {};
245
+ for (const [key, exp] of Object.entries(data.experiments))
246
+ result.x[key] = projectExperiment(exp);
247
+ }
248
+ return result;
249
+ }
250
+ function projectExperiment(exp) {
251
+ const result = { id: exp.id };
252
+ if (exp.status) {
253
+ const c = EXPERIMENT_STATUS_TO_COMPACT[exp.status];
254
+ if (c)
255
+ result.st = c;
256
+ }
257
+ if (exp.kind) {
258
+ const c = EXPERIMENT_KIND_TO_COMPACT[exp.kind];
259
+ if (c)
260
+ result.k = c;
261
+ }
262
+ if (exp.allocationVersion)
263
+ result.av = exp.allocationVersion;
264
+ if (exp.salt)
265
+ result.sl = exp.salt;
266
+ if (exp.priority)
267
+ result.pr = exp.priority;
268
+ if (exp.arms && exp.arms.length > 0) {
269
+ result.ar = exp.arms.map((arm) => {
270
+ const ca = { id: arm.id };
271
+ if (arm.allocationBasisPoints)
272
+ ca.ab = arm.allocationBasisPoints;
273
+ if (arm.variantKey)
274
+ ca.vk = arm.variantKey;
275
+ return ca;
276
+ });
277
+ }
278
+ return result;
279
+ }
280
+ export function expandClassificationData(raw) {
281
+ if (!isObj(raw))
282
+ return INVALID;
283
+ const obj = raw;
284
+ const data = { audiences: {}, segments: {}, experiments: {} };
285
+ if (obj.au !== undefined) {
286
+ if (!isObj(obj.au))
287
+ return INVALID;
288
+ for (const [key, defRaw] of Object.entries(obj.au)) {
289
+ if (!isObj(defRaw))
290
+ return INVALID;
291
+ const ruleResult = expandRule(defRaw.r);
292
+ if (ruleResult === INVALID)
293
+ return INVALID;
294
+ data.audiences[key] = { rule: ruleResult || { all: [] } };
295
+ }
296
+ }
297
+ if (obj.sg !== undefined) {
298
+ if (!isObj(obj.sg))
299
+ return INVALID;
300
+ for (const [key, defRaw] of Object.entries(obj.sg)) {
301
+ if (!isObj(defRaw))
302
+ return INVALID;
303
+ const ruleResult = expandRule(defRaw.r);
304
+ if (ruleResult === INVALID)
305
+ return INVALID;
306
+ data.segments[key] = { rule: ruleResult || { all: [] } };
307
+ }
308
+ }
309
+ if (obj.x !== undefined) {
310
+ if (!isObj(obj.x))
311
+ return INVALID;
312
+ for (const [key, expRaw] of Object.entries(obj.x)) {
313
+ const expResult = expandExperiment(expRaw);
314
+ if (expResult === INVALID)
315
+ return INVALID;
316
+ data.experiments[key] = expResult;
317
+ }
318
+ }
319
+ return data;
320
+ }
321
+ function expandExperiment(raw) {
322
+ if (!isObj(raw))
323
+ return INVALID;
324
+ const obj = raw;
325
+ const id = obj.id;
326
+ if (typeof id !== 'string')
327
+ return INVALID;
328
+ const exp = { id, status: 'running', kind: 'ab', arms: [] };
329
+ if (typeof obj.st === 'string') {
330
+ const expanded = EXPERIMENT_STATUS_FROM_COMPACT[obj.st];
331
+ if (!expanded)
332
+ return INVALID;
333
+ exp.status = expanded;
334
+ }
335
+ if (typeof obj.k === 'string') {
336
+ const expanded = EXPERIMENT_KIND_FROM_COMPACT[obj.k];
337
+ if (!expanded)
338
+ return INVALID;
339
+ exp.kind = expanded;
340
+ }
341
+ if (typeof obj.av === 'string')
342
+ exp.allocationVersion = obj.av;
343
+ if (typeof obj.sl === 'string')
344
+ exp.salt = obj.sl;
345
+ if (typeof obj.pr === 'number')
346
+ exp.priority = obj.pr;
347
+ if (Array.isArray(obj.ar)) {
348
+ exp.arms = [];
349
+ for (const armRaw of obj.ar) {
350
+ if (!isObj(armRaw))
351
+ return INVALID;
352
+ const armObj = armRaw;
353
+ if (typeof armObj.id !== 'string')
354
+ return INVALID;
355
+ exp.arms.push({
356
+ id: armObj.id,
357
+ allocationBasisPoints: typeof armObj.ab === 'number' ? armObj.ab : 0,
358
+ variantKey: typeof armObj.vk === 'string' ? armObj.vk : '',
359
+ });
360
+ }
361
+ }
362
+ return exp;
363
+ }
364
+ // ── Marker (tri-state: record | marker | absent) ─────────────────────
365
+ export const MARKER = '{"s":1,"e":1}';
366
+ export function isMarker(raw) {
367
+ return raw === MARKER;
368
+ }
369
+ // ── Serialization helpers ────────────────────────────────────────────
370
+ export function canonicalSerialize(value) {
371
+ return JSON.stringify(sortKeys(value));
372
+ }
373
+ function sortKeys(value) {
374
+ if (value === null || typeof value !== 'object')
375
+ return value;
376
+ if (Array.isArray(value))
377
+ return value.map(sortKeys);
378
+ const sorted = {};
379
+ for (const key of Object.keys(value).sort())
380
+ sorted[key] = sortKeys(value[key]);
381
+ return sorted;
382
+ }
383
+ // ── VariantKey parsing ───────────────────────────────────────────────
384
+ export function variantValuesFromKey(key) {
385
+ const values = {};
386
+ for (const part of String(key || '').replace(/&/g, ';').split(';')) {
387
+ const sep = part.indexOf('=');
388
+ if (sep > 0)
389
+ values[part.slice(0, sep).trim()] = part.slice(sep + 1).trim();
390
+ }
391
+ return values;
392
+ }
393
+ // ── Path hashing (FNV-1a-32, hex 16 chars) ───────────────────────────
394
+ export function pathHash16(path) {
395
+ let hash = 0x811c9dc5;
396
+ for (const byte of te.encode(path)) {
397
+ hash ^= byte;
398
+ hash = Math.imul(hash, 0x01000193) >>> 0;
399
+ }
400
+ return hash.toString(16).padStart(8, '0') + '00000000';
401
+ }
@@ -0,0 +1,2 @@
1
+ import type { PersonalizationHostConfig, PersonalizationResolution } from './types.js';
2
+ export declare function resolvePersonalizationRequest(request: Request, config: PersonalizationHostConfig): Promise<PersonalizationResolution>;
@@ -0,0 +1,76 @@
1
+ // resolvePersonalizationRequest — the edge decisioning entry point.
2
+ //
3
+ // Reads cookies for classification but never writes them (R25: consent-gated
4
+ // identity model). Identity minting happens in the collect/consent endpoint,
5
+ // not here. Public personalized responses may be private while providers cache
6
+ // the selected immutable artifact by release and exact artifact path.
7
+ import { resolvePersonalizationDecision, } from './personalization-core.js';
8
+ import { consentSignals, verifyIdentityCookie, identityHashHex, verifyClaims, parseCookieHeader, } from './identity.js';
9
+ export async function resolvePersonalizationRequest(request, config) {
10
+ const cookies = parseCookieHeader(request.headers.get('cookie'));
11
+ const consent = consentSignals(request, cookies);
12
+ const now = (config.now ?? (() => new Date()))();
13
+ // Consent-gated identity model (R25): static artifact selection NEVER mints
14
+ // identity. It reads cookies for classification but never writes them.
15
+ // Identity (aci_id) is minted by the collect/consent endpoint after
16
+ // explicit consent. This keeps all content responses cacheable and is
17
+ // GDPR-compliant (no non-essential cookie before consent).
18
+ //
19
+ // GPC/DNT deny fully: no read, no identity, no experiment bucketing.
20
+ const rawIdentity = consent.privacyDenied
21
+ ? null
22
+ : await verifyIdentityCookie(cookies.get('aci_id') || '', config.identityKeys);
23
+ // The identity hash feeds experiment bucketing for identified visitors.
24
+ // Claims are a signed cookie written by /collect; the EdgeRouter
25
+ // never reads the customer object store to refresh or revoke them.
26
+ let signedClaims = cookies.get('aci_claims') || '';
27
+ let identityHash = '';
28
+ if (rawIdentity) {
29
+ identityHash = await identityHashHex('aci_id', rawIdentity);
30
+ }
31
+ if (rawIdentity && consent.allowsIdentityTier && signedClaims) {
32
+ if (!await verifyClaims(signedClaims, config.claimsKeys || {}, now)) {
33
+ signedClaims = '';
34
+ }
35
+ }
36
+ const matchedRoute = config.route;
37
+ let variantKey = '';
38
+ if (matchedRoute) {
39
+ const claims = consent.allowsIdentityTier
40
+ ? await verifyClaims(signedClaims, config.claimsKeys || {}, now)
41
+ : null;
42
+ const decision = resolvePersonalizationDecision(toPersonalizationRequestContext(request), matchedRoute, claims, config.experiments, identityHash
43
+ ? { usable: true, siteId: config.siteId, namespace: 'aci_id', identityHash }
44
+ : undefined);
45
+ if (decision.variant) {
46
+ variantKey = decision.variant.variantKey;
47
+ }
48
+ const experiment = decision.experiment
49
+ ? { id: decision.experiment.experimentId, arm: decision.experiment.armId, allocationVersion: decision.experiment.allocationVersion }
50
+ : undefined;
51
+ return { variantKey, experiment };
52
+ }
53
+ return { variantKey };
54
+ }
55
+ function toPersonalizationRequestContext(request) {
56
+ const url = new URL(request.url);
57
+ const cookies = parseCookieHeader(request.headers.get('cookie'));
58
+ const query = {};
59
+ url.searchParams.forEach((value, key) => {
60
+ (query[key] ||= []).push(value);
61
+ });
62
+ const headers = Object.fromEntries(request.headers.entries());
63
+ return {
64
+ path: url.pathname,
65
+ query,
66
+ headers,
67
+ cookies: Object.fromEntries(cookies.entries()),
68
+ signals: {
69
+ geo: {
70
+ country: headers['x-vercel-ip-country'] || headers['cloudfront-viewer-country'] || '',
71
+ region: headers['x-vercel-ip-country-region'] || headers['cloudfront-viewer-country-region'] || '',
72
+ city: headers['x-vercel-ip-city'] || headers['cloudfront-viewer-city'] || '',
73
+ },
74
+ },
75
+ };
76
+ }
@@ -1,13 +1,8 @@
1
- import { type PersonalizationExperiment } from '../edge/personalization-core.js';
2
- import { type PortableConsentSnapshot } from '../consent/portable-envelope.js';
3
- export declare const CONTENT_PATH_HEADER = "x-aci-content-path";
4
- export declare const CONTENT_PATH_CACHE_PARAM = "__aci_content_path";
5
- export declare const ROUTED_PATH_HEADER = "x-aci-routed-path";
6
- export declare const VARIANT_KEY_HEADER = "x-aci-variant-key";
7
1
  export interface EdgeDispatch {
8
- kind: 'static-direct' | 'compose-origin' | 'framework-origin' | string;
9
- artifactPath?: string;
10
- contentPath?: string;
2
+ /** The only deterministic CMS delivery mode: an immutable HTML object. */
3
+ kind: 'static-direct';
4
+ /** Release-relative immutable HTML artifact selected for this route. */
5
+ artifactPath: string;
11
6
  }
12
7
  export interface EdgeRuleCondition {
13
8
  field: string;
@@ -45,31 +40,77 @@ export interface EdgeRouteManifest {
45
40
  notFoundPage?: string;
46
41
  errorPage?: string;
47
42
  }
48
- export interface PersonalizationEnvelopeWrite {
49
- key: string;
50
- envelopeId: string;
51
- siteId: string;
52
- identityNamespace: string;
53
- identityHash: string;
54
- records: readonly unknown[];
55
- digest: string;
43
+ export interface PersonalizationRequestContext {
44
+ path: string;
45
+ query: Record<string, string[]>;
46
+ headers: Record<string, string>;
47
+ cookies: Record<string, string>;
48
+ signals?: {
49
+ geo?: Record<string, string>;
50
+ };
51
+ identityHash?: string;
52
+ identityNamespace?: string;
53
+ siteId?: string;
54
+ }
55
+ export interface PersonalizationClaims {
56
+ aud?: string[];
57
+ seg?: string[];
58
+ iat: number;
59
+ exp: number;
60
+ }
61
+ export interface PersonalizationVariant {
62
+ variantKey: string;
63
+ variantValues?: Record<string, string>;
64
+ priority?: number;
65
+ rule?: EdgeRuleExpression;
66
+ dispatch: EdgeDispatch;
56
67
  }
57
- export declare class IdentityErasureFencedError extends Error {
58
- readonly name = "IdentityErasureFencedError";
59
- constructor();
68
+ export interface PersonalizationRoute {
69
+ variants?: Record<string, PersonalizationVariant>;
60
70
  }
61
- /** Immutable release routing state read by an EdgeRouter. */
62
- export interface PersonalizationRouteStore {
63
- readEdgeManifest(releaseId: string): Promise<EdgeRouteManifest | null>;
71
+ export interface PersonalizationExperiment {
72
+ id: string;
73
+ status: string;
74
+ kind: string;
75
+ priority?: number;
76
+ allocationVersion?: string;
77
+ salt?: string;
78
+ arms: Array<{
79
+ id: string;
80
+ allocationBasisPoints?: number;
81
+ variantKey: string;
82
+ }>;
64
83
  }
65
- /** Customer-owned collection state. This is deliberately not an EdgeRouter dependency. */
66
- export interface PersonalizationCollectionStore {
67
- readClaims(siteId: string, namespace: string, identityHash: string): Promise<string | null>;
68
- appendEnvelope(write: PersonalizationEnvelopeWrite): Promise<void>;
69
- putSuppression(key: string, record: unknown): Promise<void>;
84
+ export interface PersonalizationIdentity {
85
+ usable: boolean;
86
+ siteId: string;
87
+ namespace: string;
88
+ identityHash: string;
70
89
  }
71
- /** @deprecated Use the narrower router or collection store at each boundary. */
72
- export interface PersonalizationBlobStore extends PersonalizationRouteStore, PersonalizationCollectionStore {
90
+ export interface PersonalizationExperimentDecision {
91
+ experimentId: string;
92
+ armId: string;
93
+ allocationVersion: string;
94
+ variantKey: string;
95
+ bucket: number;
96
+ }
97
+ export interface PersonalizationDecision {
98
+ variant: PersonalizationVariant | null;
99
+ experiment: PersonalizationExperimentDecision | null;
100
+ }
101
+ export interface ClassificationData {
102
+ audiences: Record<string, {
103
+ rule: EdgeRuleExpression;
104
+ }>;
105
+ segments: Record<string, {
106
+ rule: EdgeRuleExpression;
107
+ }>;
108
+ experiments: Record<string, PersonalizationExperiment>;
109
+ }
110
+ export interface Classification {
111
+ matchedAudiences: string[];
112
+ matchedSegments: string[];
113
+ experimentArms: Record<string, PersonalizationExperimentDecision>;
73
114
  }
74
115
  export interface SigningKey {
75
116
  kid: string;
@@ -79,16 +120,6 @@ export interface IdentitySigningKeys {
79
120
  current: SigningKey;
80
121
  previous?: SigningKey;
81
122
  }
82
- export interface PersonalizationHostConfig {
83
- siteId: string;
84
- store: PersonalizationRouteStore;
85
- identityKeys: IdentitySigningKeys;
86
- claimsKeys?: Record<string, string>;
87
- identityCookieTTLSeconds?: number;
88
- secureCookies?: boolean;
89
- now?: () => Date;
90
- randomBytes?: (length: number) => Uint8Array;
91
- }
92
123
  export interface CookieMutation {
93
124
  name: string;
94
125
  value: string;
@@ -101,34 +132,50 @@ export interface CookieMutation {
101
132
  expires?: Date;
102
133
  };
103
134
  }
135
+ export interface ConsentSnapshot {
136
+ version: number;
137
+ purposes: string[];
138
+ grantedAt: string;
139
+ source?: string;
140
+ }
141
+ export interface ConsentSignals {
142
+ hasConsent: boolean;
143
+ consentInvalid: boolean;
144
+ snapshot: ConsentSnapshot | null;
145
+ allowsIdentityTier: boolean;
146
+ privacyDenied: boolean;
147
+ }
148
+ export interface PersonalizationEnvelopeWrite {
149
+ key: string;
150
+ envelopeId: string;
151
+ siteId: string;
152
+ identityNamespace: string;
153
+ identityHash: string;
154
+ records: readonly unknown[];
155
+ digest: string;
156
+ }
157
+ export interface PersonalizationCollectionStore {
158
+ readClaims(siteId: string, namespace: string, identityHash: string, now?: Date): Promise<string | null>;
159
+ appendEnvelope(write: PersonalizationEnvelopeWrite): Promise<void>;
160
+ putSuppression(key: string, record: unknown): Promise<void>;
161
+ }
162
+ export interface PersonalizationHostConfig {
163
+ siteId: string;
164
+ route: EdgeRouteEntry | null;
165
+ experiments?: Record<string, PersonalizationExperiment>;
166
+ identityKeys: IdentitySigningKeys;
167
+ claimsKeys?: Record<string, string>;
168
+ identityCookieTTLSeconds?: number;
169
+ secureCookies?: boolean;
170
+ now?: () => Date;
171
+ randomBytes?: (length: number) => Uint8Array;
172
+ }
104
173
  export interface PersonalizationResolution {
105
- requestHeaders: Headers;
106
- contentPath: string;
107
174
  variantKey: string;
108
175
  experiment?: {
109
176
  id: string;
110
177
  arm: string;
111
178
  allocationVersion: string;
112
179
  };
113
- cookies: CookieMutation[];
114
180
  }
115
- export type ConsentSnapshot = PortableConsentSnapshot;
116
- interface ClaimsPayload {
117
- aud?: string[];
118
- seg?: string[];
119
- iat: number;
120
- exp: number;
121
- }
122
- export declare function resolvePersonalizationRequest(request: Request, releaseId: string, config: PersonalizationHostConfig): Promise<PersonalizationResolution>;
123
- export declare function consentSignals(request: Request, cookies: Map<string, string>): {
124
- hasConsent: boolean;
125
- snapshot: ConsentSnapshot | null;
126
- allowsIdentityTier: boolean;
127
- privacyDenied: boolean;
128
- };
129
- export declare function parseCookieHeader(value: string | null): Map<string, string>;
130
- export declare function verifyIdentityCookie(value: string, keys: IdentitySigningKeys): Promise<string | null>;
131
- export declare function sha256Hex(value: string): Promise<string>;
132
- export declare function identityHashHex(namespace: string, value: string): Promise<string>;
133
- export declare function verifyClaims(value: string, keys: Record<string, string>, now: Date): Promise<ClaimsPayload | null>;
134
- export {};
181
+ export { IdentityErasureFencedError } from '../../platform/collectionstore/errors.js';