@pyric/cli 0.1.0-alpha.26 → 0.1.0-alpha.27

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 (119) hide show
  1. package/README.md +1 -1
  2. package/dist/assurance/.generated/conformance-verdicts.d.ts +5 -0
  3. package/dist/assurance/.generated/conformance-verdicts.d.ts.map +1 -1
  4. package/dist/assurance/.generated/conformance-verdicts.js +5 -0
  5. package/dist/assurance/.generated/conformance-verdicts.js.map +1 -1
  6. package/dist/assurance/capabilities.d.ts.map +1 -1
  7. package/dist/assurance/capabilities.js +24 -5
  8. package/dist/assurance/capabilities.js.map +1 -1
  9. package/dist/assurance/runner.d.ts.map +1 -1
  10. package/dist/assurance/runner.js +37 -50
  11. package/dist/assurance/runner.js.map +1 -1
  12. package/dist/assurance/types.d.ts +5 -0
  13. package/dist/assurance/types.d.ts.map +1 -1
  14. package/dist/assurance/types.js.map +1 -1
  15. package/dist/assurance/validation.d.ts +2 -16
  16. package/dist/assurance/validation.d.ts.map +1 -1
  17. package/dist/assurance/validation.js +121 -1
  18. package/dist/assurance/validation.js.map +1 -1
  19. package/dist/bridge/surface/arguments/assurance.d.ts +25 -0
  20. package/dist/bridge/surface/arguments/assurance.d.ts.map +1 -1
  21. package/dist/bridge/surface/arguments/assurance.js +1 -0
  22. package/dist/bridge/surface/arguments/assurance.js.map +1 -1
  23. package/dist/bridge/surface/methods/assurance/define.d.ts +2 -2
  24. package/dist/bridge/surface/methods/assurance/map.d.ts +23 -0
  25. package/dist/bridge/surface/methods/assurance/map.d.ts.map +1 -1
  26. package/dist/bridge/surface/methods/assurance/propose.d.ts +7 -0
  27. package/dist/bridge/surface/methods/assurance/propose.d.ts.map +1 -1
  28. package/dist/bridge/surface/render/discriminator-schemas.d.ts +2 -2
  29. package/dist/cli/database-rules.js +1 -1
  30. package/dist/cli/database-rules.js.map +1 -1
  31. package/dist/cli/index.d.ts.map +1 -1
  32. package/dist/cli/index.js +13 -2
  33. package/dist/cli/index.js.map +1 -1
  34. package/dist/cli/pyric-config.d.ts +8 -0
  35. package/dist/cli/pyric-config.d.ts.map +1 -1
  36. package/dist/cli/pyric-config.js.map +1 -1
  37. package/dist/cli/serve.d.ts.map +1 -1
  38. package/dist/cli/serve.js +5 -3
  39. package/dist/cli/serve.js.map +1 -1
  40. package/dist/cli/verify.d.ts +6 -0
  41. package/dist/cli/verify.d.ts.map +1 -1
  42. package/dist/cli/verify.js +240 -15
  43. package/dist/cli/verify.js.map +1 -1
  44. package/dist/conformance/.generated/can-i-use.d.ts.map +1 -1
  45. package/dist/conformance/.generated/can-i-use.js +1 -1
  46. package/dist/conformance/.generated/can-i-use.js.map +1 -1
  47. package/dist/conformance/.generated/conformance-docs.d.ts.map +1 -1
  48. package/dist/conformance/.generated/conformance-docs.js +1 -1
  49. package/dist/conformance/.generated/conformance-docs.js.map +1 -1
  50. package/dist/serve/repro/denials.d.ts.map +1 -1
  51. package/dist/serve/repro/denials.js +2 -1
  52. package/dist/serve/repro/denials.js.map +1 -1
  53. package/dist/serve/site-ui/_astro/{app.BpEphDR7.js → app.CiMHEvEW.js} +15 -15
  54. package/dist/serve/site-ui/_astro/{app.DFdWhpVY.js → app.DpzsBfos.js} +1 -1
  55. package/dist/serve/site-ui/_astro/{seed.vvXV1ioI.js → seed.BpXSRzLo.js} +1 -1
  56. package/dist/serve/site-ui/auth/index.html +1 -1
  57. package/dist/serve/site-ui/docs/database-compat/index.html +4 -0
  58. package/dist/serve/site-ui/docs/database-compat.md +4 -0
  59. package/dist/serve/site-ui/docs/get-started/start-building/index.html +1 -0
  60. package/dist/serve/site-ui/docs/get-started/start-building.md +2 -0
  61. package/dist/serve/site-ui/docs/pyric-cli-assurance-reference-api/index.html +23 -4
  62. package/dist/serve/site-ui/docs/pyric-cli-assurance-reference-api.md +11 -4
  63. package/dist/serve/site-ui/docs/pyric-cli-verify-reference-api/index.html +15 -2
  64. package/dist/serve/site-ui/docs/pyric-cli-verify-reference-api.md +11 -10
  65. package/dist/serve/site-ui/docs/secure/write-a-rules-test-suite/index.html +1 -1
  66. package/dist/serve/site-ui/docs/secure/write-a-rules-test-suite.md +1 -1
  67. package/dist/serve/site-ui/docs/ship/ship-to-production/index.html +4 -1
  68. package/dist/serve/site-ui/docs/ship/ship-to-production.md +7 -1
  69. package/dist/serve/site-ui/examples/chess/index.html +1 -1
  70. package/dist/serve/site-ui/firestore/index.html +1 -1
  71. package/dist/serve/site-ui/rtdb/index.html +1 -1
  72. package/dist/serve/site-ui/settings/index.html +1 -1
  73. package/dist/serve/site-ui/storage/index.html +1 -1
  74. package/dist/serve/site-ui/studio/index.html +1 -1
  75. package/dist/serve/site-ui/traffic/index.html +1 -1
  76. package/dist/serve/verify-hint.d.ts +2 -0
  77. package/dist/serve/verify-hint.d.ts.map +1 -0
  78. package/dist/serve/verify-hint.js +10 -0
  79. package/dist/serve/verify-hint.js.map +1 -0
  80. package/dist/serve/vite-generation-rules-watch.d.ts.map +1 -1
  81. package/dist/serve/vite-generation-rules-watch.js +5 -3
  82. package/dist/serve/vite-generation-rules-watch.js.map +1 -1
  83. package/dist/verify/index.d.ts +16 -2
  84. package/dist/verify/index.d.ts.map +1 -1
  85. package/dist/verify/index.js +100 -18
  86. package/dist/verify/index.js.map +1 -1
  87. package/dist/verify/staging/engine.d.ts +102 -0
  88. package/dist/verify/staging/engine.d.ts.map +1 -0
  89. package/dist/verify/staging/engine.js +371 -0
  90. package/dist/verify/staging/engine.js.map +1 -0
  91. package/dist/verify/staging/rest.d.ts +68 -0
  92. package/dist/verify/staging/rest.d.ts.map +1 -0
  93. package/dist/verify/staging/rest.js +93 -0
  94. package/dist/verify/staging/rest.js.map +1 -0
  95. package/dist/verify/staging/target.d.ts +39 -0
  96. package/dist/verify/staging/target.d.ts.map +1 -0
  97. package/dist/verify/staging/target.js +117 -0
  98. package/dist/verify/staging/target.js.map +1 -0
  99. package/package.json +4 -4
  100. package/src/assurance/.generated/conformance-verdicts.ts +5 -0
  101. package/src/assurance/capabilities.ts +22 -4
  102. package/src/assurance/runner.ts +40 -58
  103. package/src/assurance/types.ts +3 -0
  104. package/src/assurance/validation.ts +137 -0
  105. package/src/bridge/surface/arguments/assurance.ts +1 -0
  106. package/src/cli/database-rules.ts +1 -1
  107. package/src/cli/index.ts +13 -2
  108. package/src/cli/pyric-config.ts +5 -0
  109. package/src/cli/serve.ts +6 -3
  110. package/src/cli/verify.ts +244 -18
  111. package/src/conformance/.generated/can-i-use.ts +1 -1
  112. package/src/conformance/.generated/conformance-docs.ts +1 -1
  113. package/src/serve/repro/denials.ts +2 -1
  114. package/src/serve/verify-hint.ts +8 -0
  115. package/src/serve/vite-generation-rules-watch.ts +6 -3
  116. package/src/verify/index.ts +119 -19
  117. package/src/verify/staging/engine.ts +455 -0
  118. package/src/verify/staging/rest.ts +129 -0
  119. package/src/verify/staging/target.ts +137 -0
