@gradial/aci 0.1.45 → 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.
Files changed (39) hide show
  1. package/dist/audience-data-models.d.ts +142 -2
  2. package/dist/audience-data-models.js +2 -0
  3. package/dist/cli/compile-registry.d.mts +1 -5
  4. package/dist/cli/compile-registry.mjs +10 -25
  5. package/dist/cli/validate-content.d.mts +1 -31
  6. package/dist/cli/validate-content.mjs +12 -50
  7. package/dist/cli/verify-next-cdn-routes.mjs +1 -0
  8. package/dist/content/provider.d.ts +1 -1
  9. package/dist/content/resolve-slots.js +0 -1
  10. package/dist/content/types.d.ts +0 -2
  11. package/dist/internal/render-proof.d.ts +1 -0
  12. package/dist/internal/render-proof.js +197 -0
  13. package/dist/next/middleware.js +9 -54
  14. package/dist/next/page.js +9 -4
  15. package/dist/next/personalization-host.d.ts +5 -0
  16. package/dist/next/server.js +88 -75
  17. package/dist/next/vercel-collect-sdk-route.js +6 -30
  18. package/dist/next/vercel-edge-projection-v1.d.ts +24 -0
  19. package/dist/next/vercel-edge-projection-v1.js +880 -0
  20. package/dist/next/vercel-s3-personalization.js +7 -11
  21. package/dist/providers/s3.d.ts +2 -4
  22. package/dist/providers/s3.js +173 -19
  23. package/dist/types/block-ref.d.ts +0 -2
  24. package/dist/types/block-ref.js +0 -1
  25. package/package.json +2 -2
  26. package/src/cli/compile-registry.mjs +10 -24
  27. package/src/cli/validate-content.mjs +13 -52
  28. package/src/cli/verify-next-cdn-routes.mjs +6 -0
  29. package/src/types/block-ref.ts +0 -2
  30. package/templates/astro/starter-content/src/fixtures/atomicBlocks.ts +6 -7
  31. package/templates/astro/starter-content/src/fixtures/composition.ts +2 -3
  32. package/templates/astro/starter-content/src/fixtures/sections.ts +1 -2
  33. package/templates/common/.agents/skills/authoring/SKILL.md +3 -11
  34. package/templates/common/.agents/skills/authoring/references/block-slots.md +0 -3
  35. package/templates/common/.content/pages/home/_index.json +0 -10
  36. package/templates/common/starter-content/.content/pages/about/_index.json +6 -6
  37. package/templates/common/starter-content/.content/pages/home/_index.json +38 -22
  38. package/src/types/block-ref.test.ts +0 -45
  39. package/templates/nextjs/template/src/app/collect/route.ts +0 -18
