@gradial/aci 0.1.44 → 0.1.46

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,15 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.1.45 - 2026-07-29
4
+
5
+ ### Fixed
6
+
7
+ - Require the explicit `$type: "component"` discriminator for top-level and
8
+ nested component blocks across schema generation, content validation, and
9
+ rendering.
10
+ - Execute the registry compiler and content validator correctly through
11
+ package-manager symlinks while keeping their reusable functions importable.
12
+
3
13
  ## 0.1.44 - 2026-07-28
4
14
 
5
15
  ### Fixed
package/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # Agentic Content Infrastructure SDK
2
2
 
3
- `@gradial/aci` `0.1.44` is a pre-1.0 public-surface reset for the ACI
3
+ `@gradial/aci` `0.1.45` 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.44 zod@^4
11
+ npm install @gradial/aci@0.1.45 zod@^4
12
12
  ```
13
13
 
14
14
  ## Root Import
@@ -85,13 +85,13 @@ go through an app-owned HTTP endpoint backed server-side by a `ContentProvider`.
85
85
  The first RC scaffold path is Next:
86
86
 
87
87
  ```bash
88
- npx @gradial/aci@0.1.44 init my-site --template next-starter
88
+ npx @gradial/aci@0.1.45 init my-site --template next-starter
89
89
  ```
90
90
 
91
91
  List available templates:
92
92
 
93
93
  ```bash