@@ -179,5 +179,6 @@ export function formatDenials(groups: DenialGroup[], loggedFrom?: number): strin
179
179
  for (const reason of group.trace) lines.push(` trace: ${reason}`);
180
180
  return lines.join('\n');
181
181
  });
182
- return `${bounded}${blocks.join('\n\n')}\n`;
182
+ const tip = '\nTip: After changing rules, run `pyric verify` to replay the captured session against them and confirm your fixes.';
183
+ return `${bounded}${blocks.join('\n\n')}\n${tip}\n`;
183
184
  }
@@ -0,0 +1,8 @@
1
+ export function createVerifyHint(): () => string {
2
+ let printedVerifyHint = false;
3
+ return () => {
4
+ if (printedVerifyHint) return '';
5
+ printedVerifyHint = true;
6
+ return ' (run `pyric verify` to replay the captured session against these rules)';
7
+ };
8
+ }
@@ -2,6 +2,7 @@ import path from 'node:path';
2
2
  import type { ViteDevServer } from 'vite';
3
3
  import { formatDatabaseRulesRemoved, formatStorageRulesRemoved } from './rules.js';
4
4
  import type { SandboxSession } from './sandbox-session.js';
5
+ import { createVerifyHint } from './verify-hint.js';
5
6
 
6
7
  /**
7
8
  * Adapt Vite's watcher to the session's last-good rules reload operations.
@@ -17,6 +18,8 @@ export function watchViteGenerationRules(input: {
17
18
  server: ViteDevServer;
18
19
  session: SandboxSession;
19
20
  }): () => void {
21
+ const getVerifyHint = createVerifyHint();
22
+
20
23
  const { server, session } = input;
21
24
  const databaseFiles = session.databaseRulesFiles();
22
25
  const databaseFileByResolved = new Map(databaseFiles.map((file) => [path.resolve(file), file]));
@@ -53,7 +56,7 @@ export function watchViteGenerationRules(input: {
53
56
  server.watcher.add([...session.firestoreRulesFiles()]);
54
57
  const isReloaded = result.kind === 'reloaded';
55
58
  if (isReloaded) {
56
- server.config.logger.info(` ↻ [pyric] rules reloaded (${result.rulesHash})`);
59
+ server.config.logger.info(` ↻ [pyric] rules reloaded (${result.rulesHash})${getVerifyHint()}`);
57
60
  if (result.fixtureCheck && result.fixtureCheck.ran > 0) {
58
61
  const logFn = result.fixtureCheck.ok
59
62
  ? (m: string) => server.config.logger.info(m)
@@ -74,7 +77,7 @@ export function watchViteGenerationRules(input: {
74
77
  void session.reloadDatabaseRules(databaseFile).then((results) => {
75
78
  for (const { instance, result } of results) {
76
79
  if (result.kind === 'reloaded') {
77
- server.config.logger.info(` ↻ [pyric] rtdb rules reloaded for ${instance} (${result.rulesHash})`);
80
+ server.config.logger.info(` ↻ [pyric] rtdb rules reloaded for ${instance} (${result.rulesHash})${getVerifyHint()}`);
78
81
  if (result.fixtureCheck && result.fixtureCheck.ran > 0) {
79
82
  const logFn = result.fixtureCheck.ok
80
83
  ? (m: string) => server.config.logger.info(m)
@@ -94,7 +97,7 @@ export function watchViteGenerationRules(input: {
94
97
  if (isStorageMatch) {
95
98
  void session.reloadStorageRules().then((result) => {
96
99
  if (result.kind === 'reloaded') {
97
- server.config.logger.info(` ↻ [pyric] storage rules reloaded (${result.rulesHash})`);
100
+ server.config.logger.info(` ↻ [pyric] storage rules reloaded (${result.rulesHash})${getVerifyHint()}`);
98
101
  } else if (result.kind === 'rejected') {
99
102
  server.config.logger.warn(
100
103
  ` ⚠ [pyric] storage rules NOT reloaded (last-good stays live): ${result.error.message}`,
@@ -39,6 +39,8 @@ import {
39
39
  VERIFY_FIXTURE_SCHEMA,
40
40
  type PyricVerifyFixture,
41
41
  } from './fixture.js';
42
+ import { runStagingInstance, StagingDeployRefusedError, withStagingInstance, type StagingRunHooks } from './staging/engine.js';
43
+ import type { RtdbRestClient } from './staging/rest.js';
42
44
  import {
43
45
  recordedRtdbInstances,
44
46
  recordsDefaultRtdbInstance,
@@ -94,7 +96,7 @@ export {
94
96
  } from './hosted-rules-test.js';
95
97
 
96
98
  export type VerifiableService = 'firestore' | 'rtdb' | 'storage';
97
- export type VerifyEngine = 'sandbox' | 'rulesTestApi';
99
+ export type VerifyEngine = 'sandbox' | 'rulesTestApi' | 'staging';
98
100
 
99
101
  type RtdbCandidateRules = { rules: Record<string, unknown> } | RtdbRulesDocument;
100
102
 
@@ -120,6 +122,12 @@ export interface VerifyFixtureOptions {
120
122
  expressionReportLevel?: ExpressionReportLevel;
121
123
  };
122
124
  caseDerivation?: Omit<DeriveRulesTestCasesOptions, 'service'>;
125
+ /**
126
+ * The staging engine's instance: a client for a dedicated RTDB instance
127
+ * enrolled with `pyric verify staging init`, and the project it is
128
+ * enrolled for. Required when `engines` includes `staging`.
129
+ */
130
+ staging?: { client: RtdbRestClient; projectId: string } & StagingRunHooks;
123
131
  }
