CORE-132: staging measurement of the direct-edge guard¶
Status: harness safety patch under review, no staging measurement taken and no staging sign-off. The harness measures nothing about CPU or IO. No authorized staging target was found in the VM that prepared this plan, so no staging result exists and CORE-132 stays open. A scratch database run is not staging sign-off.
This document is evidence for CORE-87/73 and does not authorize an index swap or any production DDL. The CORE-126 guard is not a fleet write barrier.
Question¶
What does require_direct_edge_index (postgres_edge_index_preflight.py,
postgres_edge_scoped_writer.py) cost per write, per connection, under
concurrency, on infrastructure shaped like production? The only prior numbers
(+0.632 ms legacy, +0.843 ms scoped) are single-connection synthetic raw
inserts on a disposable PostgreSQL 16, not the public writers.
Harness¶
scripts/edge_guard_bench/run.py. Each cell calls the public
PostgresGraphBackend.add_edge / delete_edge in legacy
(tenant_guarded) and scoped modes at concurrency 1/2/4 by default. The baseline
variant replaces only the guard call with a no-op; SQL, pool and method are
identical. Variant order is shuffled per round. It reports p50/p95/p99, mean,
throughput, and server deltas (pg_stat_database commits/blocks/tuples, and
guard-statement calls and exec time when pg_stat_statements exists;
otherwise null, never zero).
Safety contract (enforced in code, scripts/edge_guard_bench/)¶
No database run is authorized by this document. Run only after a second independent review of this harness, and only against a disposable Docker PostgreSQL until the owner separately authorizes staging.
Target attestation (safety.py). The harness refuses unless every one of
EDGE_GUARD_BENCH_DSN, _TARGET_KIND, _EXPECTED_HOST, _EXPECTED_PORT,
_EXPECTED_DATABASE, _EXPECTED_USER, _EXPECTED_SERVER_ADDR is set and the
DSN (explicit host, port, database, user; no query string) matches each. Database
names that are defaults or production-like (postgres, template*, default(s),
main, app, symfonic, or containing prod/production/live) are refused.
On every connection (admin, probe, each pool connection via init) it checks
current_database(), current_user, inet_server_addr() and
inet_server_port() against the attestation; a unix-socket session
(no server address) is refused. Staging additionally needs
EDGE_GUARD_BENCH_STAGING_ACK equal to safety.ACK_PREFIX:host:port/database
for that exact database, and EDGE_GUARD_BENCH_OPERATOR_EVIDENCE_REF. Staging
keeps exact-identity semantics only; the disposable constraints below do not
apply to it and do not relax it.
Disposable gate (safety.require_disposable_shape). TARGET_KIND=disposable is
not trusted on the operator's word. It is permitted only when the DSN host and
EXPECTED_SERVER_ADDR are both explicit loopback literals (127.0.0.0/8 or
::1; localhost, hostnames, 0.0.0.0 and IPv4-mapped IPv6 are refused), the
database name starts with core132_, and the role name starts with core132_.
These checks prove DSN/session consistency and the loopback/prefix shape, not
that the server is a fresh disposable Docker instance. A loopback tunnel or a
shared local PostgreSQL with matching names can pass. "Fresh non-superuser
role" is enforced live only as non-superuser (see below); freshness is
operator provisioning, not something the code can prove. Consequently,
inet_server_addr() must itself be loopback: use host networking with the
PostgreSQL server listening on the same port as the DSN. A bridge-published
port reports the container address and is refused (fails closed).
Disposable pre-run checklist (operator evidence, not an automatic guard):
- Record the fresh Docker container ID and volume; verify the loopback listener's PID belongs to that container, with no tunnel, proxy, or shared local PostgreSQL process on the selected port.
- Provision a fresh
core132_database andcore132_role only in that container. Confirm the live database, role, server address and port match the DSN andEXPECTED_*values; use a dedicated limited role with no unrelated grants orCREATEonpublic. - Preinstall
vectoras an administrator, selectTARGET_KIND=disposable, and lower concurrency, per-worker operations and total operations for the first run. A disposable run supplies no staging sign-off.
Schema lifecycle (schema.py). CREATE SCHEMA has no IF NOT EXISTS. The
schema is dropped only if this run's CREATE SCHEMA returned and the catalog
shows the session role owns it. A collision, error or ownership mismatch
drops nothing. Residual: if the connection dies after the server created the
schema but before the reply, the schema is leaked rather than dropped; the
operator removes it (the name carries the edge_guard_bench_ prefix).
No shared DDL (schema.py, adapter.py). ensure_schema still contains
CREATE EXTENSION IF NOT EXISTS vector; during setup the connection wrapper
swallows that statement and never forwards it, and the harness refuses to start
unless vector is already installed. Setup DDL is restricted to
CREATE/ALTER TABLE and CREATE/DROP INDEX; any other DDL, GRANT, or statement
naming public. is refused. search_path is the private schema then the
schema holding vector (needed to resolve the type; this may be public,
which is read, never written). The role must have no superuser, createrole,
bypassrls, replication, or CREATE on public, else fail closed. After setup
the count of relations the role owns outside the private schema must be
unchanged.
What this does and does not prove: it proves the checks above at run time via catalog reads. It does not prove the role has no other grants (for example on tables in other schemas), and it cannot prove that a mid-run crash leaves nothing behind. Provision a dedicated role with CREATE on the database only.
Caps (limits.py), checked before any connection and again as a runtime
budget. Counted operations include seeding, both variants, all rounds, modes
and concurrency levels. Hard caps: concurrency 8, 200 ops per worker, 40,000
total; staging defaults: concurrency 2, 4,000 total. EDGE_GUARD_BENCH_MAX_CONCURRENCY,
_MAX_OPS_PER_WORKER, _MAX_TOTAL_OPS can only lower a cap. Default plan:
concurrency 1/2/4, 3 rounds, 100 ops per worker.
Counter attribution (pgstats.py). pg_stat_database and
pg_stat_statements are shared counters. Each delta is labelled
attributable only if pg_stat_activity showed no foreign client session on
the database (pool pids excluded) before and after, and the counters were
readable; otherwise the delta is kept with non_attributable_reasons. Guard
statement counters additionally need pg_stat_statements and are filtered to
the session role. The harness's own sessions excluded from the foreign count are the pooled
connections and the admin connection private_schema holds open for the whole
run (its pid is exposed as PrivateSession.admin_pid); the probe excludes
itself in SQL. Missing counters are null, never zero. pg_stat_database
deltas are indicative only: backends flush these statistics with delay, so a
sample taken right after a window can miss or smear activity. The
attributable label means no foreign session was seen, not that the delta is
exact. Per-round
timestamps are recorded, and paired holds per-round guarded-minus-baseline
p99 and mean differences with their range. The harness records no CPU or IO
figure and does not measure CPU or IO automatically: that is operator evidence referenced by
EDGE_GUARD_BENCH_OPERATOR_EVIDENCE_REF (required for staging).
Not covered: update_owned_edge, delete_node(cascade) and delete_subtree
(same guard, different SQL) and server CPU/IO.
Evidence plan¶
- Preconditions: written staging authorization naming the database; a role
with CREATE SCHEMA only;
pg_stat_statementspreloaded if possible; same PostgreSQL major, instance class and pooler topology as production. - Outside the harness, capture PG host CPU and IO (node exporter or cloud metrics) for each run window, and record start/end timestamps. The harness JSON carries no CPU figure.
- Run once at quiet hours:
python scripts/edge_guard_bench/run.py --out core132-staging.jsonwith absolutePYTHONPATHto the audited commit. Repeat on a second day; keep both files. - Report per cell: p50/p95/p99 overhead, throughput ratio, guard-statement calls per write (expect 1), catalog block reads.
- Decision input only: approve if p99 overhead and throughput ratio stay inside a bound the owner states before the run.
Open items¶
- Staging target and authorization: none found. Blocker owner: operator.
- Acceptance bound: not set; owner to state before the run.
- Disposable mechanics smoke on 2026-10-02: fresh
pgvector/pgvector:pg16containercore132_pg_lab_20261002, loopback port 55432, dedicated limited role, legacy/scoped add/delete, concurrency 1, one round, five writes per cell. Initial run exposed PostgreSQLnumeric/PythonDecimalJSON serialization; explicit SQL aggregate casts fixed it. Repeated run exited 0 with 76/76 planned operations, four cells, guard-statement calls 5 per guarded cell and 0 per baseline cell, all deltas labelled attributable, and no private schema left behind.pg_stat_databasedeltas were zero because immediate reads can miss delayed counter flushes. The scratch JSON is on the VM at/tmp/core132-lab-smoke-20261002.json; it is not a staging or performance result and supplies no acceptance evidence for a swap. - Independent safety review was conditional on operator verification of Docker provenance, which was performed before the smoke. Real asyncpg guard calls now have disposable-mechanics evidence, not staging latency evidence.