@vmz/vmz 0.0.0 → 0.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.
Files changed (75) hide show
  1. package/README.md +48 -2
  2. package/bin/vmz.js +4 -0
  3. package/dist/application-cmd.d.ts +22 -0
  4. package/dist/application-cmd.js +348 -0
  5. package/dist/bundler-adapter.d.ts +64 -0
  6. package/dist/bundler-adapter.js +111 -0
  7. package/dist/cli.d.ts +15 -0
  8. package/dist/cli.js +370 -0
  9. package/dist/dev-session.d.ts +35 -0
  10. package/dist/dev-session.js +290 -0
  11. package/dist/document-build.d.ts +99 -0
  12. package/dist/document-build.js +274 -0
  13. package/dist/document-check.d.ts +44 -0
  14. package/dist/document-check.js +246 -0
  15. package/dist/document-cmd.d.ts +9 -0
  16. package/dist/document-cmd.js +147 -0
  17. package/dist/document-designs.d.ts +9 -0
  18. package/dist/document-designs.js +126 -0
  19. package/dist/document-enrich.d.ts +23 -0
  20. package/dist/document-enrich.js +234 -0
  21. package/dist/document-evidence.d.ts +49 -0
  22. package/dist/document-evidence.js +501 -0
  23. package/dist/document-integrate.d.ts +35 -0
  24. package/dist/document-integrate.js +89 -0
  25. package/dist/document-interactive.d.ts +69 -0
  26. package/dist/document-interactive.js +255 -0
  27. package/dist/document-locale.d.ts +31 -0
  28. package/dist/document-locale.js +59 -0
  29. package/dist/document-markdown.d.ts +13 -0
  30. package/dist/document-markdown.js +39 -0
  31. package/dist/document-scan.d.ts +21 -0
  32. package/dist/document-scan.js +151 -0
  33. package/dist/document-schema.d.ts +87 -0
  34. package/dist/document-schema.js +88 -0
  35. package/dist/explain-cmd.d.ts +5 -0
  36. package/dist/explain-cmd.js +123 -0
  37. package/dist/index.d.ts +808 -0
  38. package/dist/index.js +569 -0
  39. package/dist/locale-check.d.ts +106 -0
  40. package/dist/locale-check.js +737 -0
  41. package/dist/locale-cmd.d.ts +5 -0
  42. package/dist/locale-cmd.js +443 -0
  43. package/dist/locale-delivery.d.ts +298 -0
  44. package/dist/locale-delivery.js +444 -0
  45. package/dist/locale-router.d.ts +207 -0
  46. package/dist/locale-router.js +508 -0
  47. package/dist/locale-runtime.d.ts +406 -0
  48. package/dist/locale-runtime.js +542 -0
  49. package/dist/locale-schema.d.ts +9 -0
  50. package/dist/locale-schema.js +10 -0
  51. package/dist/locale-tooling.d.ts +118 -0
  52. package/dist/locale-tooling.js +358 -0
  53. package/dist/log.d.ts +19 -0
  54. package/dist/log.js +42 -0
  55. package/dist/packages.d.ts +27 -0
  56. package/dist/packages.js +147 -0
  57. package/dist/plugin-host.d.ts +30 -0
  58. package/dist/plugin-host.js +370 -0
  59. package/dist/refactor-cmd.d.ts +8 -0
  60. package/dist/refactor-cmd.js +156 -0
  61. package/dist/resolve.d.ts +25 -0
  62. package/dist/resolve.js +56 -0
  63. package/dist/test-cmd.d.ts +9 -0
  64. package/dist/test-cmd.js +343 -0
  65. package/dist/test-compile.d.ts +2 -0
  66. package/dist/test-compile.js +3 -0
  67. package/dist/test-discover.d.ts +2 -0
  68. package/dist/test-discover.js +3 -0
  69. package/dist/test-logic.d.ts +2 -0
  70. package/dist/test-logic.js +3 -0
  71. package/dist/test-protocol.d.ts +2 -0
  72. package/dist/test-protocol.js +3 -0
  73. package/dist/watch-diff.d.ts +17 -0
  74. package/dist/watch-diff.js +56 -0
  75. package/package.json +86 -4
