@hyprcart/app-manifest 1.0.0 → 1.0.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.
package/dist/index.js CHANGED
@@ -3,16 +3,331 @@ var supportedSurfaces = [
3
3
  "storefront_block",
4
4
  "admin_surface",
5
5
  "checkout_ui",
6
+ "deterministic_function",
6
7
  "checkout_function",
7
8
  "pixel",
9
+ "event_subscription",
10
+ "background_job",
8
11
  "event",
9
12
  "flow",
10
13
  "mcp"
11
14
  ];
15
+ var storefrontBlockRenderTiers = ["static", "edge_render", "edge_stream"];
16
+ var legacyStorefrontBlockRenderTiers = ["edge-function", "edge-stream"];
17
+ var hydrationTriggers = ["load", "visible", "interaction", "idle", "manual"];
12
18
  var defaultRequiredLocales = ["en", "de"];
13
19
  function isSupportedSurface(value) {
14
20
  return supportedSurfaces.includes(value);
15
21
  }
22
+ function normalizeStorefrontBlockRenderTier(value) {
23
+ if (value === "edge-function") return "edge_render";
24
+ if (value === "edge-stream") return "edge_stream";
25
+ if (storefrontBlockRenderTiers.includes(value)) return value;
26
+ return "static";
27
+ }
28
+
29
+ // src/customer-privacy/privacy-manifest.ts
30
+ import * as z from "zod";
31
+ import { en } from "zod/locales";
32
+ z.config(en());
33
+ var key = z.string().min(1).max(120).regex(/^[a-zA-Z0-9._:-]+$/);
34
+ var messageKey = z.string().min(1).max(160).regex(/^[a-zA-Z0-9._-]+$/);
35
+ var safeEndpoint = z.string().url().refine((value) => {
36
+ return value.startsWith("https://") && !/^https:\/\/[^/]*@/u.test(value);
37
+ }, "privacy.endpoint.invalid");
38
+ var terminalAccessSchema = z.object({
39
+ type: z.enum([
40
+ "cookie",
41
+ "local_storage",
42
+ "session_storage",
43
+ "indexed_db",
44
+ "cache_storage",
45
+ "device_access",
46
+ "memory"
47
+ ]),
48
+ key,
49
+ durationSeconds: z.number().int().nonnegative().max(315576e3).nullable(),
50
+ removable: z.boolean()
51
+ }).strict();
52
+ var recipientSchema = z.object({
53
+ id: key,
54
+ endpoint: safeEndpoint,
55
+ retentionDays: z.number().int().nonnegative().max(3650)
56
+ }).strict();
57
+ var boundedOperationSchema = z.object({
58
+ id: key,
59
+ eventClasses: z.array(key).min(1).max(100),
60
+ fieldKeys: z.array(key).min(1).max(100),
61
+ limits: z.object({
62
+ eventCount: z.number().int().positive().max(1e4),
63
+ byteCount: z.number().int().positive().max(1e7),
64
+ maximumAgeSeconds: z.number().int().positive().max(86400)
65
+ }).strict(),
66
+ cleanupTriggers: z.array(key).min(1).max(32)
67
+ }).strict();
68
+ var privacyManifestCapabilitySchema = z.object({
69
+ key,
70
+ type: z.enum([
71
+ "app_block",
72
+ "client_island",
73
+ "checkout_extension",
74
+ "pixel",
75
+ "server_event",
76
+ "background_delivery",
77
+ "analytics_destination",
78
+ "platform_feature"
79
+ ]),
80
+ purposes: z.array(key).min(1).max(16),
81
+ dataCategories: z.array(key).min(1).max(32),
82
+ terminalAccess: z.array(terminalAccessSchema).max(32),
83
+ recipients: z.array(recipientSchema).max(32),
84
+ boundedOperations: z.array(boundedOperationSchema).max(16).optional(),
85
+ regions: z.array(z.string().min(1).max(16)).min(1).max(64),
86
+ fallbackMessageKey: messageKey,
87
+ cleanup: z.object({
88
+ supported: z.boolean(),
89
+ callback: key.nullable().optional()
90
+ }).strict(),
91
+ exportSupport: z.enum(["supported", "unsupported", "not_applicable"]),
92
+ erasureSupport: z.enum(["supported", "unsupported", "not_applicable"]),
93
+ strictNecessity: z.object({
94
+ claimed: z.boolean(),
95
+ justification: z.string().min(1).max(2e3),
96
+ platformApprovalId: key.nullable()
97
+ }).strict().optional()
98
+ }).strict();
99
+ var privacyManifestSchema = z.object({
100
+ schemaVersion: z.literal("2026-07-01"),
101
+ messageKeys: z.object({
102
+ benefit: messageKey,
103
+ refusal: messageKey,
104
+ fallback: messageKey
105
+ }).strict(),
106
+ capabilities: z.array(privacyManifestCapabilitySchema).max(64),
107
+ removed: z.literal(true).optional()
108
+ }).strict().superRefine((value, context) => {
109
+ if (value.removed !== true && value.capabilities.length === 0) {
110
+ context.addIssue({
111
+ code: "custom",
112
+ message: "privacy.manifest.capability_required",
113
+ path: ["capabilities"]
114
+ });
115
+ }
116
+ });
117
+ function validatePrivacyManifest(input, catalogs, options = {}) {
118
+ const parsed = privacyManifestSchema.safeParse(input);
119
+ const issues = [];
120
+ if (!parsed.success) {
121
+ for (const issue5 of parsed.error.issues) {
122
+ const code = issue5.message === "privacy.endpoint.invalid" ? "privacy.endpoint.invalid" : "privacy.manifest.invalid";
123
+ issues.push({ code, path: issue5.path.join(".") });
124
+ }
125
+ }
126
+ const candidate = parsed.success ? parsed.data : input && typeof input === "object" ? input : null;
127
+ if (candidate?.capabilities) {
128
+ for (const [index, capability] of candidate.capabilities.entries()) {
129
+ if (capability.strictNecessity?.claimed && !capability.strictNecessity.platformApprovalId && !options.allowPendingNecessityReview) {
130
+ issues.push({
131
+ code: "privacy.necessity.approval_required",
132
+ path: `capabilities.${index}.strictNecessity.platformApprovalId`
133
+ });
134
+ }
135
+ }
136
+ }
137
+ const requiredKeys = candidate?.messageKeys ? Object.values(candidate.messageKeys) : [];
138
+ for (const capability of candidate?.capabilities ?? []) {
139
+ if (capability.fallbackMessageKey)
140
+ requiredKeys.push(capability.fallbackMessageKey);
141
+ }
142
+ for (const [locale, catalog] of Object.entries(catalogs)) {
143
+ for (const requiredKey of new Set(requiredKeys)) {
144
+ if (!catalog[requiredKey]?.trim()) {
145
+ issues.push({
146
+ code: "privacy.message.missing",
147
+ path: `${locale}.${requiredKey}`
148
+ });
149
+ }
150
+ }
151
+ }
152
+ return { valid: issues.length === 0, issues };
153
+ }
154
+ function classifyPrivacyManifestChange(previous, next) {
155
+ const before = privacyManifestSchema.parse(previous);
156
+ const after = privacyManifestSchema.parse(next);
157
+ const reasons = /* @__PURE__ */ new Set();
158
+ const byKey = new Map(
159
+ before.capabilities.map((capability) => [capability.key, capability])
160
+ );
161
+ const afterKeys = new Set(
162
+ after.capabilities.map((capability) => capability.key)
163
+ );
164
+ if (before.removed !== after.removed || before.capabilities.some((capability) => !afterKeys.has(capability.key))) {
165
+ reasons.add("capabilities");
166
+ }
167
+ for (const capability of after.capabilities) {
168
+ const prior = byKey.get(capability.key);
169
+ if (!prior) {
170
+ reasons.add("capabilities");
171
+ continue;
172
+ }
173
+ for (const field of [
174
+ "purposes",
175
+ "dataCategories",
176
+ "terminalAccess",
177
+ "recipients",
178
+ "regions",
179
+ "boundedOperations"
180
+ ]) {
181
+ if (JSON.stringify(prior[field]) !== JSON.stringify(capability[field])) {
182
+ reasons.add(field === "dataCategories" ? "fields" : field);
183
+ }
184
+ }
185
+ }
186
+ return { material: reasons.size > 0, reasons: [...reasons].sort() };
187
+ }
188
+
189
+ // src/surface-contracts.ts
190
+ function validateExtensionSurfaceDeclarations(manifest) {
191
+ return (manifest.surfaces ?? []).map((surface, index) => validateExtensionSurface(surface, `surfaces.${index}`));
192
+ }
193
+ function validateExtensionSurface(surface, basePath = "surface") {
194
+ const diagnostics = [];
195
+ if (!surface.label || Object.keys(surface.label).length === 0) {
196
+ diagnostics.push(diagnostic("surface.label.missing", "blocking", `${basePath}.label`, "Surface must declare localized labels."));
197
+ }
198
+ if (!surface.entry?.trim()) {
199
+ diagnostics.push(diagnostic("surface.entry.missing", "blocking", `${basePath}.entry`, "Surface must declare an entry file."));
200
+ }
201
+ if (surface.type === "admin_surface") validateAdminSurface(surface, basePath, diagnostics);
202
+ if (surface.type === "checkout_ui") validateCheckoutSurface(surface, basePath, diagnostics);
203
+ if (surface.type === "deterministic_function" || surface.type === "checkout_function") validateDeterministicFunction(surface, basePath, diagnostics);
204
+ if (surface.type === "pixel") validatePixel(surface, basePath, diagnostics);
205
+ if (surface.type === "event_subscription") validateEventSubscription(surface, basePath, diagnostics);
206
+ if (surface.type === "background_job") validateBackgroundJob(surface, basePath, diagnostics);
207
+ if (surface.type === "flow" || surface.type === "event") {
208
+ diagnostics.push(
209
+ diagnostic(
210
+ "surface.flow.deferred_to_075a",
211
+ "warning",
212
+ `${basePath}.type`,
213
+ "Flow trigger/action declarations are owned by 075a and are not activated by 049c.",
214
+ "https://hyprcart.com/docs/app-platform/storefront-app-blocks-internal"
215
+ )
216
+ );
217
+ }
218
+ const blocking = diagnostics.some((entry) => entry.severity === "blocking");
219
+ return {
220
+ surfaceId: surface.id,
221
+ surfaceType: surface.type,
222
+ status: blocking ? "rejected" : surface.type === "storefront_block" ? "active" : surface.type === "flow" || surface.type === "event" ? "deferred" : "preview",
223
+ runtimeOwner: ownerForSurface(surface.type),
224
+ diagnostics
225
+ };
226
+ }
227
+ function validateAdminSurface(surface, basePath, diagnostics) {
228
+ if (!surface.adminSurfaceType) {
229
+ diagnostics.push(diagnostic("surface.admin.type_missing", "blocking", `${basePath}.adminSurfaceType`, "Admin surfaces must declare a specific admin surface type."));
230
+ }
231
+ if (!surface.placement && !surface.compatibleSlots?.length) {
232
+ diagnostics.push(diagnostic("surface.admin.placement_missing", "blocking", `${basePath}.placement`, "Admin surfaces must declare placement targets."));
233
+ }
234
+ if (!surface.scopes?.length) {
235
+ diagnostics.push(diagnostic("surface.admin.scopes_missing", "blocking", `${basePath}.scopes`, "Admin surfaces must declare required scopes."));
236
+ }
237
+ if (!surface.componentKit?.minVersion || !surface.componentKit.components.length) {
238
+ diagnostics.push(diagnostic("surface.admin.component_kit_missing", "blocking", `${basePath}.componentKit`, "Admin surfaces must declare Hyprcart admin component kit version and components."));
239
+ }
240
+ if (surface.adminSurfaceType === "admin_ui_block") {
241
+ const removal = new Set(surface.removalBehavior ?? []);
242
+ if (!removal.has("remove_on_disable") || !removal.has("remove_on_uninstall")) {
243
+ diagnostics.push(diagnostic("surface.admin_block.removal_missing", "blocking", `${basePath}.removalBehavior`, "Admin UI blocks must remove placements on disable and uninstall without tombstones."));
244
+ }
245
+ if (!surface.dimensions) {
246
+ diagnostics.push(diagnostic("surface.admin_block.dimensions_missing", "blocking", `${basePath}.dimensions`, "Admin UI blocks must reserve stable dimensions."));
247
+ }
248
+ }
249
+ if (!surface.inputSchema && !surface.outputSchema) {
250
+ diagnostics.push(diagnostic("surface.admin.schema_missing", "warning", `${basePath}.inputSchema`, "Admin surfaces should declare typed input or output schemas."));
251
+ }
252
+ if (!surface.sandboxPolicy) {
253
+ diagnostics.push(diagnostic("surface.admin.sandbox_missing", "warning", `${basePath}.sandboxPolicy`, "Admin surfaces should declare a sandbox policy."));
254
+ }
255
+ }
256
+ function validateCheckoutSurface(surface, basePath, diagnostics) {
257
+ const sandbox = surface.sandboxPolicy ?? {};
258
+ if (sandbox.allowArbitraryDom === true || sandbox.allowScripts === true || sandbox.allowPaymentBypass === true) {
259
+ diagnostics.push(diagnostic("surface.checkout.unsupported_behavior", "blocking", `${basePath}.sandboxPolicy`, "Checkout UI extensions cannot declare arbitrary DOM, scripts, or payment bypass behavior."));
260
+ }
261
+ if (!surface.placement && !surface.compatibleSlots?.length) {
262
+ diagnostics.push(diagnostic("surface.checkout.placement_missing", "blocking", `${basePath}.placement`, "Checkout UI extensions must declare placement targets."));
263
+ }
264
+ }
265
+ function validateDeterministicFunction(surface, basePath, diagnostics) {
266
+ if (surface.determinism?.networkAccess) {
267
+ diagnostics.push(diagnostic("surface.function.network_forbidden", "blocking", `${basePath}.determinism.networkAccess`, "Deterministic functions cannot use arbitrary network access."));
268
+ }
269
+ if (surface.determinism?.mutationSideEffects) {
270
+ diagnostics.push(diagnostic("surface.function.side_effects_forbidden", "blocking", `${basePath}.determinism.mutationSideEffects`, "Deterministic functions cannot perform direct mutation side effects."));
271
+ }
272
+ if (!surface.inputSchema || !surface.outputSchema) {
273
+ diagnostics.push(diagnostic("surface.function.schema_missing", "blocking", `${basePath}.inputSchema`, "Deterministic functions require typed input and output schemas."));
274
+ }
275
+ }
276
+ function validatePixel(surface, basePath, diagnostics) {
277
+ const sandbox = surface.sandboxPolicy ?? {};
278
+ if (sandbox.domRead === true || sandbox.domWrite === true || sandbox.allowDomScrape === true || sandbox.allowDomWrite === true) {
279
+ diagnostics.push(diagnostic("surface.pixel.dom_access_forbidden", "blocking", `${basePath}.sandboxPolicy`, "Pixels cannot scrape or write storefront DOM."));
280
+ }
281
+ if (surface.consent?.bypass === true || sandbox.consentBypass === true) {
282
+ diagnostics.push(diagnostic("surface.pixel.consent_bypass_forbidden", "blocking", `${basePath}.consent`, "Pixels cannot bypass consent checks."));
283
+ }
284
+ if (!surface.consent?.category) {
285
+ diagnostics.push(diagnostic("surface.pixel.consent_missing", "blocking", `${basePath}.consent.category`, "Pixels must declare a consent category."));
286
+ }
287
+ if (!surface.consent?.events?.length) {
288
+ diagnostics.push(diagnostic("surface.pixel.events_missing", "blocking", `${basePath}.consent.events`, "Pixels must declare events."));
289
+ }
290
+ if (!surface.consent?.destinations?.length) {
291
+ diagnostics.push(diagnostic("surface.pixel.destinations_missing", "blocking", `${basePath}.consent.destinations`, "Pixels must declare destinations."));
292
+ }
293
+ if (!surface.consent?.regions?.length) {
294
+ diagnostics.push(diagnostic("surface.pixel.regions_missing", "warning", `${basePath}.consent.regions`, "Pixels should declare regional availability."));
295
+ }
296
+ const fields = surface.consent?.fields ?? [];
297
+ if (!fields.length || fields.some((field) => /email|phone|name|address|token|secret|session|payment|card/i.test(field))) {
298
+ diagnostics.push(diagnostic("surface.pixel.field_selection_invalid", "blocking", `${basePath}.consent.fields`, "Pixels must declare minimized buyer-safe fields only."));
299
+ }
300
+ }
301
+ function validateEventSubscription(surface, basePath, diagnostics) {
302
+ if (!surface.consent?.events?.length && !surface.requiredContexts?.length) {
303
+ diagnostics.push(diagnostic("surface.event.events_missing", "blocking", `${basePath}.requiredContexts`, "Event subscriptions must declare subscribed event names."));
304
+ }
305
+ if (!surface.scopes?.length) {
306
+ diagnostics.push(diagnostic("surface.event.scopes_missing", "blocking", `${basePath}.scopes`, "Event subscriptions must declare required scopes."));
307
+ }
308
+ }
309
+ function validateBackgroundJob(surface, basePath, diagnostics) {
310
+ if (!surface.retryPolicy) {
311
+ diagnostics.push(diagnostic("surface.background_job.retry_missing", "blocking", `${basePath}.retryPolicy`, "Background jobs must declare bounded retry policy."));
312
+ }
313
+ if ((surface.adminSurfaceType === "bulk_action" || surface.type === "background_job") && surface.overlapPolicy === void 0) {
314
+ diagnostics.push(diagnostic("surface.background_job.overlap_missing", "blocking", `${basePath}.overlapPolicy`, "Background jobs must declare overlap policy."));
315
+ }
316
+ if (!surface.quotaClass) {
317
+ diagnostics.push(diagnostic("surface.background_job.quota_missing", "warning", `${basePath}.quotaClass`, "Background jobs should declare a quota class."));
318
+ }
319
+ }
320
+ function ownerForSurface(surface) {
321
+ if (surface === "storefront_block") return "049c";
322
+ if (surface === "flow" || surface === "event") return "075a";
323
+ if (surface === "admin_surface" || surface === "checkout_ui" || surface === "checkout_function" || surface === "deterministic_function" || surface === "pixel" || surface === "event_subscription" || surface === "background_job") {
324
+ return "049e";
325
+ }
326
+ return "deferred";
327
+ }
328
+ function diagnostic(code, severity, path, message, docsUrl) {
329
+ return { code, severity, path, message, docsUrl };
330
+ }
16
331
 
