The schema now travels with its paperwork. Extraction captures object grants, table and column comments, and the database character set in both script tiers; the converter emits COMMENT ON for every surviving object and GRANT statements with their roles bootstrapped idempotently. Oracle READ maps to SELECT, sequence privileges to USAGE and UPDATE, and everything without a direct counterpart - EXECUTE included - is a named residue line, because PostgreSQL grants on routines need argument lists.
Every emitted name now passes through a namespace map that mirrors the target's. PostgreSQL keeps tables, views, materialized views, sequences, indexes, and key-backing indexes in one namespace per schema and truncates identifiers to 63 bytes; Oracle divides its namespaces differently and allows 128-byte names from 12.2, so a legal source schema can hold pairs that fold to a single name on the target. The first claimant wins and every later collider becomes a residue line naming the earlier object - including where CREATE OR REPLACE would otherwise replace it silently.
The runbook writes apply_schema.sh, the ordered apply script with an error log. The rule catalog reaches 76. A malformed spool row now degrades instead of crashing a load. And the claim on the tin got a harness: CI applies the bundled sample's conversion to a live PostgreSQL 16 on every commit - the release you are reading passed it.
Everything here was applied to a live PostgreSQL before tagging, per the standing rule: nothing invalid ships, nothing is lost silently.