mirror of
https://github.com/THU-MAIC/OpenMAIC.git
synced 2026-10-17 05:29:46 +08:00
* fix(release): dedupe the published dsl, close two release-path blind spots
Three independent defects on the release path.
1. `@openmaic/storage`, `@openmaic/renderer` and `@openmaic/importer`
declared `"@openmaic/dsl": "workspace:*"`, which pnpm publishes as an
EXACT pin. The tarballs on the registry today say storage@0.1.1 needs
dsl 0.5.0, renderer@0.0.3 needs dsl 0.4.0 and importer@0.1.1 needs dsl
0.4.0, so installing storage together with renderer yields two copies
of the dsl. The dsl carries the schema, the validators and the version
constants, so a document produced against one copy can be validated by
the other's schema revision.
`workspace:^` publishes as `^0.5.0`. Under 0.x that admits 0.5.x, so a
dsl patch reaches dependents without republishing them while a dsl
minor still requires a deliberate dependent release. All three
packages take a patch version increase, both because
check-package-version-bumps.mjs requires one and because the corrected
range only reaches consumers through a new release.
The tarball smoke test now asserts the packed range, so an exact pin
cannot come back unnoticed.
2. The publish workflow's build-integrity guard ran `git diff --quiet --
packages/@openmaic`, which compares the worktree to the INDEX. The
steps before it run third-party package code, which needs no
credential to rewrite a tracked file and `git add` it, after which the
guard reported clean and the rewritten bytes were packed and
published. Both copies of the guard now compare against HEAD, the
commit actually being released.
3. `@openmaic/storage`'s PostgreSQL contract suites refuse to skip when
STORAGE_PG_CONTRACT_REQUIRED=1, but that refusal is a `throw` inside
the test modules, so it never fires if vitest stops collecting them.
Collection is decided by `packages/@openmaic/storage/vitest.config.ts`,
which is on the ignore list of publishable inputs, so excluding
`*.pg.test.ts` there turns both PostgreSQL backends off with a green
run and no version bump.
scripts/assert-pg-contract-suites.mjs reads vitest's json results and
requires both suite files to be present, passed and non-empty, keyed
on the file names rather than on a count. It runs in the publish
workflow's validate job and in the storage PostgreSQL contract
workflow.
Merging this publishes @openmaic/storage@0.1.2, @openmaic/renderer@0.0.4
and @openmaic/importer@0.1.2. That republish is the fix for (1), not a
side effect of it: the corrected dependency range exists only in a
tarball that has not been cut yet.
* fix(release): audit the contract database, pin the format version to a minor
Follow-up on cross-vendor review of the three release-path fixes.
1. The PostgreSQL assertion proved far less than it claimed. Vitest's
`assertionResults` are test CASES, not `expect()` calls, so one
`test('x', () => {})` per required filename satisfied it — and the
whole surface is reachable without a version bump, because `test/` is
on the same ignore list as `vitest.config.ts`. A `vi.mock('pg', ...)`
in the already-wired `test/setup.ts`, or a stubbed suite body, made it
print "ran against a real database" having checked nothing of the
sort.
The script now runs in two phases. Phase 1 keeps the file/status
checks. Phase 2 connects to PG_CONTRACT_URL from outside the vitest
process and requires all five tables the two backends own to exist and
to show inserts in pg_stat_user_tables. Nothing inside `test/` can
forge that. Cumulative insert counters rather than surviving rows,
because the suites clean up after themselves. The success message now
states only what was checked.
2. Validation packed with lifecycle scripts while publishing runs
`--ignore-scripts`, so committing `workspace:*` plus a `prepack` that
rewrites it to `workspace:^` passed the tarball assertion and then
published the exact pin. Both the smoke test and the workflow's
dry-run pack now pass `--config.ignore-scripts=true`, so validation
and publication have identical lifecycle semantics.
3. `workspace:^` unpins the serialized-format version that the exact pin
was silently holding. DSL_VERSION and RUNTIME_DSL_VERSION are
decoupled from the npm version by design, and storage compares them by
value across the package boundary. Under `^0.5.0` a dsl PATCH could
therefore hand a new format to an already-published storage: one
install resolves it and stamps the new version into document_stages,
another install pinned to the older patch reads that row and hard-
fails. Two installs of one published storage version, data-
incompatible.
check-package-version-bumps.mjs now requires a change to either
constant to carry at least a MINOR increase of the dsl package
version, since a caret does not cross a 0.x minor. The rule is
documented next to both constants. That doc edit is itself a
publishable change, so dsl takes a patch bump to 0.5.1.
4. The range and build-integrity guards ran only in the publish workflow,
so a pull request restoring `workspace:*` or leaving a stale generated
file failed at release time, after a version number had been spent.
ci.yml gains a cheap source-level range check
(scripts/check-internal-dependency-ranges.mjs) and the
`git diff --quiet HEAD` integrity check after its install step. The
tarball assertion stays where it is as the release-time proof of
actual published behaviour.
Merging this now publishes @openmaic/dsl@0.5.1 as well as
@openmaic/storage@0.1.2, @openmaic/renderer@0.0.4 and
@openmaic/importer@0.1.2.
* fix(release): fail the gates closed, and state what they actually prove
Confirmation round on the release-path fixes.
1. The format-version rule failed open if version.ts moved. It hard-coded
one source path and returned successfully when either revision lacked
it, so renaming the file, changing DSL_VERSION and bumping dsl by a
patch passed both checks. It now reports an error whenever the file is
absent at either revision while dsl has any publishable change. The
constant regex is anchored to the start of a line, so a commented-out
declaration cannot be read as the value, and a duplicate declaration is
an error rather than a coin flip.
2. "At least a MINOR" was wrong past 1.0.0: `^1.0.0` admits minors, so a
format change shipped as a minor would float into published dependents
exactly as the rule exists to prevent. The rule is now stated as what
it needs to be — an increase the dependents' caret range will NOT admit
— computed from the pre-change version: a minor while dsl is 0.x, a
major once it reaches 1.0.0. The error message and the docs in
version.ts say the same thing.
3. Internal declarations outside `dependencies` bypassed both range
checks. Keeping `dependencies` at `workspace:^` while adding an exact
`peerDependencies` or `optionalDependencies` entry published a second,
tighter constraint and passed. An owned package must now appear exactly
once, in `dependencies`; entries in the peer and optional fields are
rejected outright, in the source check and in the packed-manifest
assertion. The global count test is replaced by naming the three
dependents that must be present.
4. The format rule compared against the base tip, so an un-rebased branch
produced an inverted message. It now uses the merge base, matching the
`base...HEAD` form the rest of diff mode uses. Deliberately not applied
to the version comparison, where the tip is the correct reference.
5. Phase 2 had no baseline, so cumulative counters from an earlier run
satisfied it forever against a non-ephemeral database. It now captures
the counters before the vitest step and requires a positive delta.
Both workflows gained the capture step.
6. Phase 2 overclaimed. It proves rows were inserted into those five
tables during the run, not that the built PgDocumentStore and
PgRuntimeStore inserted them — `test/` is on the publishable-input
ignore list, so test code could write directly. The success message now
says exactly that, and the script documents the threat model: this
catches accidental silencing, which is the defect it was written for;
it is not a defence against someone who can merge changes to `test/`,
who can alter production code just as easily.
Also: scripts/openmaic-packages.mjs is now the single source for the
owned package list, consumed by all three checking scripts, and it
cross-checks itself against both the packages directory and
publish-packages.yml so a package added in one place and forgotten in
another fails rather than going quietly exempt. The workflow's own YAML
lists cannot import it, which is why they are cross-checked instead.
* fix(release): let the format-version source move without opening the gate
Failing closed on a missing version.ts is right, but on its own it made a
rename impossible to merge: the new path does not exist at the base
revision either, so the check refused both before and after the move.
DSL_VERSION_SOURCES is now an ordered candidate list, resolved per
revision against the first path that exists there. A rename lands by
prepending the new path in the same change, and the comparison still
happens across the move. Absent everywhere at either revision, while dsl
has any publishable change, remains an error.
* fix(release): report package-list drift instead of crashing on it
Every check in check-internal-dependency-ranges.mjs reads the shared
package list, so a list naming a package that does not exist threw an
ENOENT out of readManifest before any finding was printed. It failed
closed, but unreadably. The list is now validated first and fatally, with
its own headline.
* fix(release): anchor the caret to the base branch, and check the gates' own inputs
Final confirmation round.
1. The caret escape was computed from the merge base, which is the wrong
reference for it. With merge base dsl 0.5.1, an ordinary minor landing
on main taking the tip to 0.6.0, and HEAD at 0.6.1 with a changed
DSL_VERSION, the rule computed the escape from 0.5.1, got 0.6.0, and
accepted 0.6.1 — but dependents released against 0.6.0 publish
`^0.6.0`, which admits 0.6.1, so the new format reached them anyway.
0.6.1 is also the minimum the ordinary version check allows, making
that the default outcome rather than an unlucky one.
The escape is now computed from the HIGHEST dsl version reachable on
the base branch. Whether the format moved still comes from the merge
base, because that is a question about the branch. The comment on
mergeBaseWithHead now records that it is a no-op in both current CI
invocations (checkout uses the merge ref on pull_request, and the
before-SHA is an ancestor on push) rather than claiming a case that is
not exercised there.
2. The workflow cross-check matched raw text, so a commented-out trigger
path still satisfied it while silently disabling that package's
release. Full-line comments are now stripped first; the `on.push.paths`
list and every `for pkg in ...` loop are compared as exact sets, so an
unexpected entry fails as well as a missing one; and `--filter` usage
is checked more loosely, because individual steps legitimately filter
subsets. The limits of a textual check are stated where it lives.
3. `locate()` returned the first candidate format-version source that
existed, so during a half-finished rename the gate could compare a file
that is no longer the exported one. It now resolves every candidate per
revision and fails unless exactly one declares the constants.
4. INTERNAL_DEPENDENTS was checked in one direction only, so deleting an
entry exempted that package from the source check and from the packed
assertion that iterates the same map. It is now cross-checked both
ways, an owned package in another owned package's devDependencies is
rejected outright, and multiple owned dependencies no longer collapse
to whichever was declared last.
5. assertPackageListIsComplete() now also runs in release mode, which is
the gate that decides what gets published; and the PostgreSQL script
consumes flag values properly instead of treating the first non-flag
argument as the results path.
Also documents, in openmaic-packages.mjs, the threat model these gates
share: they catch mistakes, and anyone who can merge edits to scripts/
can edit production code directly, so deliberate subversion is out of
scope and cannot be closed by adding further checks.
317 lines
13 KiB
JavaScript
317 lines
13 KiB
JavaScript
import { createRequire } from 'node:module';
|
|
import { readFileSync, writeFileSync } from 'node:fs';
|
|
|
|
/**
|
|
* Assert that @openmaic/storage's PostgreSQL contract suites really exercised a
|
|
* real PostgreSQL, from evidence that cannot be produced by editing the tests.
|
|
*
|
|
* WHY THIS EXISTS AT ALL. The suites already refuse to skip when
|
|
* STORAGE_PG_CONTRACT_REQUIRED=1, but that refusal is a `throw` inside the test
|
|
* modules, so it cannot fire if vitest stops collecting them. Collection is
|
|
* decided by `packages/@openmaic/storage/vitest.config.ts`, and both that file
|
|
* and the whole `test/` directory are on the ignore list of publishable inputs
|
|
* in `check-package-version-bumps.mjs`. The entire surface those suites live on
|
|
* can therefore be rewritten with no version bump and no release gate noticing.
|
|
*
|
|
* WHY THE VITEST RESULTS ARE NOT ENOUGH ON THEIR OWN. `assertionResults` are
|
|
* test CASES, not `expect()` calls, so a file named `pg-document-store.pg.test.ts`
|
|
* containing nothing but `test('x', () => {})` satisfies every check in phase 1
|
|
* below. And because `test/setup.ts` is already wired as `setupFiles`, a single
|
|
* `vi.mock('pg', ...)` there makes both suites collect, run and pass green
|
|
* against an in-memory fake. Both edits are one line, in `test/`, and need no
|
|
* version bump. Phase 1 therefore proves only that two files with those names
|
|
* ran and reported passing cases — nothing whatsoever about a database.
|
|
*
|
|
* WHAT PHASE 2 ADDS. It connects to the contract database from OUTSIDE the
|
|
* vitest process and asks PostgreSQL itself what happened: the five tables
|
|
* these two backends own must exist, and each must have gained inserts DURING
|
|
* the run. Nothing inside `test/` can forge that, because producing it requires
|
|
* actually writing to the database this script independently connects to.
|
|
*
|
|
* Insert counters rather than surviving rows: the suites clean up after
|
|
* themselves, so counting rows would prove nothing, while `n_tup_ins` survives
|
|
* the cleanup. Counted as a delta against a baseline captured before the run
|
|
* rather than as an absolute, so a non-ephemeral database cannot satisfy the
|
|
* check forever on the strength of some earlier run. Both current workflows use
|
|
* a fresh per-job service container, but this check should not depend on that
|
|
* staying true.
|
|
*
|
|
* ── THREAT MODEL, STATED HONESTLY ────────────────────────────────────────────
|
|
*
|
|
* What this proves: during this run, rows were inserted into those five tables
|
|
* in a real PostgreSQL, and two files with the contract suites' names ran and
|
|
* passed.
|
|
*
|
|
* What it does NOT prove: that the built `PgDocumentStore` and `PgRuntimeStore`
|
|
* were the code that inserted them. The whole `test/` directory is on the
|
|
* publishable-input ignore list, so test code can create the schema and insert
|
|
* directly, and this audit would read the same either way. Closing that needs a
|
|
* harness living outside the ignored `test/` surface — separate work, not
|
|
* attempted here.
|
|
*
|
|
* That limit is acceptable because of who each threat is. This guard exists to
|
|
* catch ACCIDENTAL silencing: a vitest `include`/`exclude` change, a missing
|
|
* environment variable, a renamed suite file, a dropped workflow step. Those
|
|
* are the ways this coverage actually disappears, and they are all caught.
|
|
* It is not a defence against someone deliberately faking coverage from inside
|
|
* `test/` — and it does not need to be, because that person can merge changes
|
|
* to the production sources just as easily.
|
|
*/
|
|
|
|
const REQUIRED_SUITES = [
|
|
'packages/@openmaic/storage/test/pg-document-store.pg.test.ts',
|
|
'packages/@openmaic/storage/test/pg-runtime-store.pg.test.ts',
|
|
];
|
|
|
|
/**
|
|
* The tables created by `DOCUMENT_PG_SCHEMA` and `RUNTIME_PG_SCHEMA`. Kept
|
|
* explicit rather than parsed out of those sources: this list is the
|
|
* independent statement of what the contract must have touched, and deriving it
|
|
* from the code under test would let that code narrow its own audit.
|
|
*/
|
|
const REQUIRED_TABLES = [
|
|
'document_stages',
|
|
'document_scenes',
|
|
'document_outlines',
|
|
'runtime_sessions',
|
|
'runtime_records',
|
|
];
|
|
|
|
const usage = [
|
|
'Usage:',
|
|
' assert-pg-contract-suites.mjs --capture-baseline <file>',
|
|
' Record the current insert counters. Run BEFORE the vitest invocation.',
|
|
' assert-pg-contract-suites.mjs <vitest-json-results> --baseline <file>',
|
|
' Audit the run against that baseline. Run AFTER the vitest invocation.',
|
|
].join('\n');
|
|
|
|
// Parsed by consuming each flag with its value, so the remaining positionals
|
|
// are exactly the positionals. Scanning for "the first argument that does not
|
|
// start with --" would silently accept a flag's value as the results path.
|
|
const argv = process.argv.slice(2);
|
|
let capturingBaseline = false;
|
|
let baselinePath;
|
|
const positionals = [];
|
|
for (let i = 0; i < argv.length; i += 1) {
|
|
const arg = argv[i];
|
|
if (arg === '--baseline' || arg === '--capture-baseline') {
|
|
capturingBaseline ||= arg === '--capture-baseline';
|
|
i += 1;
|
|
baselinePath = argv[i];
|
|
if (baselinePath === undefined) {
|
|
console.error(`${arg} needs a file path.\n\n${usage}`);
|
|
process.exit(2);
|
|
}
|
|
continue;
|
|
}
|
|
if (arg.startsWith('--')) {
|
|
console.error(`Unknown option ${arg}.\n\n${usage}`);
|
|
process.exit(2);
|
|
}
|
|
positionals.push(arg);
|
|
}
|
|
|
|
const expectedPositionals = capturingBaseline ? 0 : 1;
|
|
if (!baselinePath || positionals.length !== expectedPositionals) {
|
|
console.error(usage);
|
|
process.exit(2);
|
|
}
|
|
const resultsPath = positionals[0];
|
|
|
|
const contractUrl = process.env.PG_CONTRACT_URL;
|
|
if (!contractUrl) {
|
|
console.error(
|
|
'PG_CONTRACT_URL is unset, so there is no database to audit and this check cannot ' +
|
|
'establish that the PostgreSQL contract ran. Invoke it in the same job, with the ' +
|
|
'same PG_CONTRACT_URL, as the vitest run it is auditing.',
|
|
);
|
|
process.exit(2);
|
|
}
|
|
|
|
// The database side -----------------------------------------------------------
|
|
|
|
// `pg` is a devDependency of @openmaic/storage, not of the repository root, so
|
|
// resolve it from the package that owns it rather than assuming hoisting.
|
|
const requireFromStorage = createRequire(
|
|
new URL('../packages/@openmaic/storage/package.json', import.meta.url),
|
|
);
|
|
const { Client } = requireFromStorage('pg');
|
|
|
|
async function collectInsertCounts(client) {
|
|
const { rows } = await client.query(
|
|
`SELECT t.relname AS table_name,
|
|
to_regclass('public.' || t.relname) IS NOT NULL AS present,
|
|
COALESCE(s.n_tup_ins, 0)::bigint AS inserts
|
|
FROM unnest($1::text[]) AS t(relname)
|
|
LEFT JOIN pg_stat_user_tables s
|
|
ON s.schemaname = 'public' AND s.relname = t.relname`,
|
|
[REQUIRED_TABLES],
|
|
);
|
|
return Object.fromEntries(
|
|
rows.map((row) => [row.table_name, { present: row.present, inserts: Number(row.inserts) }]),
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Read the counters, optionally waiting for them to move past `baseline`.
|
|
*
|
|
* Backends flush statistics at transaction end and on exit, so by the time
|
|
* vitest has returned they are normally already visible. Re-read a few times
|
|
* anyway rather than racing a slow flush, discarding the per-session snapshot
|
|
* each round because a backend caches it for the whole transaction. A flush
|
|
* that never arrives fails the check rather than passing it.
|
|
*/
|
|
async function readCounters({ waitFor } = {}) {
|
|
const client = new Client({ connectionString: contractUrl });
|
|
try {
|
|
await client.connect();
|
|
let counts = await collectInsertCounts(client);
|
|
if (!waitFor) return counts;
|
|
for (let attempt = 1; attempt < 5; attempt += 1) {
|
|
const unproven = REQUIRED_TABLES.filter(
|
|
(table) => !(counts[table]?.inserts > (waitFor[table]?.inserts ?? 0)),
|
|
);
|
|
if (unproven.length === 0) break;
|
|
await new Promise((resolve) => setTimeout(resolve, 500));
|
|
await client.query('SELECT pg_stat_clear_snapshot()');
|
|
counts = await collectInsertCounts(client);
|
|
}
|
|
return counts;
|
|
} catch (error) {
|
|
console.error(
|
|
`Cannot reach the contract database at PG_CONTRACT_URL: ${error.message}. ` +
|
|
'Without it there is no evidence the suites touched a real PostgreSQL.',
|
|
);
|
|
process.exit(2);
|
|
} finally {
|
|
await client.end().catch(() => {});
|
|
}
|
|
}
|
|
|
|
if (capturingBaseline) {
|
|
const baseline = await readCounters();
|
|
writeFileSync(baselinePath, `${JSON.stringify(baseline, null, 2)}\n`);
|
|
const summary = REQUIRED_TABLES.map((table) => `${table}=${baseline[table]?.inserts ?? 0}`);
|
|
console.log(`Captured contract-database baseline to ${baselinePath}: ${summary.join(' ')}.`);
|
|
process.exit(0);
|
|
}
|
|
|
|
// Phase 1 ---------------------------------------------------------------------
|
|
// The two suite files were collected and reported passing cases.
|
|
|
|
let results;
|
|
try {
|
|
results = JSON.parse(readFileSync(resultsPath, 'utf8'));
|
|
} catch (error) {
|
|
console.error(`Cannot read the vitest results at ${resultsPath}: ${error.message}`);
|
|
process.exit(2);
|
|
}
|
|
|
|
const files = Array.isArray(results.testResults) ? results.testResults : undefined;
|
|
if (!files) {
|
|
console.error(
|
|
`${resultsPath} has no testResults array, so it cannot show which suites ran. ` +
|
|
'Was the run invoked with the json reporter?',
|
|
);
|
|
process.exit(2);
|
|
}
|
|
|
|
const failures = [];
|
|
for (const suite of REQUIRED_SUITES) {
|
|
// The reporter records absolute paths; match on the repository-relative tail.
|
|
const entry = files.find((file) => typeof file.name === 'string' && file.name.endsWith(suite));
|
|
if (!entry) {
|
|
failures.push(
|
|
`${suite} did not run, so the PostgreSQL contract is unverified. ` +
|
|
"Check vitest's `include`/`exclude` in packages/@openmaic/storage/vitest.config.ts.",
|
|
);
|
|
continue;
|
|
}
|
|
const cases = Array.isArray(entry.assertionResults) ? entry.assertionResults : [];
|
|
const passed = cases.filter((testCase) => testCase.status === 'passed').length;
|
|
const pending = cases.filter((testCase) => testCase.status !== 'passed');
|
|
if (entry.status !== 'passed') {
|
|
failures.push(`${suite} reported status "${entry.status}".`);
|
|
continue;
|
|
}
|
|
if (passed === 0) {
|
|
failures.push(`${suite} ran but reported no passing test cases, so it asserted nothing.`);
|
|
continue;
|
|
}
|
|
if (pending.length > 0) {
|
|
failures.push(
|
|
`${suite} left ${pending.length} test case(s) not passing ` +
|
|
`(${[...new Set(pending.map((testCase) => testCase.status))].join(', ')}).`,
|
|
);
|
|
continue;
|
|
}
|
|
console.log(`${suite}: ${passed} test cases ran and passed.`);
|
|
}
|
|
|
|
if (failures.length > 0) {
|
|
console.error(
|
|
[
|
|
'The PostgreSQL contract suites did not run as required:',
|
|
...failures.map((failure) => `- ${failure}`),
|
|
].join('\n'),
|
|
);
|
|
process.exit(1);
|
|
}
|
|
|
|
// Phase 2 ---------------------------------------------------------------------
|
|
// PostgreSQL's own account of what changed during the run.
|
|
|
|
let baseline;
|
|
try {
|
|
baseline = JSON.parse(readFileSync(baselinePath, 'utf8'));
|
|
} catch (error) {
|
|
console.error(
|
|
`Cannot read the pre-run baseline at ${baselinePath}: ${error.message}. Capture it with ` +
|
|
'`--capture-baseline` before the vitest step; without it, counters left by an earlier ' +
|
|
'run against a non-ephemeral database would satisfy this check forever.',
|
|
);
|
|
process.exit(2);
|
|
}
|
|
|
|
const counts = await readCounters({ waitFor: baseline });
|
|
|
|
const databaseFailures = [];
|
|
for (const table of REQUIRED_TABLES) {
|
|
const observed = counts[table];
|
|
const before = baseline[table]?.inserts ?? 0;
|
|
if (!observed?.present) {
|
|
databaseFailures.push(
|
|
`${table} does not exist in the contract database, so the suites never created it ` +
|
|
'and did not run against this PostgreSQL.',
|
|
);
|
|
continue;
|
|
}
|
|
const gained = observed.inserts - before;
|
|
if (!(gained > 0)) {
|
|
databaseFailures.push(
|
|
`${table} exists but gained no inserts during this run (before ${before}, after ` +
|
|
`${observed.inserts}), so the suites did not write to this database. Check for a ` +
|
|
'mocked driver in packages/@openmaic/storage/test/setup.ts, or a stubbed-out suite body.',
|
|
);
|
|
continue;
|
|
}
|
|
console.log(`${table}: ${gained} inserts during this run (${before} -> ${observed.inserts}).`);
|
|
}
|
|
|
|
if (databaseFailures.length > 0) {
|
|
console.error(
|
|
[
|
|
'The contract database shows no evidence that these suites wrote to it during this run:',
|
|
...databaseFailures.map((failure) => `- ${failure}`),
|
|
].join('\n'),
|
|
);
|
|
process.exit(1);
|
|
}
|
|
|
|
console.log(
|
|
`Verified: both contract suite files ran and passed, and all ${REQUIRED_TABLES.length} tables ` +
|
|
'the two PostgreSQL backends own gained inserts in a real database during this run. ' +
|
|
'This does not attribute those inserts to the built PgDocumentStore / PgRuntimeStore ' +
|
|
'specifically — see the threat model at the top of this script.',
|
|
);
|