124
132
 
125
133
  export interface VerifyResult {
@@ -198,7 +206,10 @@ type VerifyDivergenceKind =
198
206
  path?: string;
199
207
  method?: string;
200
208
  sandbox: string;
201
- rulesTestApi: string;
209
+ /** Firestore: the hosted Rules Test API's decision. */
210
+ rulesTestApi?: string;
211
+ /** RTDB: what production did on the staging instance. */
212
+ staging?: string;
202
213
  reason?: string;
203
214
  };
204
215
 
@@ -224,10 +235,15 @@ export async function verifyFixture(
224
235
 
225
236
  const services: Partial<Record<VerifiableService, VerifyServiceResult>> = {};
226
237
  for (const service of selected) {
238
+ if (engines.includes('staging') && service !== 'rtdb') {
239
+ throw new VerifyInputError(
240
+ `the staging engine checks RTDB only; ${service} is not supported by it yet. Select --service rtdb, or the sandbox engine for ${service}.`,
241
+ );
242
+ }
227
243
  if (service === 'firestore') {
228
244
  services.firestore = await verifyFirestore(fixture, requireFirestoreRules(opts.rules), engines, opts);
229
245
  } else if (service === 'rtdb') {
230
- services.rtdb = await verifyRtdb(fixture, opts.rules, engines);
246
+ services.rtdb = await verifyRtdb(fixture, opts.rules, engines, opts.staging);
231
247
  } else if (service === 'storage') {
232
248
  services.storage = await verifyStorage(fixture, opts.rules, engines);
233
249
  } else {
@@ -578,19 +594,24 @@ async function verifyRtdb(
578
594
  fixture: PyricVerifyFixture,
579
595
  rules: VerifyRulesInput,
580
596
  engines: VerifyEngine[],
597
+ staging: VerifyFixtureOptions['staging'],
581
598
  ): Promise<VerifyServiceResult> {
582
599
  const rtdb = fixture.services.rtdb;
583
600
  if (!rtdb) {
584
601
  throw new VerifyInputError('fixture does not contain services.rtdb.');
585
602
  }
586
603
  if (engines.includes('rulesTestApi')) {
587
- throw new VerifyInputError('rulesTestApi verification is Firestore-only; select --service firestore or use the sandbox engine for RTDB.');
604
+ throw new VerifyInputError('rulesTestApi verification is Firestore-only; select --service firestore or use the sandbox or staging engine for RTDB.');
605
+ }
606
+ if (engines.includes('staging') && staging === undefined) {
607
+ throw new VerifyInputError('the staging engine needs a staging instance (opts.staging).');
588
608
  }
589
609
 
590
- const replays = [];
610
+ // The instances to check, each with its candidate rules.
611
+ const checks: Array<{ instance?: string; rules: RtdbRulesJson; capturedState: unknown }> = [];
591
612
  const checksDefault = rules.rtdb !== undefined || recordsDefaultRtdbInstance(fixture);
592
613
  if (checksDefault) {
593
- replays.push(await replayRtdbInstance(fixture, requireRtdbRules(rules.rtdb, 'missing candidate RTDB rules. Pass rules.rtdb.')));
614
+ checks.push({ rules: requireRtdbRules(rules.rtdb, 'missing candidate RTDB rules. Pass rules.rtdb.'), capturedState: rtdb.state.tree });
594
615
  }
595
616
  const skippedInstances: string[] = [];
596
617
  for (const instance of recordedRtdbInstances(fixture)) {
@@ -600,28 +621,107 @@ async function verifyRtdb(
600
621
  continue;
601
622
  }
602
623
  const parsed = requireRtdbRules(candidate, `missing candidate RTDB rules for instance "${instance}".`, instance);
603
- replays.push(await replayRtdbInstance(fixture, parsed, instance));
624
+ checks.push({ instance, rules: parsed, capturedState: rtdb.instances?.[instance]?.state.tree });
604
625
  }
605
626
 
606
- const divergences = replays.flatMap((replayed) => replayed.divergences.map(mapRtdbReplayDivergence));
607
- const checkedEvents = replays.reduce((total, replayed) => total + replayed.checkedEvents, 0);
608
- const sandbox: VerifyEngineResult = {
609
- engine: 'sandbox',
610
- ok: divergences.every(isInformational),
611
- checkedEvents,
612
- divergences,
613
- };
627
+ const engineResults: Partial<Record<VerifyEngine, VerifyEngineResult>> = {};
628
+ if (engines.includes('sandbox')) {
629
+ const replays = [];
630
+ for (const check of checks) replays.push(await replayRtdbInstance(fixture, check.rules, check.instance));
631
+ const divergences = replays.flatMap((replayed) => replayed.divergences.map(mapRtdbReplayDivergence));
632
+ engineResults.sandbox = {
633
+ engine: 'sandbox',
634
+ ok: divergences.every(isInformational),
635
+ checkedEvents: replays.reduce((total, replayed) => total + replayed.checkedEvents, 0),
636
+ divergences,
637
+ };
638
+ }
639
+ if (engines.includes('staging') && staging !== undefined) {
640
+ engineResults.staging = await verifyRtdbOnStaging(fixture, checks, staging);
641
+ }
642
+ if (engineResults.sandbox && engineResults.staging) {
643
+ addRtdbEngineDrift(engineResults.sandbox, engineResults.staging);
644
+ }
614
645
 
646
+ const divergences = collectEngineDivergences(engineResults);
615
647
  return {
616
648
  service: 'rtdb',
617
649
  ok: divergences.every(isInformational),
618
- checkedEvents,
650
+ checkedEvents: engineResults.sandbox?.checkedEvents ?? engineResults.staging?.checkedEvents ?? 0,
619
651
  divergences,
620
- engines: { sandbox },
652
+ engines: engineResults,
621
653
  ...(skippedInstances.length > 0 ? { skippedInstances } : {}),
622
654
  };
623
655
  }
624
656
 
657
+ /**
658
+ * Replay each instance on the staging instance in turn, each run guarded and
659
+ * the instance put back before the next. Production refusing to deploy an
660
+ * instance's candidate rules is reported for that instance; the others run.
661
+ */
662
+ async function verifyRtdbOnStaging(
663
+ fixture: PyricVerifyFixture,
664
+ checks: Array<{ instance?: string; rules: RtdbRulesJson; capturedState: unknown }>,
665
+ staging: NonNullable<VerifyFixtureOptions['staging']>,
666
+ ): Promise<VerifyEngineResult> {
667
+ const divergences: VerifyDivergence[] = [];
668
+ let checkedEvents = 0;
669
+ for (const check of checks) {
670
+ const tag = check.instance === undefined ? {} : { instance: check.instance };
671
+ try {
672
+ const { result } = await withStagingInstance(staging.client, staging.projectId, () =>
673
+ runStagingInstance({
674
+ client: staging.client,
675
+ events: fixture.events,
676
+ rules: check.rules,
677
+ capturedState: check.capturedState,
678
+ ...(check.instance === undefined ? {} : { instance: check.instance }),
679
+ ...(staging.signal ? { signal: staging.signal } : {}),
680
+ }), staging);
681
+ checkedEvents += result.checkedEvents;
682
+ divergences.push(...result.divergences.map((divergence) => ({ service: 'rtdb', ...divergence, ...tag })));
683
+ } catch (error) {
684
+ if (!(error instanceof StagingDeployRefusedError)) throw error;
685
+ divergences.push({ service: 'rtdb', kind: 'unsupported', reason: error.message, ...tag });
686
+ }
687
+ }
688
+ return { engine: 'staging', ok: divergences.every(isInformational), checkedEvents, divergences };
689
+ }
690
+
691
+ /**
692
+ * Where the sandbox and production decided a captured step differently: one
693
+ * engine reports `now-denied`, `now-allowed` or `unsupported` for a step and
694
+ * the other reports something else, or nothing. Steps are matched by their
695
+ * index in the replay both engines walk.
696
+ */
697
+ function addRtdbEngineDrift(sandbox: VerifyEngineResult, staging: VerifyEngineResult): void {
698
+ const decisive = new Set(['now-denied', 'now-allowed', 'unsupported']);
699
+ const keyOf = (divergence: VerifyDivergence) =>
700
+ `${divergence.instance ?? ''}|${(divergence as { step?: number }).step ?? `${(divergence as { method?: string }).method}|${divergence.path}`}`;
701
+ const byKey = (divergences: VerifyDivergence[]) =>
702
+ new Map(divergences.filter((divergence) => decisive.has(divergence.kind)).map((divergence) => [keyOf(divergence), divergence]));
703
+ const local = byKey(sandbox.divergences);
704
+ const remote = byKey(staging.divergences);
705
+ for (const key of new Set([...local.keys(), ...remote.keys()])) {
706
+ const sandboxKind = local.get(key)?.kind ?? 'unchanged';
707
+ const stagingKind = remote.get(key)?.kind ?? 'unchanged';
708
+ if (sandboxKind === stagingKind) continue;
709
+ const sample = (local.get(key) ?? remote.get(key))!;
710
+ staging.divergences.push({
711
+ service: 'rtdb',
712
+ kind: 'engine-drift',
713
+ path: sample.path,
714
+ method: (sample as { method?: string }).method,
715
+ sandbox: sandboxKind,
716
+ staging: stagingKind,
717
+ reason: `the sandbox reported ${sandboxKind}; production reported ${stagingKind}.`,
718
+ ...(sample.instance === undefined ? {} : { instance: sample.instance }),
719
+ ...((sample as { step?: number }).step === undefined ? {} : { step: (sample as { step?: number }).step }),
720
+ });
721
+ }
722
+ staging.ok = staging.divergences.every(isInformational);
723
+ }
724
+
625
725
  async function verifyStorage(
626
726
  fixture: PyricVerifyFixture,
627
727
  rules: VerifyRulesInput,
@@ -864,7 +964,7 @@ function normalizeEngines(input: VerifyEngine[] | undefined): VerifyEngine[] {
864
964
  const engines = input ?? ['sandbox'];
865
965
  const out: VerifyEngine[] = [];
866
966
  for (const engine of engines) {
867
- if (engine !== 'sandbox' && engine !== 'rulesTestApi') {
967
+ if (engine !== 'sandbox' && engine !== 'rulesTestApi' && engine !== 'staging') {
868
968
  throw new VerifyInputError(`unsupported verify engine: ${String(engine)}`);
869
969
  }
870
970
  if (!out.includes(engine)) out.push(engine);
@@ -881,7 +981,7 @@ function collectEngineDivergences(
881
981
  function serviceCheckedEvents(
882
982
  engines: Partial<Record<VerifyEngine, VerifyEngineResult>>,
883
983
  ): number {
884
- return engines.sandbox?.checkedEvents ?? engines.rulesTestApi?.checkedEvents ?? 0;
984
+ return engines.sandbox?.checkedEvents ?? engines.rulesTestApi?.checkedEvents ?? engines.staging?.checkedEvents ?? 0;
885
985
  }
886
986
 
887
987
  function mapUnsupportedEvent(event: VerifyUnsupportedEvent): VerifyDivergence {