@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.
- package/README.md +1 -1
- package/dist/assurance/.generated/conformance-verdicts.d.ts +5 -0
- package/dist/assurance/.generated/conformance-verdicts.d.ts.map +1 -1
- package/dist/assurance/.generated/conformance-verdicts.js +5 -0
- package/dist/assurance/.generated/conformance-verdicts.js.map +1 -1
- package/dist/assurance/capabilities.d.ts.map +1 -1
- package/dist/assurance/capabilities.js +24 -5
- package/dist/assurance/capabilities.js.map +1 -1
- package/dist/assurance/runner.d.ts.map +1 -1
- package/dist/assurance/runner.js +37 -50
- package/dist/assurance/runner.js.map +1 -1
- package/dist/assurance/types.d.ts +5 -0
- package/dist/assurance/types.d.ts.map +1 -1
- package/dist/assurance/types.js.map +1 -1
- package/dist/assurance/validation.d.ts +2 -16
- package/dist/assurance/validation.d.ts.map +1 -1
- package/dist/assurance/validation.js +121 -1
- package/dist/assurance/validation.js.map +1 -1
- package/dist/bridge/surface/arguments/assurance.d.ts +25 -0
- package/dist/bridge/surface/arguments/assurance.d.ts.map +1 -1
- package/dist/bridge/surface/arguments/assurance.js +1 -0
- package/dist/bridge/surface/arguments/assurance.js.map +1 -1
- package/dist/bridge/surface/methods/assurance/define.d.ts +2 -2
- package/dist/bridge/surface/methods/assurance/map.d.ts +23 -0
- package/dist/bridge/surface/methods/assurance/map.d.ts.map +1 -1
- package/dist/bridge/surface/methods/assurance/propose.d.ts +7 -0
- package/dist/bridge/surface/methods/assurance/propose.d.ts.map +1 -1
- package/dist/bridge/surface/render/discriminator-schemas.d.ts +2 -2
- package/dist/cli/database-rules.js +1 -1
- package/dist/cli/database-rules.js.map +1 -1
- package/dist/cli/index.d.ts.map +1 -1
- package/dist/cli/index.js +13 -2
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/pyric-config.d.ts +8 -0
- package/dist/cli/pyric-config.d.ts.map +1 -1
- package/dist/cli/pyric-config.js.map +1 -1
- package/dist/cli/serve.d.ts.map +1 -1
- package/dist/cli/serve.js +5 -3
- package/dist/cli/serve.js.map +1 -1
- package/dist/cli/verify.d.ts +6 -0
- package/dist/cli/verify.d.ts.map +1 -1
- package/dist/cli/verify.js +240 -15
- package/dist/cli/verify.js.map +1 -1
- package/dist/conformance/.generated/can-i-use.d.ts.map +1 -1
- package/dist/conformance/.generated/can-i-use.js +1 -1
- package/dist/conformance/.generated/can-i-use.js.map +1 -1
- package/dist/conformance/.generated/conformance-docs.d.ts.map +1 -1
- package/dist/conformance/.generated/conformance-docs.js +1 -1
- package/dist/conformance/.generated/conformance-docs.js.map +1 -1
- package/dist/serve/repro/denials.d.ts.map +1 -1
- package/dist/serve/repro/denials.js +2 -1
- package/dist/serve/repro/denials.js.map +1 -1
- package/dist/serve/site-ui/_astro/{app.BpEphDR7.js → app.CiMHEvEW.js} +15 -15
- package/dist/serve/site-ui/_astro/{app.DFdWhpVY.js → app.DpzsBfos.js} +1 -1
- package/dist/serve/site-ui/_astro/{seed.vvXV1ioI.js → seed.BpXSRzLo.js} +1 -1
- package/dist/serve/site-ui/auth/index.html +1 -1
- package/dist/serve/site-ui/docs/database-compat/index.html +4 -0
- package/dist/serve/site-ui/docs/database-compat.md +4 -0
- package/dist/serve/site-ui/docs/get-started/start-building/index.html +1 -0
- package/dist/serve/site-ui/docs/get-started/start-building.md +2 -0
- package/dist/serve/site-ui/docs/pyric-cli-assurance-reference-api/index.html +23 -4
- package/dist/serve/site-ui/docs/pyric-cli-assurance-reference-api.md +11 -4
- package/dist/serve/site-ui/docs/pyric-cli-verify-reference-api/index.html +15 -2
- package/dist/serve/site-ui/docs/pyric-cli-verify-reference-api.md +11 -10
- package/dist/serve/site-ui/docs/secure/write-a-rules-test-suite/index.html +1 -1
- package/dist/serve/site-ui/docs/secure/write-a-rules-test-suite.md +1 -1
- package/dist/serve/site-ui/docs/ship/ship-to-production/index.html +4 -1
- package/dist/serve/site-ui/docs/ship/ship-to-production.md +7 -1
- package/dist/serve/site-ui/examples/chess/index.html +1 -1
- package/dist/serve/site-ui/firestore/index.html +1 -1
- package/dist/serve/site-ui/rtdb/index.html +1 -1
- package/dist/serve/site-ui/settings/index.html +1 -1
- package/dist/serve/site-ui/storage/index.html +1 -1
- package/dist/serve/site-ui/studio/index.html +1 -1
- package/dist/serve/site-ui/traffic/index.html +1 -1
- package/dist/serve/verify-hint.d.ts +2 -0
- package/dist/serve/verify-hint.d.ts.map +1 -0
- package/dist/serve/verify-hint.js +10 -0
- package/dist/serve/verify-hint.js.map +1 -0
- package/dist/serve/vite-generation-rules-watch.d.ts.map +1 -1
- package/dist/serve/vite-generation-rules-watch.js +5 -3
- package/dist/serve/vite-generation-rules-watch.js.map +1 -1
- package/dist/verify/index.d.ts +16 -2
- package/dist/verify/index.d.ts.map +1 -1
- package/dist/verify/index.js +100 -18
- package/dist/verify/index.js.map +1 -1
- package/dist/verify/staging/engine.d.ts +102 -0
- package/dist/verify/staging/engine.d.ts.map +1 -0
- package/dist/verify/staging/engine.js +371 -0
- package/dist/verify/staging/engine.js.map +1 -0
- package/dist/verify/staging/rest.d.ts +68 -0
- package/dist/verify/staging/rest.d.ts.map +1 -0
- package/dist/verify/staging/rest.js +93 -0
- package/dist/verify/staging/rest.js.map +1 -0
- package/dist/verify/staging/target.d.ts +39 -0
- package/dist/verify/staging/target.d.ts.map +1 -0
- package/dist/verify/staging/target.js +117 -0
- package/dist/verify/staging/target.js.map +1 -0
- package/package.json +4 -4
- package/src/assurance/.generated/conformance-verdicts.ts +5 -0
- package/src/assurance/capabilities.ts +22 -4
- package/src/assurance/runner.ts +40 -58
- package/src/assurance/types.ts +3 -0
- package/src/assurance/validation.ts +137 -0
- package/src/bridge/surface/arguments/assurance.ts +1 -0
- package/src/cli/database-rules.ts +1 -1
- package/src/cli/index.ts +13 -2
- package/src/cli/pyric-config.ts +5 -0
- package/src/cli/serve.ts +6 -3
- package/src/cli/verify.ts +244 -18
- package/src/conformance/.generated/can-i-use.ts +1 -1
- package/src/conformance/.generated/conformance-docs.ts +1 -1
- package/src/serve/repro/denials.ts +2 -1
- package/src/serve/verify-hint.ts +8 -0
- package/src/serve/vite-generation-rules-watch.ts +6 -3
- package/src/verify/index.ts +119 -19
- package/src/verify/staging/engine.ts +455 -0
- package/src/verify/staging/rest.ts +129 -0
- 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
|
-
|
|
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
|
}
|
|
@@ -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}`,
|
package/src/verify/index.ts
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
624
|
+
checks.push({ instance, rules: parsed, capturedState: rtdb.instances?.[instance]?.state.tree });
|
|
604
625
|
}
|
|
605
626
|
|
|
606
|
-
const
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
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:
|
|
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 {
|