@@ -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({
@@ -1,6 +1,2 @@
1
1
  #!/usr/bin/env node
2
- export function compileRegistry(options: any): Promise<{
3
- registry: string;
4
- schemas: string[];
5
- }>;
6
- export function generatePageSchema(schemaDir: any, componentEntries: any, outDir: any): Promise<string>;
2
+ export {};
@@ -1,8 +1,7 @@
1
1
  #!/usr/bin/env node
2
- import { realpathSync } from 'node:fs';
3
2
  import { mkdir, writeFile } from 'node:fs/promises';
4
3
  import path from 'node:path';
5
- import { fileURLToPath, pathToFileURL } from 'node:url';
4
+ import { pathToFileURL } from 'node:url';
6
5
  import { z } from 'zod';
7
6
  import { ImageSchema } from '../types/image.ts';
8
7
  import { VideoSchema } from '../types/video.ts';
@@ -19,16 +18,14 @@ try {
19
18
  globalThis.React = React;
20
19
  }
21
20
  catch { /* react not available — component files without JSX will still work */ }
22
- if (isMainModule()) {
23
- const args = parseArgs(process.argv.slice(2));
24
- try {
25
- const result = await compileRegistry(args);
26
- console.log(JSON.stringify({ success: true, ...result, errors: [] }, null, 2));
27
- }
28
- catch (error) {
29
- console.error(JSON.stringify({ success: false, errors: [String(error?.message ?? error)] }, null, 2));
30
- process.exitCode = 1;
31
- }
21
+ const args = parseArgs(process.argv.slice(2));
22
+ try {
23
+ const result = await compileRegistry(args);
24
+ console.log(JSON.stringify({ success: true, ...result, errors: [] }, null, 2));
25
+ }
26
+ catch (error) {
27
+ console.error(JSON.stringify({ success: false, errors: [String(error?.message ?? error)] }, null, 2));
28
+ process.exitCode = 1;
32
29
  }
33
30
  async function compileRegistry(options) {
34
31
  const required = ['components', 'layouts', 'output'];
@@ -443,10 +440,9 @@ async function generatePageSchema(schemaDir, componentEntries, outDir) {
443
440
  const defs = {
444
441
  baseBlock: {
445
442
  type: 'object',
446
- required: ['$type', 'id', 'component', 'props'],
443
+ required: ['id', 'component', 'props'],
447
444
  additionalProperties: true,
448
445
  properties: {
449
- $type: { const: 'component' },
450
446
  id: { type: 'string', minLength: 1 },
451
447
  component: { type: 'string' },
452
448
  props: { type: 'object' },
@@ -761,14 +757,3 @@ function parseArgs(argv) {
761
757
  }
762
758
  return out;
763
759
  }
764
- function isMainModule() {
765
- if (!process.argv[1])
766
- return false;
767
- try {
768
- return realpathSync(fileURLToPath(import.meta.url)) === realpathSync(path.resolve(process.argv[1]));
769
- }
770
- catch {
771
- return false;
772
- }
773
- }
774
- export { compileRegistry, generatePageSchema };
@@ -1,32 +1,2 @@
1
1
  #!/usr/bin/env node
2
- export function validateContent(options: any): Promise<{
3
- warnings?: any[] | undefined;
4
- success: boolean;
5
- pagesValidated: number;
6
- blocksValidated: number;
7
- errors: ({
8
- page: string;
9
- message: string;
10
- fragment?: undefined;
11
- component?: undefined;
12
- path?: undefined;
13
- } | {
14
- fragment: string;
15
- message: string;
16
- page?: undefined;
17
- component?: undefined;
18
- path?: undefined;
19
- } | {
20
- fragment: string;
21
- component: any;
22
- message: string;
23
- page?: undefined;
24
- path?: undefined;
25
- } | {
26
- fragment: string;
27
- component: any;
28
- path: any;
29
- message: any;
30
- page?: undefined;
31
- })[];
32
- }>;
2
+ export {};
@@ -19,25 +19,21 @@
19
19
  * Uses the JSON schemas generated by compile-registry — portable, no React/CSS
20
20
  * imports needed, same schemas usable by Go backend or other tools.
21
21
  */
22
- import { realpathSync } from 'node:fs';
23
22
  import { readdir, readFile } from 'node:fs/promises';
24
23
  import path from 'node:path';
25
- import { fileURLToPath } from 'node:url';
26
- if (isMainModule()) {
27
- const args = parseArgs(process.argv.slice(2));
28
- try {
29
- const result = await validateContent(args);
30
- console.log(JSON.stringify(result, null, 2));
31
- if (!result.success)
32
- process.exitCode = 1;
33
- }
34
- catch (error) {
35
- console.error(JSON.stringify({
36
- success: false,
37
- errors: [{ message: String(error?.message ?? error) }],
38
- }, null, 2));
24
+ const args = parseArgs(process.argv.slice(2));
25
+ try {
26
+ const result = await validateContent(args);
27
+ console.log(JSON.stringify(result, null, 2));
28
+ if (!result.success)
39
29
  process.exitCode = 1;
40
- }
30
+ }
31
+ catch (error) {
32
+ console.error(JSON.stringify({
33
+ success: false,
34
+ errors: [{ message: String(error?.message ?? error) }],
35
+ }, null, 2));
36
+ process.exitCode = 1;
41
37
  }
42
38
  // ---------------------------------------------------------------------------
43
39
  // Main
@@ -90,22 +86,6 @@ async function validateContent(options) {
90
86
  const blocks = collectBlocks(pageData);
91
87
  for (const block of blocks) {
92
88
  blocksValidated++;
93
- if (!block || typeof block !== 'object') {
94
- collector.push({
95
- page: relativePath,
96
- blockId: '<unknown>',
97
- message: 'page region entry must be a component block',
98
- });
99
- continue;
100
- }
101
- if (block.$type !== 'component') {
102
- collector.push({
103
- page: relativePath,
104
- blockId: block.id ?? '<unknown>',
105
- path: '$type',
106
- message: 'block "$type" must be "component"',
107
- });
108
- }
109
89
  if (!block.component) {
110
90
  collector.push({
111
91
  page: relativePath,
@@ -368,13 +348,6 @@ function validateNestedSlots(parentBlock, componentMap, schemaMap, path = 'props
368
348
  issues.push({ path: nestedPath, message: `${parentBlock.component}: block ref child must be a block` });
369
349
  continue;
370
350
  }
371
- if (nestedBlock.$type !== 'component') {
372
- issues.push({
373
- path: `${nestedPath}.$type`,
374
- message: `${parentBlock.component}: block ref child "$type" must be "component"`,
375
- });
376
- continue;
377
- }
378
351
  if (!componentMap.has(nestedBlock.component)) {
379
352
  issues.push({ path: `${nestedPath}.component`, message: `${parentBlock.component}: unknown nested component "${nestedBlock.component}"` });
380
353
  continue;
@@ -451,14 +424,3 @@ function parseArgs(argv) {
451
424
  }
452
425
  return out;
453
426
  }
454
- function isMainModule() {
455
- if (!process.argv[1])
456
- return false;
457
- try {
458
- return realpathSync(fileURLToPath(import.meta.url)) === realpathSync(path.resolve(process.argv[1]));
459
- }
460
- catch {
461
- return false;
462
- }
463
- }
464
- export { validateContent };
@@ -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 {
@@ -12,7 +12,6 @@ export async function resolveSlots(layout, pageRegions, getFragment) {
12
12
  for (const ref of defaults) {
13
13
  const fragment = await getFragment(ref.fragmentId);
14
14
  blocks.push({
15
- $type: 'component',
16
15
  id: fragment.id,
17
16
  component: fragment.component,
18
17
  props: fragment.props || {},
@@ -42,13 +42,11 @@ export interface KernelPageSeo {
42
42
  canonical?: string;
43
43
  }
44
44
  export interface KernelPageBlock {
45
- $type: 'component';
46
45
  id: string;
47
46
  component: string;
48
47
  props: Record<string, unknown>;
49
48
  }
50
49
  export interface KernelAtomicBlock {
51
- $type: 'component';
52
50
  id: string;
53
51
  component: string;
54
52
  props: Record<string, unknown>;
@@ -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
+ }