@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 +10 -0
- package/README.md +4 -4
- package/dist/audience-data-models.d.ts +142 -2
- package/dist/audience-data-models.js +2 -0
- package/dist/cli/verify-next-cdn-routes.mjs +1 -0
- package/dist/content/provider.d.ts +1 -1
- package/dist/internal/render-proof.d.ts +1 -0
- package/dist/internal/render-proof.js +197 -0
- package/dist/next/middleware.js +9 -54
- package/dist/next/page.js +9 -4
- package/dist/next/personalization-host.d.ts +5 -0
- package/dist/next/server.js +88 -75
- package/dist/next/vercel-collect-sdk-route.js +6 -30
- package/dist/next/vercel-edge-projection-v1.d.ts +24 -0
- package/dist/next/vercel-edge-projection-v1.js +880 -0
- package/dist/next/vercel-s3-personalization.js +7 -11
- package/dist/providers/s3.d.ts +2 -4
- package/dist/providers/s3.js +173 -19
- package/package.json +8 -8
- package/src/cli/verify-next-cdn-routes.mjs +6 -0
- package/templates/common/starter-content/.content/pages/about/_index.json +6 -3
- package/templates/common/starter-content/.content/pages/home/_index.json +38 -13
- package/templates/nextjs/template/src/app/collect/route.ts +0 -18
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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);
|
|
@@ -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
|
+
}
|
package/dist/next/middleware.js
CHANGED
|
@@ -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 (
|
|
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
|
|
380
|
-
if (
|
|
380
|
+
const compatibilityValue = await getEdgeConfigValue(edgeConfig, deploymentCompatibilityKey(siteId, deploymentId));
|
|
381
|
+
if (compatibilityValue == null)
|
|
381
382
|
return active.releaseId;
|
|
382
|
-
return
|
|
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,
|
|
412
|
-
if (
|
|
413
|
-
return
|
|
414
|
-
|
|
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,
|
|
70
|
+
const input = await getRenderInput(route, fetchConfig);
|
|
69
71
|
if (!input.page)
|
|
70
72
|
notFound();
|
|
71
|
-
const
|
|
72
|
-
const
|
|
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: [
|
|
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
|
}
|