Skip to content

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 and core132_ role only in that container. Confirm the live database, role, server address and port match the DSN and EXPECTED_* values; use a dedicated limited role with no unrelated grants or CREATE on public.
  • Preinstall vector as an administrator, select TARGET_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

  1. Preconditions: written staging authorization naming the database; a role with CREATE SCHEMA only; pg_stat_statements preloaded if possible; same PostgreSQL major, instance class and pooler topology as production.
  2. 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.
  3. Run once at quiet hours: python scripts/edge_guard_bench/run.py --out core132-staging.json with absolute PYTHONPATH to the audited commit. Repeat on a second day; keep both files.
  4. Report per cell: p50/p95/p99 overhead, throughput ratio, guard-statement calls per write (expect 1), catalog block reads.
  5. 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:pg16 container core132_pg_lab_20261002, loopback port 55432, dedicated limited role, legacy/scoped add/delete, concurrency 1, one round, five writes per cell. Initial run exposed PostgreSQL numeric/Python Decimal JSON 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_database deltas 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.