@@ -0,0 +1,508 @@
1
+ // @ts-nocheck
2
+ /**
3
+ * Locale I3 router / PageMeta: route realization · canonical · hreflang ·
4
+ * Link locale retain · locale-aware cache key.
5
+ * Design: 规划设计/vmz/28 §6 · §10 · 20
6
+ *
7
+ * LocaleId is a RouteNode realization dimension — not part of stable RouteId.
8
+ */
9
+ import { negotiateLocale } from './locale-runtime.js';
10
+ import { DIAG_LOCALE_CACHE_KEY_STEALS_CONTENT, DIAG_LOCALE_CANONICAL_MISSING, DIAG_LOCALE_HREFLANG_INCOMPLETE, DIAG_LOCALE_LINK_HARDCODED_PATH, DIAG_LOCALE_META_LOCALE_MISMATCH, DIAG_LOCALE_PREFIX_OMIT_WITHOUT_REDIRECT, DIAG_LOCALE_ROUTE_COLLISION, LOCALE_LINK_RESOLUTION_SCHEMA, LOCALE_PAGE_META_SCHEMA, LOCALE_ROUTE_REALIZATION_SCHEMA, LOCALE_ROUTER_CHECK_SCHEMA, } from './locale-schema.js';
11
+ /**
12
+ * @param {string} path
13
+ */
14
+ function normalizePath(path) {
15
+ let p = String(path || '/');
16
+ if (!p.startsWith('/'))
17
+ p = `/${p}`;
18
+ if (p.length > 1 && p.endsWith('/'))
19
+ p = p.slice(0, -1);
20
+ return p || '/';
21
+ }
22
+ /**
23
+ * Join locale prefix with a stable route path pattern.
24
+ * @param {string} localeId
25
+ * @param {string} pathPattern stable path without locale (e.g. /account/profile)
26
+ * @param {{ strategy?: string, defaultPrefix?: string, defaultLocale?: string }} routing
27
+ */
28
+ export function realizeRoutePath(localeId, pathPattern, routing = {}) {
29
+ const strategy = routing.strategy || 'prefix';
30
+ const defaultPrefix = routing.defaultPrefix || 'include';
31
+ const defaultLocale = routing.defaultLocale;
32
+ const base = normalizePath(pathPattern);
33
+ if (strategy === 'none' || strategy === 'domain') {
34
+ return {
35
+ schema: LOCALE_ROUTE_REALIZATION_SCHEMA,
36
+ localeId,
37
+ path: base,
38
+ prefixed: false,
39
+ strategy,
40
+ };
41
+ }
42
+ // prefix
43
+ const omitDefault = defaultPrefix === 'omit' && localeId === defaultLocale;
44
+ if (omitDefault) {
45
+ return {
46
+ schema: LOCALE_ROUTE_REALIZATION_SCHEMA,
47
+ localeId,
48
+ path: base,
49
+ prefixed: false,
50
+ strategy: 'prefix',
51
+ defaultPrefix: 'omit',
52
+ };
53
+ }
54
+ const path = base === '/' ? `/${localeId}` : `/${localeId}${base}`;
55
+ return {
56
+ schema: LOCALE_ROUTE_REALIZATION_SCHEMA,
57
+ localeId,
58
+ path,
59
+ prefixed: true,
60
+ strategy: 'prefix',
61
+ defaultPrefix,
62
+ };
63
+ }
64
+ /**
65
+ * Build RouteId × LocaleId realization table.
66
+ * @param {{
67
+ * routes: Array<{ routeId: string, path: string }>,
68
+ * locales: string[],
69
+ * defaultLocale: string,
70
+ * routing?: { strategy?: string, defaultPrefix?: string },
71
+ * }} input
72
+ */
73
+ export function buildLocaleRouteRealizationTable(input) {
74
+ const routing = {
75
+ strategy: input.routing?.strategy || 'prefix',
76
+ defaultPrefix: input.routing?.defaultPrefix || 'include',
77
+ defaultLocale: input.defaultLocale,
78
+ };
79
+ /** @type {any[]} */
80
+ const realizations = [];
81
+ /** @type {Map<string, string>} */
82
+ const pathOwners = new Map();
83
+ /** @type {any[]} */
84
+ const diagnostics = [];
85
+ for (const route of input.routes || []) {
86
+ for (const localeId of input.locales || []) {
87
+ const r = realizeRoutePath(localeId, route.path, routing);
88
+ const entry = {
89
+ ...r,
90
+ routeId: route.routeId,
91
+ pathPattern: normalizePath(route.path),
92
+ };
93
+ realizations.push(entry);
94
+ const prev = pathOwners.get(entry.path);
95
+ if (prev && prev !== `${route.routeId}@${localeId}`) {
96
+ diagnostics.push({
97
+ code: DIAG_LOCALE_ROUTE_COLLISION,
98
+ severity: 'error',
99
+ message: `path ${entry.path} claimed by ${prev} and ${route.routeId}@${localeId}`,
100
+ });
101
+ }
102
+ else {
103
+ pathOwners.set(entry.path, `${route.routeId}@${localeId}`);
104
+ }
105
+ }
106
+ }
107
+ return {
108
+ schema: LOCALE_ROUTE_REALIZATION_SCHEMA,
109
+ status: diagnostics.length ? 'failed' : 'ready',
110
+ routing,
111
+ realizations,
112
+ diagnostics,
113
+ };
114
+ }
115
+ /**
116
+ * Absolute URL helper.
117
+ * @param {string} origin
118
+ * @param {string} path
119
+ */
120
+ export function absoluteUrl(origin, path) {
121
+ const o = String(origin || '').replace(/\/$/, '');
122
+ const p = normalizePath(path);
123
+ return `${o}${p}`;
124
+ }
125
+ /**
126
+ * Locale-aware PageMeta for one RouteId × LocaleId.
127
+ * @param {{
128
+ * routeId: string,
129
+ * localeId: string,
130
+ * direction?: string,
131
+ * title: string,
132
+ * description?: string,
133
+ * origin: string,
134
+ * realizations: Array<{ routeId: string, localeId: string, path: string }>,
135
+ * locales: string[],
136
+ * defaultLocale: string,
137
+ * }} input
138
+ */
139
+ export function buildLocalePageMeta(input) {
140
+ const forRoute = (input.realizations || []).filter((r) => r.routeId === input.routeId);
141
+ const self = forRoute.find((r) => r.localeId === input.localeId);
142
+ /** @type {any[]} */
143
+ const diagnostics = [];
144
+ if (!self?.path) {
145
+ diagnostics.push({
146
+ code: DIAG_LOCALE_CANONICAL_MISSING,
147
+ severity: 'error',
148
+ message: `no realization for ${input.routeId}@${input.localeId}`,
149
+ });
150
+ }
151
+ const canonicalPath = self?.path || '/';
152
+ const canonical = absoluteUrl(input.origin, canonicalPath);
153
+ /** @type {Array<{ localeId: string, hreflang: string, href: string }>} */
154
+ const alternates = [];
155
+ for (const loc of input.locales || []) {
156
+ const hit = forRoute.find((r) => r.localeId === loc);
157
+ if (!hit) {
158
+ diagnostics.push({
159
+ code: DIAG_LOCALE_HREFLANG_INCOMPLETE,
160
+ severity: 'error',
161
+ message: `missing alternate realization for ${input.routeId}@${loc}`,
162
+ });
163
+ continue;
164
+ }
165
+ alternates.push({
166
+ localeId: loc,
167
+ hreflang: loc,
168
+ href: absoluteUrl(input.origin, hit.path),
169
+ });
170
+ }
171
+ const defaultHit = forRoute.find((r) => r.localeId === input.defaultLocale);
172
+ if (defaultHit) {
173
+ alternates.push({
174
+ localeId: input.defaultLocale,
175
+ hreflang: 'x-default',
176
+ href: absoluteUrl(input.origin, defaultHit.path),
177
+ });
178
+ }
179
+ return {
180
+ schema: LOCALE_PAGE_META_SCHEMA,
181
+ routeId: input.routeId,
182
+ locale: input.localeId,
183
+ htmlLang: input.localeId,
184
+ dir: input.direction || 'ltr',
185
+ title: input.title,
186
+ description: input.description || '',
187
+ canonical,
188
+ alternates,
189
+ diagnostics,
190
+ status: diagnostics.length ? 'failed' : 'ready',
191
+ };
192
+ }
193
+ /**
194
+ * `<Link to="routeId">` retains current locale — never hand-written localized paths.
195
+ * @param {{
196
+ * to: string,
197
+ * currentLocale: string,
198
+ * realizations: Array<{ routeId: string, localeId: string, path: string }>,
199
+ * }} input
200
+ */
201
+ export function resolveLinkHref(input) {
202
+ const to = input.to;
203
+ if (typeof to === 'string' && (to.startsWith('/') || /^https?:/i.test(to))) {
204
+ const looksLocalized = /^\/[a-z]{2,3}(-[a-z0-9]+)?(\/|$)/.test(to);
205
+ return {
206
+ schema: LOCALE_LINK_RESOLUTION_SCHEMA,
207
+ status: 'failed',
208
+ to,
209
+ href: null,
210
+ localeId: input.currentLocale,
211
+ diagnostics: [
212
+ {
213
+ code: DIAG_LOCALE_LINK_HARDCODED_PATH,
214
+ severity: 'error',
215
+ message: looksLocalized
216
+ ? `Link must use RouteId, not localized path ${JSON.stringify(to)}`
217
+ : `Link must use RouteId, not path ${JSON.stringify(to)}`,
218
+ },
219
+ ],
220
+ };
221
+ }
222
+ const hit = (input.realizations || []).find((r) => r.routeId === to && r.localeId === input.currentLocale);
223
+ if (!hit) {
224
+ return {
225
+ schema: LOCALE_LINK_RESOLUTION_SCHEMA,
226
+ status: 'failed',
227
+ to,
228
+ href: null,
229
+ localeId: input.currentLocale,
230
+ diagnostics: [
231
+ {
232
+ code: DIAG_LOCALE_CANONICAL_MISSING,
233
+ severity: 'error',
234
+ message: `no realization for Link to=${to} locale=${input.currentLocale}`,
235
+ },
236
+ ],
237
+ };
238
+ }
239
+ return {
240
+ schema: LOCALE_LINK_RESOLUTION_SCHEMA,
241
+ status: 'ready',
242
+ to,
243
+ routeId: to,
244
+ localeId: input.currentLocale,
245
+ href: hit.path,
246
+ diagnostics: [],
247
+ };
248
+ }
249
+ /**
250
+ * Parse locale prefix from a pathname under prefix strategy.
251
+ * @param {string} pathname
252
+ * @param {string[]} supportedLocales
253
+ */
254
+ export function parseLocaleFromPath(pathname, supportedLocales) {
255
+ const parts = normalizePath(pathname).split('/').filter(Boolean);
256
+ if (!parts.length)
257
+ return { localeId: null, restPath: '/' };
258
+ if (supportedLocales.includes(parts[0])) {
259
+ const rest = parts.slice(1);
260
+ return { localeId: parts[0], restPath: rest.length ? `/${rest.join('/')}` : '/' };
261
+ }
262
+ return { localeId: null, restPath: normalizePath(pathname) };
263
+ }
264
+ /**
265
+ * Plan redirect / negotiation for an incoming URL (omit-prefix aware).
266
+ * @param {{
267
+ * pathname: string,
268
+ * supportedLocales: string[],
269
+ * defaultLocale: string,
270
+ * routing?: { strategy?: string, defaultPrefix?: string },
271
+ * hostCandidates?: string[],
272
+ * preference?: string|null,
273
+ * userChoice?: string|null,
274
+ * }} input
275
+ */
276
+ export function planLocalePathNavigation(input) {
277
+ const routing = {
278
+ strategy: input.routing?.strategy || 'prefix',
279
+ defaultPrefix: input.routing?.defaultPrefix || 'include',
280
+ defaultLocale: input.defaultLocale,
281
+ };
282
+ const parsed = parseLocaleFromPath(input.pathname, input.supportedLocales);
283
+ const negotiated = negotiateLocale({
284
+ supportedLocales: input.supportedLocales,
285
+ defaultLocale: input.defaultLocale,
286
+ routeLocale: parsed.localeId,
287
+ userChoice: input.userChoice,
288
+ preference: input.preference,
289
+ hostCandidates: input.hostCandidates,
290
+ });
291
+ /** @type {any[]} */
292
+ const diagnostics = [];
293
+ let redirectTo = null;
294
+ if (routing.strategy === 'prefix' && routing.defaultPrefix === 'omit') {
295
+ // Prefixed default locale must redirect to unprefixed canonical.
296
+ if (parsed.localeId === input.defaultLocale) {
297
+ redirectTo = parsed.restPath;
298
+ }
299
+ // Unprefixed path is always defaultLocale content — Accept-Language must not steal.
300
+ if (!parsed.localeId && negotiated !== input.defaultLocale && !input.userChoice && !input.preference) {
301
+ // Host candidates alone cannot rewrite unprefixed default path content.
302
+ // negotiated falls back through hostCandidates; force default for this URL.
303
+ }
304
+ }
305
+ if (routing.strategy === 'prefix' && routing.defaultPrefix === 'omit' && parsed.localeId === input.defaultLocale) {
306
+ if (!redirectTo) {
307
+ diagnostics.push({
308
+ code: DIAG_LOCALE_PREFIX_OMIT_WITHOUT_REDIRECT,
309
+ severity: 'error',
310
+ message: `default locale ${input.defaultLocale} prefixed URL lacks redirect to canonical`,
311
+ });
312
+ }
313
+ }
314
+ const realized = realizeRoutePath(negotiated, parsed.localeId ? parsed.restPath : parsed.restPath, routing);
315
+ return {
316
+ schema: LOCALE_ROUTER_CHECK_SCHEMA,
317
+ kind: 'path_navigation',
318
+ pathname: normalizePath(input.pathname),
319
+ routeLocale: parsed.localeId,
320
+ negotiatedLocale: negotiated,
321
+ restPath: parsed.restPath,
322
+ redirectTo,
323
+ realizedPath: realized.path,
324
+ diagnostics,
325
+ // Unprefixed public path is bound to defaultLocale when omit — not Accept-Language.
326
+ contentLocale: routing.defaultPrefix === 'omit' && !parsed.localeId ? input.defaultLocale : negotiated,
327
+ };
328
+ }
329
+ /**
330
+ * Locale-aware cache key — must include LocaleId so Accept-Language cannot steal content.
331
+ * @param {{ routeId: string, localeId: string, path: string }} input
332
+ */
333
+ export function localeAwareCacheKey(input) {
334
+ return `locale=${input.localeId}|route=${input.routeId}|path=${normalizePath(input.path)}`;
335
+ }
336
+ /**
337
+ * Reject Vary: Accept-Language on a cache key that does not encode LocaleId.
338
+ * @param {{ cacheKey: string, varyAcceptLanguage?: boolean, localeId?: string }} input
339
+ */
340
+ export function assertLocaleCacheKey(input) {
341
+ /** @type {any[]} */
342
+ const diagnostics = [];
343
+ const key = String(input.cacheKey || '');
344
+ const hasLocale = /(?:^|[|&])locale=/.test(key) || (input.localeId && key.includes(input.localeId));
345
+ if (input.varyAcceptLanguage && !hasLocale) {
346
+ diagnostics.push({
347
+ code: DIAG_LOCALE_CACHE_KEY_STEALS_CONTENT,
348
+ severity: 'error',
349
+ message: 'public cache key must include LocaleId; Accept-Language alone must not swap body',
350
+ });
351
+ }
352
+ return { ok: diagnostics.length === 0, diagnostics };
353
+ }
354
+ /**
355
+ * LocaleTransition must commit Route realization + PageMeta together.
356
+ * @param {{
357
+ * fromLocale: string,
358
+ * toLocale: string,
359
+ * routeId: string,
360
+ * realizations: Array<{ routeId: string, localeId: string, path: string }>,
361
+ * pageMetaByLocale: Record<string, { locale: string, canonical: string }>,
362
+ * }} input
363
+ */
364
+ export function commitLocaleRouteMetaTransition(input) {
365
+ /** @type {any[]} */
366
+ const diagnostics = [];
367
+ const fromPath = (input.realizations || []).find((r) => r.routeId === input.routeId && r.localeId === input.fromLocale)?.path;
368
+ const toPath = (input.realizations || []).find((r) => r.routeId === input.routeId && r.localeId === input.toLocale)?.path;
369
+ const meta = input.pageMetaByLocale?.[input.toLocale];
370
+ if (!toPath) {
371
+ diagnostics.push({
372
+ code: DIAG_LOCALE_CANONICAL_MISSING,
373
+ severity: 'error',
374
+ message: `transition missing realization ${input.routeId}@${input.toLocale}`,
375
+ });
376
+ }
377
+ if (!meta?.canonical) {
378
+ diagnostics.push({
379
+ code: DIAG_LOCALE_CANONICAL_MISSING,
380
+ severity: 'error',
381
+ message: `transition missing PageMeta canonical for ${input.toLocale}`,
382
+ });
383
+ }
384
+ if (meta && meta.locale !== input.toLocale) {
385
+ diagnostics.push({
386
+ code: DIAG_LOCALE_META_LOCALE_MISMATCH,
387
+ severity: 'error',
388
+ message: `PageMeta.locale ${meta.locale} != transition ${input.toLocale}`,
389
+ });
390
+ }
391
+ return {
392
+ schema: LOCALE_ROUTER_CHECK_SCHEMA,
393
+ kind: 'locale_route_meta_transition',
394
+ status: diagnostics.length ? 'failed' : 'committed',
395
+ fromLocale: input.fromLocale,
396
+ toLocale: input.toLocale,
397
+ fromPath,
398
+ toPath,
399
+ pageMeta: meta || null,
400
+ diagnostics,
401
+ };
402
+ }
403
+ /**
404
+ * Aggregate I3 router/meta proof.
405
+ * @param {{
406
+ * manifest: {
407
+ * defaultLocale: string,
408
+ * locales: Array<{ id: string, direction?: string }>,
409
+ * routing?: { strategy?: string, defaultPrefix?: string },
410
+ * },
411
+ * routes: Array<{ routeId: string, path: string }>,
412
+ * titles?: Record<string, Record<string, string>>,
413
+ * origin?: string,
414
+ * }} input
415
+ */
416
+ export function checkLocaleRouter(input) {
417
+ /** @type {any[]} */
418
+ const diagnostics = [];
419
+ const locales = (input.manifest?.locales || []).map((l) => l.id);
420
+ const directions = Object.fromEntries((input.manifest?.locales || []).map((l) => [l.id, l.direction || 'ltr']));
421
+ const defaultLocale = input.manifest?.defaultLocale;
422
+ const table = buildLocaleRouteRealizationTable({
423
+ routes: input.routes,
424
+ locales,
425
+ defaultLocale,
426
+ routing: input.manifest?.routing,
427
+ });
428
+ diagnostics.push(...table.diagnostics);
429
+ const origin = input.origin || 'https://example.test';
430
+ /** @type {any[]} */
431
+ const pageMetas = [];
432
+ for (const route of input.routes || []) {
433
+ for (const loc of locales) {
434
+ const title = input.titles?.[route.routeId]?.[loc] || input.titles?.[route.routeId]?.[defaultLocale] || route.routeId;
435
+ const meta = buildLocalePageMeta({
436
+ routeId: route.routeId,
437
+ localeId: loc,
438
+ direction: directions[loc],
439
+ title,
440
+ origin,
441
+ realizations: table.realizations,
442
+ locales,
443
+ defaultLocale,
444
+ });
445
+ diagnostics.push(...meta.diagnostics);
446
+ if (meta.locale !== loc) {
447
+ diagnostics.push({
448
+ code: DIAG_LOCALE_META_LOCALE_MISMATCH,
449
+ severity: 'error',
450
+ message: `PageMeta.locale ${meta.locale} != ${loc}`,
451
+ });
452
+ }
453
+ pageMetas.push(meta);
454
+ }
455
+ }
456
+ // omit-prefix requires redirect plan for prefixed default locale URLs
457
+ if (input.manifest?.routing?.defaultPrefix === 'omit') {
458
+ const sample = input.routes?.[0];
459
+ if (sample) {
460
+ const prefixedDefault = `/${defaultLocale}${normalizePath(sample.path)}`;
461
+ const nav = planLocalePathNavigation({
462
+ pathname: prefixedDefault,
463
+ supportedLocales: locales,
464
+ defaultLocale,
465
+ routing: input.manifest.routing,
466
+ });
467
+ if (!nav.redirectTo) {
468
+ diagnostics.push({
469
+ code: DIAG_LOCALE_PREFIX_OMIT_WITHOUT_REDIRECT,
470
+ severity: 'error',
471
+ message: `omit defaultPrefix requires redirect from ${prefixedDefault}`,
472
+ });
473
+ }
474
+ diagnostics.push(...nav.diagnostics);
475
+ }
476
+ }
477
+ // Cache key must encode locale
478
+ const sampleReal = table.realizations[0];
479
+ if (sampleReal) {
480
+ const goodKey = localeAwareCacheKey({
481
+ routeId: sampleReal.routeId,
482
+ localeId: sampleReal.localeId,
483
+ path: sampleReal.path,
484
+ });
485
+ const good = assertLocaleCacheKey({ cacheKey: goodKey, varyAcceptLanguage: true, localeId: sampleReal.localeId });
486
+ diagnostics.push(...good.diagnostics);
487
+ const bad = assertLocaleCacheKey({
488
+ cacheKey: `route=${sampleReal.routeId}|path=${sampleReal.path}`,
489
+ varyAcceptLanguage: true,
490
+ });
491
+ // Expected failure — record as proof probe, not as check failure if we only assert detection.
492
+ if (bad.ok) {
493
+ diagnostics.push({
494
+ code: DIAG_LOCALE_CACHE_KEY_STEALS_CONTENT,
495
+ severity: 'error',
496
+ message: 'cache key probe failed to detect Accept-Language steal',
497
+ });
498
+ }
499
+ }
500
+ const hasErrors = diagnostics.some((d) => d.severity === 'error');
501
+ return {
502
+ schema: LOCALE_ROUTER_CHECK_SCHEMA,
503
+ status: hasErrors ? 'failed' : 'ready',
504
+ realizationTable: table,
505
+ pageMetas,
506
+ diagnostics,
507
+ };
508
+ }