94
- npx @gradial/aci@0.1.44 init --list
94
+ npx @gradial/aci@0.1.45 init --list
95
95
  ```
96
96
 
97
97
  ## Personalization: The One-Tag Install
@@ -80,6 +80,41 @@ export declare const segmentDataModel: import("./index.js").DataModelContract<z.
80
80
  properties: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnion<readonly [z.ZodString, z.ZodNumber, z.ZodBoolean]>>>;
81
81
  type: z.ZodLiteral<"event_latest">;
82
82
  }, z.core.$strict>], "type">>>;
83
+ none: z.ZodOptional<z.ZodArray<z.ZodDiscriminatedUnion<[z.ZodObject<{
84
+ minCount: z.ZodNumber;
85
+ event: z.ZodString;
86
+ path: z.ZodOptional<z.ZodString>;
87
+ referrer: z.ZodOptional<z.ZodString>;
88
+ withinDays: z.ZodOptional<z.ZodNumber>;
89
+ experiment: z.ZodOptional<z.ZodObject<{
90
+ experimentId: z.ZodString;
91
+ armId: z.ZodOptional<z.ZodString>;
92
+ }, z.core.$strict>>;
93
+ properties: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnion<readonly [z.ZodString, z.ZodNumber, z.ZodBoolean]>>>;
94
+ type: z.ZodLiteral<"event_count">;
95
+ }, z.core.$strict>, z.ZodObject<{
96
+ event: z.ZodString;
97
+ path: z.ZodOptional<z.ZodString>;
98
+ referrer: z.ZodOptional<z.ZodString>;
99
+ withinDays: z.ZodOptional<z.ZodNumber>;
100
+ experiment: z.ZodOptional<z.ZodObject<{
101
+ experimentId: z.ZodString;
102
+ armId: z.ZodOptional<z.ZodString>;
103
+ }, z.core.$strict>>;
104
+ properties: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnion<readonly [z.ZodString, z.ZodNumber, z.ZodBoolean]>>>;
105
+ type: z.ZodLiteral<"event_exists">;
106
+ }, z.core.$strict>, z.ZodObject<{
107
+ event: z.ZodString;
108
+ path: z.ZodOptional<z.ZodString>;
109
+ referrer: z.ZodOptional<z.ZodString>;
110
+ withinDays: z.ZodOptional<z.ZodNumber>;
111
+ experiment: z.ZodOptional<z.ZodObject<{
112
+ experimentId: z.ZodString;
113
+ armId: z.ZodOptional<z.ZodString>;
114
+ }, z.core.$strict>>;
115
+ properties: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnion<readonly [z.ZodString, z.ZodNumber, z.ZodBoolean]>>>;
116
+ type: z.ZodLiteral<"event_latest">;
117
+ }, z.core.$strict>], "type">>>;
83
118
  }, z.core.$strict>, z.ZodObject<{
84
119
  all: z.ZodOptional<z.ZodArray<z.ZodDiscriminatedUnion<[z.ZodObject<{
85
120
  minCount: z.ZodNumber;
@@ -151,6 +186,41 @@ export declare const segmentDataModel: import("./index.js").DataModelContract<z.
151
186
  properties: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnion<readonly [z.ZodString, z.ZodNumber, z.ZodBoolean]>>>;
152
187
  type: z.ZodLiteral<"event_latest">;
153
188
  }, z.core.$strict>], "type">>;
189
+ none: z.ZodOptional<z.ZodArray<z.ZodDiscriminatedUnion<[z.ZodObject<{
190
+ minCount: z.ZodNumber;
191
+ event: z.ZodString;
192
+ path: z.ZodOptional<z.ZodString>;
193
+ referrer: z.ZodOptional<z.ZodString>;
194
+ withinDays: z.ZodOptional<z.ZodNumber>;
195
+ experiment: z.ZodOptional<z.ZodObject<{
196
+ experimentId: z.ZodString;
197
+ armId: z.ZodOptional<z.ZodString>;
198
+ }, z.core.$strict>>;
199
+ properties: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnion<readonly [z.ZodString, z.ZodNumber, z.ZodBoolean]>>>;
200
+ type: z.ZodLiteral<"event_count">;
201
+ }, z.core.$strict>, z.ZodObject<{
202
+ event: z.ZodString;
203
+ path: z.ZodOptional<z.ZodString>;
204
+ referrer: z.ZodOptional<z.ZodString>;
205
+ withinDays: z.ZodOptional<z.ZodNumber>;
206
+ experiment: z.ZodOptional<z.ZodObject<{
207
+ experimentId: z.ZodString;
208
+ armId: z.ZodOptional<z.ZodString>;
209
+ }, z.core.$strict>>;
210
+ properties: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnion<readonly [z.ZodString, z.ZodNumber, z.ZodBoolean]>>>;
211
+ type: z.ZodLiteral<"event_exists">;
212
+ }, z.core.$strict>, z.ZodObject<{
213
+ event: z.ZodString;
214
+ path: z.ZodOptional<z.ZodString>;
215
+ referrer: z.ZodOptional<z.ZodString>;
216
+ withinDays: z.ZodOptional<z.ZodNumber>;
217
+ experiment: z.ZodOptional<z.ZodObject<{
218
+ experimentId: z.ZodString;
219
+ armId: z.ZodOptional<z.ZodString>;
220
+ }, z.core.$strict>>;
221
+ properties: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnion<readonly [z.ZodString, z.ZodNumber, z.ZodBoolean]>>>;
222
+ type: z.ZodLiteral<"event_latest">;
223
+ }, z.core.$strict>], "type">>>;
154
224
  }, z.core.$strict>]>>;
155
225
  }, z.core.$strip>, "segment">;
156
226
  export declare const audienceDataModel: import("./index.js").DataModelContract<z.ZodObject<{
@@ -163,11 +233,11 @@ export declare const audienceDataModel: import("./index.js").DataModelContract<z
163
233
  }>;
164
234
  priority: z.ZodNumber;
165
235
  membershipSource: z.ZodEnum<{
236
+ none: "none";
166
237
  segment: "segment";
167
238
  rule: "rule";
168
239
  manual: "manual";
169
240
  external: "external";
170
- none: "none";
171
241
  }>;
172
242
  ruleRef: z.ZodOptional<z.ZodString>;
173
243
  segmentRef: z.ZodOptional<z.ZodString>;
@@ -473,6 +543,41 @@ export declare const audienceDataModels: (import("./index.js").DataModelContract
473
543
  properties: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnion<readonly [z.ZodString, z.ZodNumber, z.ZodBoolean]>>>;
474
544
  type: z.ZodLiteral<"event_latest">;
475
545
  }, z.core.$strict>], "type">>>;
546
+ none: z.ZodOptional<z.ZodArray<z.ZodDiscriminatedUnion<[z.ZodObject<{
547
+ minCount: z.ZodNumber;
548
+ event: z.ZodString;
549
+ path: z.ZodOptional<z.ZodString>;
550
+ referrer: z.ZodOptional<z.ZodString>;
551
+ withinDays: z.ZodOptional<z.ZodNumber>;
552
+ experiment: z.ZodOptional<z.ZodObject<{
553
+ experimentId: z.ZodString;
554
+ armId: z.ZodOptional<z.ZodString>;
555
+ }, z.core.$strict>>;
556
+ properties: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnion<readonly [z.ZodString, z.ZodNumber, z.ZodBoolean]>>>;
557
+ type: z.ZodLiteral<"event_count">;
558
+ }, z.core.$strict>, z.ZodObject<{
559
+ event: z.ZodString;
560
+ path: z.ZodOptional<z.ZodString>;
561
+ referrer: z.ZodOptional<z.ZodString>;
562
+ withinDays: z.ZodOptional<z.ZodNumber>;
563
+ experiment: z.ZodOptional<z.ZodObject<{
564
+ experimentId: z.ZodString;
565
+ armId: z.ZodOptional<z.ZodString>;
566
+ }, z.core.$strict>>;
567
+ properties: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnion<readonly [z.ZodString, z.ZodNumber, z.ZodBoolean]>>>;
568
+ type: z.ZodLiteral<"event_exists">;
569
+ }, z.core.$strict>, z.ZodObject<{
570
+ event: z.ZodString;
571
+ path: z.ZodOptional<z.ZodString>;
572
+ referrer: z.ZodOptional<z.ZodString>;
573
+ withinDays: z.ZodOptional<z.ZodNumber>;
574
+ experiment: z.ZodOptional<z.ZodObject<{
575
+ experimentId: z.ZodString;
576
+ armId: z.ZodOptional<z.ZodString>;
577
+ }, z.core.$strict>>;
578
+ properties: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnion<readonly [z.ZodString, z.ZodNumber, z.ZodBoolean]>>>;
579
+ type: z.ZodLiteral<"event_latest">;
580
+ }, z.core.$strict>], "type">>>;
476
581
  }, z.core.$strict>, z.ZodObject<{
477
582
  all: z.ZodOptional<z.ZodArray<z.ZodDiscriminatedUnion<[z.ZodObject<{
478
583
  minCount: z.ZodNumber;
@@ -544,6 +649,41 @@ export declare const audienceDataModels: (import("./index.js").DataModelContract
544
649
  properties: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnion<readonly [z.ZodString, z.ZodNumber, z.ZodBoolean]>>>;
545
650
  type: z.ZodLiteral<"event_latest">;
546
651
  }, z.core.$strict>], "type">>;
652
+ none: z.ZodOptional<z.ZodArray<z.ZodDiscriminatedUnion<[z.ZodObject<{
653
+ minCount: z.ZodNumber;
654
+ event: z.ZodString;
655
+ path: z.ZodOptional<z.ZodString>;
656
+ referrer: z.ZodOptional<z.ZodString>;
657
+ withinDays: z.ZodOptional<z.ZodNumber>;
658
+ experiment: z.ZodOptional<z.ZodObject<{
659
+ experimentId: z.ZodString;
660
+ armId: z.ZodOptional<z.ZodString>;
661
+ }, z.core.$strict>>;
662
+ properties: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnion<readonly [z.ZodString, z.ZodNumber, z.ZodBoolean]>>>;
663
+ type: z.ZodLiteral<"event_count">;
664
+ }, z.core.$strict>, z.ZodObject<{
665
+ event: z.ZodString;
666
+ path: z.ZodOptional<z.ZodString>;
667
+ referrer: z.ZodOptional<z.ZodString>;
668
+ withinDays: z.ZodOptional<z.ZodNumber>;
669
+ experiment: z.ZodOptional<z.ZodObject<{
670
+ experimentId: z.ZodString;
671
+ armId: z.ZodOptional<z.ZodString>;
672
+ }, z.core.$strict>>;
673
+ properties: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnion<readonly [z.ZodString, z.ZodNumber, z.ZodBoolean]>>>;
674
+ type: z.ZodLiteral<"event_exists">;
675
+ }, z.core.$strict>, z.ZodObject<{
676
+ event: z.ZodString;
677
+ path: z.ZodOptional<z.ZodString>;
678
+ referrer: z.ZodOptional<z.ZodString>;
679
+ withinDays: z.ZodOptional<z.ZodNumber>;
680
+ experiment: z.ZodOptional<z.ZodObject<{
681
+ experimentId: z.ZodString;
682
+ armId: z.ZodOptional<z.ZodString>;
683
+ }, z.core.$strict>>;
684
+ properties: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnion<readonly [z.ZodString, z.ZodNumber, z.ZodBoolean]>>>;
685
+ type: z.ZodLiteral<"event_latest">;
686
+ }, z.core.$strict>], "type">>>;
547
687
  }, z.core.$strict>]>>;
548
688
  }, z.core.$strip>, "segment"> | import("./index.js").DataModelContract<z.ZodObject<{
549
689
  id: z.ZodString;
@@ -555,11 +695,11 @@ export declare const audienceDataModels: (import("./index.js").DataModelContract
555
695
  }>;
556
696
  priority: z.ZodNumber;
557
697
  membershipSource: z.ZodEnum<{
698
+ none: "none";
558
699
  segment: "segment";
559
700
  rule: "rule";
560
701
  manual: "manual";
561
702
  external: "external";
562
- none: "none";
563
703
  }>;
564
704
  ruleRef: z.ZodOptional<z.ZodString>;
565
705
  segmentRef: z.ZodOptional<z.ZodString>;
@@ -43,10 +43,12 @@ const segmentCriteriaSchema = z.union([
43
43
  z.object({
44
44
  all: z.array(segmentCriteriaConditionSchema).min(1),
45
45
  any: z.array(segmentCriteriaConditionSchema).optional(),
46
+ none: z.array(segmentCriteriaConditionSchema).optional(),
46
47
  }).strict(),
47
48
  z.object({
48
49
  all: z.array(segmentCriteriaConditionSchema).optional(),
49
50
  any: z.array(segmentCriteriaConditionSchema).min(1),
51
+ none: z.array(segmentCriteriaConditionSchema).optional(),
50
52
  }).strict(),
51
53
  ]);
52
54
  const requestRuleConditionSchema = z.object({
@@ -32,6 +32,7 @@ checkMissing('legacy required catch-all is removed', 'src/app/[...slug]/page.tsx
32
32
  checkFile('canonical asset route exists', 'src/app/gradial/assets/[...path]/route.ts', (content) => content.includes('@gradial/aci/next/asset-route'), 'Expected the canonical asset route to use @gradial/aci/next/asset-route.');
33
33
  checkMissing('legacy release-scoped asset route is removed', 'src/app/gradial/assets/[releaseId]/[...path]/route.ts', 'Release-scoped asset URLs are produced by withAci() rewrites into the canonical src/app/gradial/assets/[...path]/route.ts; do not add a [releaseId] route.');
34
34
  checkMissing('no separate __r route folder', 'src/app/__r/[releaseId]/routes/[[...slug]]/page.tsx', 'The root catch-all handles release-scoped rewrites. Do not create a separate __r route folder.');
35
+ checkMissing('platform-owned collect route is absent', 'src/app/collect/route.ts', 'ACI injects the reserved /collect function during Vercel Build Output composition; customer source must not own it.');
35
36
  checkFile('next.config uses withAci', 'next.config.mjs', (content) => content.includes('withAci'), 'Expected next.config.mjs to use withAci() from @gradial/aci/next.');
36
37
  for (const line of checks) {
37
38
  console.log(line);
@@ -10,7 +10,7 @@ export interface RouteEntry {
10
10
  export interface RouteVariantEntry {
11
11
  variantKey: string;
12
12
  payloadRef: string;
13
- digest?: string;
13
+ digest: string;
14
14
  variantValues?: Record<string, string>;
15
15
  }
16
16
  export interface FragmentEntry {
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,197 @@
1
+ const RENDER_PROOF_VERSION = 'gradial.aci.renderProof.v1';
2
+ const RENDER_PROOF_KEYS = [
3
+ 'version',
4
+ 'releaseId',
5
+ 'route',
6
+ 'contentRef',
7
+ 'contentSha256',
8
+ 'variantKey',
9
+ ];
10
+ const MAX_RENDER_PROOF_CONTENT_BYTES = 4096;
11
+ const receiptBrand = Symbol('aci.verified-page-receipt');
12
+ const renderProofReceipts = new WeakMap();
13
+ /** @internal */
14
+ export const selectVerifiedPage = Symbol('aci.select-verified-page');
15
+ /** @internal */
16
+ export function createVerifiedPageReceipt(value) {
17
+ // Selection itself must fail if the coupled marker cannot be emitted.
18
+ // `getPage` also uses this capability and intentionally discards the
19
+ // receipt, so deferring the size check to React would permit markerless
20
+ // success on that public server loader.
21
+ validateVerifiedPageReceipt(value);
22
+ return Object.freeze({
23
+ ...value,
24
+ [receiptBrand]: true,
25
+ });
26
+ }
27
+ /** @internal */
28
+ export function validateVerifiedPageReceipt(value) {
29
+ encodeRenderProofContent(toRenderProof(value));
30
+ }
31
+ /** @internal */
32
+ export function attachRenderProofReceipt(input, receipt) {
33
+ if (receipt[receiptBrand] !== true) {
34
+ throw new Error('ACI render proof integrity error: unverified receipt');
35
+ }
36
+ renderProofReceipts.set(input, receipt);
37
+ }
38
+ /** @internal */
39
+ export function renderProofContentFor(input) {
40
+ const receipt = renderProofReceipts.get(input);
41
+ return receipt ? encodeRenderProofContent(toRenderProof(receipt)) : null;
42
+ }
43
+ /** @internal */
44
+ export function encodeRenderProofContent(proof) {
45
+ validateRenderProof(proof);
46
+ const canonicalJSON = JSON.stringify({
47
+ version: proof.version,
48
+ releaseId: proof.releaseId,
49
+ route: proof.route,
50
+ contentRef: proof.contentRef,
51
+ contentSha256: proof.contentSha256,
52
+ variantKey: proof.variantKey,
53
+ });
54
+ const content = Buffer.from(canonicalJSON, 'utf8').toString('base64url');
55
+ if (content.length < 1 || content.length > MAX_RENDER_PROOF_CONTENT_BYTES) {
56
+ throw new Error(`ACI render proof integrity error: marker content is ${content.length} bytes`);
57
+ }
58
+ return content;
59
+ }
60
+ /** @internal */
61
+ export function decodeRenderProofContent(content) {
62
+ if (content.length < 1
63
+ || content.length > MAX_RENDER_PROOF_CONTENT_BYTES
64
+ || !/^[A-Za-z0-9_-]+$/.test(content)) {
65
+ throw new Error('ACI render proof integrity error: invalid marker encoding');
66
+ }
67
+ const bytes = Buffer.from(content, 'base64url');
68
+ if (bytes.toString('base64url') !== content) {
69
+ throw new Error('ACI render proof integrity error: non-canonical base64url');
70
+ }
71
+ let canonicalJSON = '';
72
+ try {
73
+ canonicalJSON = new TextDecoder('utf-8', { fatal: true }).decode(bytes);
74
+ }
75
+ catch (cause) {
76
+ throw integrityError('marker is not valid UTF-8', cause);
77
+ }
78
+ let decoded;
79
+ try {
80
+ decoded = JSON.parse(canonicalJSON);
81
+ }
82
+ catch (cause) {
83
+ throw integrityError('marker is not valid JSON', cause);
84
+ }
85
+ if (!isRecord(decoded)) {
86
+ throw new Error('ACI render proof integrity error: marker must be an object');
87
+ }
88
+ if (!sameKeys(Object.keys(decoded), RENDER_PROOF_KEYS)) {
89
+ throw new Error('ACI render proof integrity error: marker keys are not canonical');
90
+ }
91
+ const proof = {
92
+ version: decoded.version,
93
+ releaseId: decoded.releaseId,
94
+ route: decoded.route,
95
+ contentRef: decoded.contentRef,
96
+ contentSha256: decoded.contentSha256,
97
+ variantKey: decoded.variantKey,
98
+ };
99
+ validateRenderProof(proof);
100
+ if (JSON.stringify(proof) !== canonicalJSON) {
101
+ throw new Error('ACI render proof integrity error: marker JSON is not canonical');
102
+ }
103
+ return Object.freeze(proof);
104
+ }
105
+ function toRenderProof(receipt) {
106
+ return {
107
+ version: RENDER_PROOF_VERSION,
108
+ releaseId: receipt.releaseId,
109
+ route: receipt.route,
110
+ contentRef: receipt.contentRef,
111
+ contentSha256: receipt.contentSha256,
112
+ variantKey: receipt.variantKey,
113
+ };
114
+ }
115
+ function validateRenderProof(proof) {
116
+ if (!isRecord(proof)) {
117
+ throw new Error('ACI render proof integrity error: proof must be an object');
118
+ }
119
+ for (const key of RENDER_PROOF_KEYS) {
120
+ if (typeof proof[key] !== 'string') {
121
+ throw new Error(`ACI render proof integrity error: ${key} must be a string`);
122
+ }
123
+ if (hasUnpairedSurrogate(proof[key])) {
124
+ throw new Error(`ACI render proof integrity error: ${key} has invalid Unicode`);
125
+ }
126
+ }
127
+ if (proof.version !== RENDER_PROOF_VERSION) {
128
+ throw new Error('ACI render proof integrity error: unsupported version');
129
+ }
130
+ if (Buffer.byteLength(proof.releaseId, 'ascii') > 128
131
+ || !/^[A-Za-z0-9][A-Za-z0-9._-]*$/.test(proof.releaseId)) {
132
+ throw new Error('ACI render proof integrity error: invalid releaseId');
133
+ }
134
+ if (Buffer.byteLength(proof.route, 'utf8') > 2048
135
+ || !isCanonicalRoute(proof.route)) {
136
+ throw new Error('ACI render proof integrity error: invalid route');
137
+ }
138
+ if (Buffer.byteLength(proof.contentRef, 'utf8') > 2048
139
+ || !isCleanContentRef(proof.contentRef)) {
140
+ throw new Error('ACI render proof integrity error: invalid contentRef');
141
+ }
142
+ if (!/^[0-9a-f]{64}$/.test(proof.contentSha256)) {
143
+ throw new Error('ACI render proof integrity error: invalid contentSha256');
144
+ }
145
+ if (Buffer.byteLength(proof.variantKey, 'utf8') > 1024
146
+ || /[\u0000-\u001f\u007f]/.test(proof.variantKey)) {
147
+ throw new Error('ACI render proof integrity error: invalid variantKey');
148
+ }
149
+ }
150
+ function isCanonicalRoute(route) {
151
+ if (route === '/')
152
+ return true;
153
+ if (!route.startsWith('/')
154
+ || route.startsWith('//')
155
+ || route.endsWith('/')
156
+ || /[\u0000-\u001f\u007f]/.test(route)) {
157
+ return false;
158
+ }
159
+ return route.slice(1).split('/').every((segment) => (segment !== '' && segment !== '.' && segment !== '..'));
160
+ }
161
+ function isCleanContentRef(ref) {
162
+ if (!ref
163
+ || ref.startsWith('/')
164
+ || ref.endsWith('/')
165
+ || ref.includes('\\')
166
+ || /[\u0000-\u001f\u007f]/.test(ref)) {
167
+ return false;
168
+ }
169
+ return ref.split('/').every((segment) => (segment !== '' && segment !== '.' && segment !== '..'));
170
+ }
171
+ function isRecord(value) {
172
+ return value !== null && typeof value === 'object' && !Array.isArray(value);
173
+ }
174
+ function sameKeys(actual, expected) {
175
+ return actual.length === expected.length
176
+ && actual.every((key, index) => key === expected[index]);
177
+ }
178
+ function hasUnpairedSurrogate(value) {
179
+ for (let index = 0; index < value.length; index += 1) {
180
+ const code = value.charCodeAt(index);
181
+ if (code >= 0xd800 && code <= 0xdbff) {
182
+ const next = value.charCodeAt(index + 1);
183
+ if (!(next >= 0xdc00 && next <= 0xdfff))
184
+ return true;
185
+ index += 1;
186
+ }
187
+ else if (code >= 0xdc00 && code <= 0xdfff) {
188
+ return true;
189
+ }
190
+ }
191
+ return false;
192
+ }
193
+ function integrityError(message, cause) {
194
+ const error = new Error(`ACI render proof integrity error: ${message}`);
195
+ error.cause = cause;
196
+ return error;
197
+ }
@@ -2,6 +2,7 @@ import { NextResponse } from 'next/server.js';
2
2
  import { getUncachedEdgeConfigValue } from './edge-config.js';
3
3
  import { PREVIEW_TOKEN_COOKIE, PREVIEW_RELEASE_COOKIE, extractPreviewToken, resolvePreviewContext, createPreviewCookies, previewSignKey, } from '../preview/core.js';
4
4
  import { CONTENT_PATH_HEADER, CONTENT_PATH_CACHE_PARAM, resolvePersonalizationRequest, } from './personalization-host.js';
5
+ import { activeReleaseV1Key, clampDeploymentCompatibleReleaseV1, decodeActiveReleaseV1, decodeDeploymentCompatibilityV1, deploymentCompatibilityKey, } from './vercel-edge-projection-v1.js';
5
6
  const RELEASE_SCOPED_PREFIX = '/__r';
6
7
  const FRAMEWORK_ORIGIN_AUTH_HEADER = 'x-gradial-framework-origin-auth';
7
8
  const FRAMEWORK_SITE_ID_HEADER = 'x-baremetal-site-id';
@@ -371,73 +372,27 @@ async function activeReleaseFromEdgeConfig(config) {
371
372
  if (!edgeConfig || !siteId)
372
373
  return '';
373
374
  const active = await activeReleasePointer(edgeConfig, siteId);
374
- if (!active.releaseId)
375
+ if (active == null)
375
376
  return '';
376
377
  const deploymentId = config.deploymentId || process.env.VERCEL_DEPLOYMENT_ID || '';
377
378
  if (!deploymentId)
378
379
  return active.releaseId;
379
- const compatibility = await getEdgeConfigJSON(edgeConfig, deploymentCompatibilityKey(siteId, deploymentId));
380
- if (!compatibility)
380
+ const compatibilityValue = await getEdgeConfigValue(edgeConfig, deploymentCompatibilityKey(siteId, deploymentId));
381
+ if (compatibilityValue == null)
381
382
  return active.releaseId;
382
- return clampRelease(active.releaseId, active.sequence || 0, compatibility);
383
+ return clampDeploymentCompatibleReleaseV1(active, decodeDeploymentCompatibilityV1(compatibilityValue));
383
384
  }
384
385
  function siteIdForConfig(config) {
385
386
  return config.siteId || process.env.ACI_SITE_ID || '';
386
387
  }
387
- async function getEdgeConfigJSON(edgeConfig, key) {
388
- const value = await getEdgeConfigValue(edgeConfig, key);
389
- if (!value)
390
- return null;
391
- if (typeof value === 'string') {
392
- try {
393
- return JSON.parse(value);
394
- }
395
- catch {
396
- return null;
397
- }
398
- }
399
- return value;
400
- }
401
388
  async function getEdgeConfigValue(edgeConfig, key) {
402
389
  return getUncachedEdgeConfigValue(edgeConfig, key);
403
390
  }
404
- function activeReleaseKey(siteId) {
405
- return `activeRelease_${edgeKeySegment(siteId)}`;
406
- }
407
- function deploymentCompatibilityKey(siteId, deploymentId) {
408
- return `deploymentCompatibility_${edgeKeySegment(siteId)}_${edgeKeySegment(deploymentId)}`;
409
- }
410
391
  async function activeReleasePointer(edgeConfig, siteId) {
411
- const value = await getEdgeConfigValue(edgeConfig, activeReleaseKey(siteId));
412
- if (!value)
413
- return { releaseId: '' };
414
- if (typeof value === 'string') {
415
- try {
416
- const parsed = JSON.parse(value);
417
- if (parsed.releaseId)
418
- return parsed;
419
- }
420
- catch {
421
- return { releaseId: value };
422
- }
423
- return { releaseId: value };
424
- }
425
- const pointer = value;
426
- return { releaseId: pointer.releaseId || '', sequence: pointer.sequence };
427
- }
428
- function clampRelease(activeReleaseId, activeSequence, compatibility) {
429
- if (!activeSequence)
430
- return activeReleaseId;
431
- if (compatibility.minSequence && activeSequence < compatibility.minSequence && compatibility.minReleaseId) {
432
- return compatibility.minReleaseId;
433
- }
434
- if (compatibility.maxSequence && activeSequence > compatibility.maxSequence && compatibility.maxReleaseId) {
435
- return compatibility.maxReleaseId;
436
- }
437
- return activeReleaseId;
438
- }
439
- function edgeKeySegment(value) {
440
- return value.replace(/[^A-Za-z0-9_-]/g, '_');
392
+ const value = await getEdgeConfigValue(edgeConfig, activeReleaseV1Key(siteId));
393
+ if (value == null)
394
+ return null;
395
+ return decodeActiveReleaseV1(value);
441
396
  }
442
397
  function isPassthroughRoute(pathname) {
443
398
  return PASSTHROUGH_ROUTES.some((route) => routeMatches(pathname, route));
package/dist/next/page.js CHANGED
@@ -6,6 +6,7 @@ import { routeMetadataForContent, resolveSlots, } from '../content/index.js';
6
6
  import { renderBlocks } from './render.js';
7
7
  import { AssignmentsScript } from './assignments-script.js';
8
8
  import { CollectScript } from './collect-script.js';
9
+ import { renderProofContentFor } from '../internal/render-proof.js';
9
10
  /**
10
11
  * Creates the catch-all route handler for a Next.js ACI site.
11
12
  *
@@ -64,12 +65,14 @@ export function createPage(registry, options) {
64
65
  }
65
66
  async function CatchAllPage({ params }) {
66
67
  const { route, releaseId } = await resolveRouteAndRelease(params);
68
+ const fetchConfig = { releaseId };
67
69
  try {
68
- const input = await getRenderInput(route, { releaseId });
70
+ const input = await getRenderInput(route, fetchConfig);
69
71
  if (!input.page)
70
72
  notFound();
71
- const layout = await getLayout(input.page.layout);
72
- const resolvedSlots = await resolveSlots(layout, input.page.regions || {}, (id) => getFragment(id));
73
+ const renderProofContent = renderProofContentFor(input);
74
+ const layout = await getLayout(input.page.layout, fetchConfig);
75
+ const resolvedSlots = await resolveSlots(layout, input.page.regions || {}, (id) => getFragment(id, fetchConfig));
73
76
  const renderedSlots = await Promise.all(layout.slots.map(async (slot) => {
74
77
  const blocks = resolvedSlots[slot.name];
75
78
  if (!blocks?.length)
@@ -81,7 +84,9 @@ export function createPage(registry, options) {
81
84
  }
82
85
  return (_jsx(Fragment, { children: renderedBlocks }, slot.name));
83
86
  }));
84
- return (_jsxs(_Fragment, { children: [renderedSlots.filter(Boolean), _jsx(AssignmentsScript, { page: input.page }), _jsx(CollectScript, { siteConfig: input.siteConfig })] }));
87
+ return (_jsxs(_Fragment, { children: [renderProofContent
88
+ ? _jsx("meta", { name: "aci-render-proof", content: renderProofContent })
89
+ : null, renderedSlots.filter(Boolean), _jsx(AssignmentsScript, { page: input.page }), _jsx(CollectScript, { siteConfig: input.siteConfig })] }));
85
90
  }
86
91
  catch (error) {
87
92
  if (error instanceof PageNotFoundError)
@@ -29,12 +29,17 @@ export interface EdgeRouteEntry {
29
29
  dispatch: EdgeDispatch;
30
30
  variants?: Record<string, EdgeRouteVariant>;
31
31
  }
32
+ export interface CollectSDKEntry {
33
+ hash: string;
34
+ path: string;
35
+ }
32
36
  export interface EdgeRouteManifest {
33
37
  manifestVersion: string;
34
38
  releaseId: string;
35
39
  siteId: string;
36
40
  routes: Record<string, EdgeRouteEntry>;
37
41
  experiments?: Record<string, PersonalizationExperiment>;
42
+ collectSdk?: CollectSDKEntry;
38
43
  notFoundPage?: string;
39
44
  errorPage?: string;
40
45
  }