17
332
  // src/validation.ts
18
333
  var riskyDependencyPatterns = [/child_process/i, /node:fs/i, /eval\(/i];
@@ -66,7 +381,25 @@ function validateManifest(manifest, options = {}) {
66
381
  )
67
382
  );
68
383
  }
384
+ if (surface.type === "storefront_block") {
385
+ validateStorefrontBlockSurface(surface, basePath, issues);
386
+ }
69
387
  });
388
+ for (const surfaceResult of validateExtensionSurfaceDeclarations(manifest)) {
389
+ for (const diagnostic2 of surfaceResult.diagnostics) {
390
+ issues.push(
391
+ issue(
392
+ diagnostic2.code,
393
+ diagnostic2.severity === "blocking" ? "blocker" : diagnostic2.severity === "error" ? "error" : diagnostic2.severity === "warning" ? "warning" : "info",
394
+ diagnostic2.path,
395
+ diagnostic2.message,
396
+ void 0,
397
+ diagnostic2.docsUrl
398
+ )
399
+ );
400
+ }
401
+ }
402
+ validateCustomerPrivacyDeclarations(manifest, requiredLocales, issues);
70
403
  const translationStatus = getTranslationStatus(manifest, requiredLocales);
71
404
  for (const gap of translationStatus.missing) {
72
405
  issues.push(
@@ -115,6 +448,90 @@ function validateManifest(manifest, options = {}) {
115
448
  translationStatus
116
449
  };
117
450
  }
451
+ function validateCustomerPrivacyDeclarations(manifest, requiredLocales, issues) {
452
+ const relevantSurfaces = (manifest.surfaces ?? []).filter(
453
+ (surface) => surface.type === "pixel" || typeof surface.consent?.category === "string" && surface.consent.category !== "necessary" || (surface.clientIslands ?? []).some(
454
+ (island) => Boolean(island.consentCategory) && island.consentCategory !== "necessary"
455
+ )
456
+ );
457
+ if (relevantSurfaces.length > 0 && !manifest.customerPrivacy) {
458
+ issues.push(issue(
459
+ "privacy.manifest.required",
460
+ "blocker",
461
+ "customerPrivacy",
462
+ "Privacy-relevant app surfaces require a customer privacy manifest.",
463
+ "Declare each buyer-facing storage or processing capability."
464
+ ));
465
+ return;
466
+ }
467
+ if (!manifest.customerPrivacy) return;
468
+ const catalogs = Object.fromEntries(
469
+ requiredLocales.map((locale) => [
470
+ locale,
471
+ manifest.translations?.[locale] ?? {}
472
+ ])
473
+ );
474
+ const validation = validatePrivacyManifest(
475
+ manifest.customerPrivacy,
476
+ catalogs,
477
+ { allowPendingNecessityReview: true }
478
+ );
479
+ for (const privacyIssue of validation.issues) {
480
+ issues.push(issue(
481
+ privacyIssue.code,
482
+ "blocker",
483
+ `customerPrivacy.${privacyIssue.path}`,
484
+ "The customer privacy declaration is incomplete or invalid.",
485
+ "Correct the declaration and provide every localized buyer message."
486
+ ));
487
+ }
488
+ const declaredKeys = new Set(
489
+ manifest.customerPrivacy.capabilities.map((capability) => capability.key)
490
+ );
491
+ for (const surface of relevantSurfaces) {
492
+ if (!declaredKeys.has(surface.id)) {
493
+ issues.push(issue(
494
+ "privacy.surface.capability_missing",
495
+ "blocker",
496
+ `surfaces.${surface.id}.consent`,
497
+ `Privacy-relevant surface "${surface.id}" has no matching capability declaration.`,
498
+ `Add customerPrivacy.capabilities entry "${surface.id}".`
499
+ ));
500
+ }
501
+ }
502
+ }
503
+ function validateStorefrontBlockSurface(surface, basePath, issues) {
504
+ const renderTier = normalizeStorefrontBlockRenderTier(surface.renderTier ?? surface.tier);
505
+ if (renderTier === "edge_stream" && (!surface.placeholder?.width || !surface.placeholder?.height || !surface.placeholder?.skeleton)) {
506
+ issues.push(
507
+ issue(
508
+ "storefront_block.placeholder.missing",
509
+ "blocker",
510
+ `${basePath}.placeholder`,
511
+ "Streaming app blocks must declare placeholder width, height, and skeleton before install.",
512
+ "Add exact placeholder dimensions and skeleton type.",
513
+ "https://developer.hyprcart.com/docs/app-platform/surfaces"
514
+ )
515
+ );
516
+ }
517
+ for (const island of surface.clientIslands ?? []) {
518
+ if (island.compressedSizeBytes > 32 * 1024 && !island.exceptionId) {
519
+ issues.push(
520
+ issue(
521
+ "client_island.bundle_budget.exceeded",
522
+ "blocker",
523
+ `${basePath}.clientIslands`,
524
+ "Client island bundle exceeds the 32 KB compressed budget.",
525
+ "Reduce bundle size or request a platform exception.",
526
+ "https://developer.hyprcart.com/docs/app-platform/surfaces"
527
+ )
528
+ );
529
+ }
530
+ if (!island.domBoundarySelector?.startsWith("[data-hc-app-block")) {
531
+ issues.push(issue("client_island.dom_boundary.invalid", "blocker", `${basePath}.clientIslands`, "Client island DOM boundary must stay inside the app block wrapper."));
532
+ }
533
+ }
534
+ }
118
535
  function getTranslationStatus(manifest, requiredLocales) {
119
536
  const missing = [];
120
537
  const completeLocales = [];
@@ -147,27 +564,40 @@ function isCompatibleSdk(version, supportedMajor) {
147
564
  return Number.isFinite(major) && major === supportedMajor;
148
565
  }
149
566
  function hashObject(value) {
150
- const json = JSON.stringify(value, Object.keys(value).sort());
567
+ const json = stableStringify(value);
151
568
  let hash = 0;
152
569
  for (let index = 0; index < json.length; index += 1) {
153
570
  hash = hash * 31 + json.charCodeAt(index) >>> 0;
154
571
  }
155
572
  return `mf_${hash.toString(16).padStart(8, "0")}`;
156
573
  }
574
+ function stableStringify(value) {
575
+ return JSON.stringify(sortValue(value));
576
+ }
577
+ function sortValue(value) {
578
+ if (Array.isArray(value)) return value.map(sortValue);
579
+ if (!value || typeof value !== "object") return value;
580
+ return Object.fromEntries(
581
+ Object.entries(value).sort(([left], [right]) => left.localeCompare(right)).map(([key2, entry]) => [key2, sortValue(entry)])
582
+ );
583
+ }
157
584
 
158
585
  // src/contract-generation.ts
159
586
  function generateContracts(manifest, schemaVersion = "2026-06-06") {
160
587
  const surfaces = manifest.surfaces.map(renderSurfaceContract).join("\n\n");
588
+ const surfaceMetadata = validateExtensionSurfaceDeclarations(manifest);
161
589
  const content = [
162
590
  "/* Generated by Hyprcart app tooling. */",
163
591
  `export const schemaVersion = ${JSON.stringify(schemaVersion)};`,
164
592
  `export const sdkVersion = ${JSON.stringify(manifest.sdkVersion)};`,
593
+ `export const surfaceContracts = ${JSON.stringify(surfaceMetadata, null, 2)} as const;`,
165
594
  surfaces
166
595
  ].join("\n");
167
596
  return {
168
597
  manifestHash: stableHash(JSON.stringify(manifest)),
169
598
  schemaVersion,
170
599
  sdkVersion: manifest.sdkVersion,
600
+ surfaces: surfaceMetadata,
171
601
  files: [
172
602
  {
173
603
  path: "generated/hyprcart.contracts.ts",
@@ -266,15 +696,359 @@ var developerToolingOperationMetadata = [
266
696
  stability: "preview"
267
697
  }
268
698
  ];
699
+
700
+ // src/admin-component-kit.ts
701
+ var defaultSupportedComponents = [
702
+ "Page",
703
+ "Section",
704
+ "Stack",
705
+ "InlineStack",
706
+ "Grid",
707
+ "ResourcePanel",
708
+ "AdminBlock",
709
+ "Modal",
710
+ "Sheet",
711
+ "Text",
712
+ "Heading",
713
+ "Badge",
714
+ "Status",
715
+ "DescriptionList",
716
+ "DataTable",
717
+ "Timeline",
718
+ "Metric",
719
+ "EmptyState",
720
+ "Skeleton",
721
+ "Form",
722
+ "TextField",
723
+ "NumberField",
724
+ "Select",
725
+ "Combobox",
726
+ "Checkbox",
727
+ "RadioGroup",
728
+ "DatePicker",
729
+ "FilePicker",
730
+ "ResourcePicker",
731
+ "ValidationMessage",
732
+ "Button",
733
+ "IconButton",
734
+ "Banner",
735
+ "Toast",
736
+ "InlineError",
737
+ "Progress",
738
+ "Spinner",
739
+ "Tooltip",
740
+ "ConfirmDialog",
741
+ "TitleBar",
742
+ "Breadcrumbs",
743
+ "Tabs",
744
+ "ActionList",
745
+ "ContextualSaveBar",
746
+ "CommandAction",
747
+ "SearchResult"
748
+ ];
749
+ var supportedAdminComponentKitComponents = [...defaultSupportedComponents];
750
+ function validateAdminComponentKitManifest(input) {
751
+ const diagnostics = [];
752
+ if (!input.minVersion) {
753
+ diagnostics.push({ code: "admin_component_kit.version_missing", path: "componentKit.minVersion", message: "Admin component kit minimum version is required." });
754
+ }
755
+ const supported = new Set(input.supportedComponents ?? supportedAdminComponentKitComponents);
756
+ for (const component of input.components ?? []) {
757
+ if (!supported.has(component)) {
758
+ diagnostics.push({ code: "admin_component_kit.component_unsupported", path: "componentKit.components", message: `${component} is not an approved admin component.` });
759
+ }
760
+ }
761
+ for (const [key2, value] of Object.entries(input.tokens ?? {})) {
762
+ if (/(?:#[0-9a-f]{3,8}\b|rgba?\(|font|radius|shadow)/i.test(`${key2}:${String(value)}`)) {
763
+ diagnostics.push({ code: "admin_component_kit.token_forbidden", path: `componentKit.tokens.${key2}`, message: "Apps cannot declare custom visual tokens outside the platform kit." });
764
+ }
765
+ }
766
+ if (input.css && /(?:font-family|border-radius|box-shadow|position\s*:\s*fixed)/i.test(input.css)) {
767
+ diagnostics.push({ code: "admin_component_kit.css_forbidden", path: "componentKit.css", message: "Apps cannot inject global CSS or incompatible visual rules into admin surfaces." });
768
+ }
769
+ return { valid: diagnostics.length === 0, diagnostics };
770
+ }
771
+
772
+ // src/capability-declarations.ts
773
+ var appGovernanceCapabilityTypes = [
774
+ "api_operation",
775
+ "storefront_block",
776
+ "admin_surface",
777
+ "checkout_surface",
778
+ "pixel",
779
+ "event_subscription",
780
+ "mcp_tool",
781
+ "mcp_resource",
782
+ "mcp_resource_template",
783
+ "mcp_prompt",
784
+ "background_job"
785
+ ];
786
+ var appGovernanceDataClasses = ["public", "merchant_operational", "restricted_customer", "payment_adjacent", "auth_sensitive", "security", "operator_only"];
787
+ var appGovernanceRiskClasses = ["low", "medium", "high", "critical"];
788
+ var appGovernanceCostClasses = ["free", "low", "medium", "high", "operator"];
789
+ var appGovernanceApprovalPolicies = ["required", "optional", "operator_only", "automation_policy_required", "not_approvable"];
790
+ function validateAppCapabilityDeclarations(input) {
791
+ const issues = [];
792
+ const capabilityKeys = /* @__PURE__ */ new Set();
793
+ for (const [index, capability] of (input.capabilities ?? []).entries()) {
794
+ const path = `capabilities.${index}`;
795
+ if (!capability.key?.trim()) issues.push(issue2("capability.key.missing", `${path}.key`, "Capability key is required."));
796
+ if (capabilityKeys.has(capability.key)) issues.push(issue2("capability.key.duplicate", `${path}.key`, "Capability key must be unique."));
797
+ capabilityKeys.add(capability.key);
798
+ if (!isOneOf(capability.type, appGovernanceCapabilityTypes)) issues.push(issue2("capability.type.invalid", `${path}.type`, "Capability type is not supported."));
799
+ if (!isOneOf(capability.dataClass, appGovernanceDataClasses)) issues.push(issue2("capability.data_class.invalid", `${path}.dataClass`, "Data class is not supported."));
800
+ if (!isOneOf(capability.riskClass, appGovernanceRiskClasses)) issues.push(issue2("capability.risk_class.invalid", `${path}.riskClass`, "Risk class is not supported."));
801
+ if (!isOneOf(capability.costClass, appGovernanceCostClasses)) issues.push(issue2("capability.cost_class.invalid", `${path}.costClass`, "Cost class is not supported."));
802
+ if (!isOneOf(capability.approvalPolicy, appGovernanceApprovalPolicies)) issues.push(issue2("capability.approval_policy.invalid", `${path}.approvalPolicy`, "Approval policy is not supported."));
803
+ if (capability.mutating && capability.riskClass === "low") {
804
+ issues.push(issue2("capability.mutating.low_risk", `${path}.riskClass`, "Mutating capabilities must be medium risk or higher."));
805
+ }
806
+ }
807
+ for (const [index, egress] of (input.egress ?? []).entries()) {
808
+ const path = `egress.${index}`;
809
+ for (const host of egress.hosts ?? []) {
810
+ if (!isNormalizedHostPattern(host)) issues.push(issue2("egress.host.invalid", `${path}.hosts`, "Egress host must be normalized and must not contain credentials, path, query, or fragment."));
811
+ }
812
+ for (const capabilityKey of egress.requiredForCapabilities ?? []) {
813
+ if (!capabilityKeys.has(capabilityKey)) issues.push(issue2("egress.capability.unknown", `${path}.requiredForCapabilities`, `Unknown capability ${capabilityKey}.`));
814
+ }
815
+ }
816
+ for (const [index, retention] of (input.dataRetention ?? []).entries()) {
817
+ const path = `dataRetention.${index}`;
818
+ if (retention.merchantVisible !== true) {
819
+ issues.push(issue2("retention.visibility.required", `${path}.merchantVisible`, "Retention policy must be visible to merchants at install and uninstall."));
820
+ }
821
+ for (const capabilityKey of retention.appliesTo ?? []) {
822
+ if (!capabilityKeys.has(capabilityKey)) issues.push(issue2("retention.capability.unknown", `${path}.appliesTo`, `Unknown capability ${capabilityKey}.`));
823
+ }
824
+ }
825
+ return { valid: issues.length === 0, issues };
826
+ }
827
+ function isNormalizedHostPattern(host) {
828
+ if (!host || host.includes("@") || host.includes("/") || host.includes("?") || host.includes("#")) return false;
829
+ return /^[a-z0-9*.-]+(?::\d+)?$/i.test(host);
830
+ }
831
+ function isOneOf(value, values) {
832
+ return values.includes(value);
833
+ }
834
+ function issue2(code, path, message) {
835
+ return { code, path, message };
836
+ }
837
+
838
+ // src/ai-mcp-metadata.ts
839
+ var aiMcpDescriptorTypes = ["tool", "resource", "resource_template", "prompt"];
840
+ var aiMcpHumanApprovalPolicies = ["not_required", "required", "required_for_mutation", "automation_policy_allowed"];
841
+ var unsafeAuthorityPattern = /(ignore previous|system prompt|act as admin|bypass|override policy|grant .*scope|no approval)/i;
842
+ function validateAiMcpDescriptorMetadata(descriptors) {
843
+ const issues = [];
844
+ const names = /* @__PURE__ */ new Set();
845
+ descriptors.forEach((descriptor, index) => {
846
+ const path = `ai.mcp.${descriptor.descriptorType}.${index}`;
847
+ if (!descriptor.name?.trim()) issues.push(issue3("ai_mcp.name.missing", `${path}.name`, "Descriptor name is required."));
848
+ if (names.has(descriptor.name)) issues.push(issue3("ai_mcp.name.duplicate", `${path}.name`, "Descriptor name must be unique per app installation."));
849
+ names.add(descriptor.name);
850
+ if (!aiMcpDescriptorTypes.includes(descriptor.descriptorType)) issues.push(issue3("ai_mcp.type.invalid", `${path}.descriptorType`, "Descriptor type is not supported."));
851
+ if (!aiMcpHumanApprovalPolicies.includes(descriptor.humanApprovalPolicy)) issues.push(issue3("ai_mcp.approval.invalid", `${path}.humanApprovalPolicy`, "Human approval policy is not supported."));
852
+ if (descriptor.mutating && descriptor.humanApprovalPolicy === "not_required") {
853
+ issues.push(issue3("ai_mcp.mutating.approval_required", `${path}.humanApprovalPolicy`, "Mutating descriptors require human approval or automation policy."));
854
+ }
855
+ if (descriptor.resourceUriTemplate && /token|secret|bearer/i.test(descriptor.resourceUriTemplate)) {
856
+ issues.push(issue3("ai_mcp.resource_template.secret", `${path}.resourceUriTemplate`, "Resource URI templates must not embed tokens or secrets."));
857
+ }
858
+ for (const text of [descriptor.description, descriptor.promptTemplate].filter((value) => Boolean(value))) {
859
+ if (unsafeAuthorityPattern.test(text)) {
860
+ issues.push(issue3("ai_mcp.metadata.untrusted_authority", path, "Descriptor text is untrusted metadata and cannot claim platform authority."));
861
+ }
862
+ }
863
+ });
864
+ return { valid: issues.length === 0, issues };
865
+ }
866
+ function classifyAiMcpMetadataAuthority(text) {
867
+ return unsafeAuthorityPattern.test(text) ? "untrusted_authority_claim" : "safe_descriptor";
868
+ }
869
+ function issue3(code, path, message) {
870
+ return { code, path, message };
871
+ }
872
+
873
+ // src/launch-readiness.ts
874
+ var REQUIRED_LOCALES = ["en", "de"];
875
+ function validateLaunchReadinessManifest(manifest) {
876
+ const issues = [];
877
+ const launch = manifest.launch;
878
+ if (!launch) {
879
+ return issue4("launch.missing", "launch", "Launch readiness declaration is required.");
880
+ }
881
+ validateAdminApi(launch, issues);
882
+ validateAppData(launch, issues);
883
+ validateEnvironments(launch, issues);
884
+ validateSecrets(launch, issues);
885
+ validateSettings(launch, issues);
886
+ validateConsent(launch, issues);
887
+ validateLifecycleCallbacks(launch, issues);
888
+ validateAssets(launch, issues);
889
+ validateSandbox(launch, issues);
890
+ validateEvidence(launch, issues);
891
+ validateCompatibility(launch, issues);
892
+ return { valid: issues.length === 0, issues };
893
+ }
894
+ function assertLaunchReadinessManifest(manifest) {
895
+ const result = validateLaunchReadinessManifest(manifest);
896
+ if (!result.valid) {
897
+ throw new Error(result.issues.map((entry) => `${entry.code}:${entry.path}`).join(", "));
898
+ }
899
+ }
900
+ function validateAdminApi(launch, issues) {
901
+ if (!launch.adminApi?.minApiVersion) addIssue(issues, "launch.admin_api.missing", "launch.adminApi", "Admin API declaration is required.");
902
+ if (!launch.adminApi?.operations?.length) addIssue(issues, "launch.admin_api.operations_missing", "launch.adminApi.operations", "At least one Admin API operation declaration is required.");
903
+ }
904
+ function validateAppData(launch, issues) {
905
+ if (!launch.appData?.length) addIssue(issues, "launch.app_data.missing", "launch.appData", "App data declarations are required.");
906
+ for (const [index, declaration] of (launch.appData ?? []).entries()) {
907
+ if (!declaration.namespace) addIssue(issues, "launch.app_data.namespace_missing", `launch.appData.${index}.namespace`, "App data namespace is required.");
908
+ if (declaration.visibility === "storefront_public" && !declaration.storefrontMaterialization?.cacheTags?.length) {
909
+ addIssue(issues, "launch.app_data.cache_tags_missing", `launch.appData.${index}.storefrontMaterialization.cacheTags`, "Storefront-public app data requires cache tags.");
910
+ }
911
+ }
912
+ }
913
+ function validateEnvironments(launch, issues) {
914
+ const keys = new Set((launch.environments ?? []).map((environment) => environment.key));
915
+ for (const key2 of ["development", "staging", "production"]) {
916
+ if (!keys.has(key2)) addIssue(issues, "launch.environment.missing", `launch.environments.${key2}`, `${key2} environment is required.`);
917
+ }
918
+ const production = (launch.environments ?? []).find((environment) => environment.key === "production");
919
+ if (production && !production.promotionRequiresCertification) {
920
+ addIssue(issues, "launch.environment.production_certification", "launch.environments.production", "Production environment must require certification.");
921
+ }
922
+ }
923
+ function validateSecrets(launch, issues) {
924
+ if (!launch.secrets?.length) addIssue(issues, "launch.secrets.missing", "launch.secrets", "At least one secret declaration is required.");
925
+ for (const [index, secret] of (launch.secrets ?? []).entries()) {
926
+ if (secret.runtimeExposure === "runtime_only" && secret.key.toLowerCase().startsWith("next_public")) {
927
+ addIssue(issues, "launch.secrets.client_bundle", `launch.secrets.${index}.key`, "Secrets cannot target client bundles.");
928
+ }
929
+ }
930
+ }
931
+ function validateSettings(launch, issues) {
932
+ if (!launch.settings?.length) addIssue(issues, "launch.settings.missing", "launch.settings", "Merchant settings declaration is required.");
933
+ for (const [index, setting] of (launch.settings ?? []).entries()) {
934
+ for (const locale of REQUIRED_LOCALES) {
935
+ if (!setting.label[locale]) addIssue(issues, "launch.settings.locale_missing", `launch.settings.${index}.label.${locale}`, "Setting labels require English and German.");
936
+ }
937
+ }
938
+ }
939
+ function validateConsent(launch, issues) {
940
+ const consent = launch.consent;
941
+ if (!consent) {
942
+ addIssue(issues, "launch.consent.missing", "launch.consent", "Consent metadata is required.");
943
+ return;
944
+ }
945
+ for (const [field, value] of Object.entries({
946
+ appIcon: consent.appIcon,
947
+ developerName: consent.developerName,
948
+ supportUrl: consent.supportUrl,
949
+ privacyPolicyUrl: consent.privacyPolicyUrl,
950
+ termsUrl: consent.termsUrl,
951
+ reviewerInstructions: consent.reviewerInstructions
952
+ })) {
953
+ if (!value) addIssue(issues, "launch.consent.field_missing", `launch.consent.${field}`, "Consent metadata field is required.");
954
+ }
955
+ if (!consent.screenshots.length) addIssue(issues, "launch.consent.screenshots_missing", "launch.consent.screenshots", "At least one screenshot is required.");
956
+ if (!consent.dataCategories.length) addIssue(issues, "launch.consent.data_categories_missing", "launch.consent.dataCategories", "Data categories are required.");
957
+ }
958
+ function validateLifecycleCallbacks(launch, issues) {
959
+ if (!launch.lifecycleCallbacks?.length) addIssue(issues, "launch.callbacks.missing", "launch.lifecycleCallbacks", "Lifecycle callback declarations are required.");
960
+ }
961
+ function validateAssets(launch, issues) {
962
+ const kinds = new Set((launch.assets ?? []).map((asset) => asset.kind));
963
+ for (const kind of ["icon", "screenshot"]) {
964
+ if (!kinds.has(kind)) addIssue(issues, "launch.assets.required_missing", `launch.assets.${kind}`, `${kind} asset is required.`);
965
+ }
966
+ }
967
+ function validateSandbox(launch, issues) {
968
+ if (!launch.sandbox?.fixturePackId) addIssue(issues, "launch.sandbox.missing", "launch.sandbox", "Sandbox fixture declaration is required.");
969
+ if (launch.sandbox && !launch.sandbox.anonymizationRequired) {
970
+ addIssue(issues, "launch.sandbox.anonymization_required", "launch.sandbox.anonymizationRequired", "Sandbox fixtures must require anonymization.");
971
+ }
972
+ }
973
+ function validateEvidence(launch, issues) {
974
+ if (!launch.evidence?.testCommand) addIssue(issues, "launch.evidence.test_missing", "launch.evidence.testCommand", "Evidence test command is required.");
975
+ if (!launch.evidence?.buildCommand) addIssue(issues, "launch.evidence.build_missing", "launch.evidence.buildCommand", "Evidence build command is required.");
976
+ }
977
+ function validateCompatibility(launch, issues) {
978
+ if (!launch.compatibility?.adminApiVersion) addIssue(issues, "launch.compatibility.missing", "launch.compatibility", "Compatibility declaration is required.");
979
+ }
980
+ function addIssue(issues, code, path, message) {
981
+ issues.push({ code, path, message });
982
+ }
983
+ function issue4(code, path, message) {
984
+ return { valid: false, issues: [{ code, path, message }] };
985
+ }
986
+
987
+ // src/preview-validation.ts
988
+ function validateNoDeployPreviewManifest(manifest) {
989
+ const validation = validateManifest(manifest);
990
+ const diagnostics = validation.issues.map((issue5) => ({
991
+ code: `manifest.${issue5.code}`,
992
+ message: issue5.message,
993
+ path: issue5.path
994
+ }));
995
+ if (manifest.surfaces.length === 0) {
996
+ diagnostics.push({
997
+ code: "preview.surface_missing",
998
+ message: "No-deploy preview requires at least one app surface.",
999
+ path: "surfaces"
1000
+ });
1001
+ }
1002
+ for (const surface of manifest.surfaces) {
1003
+ if (!surface.entry) {
1004
+ diagnostics.push({
1005
+ code: "preview.surface_entry_missing",
1006
+ message: "Preview surfaces must declare an entry point.",
1007
+ path: `surfaces.${surface.id}.entry`
1008
+ });
1009
+ }
1010
+ }
1011
+ return {
1012
+ status: diagnostics.length === 0 ? "valid" : "invalid",
1013
+ createdProductionState: false,
1014
+ diagnostics
1015
+ };
1016
+ }
269
1017
  export {
1018
+ aiMcpDescriptorTypes,
1019
+ aiMcpHumanApprovalPolicies,
1020
+ appGovernanceApprovalPolicies,
1021
+ appGovernanceCapabilityTypes,
1022
+ appGovernanceCostClasses,
1023
+ appGovernanceDataClasses,
1024
+ appGovernanceRiskClasses,
1025
+ assertLaunchReadinessManifest,
270
1026
  checkCompatibility,
1027
+ classifyAiMcpMetadataAuthority,
1028
+ classifyPrivacyManifestChange,
271
1029
  defaultRequiredLocales,
272
1030
  developerToolingOperationMetadata,
273
1031
  generateContracts,
274
1032
  generateIdeMetadata,
275
1033
  getTranslationStatus,
1034
+ hydrationTriggers,
1035
+ isNormalizedHostPattern,
276
1036
  isSupportedSurface,
1037
+ legacyStorefrontBlockRenderTiers,
1038
+ normalizeStorefrontBlockRenderTier,
1039
+ privacyManifestCapabilitySchema,
1040
+ privacyManifestSchema,
1041
+ storefrontBlockRenderTiers,
1042
+ supportedAdminComponentKitComponents,
277
1043
  supportedSurfaces,
278
- validateManifest
1044
+ validateAdminComponentKitManifest,
1045
+ validateAiMcpDescriptorMetadata,
1046
+ validateAppCapabilityDeclarations,
1047
+ validateExtensionSurface,
1048
+ validateExtensionSurfaceDeclarations,
1049
+ validateLaunchReadinessManifest,
1050
+ validateManifest,
1051
+ validateNoDeployPreviewManifest,
1052
+ validatePrivacyManifest
279
1053
  };
280
1054
  //# sourceMappingURL=index.js.map