PostgreSQL 18.6
PostgreSQL 18.6 schemas, migrations, queries, roles, configuration and operations read from SQL text, parsed commands, configuration files and postgres-language-server. Rule IDs PG-V/S/M/Q/A/C/O/P; NEED TO blocks or asks, SHOULD warns.
Set this profile
ccpp cpo profile set postgres-18Inside Claude Code, prefix the command with ! to run it in the session. Downloading a profile needs a Builder or Team plan; listing them does not.
Policies
125 policies, grouped by what they guard.
Secrets & private data 4
Credentials, keys and personal data stay out of files, logs, commands and the conversation.
pg-a08-trust-envPG-A08 · The user confirms container setups with POSTGRES_HOST_AUTH_METHOD=trust (every published port then accepts any login)
pg-a10-command-passwordsPG-A10 · Commands carry no literal database password (URLs, PGPASSWORD=…, password= in arguments)
pg-a10-pgpass-modePG-A10 · A written .pgpass file is made owner-only (libpq ignores it otherwise)
pg-a10-sql-passwordsPG-A10 · Tracked SQL holds no literal password (CREATE/ALTER ROLE … PASSWORD, user mappings, connection strings)
Authentication & authorization 12
Who may do what, from roles, privileges and permissions to CSRF, CORS and verified tokens.
pg-a01-role-attributes-commandPG-A01 · The user confirms commands and database MCP calls giving roles SUPERUSER, BYPASSRLS, REPLICATION, CREATEROLE or server-file privileges
pg-a01-role-attributesPG-A01 · The user confirms roles given SUPERUSER, BYPASSRLS, REPLICATION, CREATEROLE or server-file privileges in SQL files
pg-a03-public-privilegesPG-A03 · PUBLIC gets no CREATE on a schema and no blanket ALL privileges: a writable schema on a search path is an attack path
pg-a04-definer-executePG-A04 · New SECURITY DEFINER functions revoke EXECUTE from PUBLIC in the same file
pg-a04-security-definerPG-A04 · SECURITY DEFINER routines pin a trusted search_path (no public, no $user, pg_temp last)
pg-a06-rls-forcePG-A06 · Tables getting row-level security are checked under the role that really runs the queries (owners bypass RLS unless FORCE)
pg-a08-hba-trustPG-A08 · pg_hba.conf never trusts network connections beyond the local host (trust over TCP needs no password at all)
pg-a11-extensions-commandPG-A11 · The user confirms commands and database MCP calls creating extensions outside allowedExtensions or loading libraries
pg-a11-extensionsPG-A11 · The user confirms new extensions outside allowedExtensions, native C functions, LOAD and preload libraries
pg-v09-md5-authenticationPG-V09 · New authentication setup uses SCRAM, not MD5 (deprecated in PostgreSQL 18)
pg-v17-ignore-cipher-failure-commandPG-V17 · The user confirms a command or MCP call using pgcrypto's ignore-cipher-failure option (recovery and re-encryption only)
pg-v17-ignore-cipher-failurePG-V17 · The user confirms pgcrypto's ignore-cipher-failure option in a file: it only strips the broken pre-18.6 PGP wrapper for re-encryption
Untrusted input 4
Requests, downloads and tool output are validated before they reach SQL, a shell, eval or a deserializer.
pg-a12-restore-sourcesPG-A12 · The user confirms restoring a dump or piping downloaded SQL into psql: a dump is executable input
pg-a13-server-copy-commandPG-A13 · The user confirms commands and database MCP calls running server-side COPY to/from a program or a server file
pg-a13-server-copyPG-A13 · The user confirms server-side COPY to/from a program or a server file in SQL files (client-side \copy needs no confirmation)
pg-q01-dynamic-sql-concatPG-Q01 · Dynamic SQL (PL/pgSQL EXECUTE) binds values with USING and quotes identifiers with format(%I)/quote_ident, never by concatenation
Network & TLS 1
Outbound data, verified certificates, destinations taken from requests and services exposed to others.
pg-a09-sslmodePG-A09 · Connections over untrusted networks verify the server (sslmode=verify-full), not just encrypt or allow plaintext
Destructive actions 5
Deletions, overwrites and irreversible commands wait for the user or a scoped target.
pg-m17-destructive-commandsPG-M17 · The user confirms destructive SQL run directly (psql, database MCP tools) or dropdb, outside disposable databases
pg-m17-destructive-ddlPG-M17 · The user confirms destructive DDL in migrations (DROP TABLE/COLUMN/SCHEMA, TRUNCATE, CASCADE) on objects this file did not create
pg-o04-live-pgdata-copyPG-O04 · The user confirms file-level copies of a data directory: a live PGDATA copy is not a backup
pg-o06-wal-deletionPG-O06 · Nobody deletes files in pg_wal (or the WAL archive) by hand
pg-s20-unloggedPG-S20 · The user confirms an UNLOGGED table: its data is emptied after a crash and never reaches physical replicas
Weakened checks 2
Suppressions, disabled lint or CI steps and relaxed strictness need a reason or the user's approval.
pg-p06-trigger-bypass-commandPG-P06 · The user confirms commands and database MCP calls disabling triggers or setting session_replication_role = replica
pg-p06-trigger-bypassPG-P06 · The user confirms disabling or dropping triggers and session_replication_role = replica in SQL files
Error handling 1
Failures are propagated or handled, never swallowed or turned into fake success.
pg-p08-swallowed-exceptionsPG-P08 · PL/pgSQL handlers do not swallow errors (WHEN OTHERS THEN NULL / a success-shaped value)
Performance & resource bounds 6
Timeouts, bounded retries and fan-out, reused clients, closed resources, no N+1 queries.
pg-q05-large-offsetPG-Q05 · Deep OFFSET pagination could become keyset pagination
pg-q06-row-loopsPG-Q06 · PL/pgSQL loops that write once per selected row become one set-based statement
pg-q13-explain-analyze-dmlPG-Q13 · The user confirms EXPLAIN ANALYZE of a data-changing statement: it executes it
pg-q14-cte-materializationPG-Q14 · CTE MATERIALIZED / NOT MATERIALIZED directives carry a measured reason
pg-q15-redundant-indexPG-Q15/V13 · New B-tree indexes do not duplicate a key or a longer index on the same table (18 skip scan may already serve later columns)
pg-q16-wide-includePG-Q16 · Wide INCLUDE payloads (four or more columns) are backed by a measured index-only scan
Databases & queries 21
Schemas, queries, transactions and the ORM used as intended.
pg-p05-trigger-generatedPG-P05 · Trigger functions that only derive NEW columns from other NEW columns become generated columns
pg-p07-plpgsql-single-queryPG-P07 · Functions whose body is one query are LANGUAGE SQL, not PL/pgSQL
pg-q02-null-comparisonPG-Q02 · NULL is tested with IS [NOT] NULL or IS [NOT] DISTINCT FROM, never with = or <> (always unknown)
pg-q03-select-star-viewPG-Q03 · Views list their result columns instead of SELECT *
pg-q04-limit-without-orderPG-Q04 · Top-N queries (LIMIT/FETCH beyond one row) have an ORDER BY, unless any rows will do
pg-q04-offset-without-orderPG-Q04 · Paginated queries (OFFSET) have an ORDER BY: without it pages overlap and skip rows
pg-q11-skip-lockedPG-Q11 · SKIP LOCKED is used for queue-like claims (bounded with LIMIT), not for ordinary results
pg-q17-immutable-functionsPG-Q17 · Functions declared IMMUTABLE do not read the clock, randomness, sequences, settings or session state
pg-q18-recursive-terminationPG-Q18 · Recursive CTEs show how they terminate: a CYCLE clause or a bound in the recursive term
pg-s01-table-keyPG-S01 · New durable tables declare a primary or candidate key
pg-s04-check-row-localPG-S04 · CHECK constraints are row-local and stable: no clock, randomness, session state, sequences or subqueries
pg-s07-composite-fk-matchPG-S07 · Composite foreign keys choose MATCH FULL or MATCH SIMPLE: by default a single NULL column skips the check
pg-s08-fk-indexPG-S08 · Foreign keys created in a SQL file have an index led by their referencing column in the same file, or a stated reason
pg-s09-serialPG-S09 · New sequence-generated columns use GENERATED … AS IDENTITY, not serial types
pg-s10-identity-uniquenessPG-S10 · A new table's identity column is backed by a primary key or unique constraint: identity alone permits duplicates
pg-s12-blank-padded-charPG-S12 · New text columns are text or varchar, not blank-padded char(n) by accident
pg-s13-timestamp-semanticsPG-S13 · New timestamp columns state their semantics: timestamptz for instants, timestamp only for local wall-clock values
pg-s15-exact-quantitiesPG-S15 · Money and exact quantities use numeric or scaled integers, not money or floating point
pg-s16-ad-hoc-typesPG-S16/S17 · Native uuid and boolean types instead of text encodings; references are not hidden in id arrays
pg-s19-partition-keysPG-S19 · Primary keys and unique constraints of a partitioned table include every partition key column
pg-s20-unlogged-partitionedPG-S20 · No UNLOGGED partitioned table: PostgreSQL 18 rejects it
Schema migrations 21
Migrations come from the tool, applied history stays immutable and risky changes are rolled out safely.
pg-m01-direct-ddlPG-M01 · The user confirms schema changes run directly through psql or a database MCP tool instead of the project's migrations
pg-m02-migration-generatorPG-M02 · New migration files come from the project's migration tool (Prisma, Supabase, Atlas, Sqitch) when it owns their names, hashes or ledgers
pg-m03-applied-historyPG-M03 · The user confirms changing a committed migration: applied history is fixed by a follow-up migration
pg-m03-migration-removalPG-M03 · The user confirms deleting or renaming committed migration files
pg-m04-if-not-exists-indexPG-M04 · CREATE INDEX IF NOT EXISTS in a migration comes with a check of the index's validity and definition
pg-m05-lock-timeoutPG-M05 · Migrations that lock existing tables set a lock_timeout (and no zero/infinite timeout without a decision)
pg-m06-rewrite-additionsPG-M06 · Columns added to existing tables avoid full rewrites: no volatile default, identity, serial or STORED generation in one step
pg-m07-unique-not-validPG-M07 · No UNIQUE, PRIMARY KEY or EXCLUDE constraint declared NOT VALID: PostgreSQL rejects it
pg-m07-validate-separatelyPG-M07 · CHECK, FOREIGN KEY and NOT NULL constraints on existing tables are added NOT VALID, then validated in a separate step
pg-m08-pending-validationPG-M08 · At the end of a turn, constraints this session added NOT VALID are listed until a VALIDATE CONSTRAINT follows them
pg-m09-concurrent-indexPG-M09 · Indexes on existing tables in migrations are built CONCURRENTLY (new and temporary tables are exempt)
pg-m10-concurrent-in-transactionPG-M10 · Concurrent index operations stay outside explicit transactions and runner-injected ones (dbmate, goose)
pg-m10-concurrent-index-alonePG-M10 · A migration with CREATE INDEX CONCURRENTLY holds nothing else: runners that send a file as one query wrap it in a transaction
pg-m10-single-transaction-scriptPG-M10 · psql --single-transaction does not run a script holding CREATE INDEX CONCURRENTLY
pg-m11-invalid-index-retryPG-M11 · After a failed concurrent index build, the invalid index is inspected or dropped before a retry
pg-m12-constraint-using-indexPG-M12 · Primary keys and unique constraints on existing tables attach a unique index built concurrently
pg-m13-partitioned-concurrent-indexPG-M13 · No CREATE INDEX CONCURRENTLY on a partitioned parent: indexes are built per partition and attached
pg-m14-expand-contractPG-M14 · Renames of existing tables and columns go through expand/contract while old code still runs
pg-m15-unbounded-backfillPG-M15 · Backfills of existing tables in migrations are bounded (batches) rather than one statement over every row
pg-m16-type-changePG-M16 · The user confirms column type and stored expression changes on existing tables (rewrite, locks, dependents)
pg-p16-introspectionPG-P16 · Before the first new migration of a session, Claude reads the existing schema from the catalog (once per session)
Database operations 18
Backups, upgrades, replication and maintenance done with the server's own tools.
pg-o01-insert-dumpsPG-O01 · Data exports are made with pg_dump, not hand-generated INSERT files (dump/backup/export files with hundreds of INSERTs)
pg-o01-insert-generationPG-O01 · Exports are not produced by SQL that prints INSERT statements (use pg_dump)
pg-o03-script-errorsPG-O03 · Scripted psql stops on the first error (ON_ERROR_STOP) and pg_restore exits on errors
pg-o05-verify-backupPG-O05 · After a successful pg_basebackup, Claude verifies it with pg_verifybackup and plans a restore test
pg-o06-wal-archivePG-O06 · archive_command refuses to overwrite and does not fake success; the WAL chain is not cut by hand
pg-o08-logical-replicationPG-O08 · New publications and subscriptions come with a plan for DDL, sequences and replica identity
pg-o09-slot-retentionPG-O09 · Replication slots come with a WAL retention budget (max_slot_wal_keep_size, idle_replication_slot_timeout)
pg-o10-upgrade-checkPG-O10 · pg_upgrade runs for real only after a successful pg_upgrade --check this session
pg-o10-upgrade-modePG-O10 · The user confirms pg_upgrade --link, --swap or --clone: the old cluster cannot be started again afterwards
pg-o11-post-upgradePG-O11 · After a successful pg_upgrade, Claude restores missing statistics and runs the generated follow-ups
pg-o12-rewriting-maintenance-commandPG-O12 · The user confirms commands running VACUUM FULL, CLUSTER, REINDEX without CONCURRENTLY or vacuumdb --full
pg-o12-rewriting-maintenancePG-O12 · The user confirms maintenance that rewrites or locks live tables (VACUUM FULL, CLUSTER, REINDEX without CONCURRENTLY, SET TABLESPACE) in SQL files
pg-o13-bulk-insertsPG-O13 · Bulk loads use COPY rather than thousands of single-row INSERTs or a psql call per row
pg-o14-copy-reject-limitPG-O14 · COPY … ON_ERROR IGNORE sets a REJECT_LIMIT (and reports rejected rows)
pg-v08-checksumsPG-V08 · Durable clusters keep data checksums (initdb's 18 default); turning them off is a stated decision
pg-v10-removed-stats-columnsPG-V10 · Monitoring queries use PostgreSQL 18 statistics columns (pg_stat_io.op_bytes, pg_stat_wal timings, memory-context parent are gone)
pg-v11-maintenance-scopePG-V11 · VACUUM/ANALYZE of a partitioned or inheritance parent states its scope: PostgreSQL 18 also processes the children unless ONLY
pg-v16-output-pluginsPG-V16 · A third-party logical decoding plugin is listed in output_plugin_libraries (18.6 only admits pgoutput and test_decoding by default)
Versions & deprecated APIs 11
Code matches the installed version, without removed, renamed or deprecated APIs.
pg-v01-version-gatePG-V01/V02/O02 · Utilities that apply, dump, restore, back up or upgrade run once the session has seen the server and utility versions, at or above the 18.6 floor
pg-v02-newer-patchPG-V02 · A pin on an 18 patch newer than the 18.6 contract asks for the contract to be updated
pg-v02-other-majorPG-V02 · The user confirms a new pin on a PostgreSQL major other than 18 (a compatibility target is a decision, not a default)
pg-v02-patch-floorPG-V02 · No new PostgreSQL 18 pin below the 18.6 floor (images, packages, CI, tool versions) and no reference to 18.5, which was never released
pg-v03-versioned-docsPG-V03 · PostgreSQL documentation links point at /docs/18 and REL_18_STABLE, not /current, /devel, older majors or master
pg-v04-generated-storagePG-V04 · New generated columns say STORED or VIRTUAL: PostgreSQL 18 defaults to VIRTUAL where 16/17 only had STORED
pg-v05-virtual-user-functionsPG-V05 · VIRTUAL generated columns call no user-defined function (schema-qualified outside pg_catalog, or created in the file)
pg-v07-uuid-extensionPG-V07 · UUID v4/v7 come from built-ins (gen_random_uuid, uuidv4, uuidv7), not from an extension added for them
pg-v12-copy-terminatorPG-V12 · Inline COPY … FROM STDIN data in a psql script ends with a standalone \. line
pg-v15-not-enforcedPG-V15 · The user confirms a NOT ENFORCED constraint: PostgreSQL 18 then never checks it, unlike NOT VALID
pg-v18-upgrade-notesPG-V18/V19/V20 · Moving a server pin to 18.6 or later from an older release comes with the 18.2-18.6 remediation checklist
Configuration 11
Settings read once at a boundary, validated, with units and without unsafe defaults.
pg-c01-durability-offPG-C01 · Durable clusters keep fsync and full_page_writes on
pg-c02-synchronous-commitPG-C02 · The user confirms reducing synchronous_commit: recent commits can be lost on a crash or failover
pg-c04-max-connectionsPG-C04 · max_connections grows only with a resource budget (memory per backend, a pooler), not as a fix for 'too many clients'
pg-c05-effective-cache-sizePG-C05 · effective_cache_size is described as a planner estimate, never as memory PostgreSQL allocates
pg-c06-global-timeoutsPG-C06 · statement, transaction and lock timeouts are scoped to roles, databases or sessions, not set server-wide
pg-c07-autovacuum-offPG-C07 · Autovacuum stays on (server-wide and per table) unless a replacement maintenance plan is stated
pg-c08-autovacuum-worker-slotsPG-C08 · autovacuum_max_workers stays within autovacuum_worker_slots (PostgreSQL 18)
pg-c10-partition-analyzePG-C10 · New partitioned tables come with a plan to ANALYZE the parent (autovacuum only analyzes the partitions)
pg-c11-planner-switchesPG-C11 · Planner features (enable_*) stay on server-wide and per role/database; disabling them is a scoped experiment
pg-c12-io-methodPG-C12 · io_method and I/O concurrency changes are measured on the target platform (PostgreSQL 18 asynchronous I/O)
pg-c14-unitsPG-C14 · Memory and time settings carry explicit units (each parameter has its own default unit)
Generated files 5
Build outputs, generated code and tool-owned files are regenerated, never edited by hand.
pg-c13-auto-confPG-C13 · postgresql.auto.conf is written by ALTER SYSTEM only, never edited by hand
pg-p11-migration-ledger-commandPG-P11 · Commands and database MCP calls do not write migration ledgers by hand
pg-p11-migration-ledgerPG-P11 · Migration ledgers (schema_migrations, flyway_schema_history, _prisma_migrations…) are written by their tool only
pg-p11-tool-owned-filesPG-P11 · Files a tool generates (dumped schemas, migration locks, atlas.sum) are not edited with file tools
pg-p15-pgdata-filesPG-P15 · Files inside a PostgreSQL data directory are never written by hand (configuration files and *.signal excepted)
Completion evidence 3
A turn ends with its checks run and passing; claims of success need proof after the last edit.
pg-evidence-ledgerRecords, per session, the PostgreSQL versions, introspection, migration runs, pg_upgrade checks and failed concurrent index builds that commands showed
pg-m18-migration-testPG-M18 · A turn that changed migrations ends only after a migration run succeeded since the last change (once per stop)
pg-p13-sql-syntaxPG-P13 · No new PostgreSQL syntax error in a SQL file (postgres-language-server), except where its pre-18 parser cannot judge