Converge privileges between a fresh install and an updated one - #96
Converge privileges between a fresh install and an updated one#96jnasbyupgrade wants to merge 2 commits into
Conversation
Add `_cat_tools.privilege__normalize()` and call it as the last statement of both the install script and the update accumulator, so a fresh `CREATE EXTENSION` and an `ALTER EXTENSION UPDATE` produce identical `pg_init_privs`, and therefore identical `pg_dump` output. Both paths now execute the same explicit GRANT/REVOKE statements, so the convergence holds by construction rather than by remembering to mirror one script in the other. Resolves defect D1 of #93. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
Important Review skippedAuto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: Organization UI Review profile: ASSERTIVE Plan: Advanced Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
|
Claude finished @jnasbyupgrade's task in 7m 58s —— View job Code review in progress
|
| END IF; | ||
|
|
||
| FOR r IN | ||
| WITH member AS ( |
There was a problem hiding this comment.
This is a shitload of code to duplicate. Eventually it'd be good for us to make something like this an actual cat_tools feature, but in the meantime lets at least have an internal view.
There was a problem hiding this comment.
Extracted the object enumeration into _cat_tools.privilege_target_v (1497030): one row per object cat_tools issues privileges on, plus what it intends that object to have (object_kind, object_identity, revoke_public, revoke_role, grant_privilege). privilege__normalize() is now just a loop over it, so the per-kind policy is a set-returning thing you can inspect with a SELECT rather than something buried in plpgsql.
Both files still have to define both the view and the function — an install sitting at 0.3.0 has neither — but what is duplicated is now the declarative list, and the function around it is ~30 lines instead of ~110. The two copies remain byte-identical. Making this a real cat_tools feature is left to separate work.
| @@ -0,0 +1,143 @@ | |||
| /* | |||
There was a problem hiding this comment.
26 lines of comment here is overkill. Again, IMPORTANT details about individual steps/things belong as comments with the relevant code. A header comment like this should only hit high-level things, and only if needed.
There was a problem hiding this comment.
Header is down to 9 lines: what this achieves, plus the one non-obvious reason it is needed (implicit ALTER DEFAULT PRIVILEGES grants never reach pg_init_privs, explicit ones do). Everything else moved to the code it explains — the REVOKE-before-GRANT rationale sits on the REVOKE in the loop, the column-ACL exclusion sits on the TABLE branch it would apply to, and the grantee scope sits on the view that defines it. Same trim applied to the copy in sql/cat_tools.sql.in.
The enumeration of what cat_tools grants on -- and what it intends each object to have -- now lives in _cat_tools.privilege_target_v, leaving privilege__normalize() a loop over it. The install script and the update accumulator must each define both (an install sitting at the last released version has neither), so keeping the duplicated part small matters. Also trims the header comment down to the one non-obvious reason the normalization exists, moving per-step detail next to the step it explains. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
Claude finished @jnasbyupgrade's task in 6m 2s —— View job Code review in progress
|
|
@claude it looks like your runs are dying... |
|
Claude finished @jnasbyupgrade's task in 2m 28s —— View job Investigation complete
Both prior runs (34648962488, 34652912518) didn't crash — the Claude Code Action's own SDK call reports
Root cause: There's no Likely fix: add Note this is unrelated to the actual PR content ( |

Add
_cat_tools.privilege__normalize()and call it as the last statement of both the install script and the update accumulator, so a freshCREATE EXTENSIONand anALTER EXTENSION UPDATEproduce identicalpg_init_privs, and therefore identicalpg_dumpoutput. Both paths now execute the same explicit GRANT/REVOKE statements, so the convergence holds by construction rather than by remembering to mirror one script in the other.Resolves defect D1 of #93.
What diverged
A fresh install granted
USAGEon thecat_toolstypes implicitly, via theALTER DEFAULT PRIVILEGES IN SCHEMA cat_tools GRANT USAGE ON TYPESnear the top of the install script. An ADP-granted privilege is never snapshotted intopg_init_privs. The update path got the same privilege from an explicitGRANT USAGE ON TYPE, which runs withcreating_extensionset and therefore is snapshotted. Identical runtime privileges, different dump.Numbers
CREATE EXTENSION cat_toolsvs.CREATE EXTENSION cat_tools VERSION '0.2.2'+ALTER EXTENSION cat_tools UPDATE, same cluster, same template:pg_init_privsrows, freshpg_init_privsrows, updatedpg_dumpdiff linesThe 8 remaining
pg_dumpdiff lines are entirely the two\restrict/\unrestrictnonce hunks, which differ on every invocation. Per-classoidbreakdown after, identical on both sides (PG17):pg_class169,pg_namespace5,pg_proc148,pg_type14.The count rises rather than just equalising because normalization states privileges that were previously implicit: all 14 grantable types get a recorded row instead of 5, the new function adds one, and two
_cat_toolsrelations that carried no explicit ACL (catalog_metadata,pg_depend_identity_v) now carry an owner-only one. No privilege changes hands — effective permissions are unchanged, and the pgTAP permission suite passes untouched.bin/structural_diff comparereports IDENTICAL on both majors, before and after; it does not look atpg_init_privs, which is the gap #95 closes.Design
ALTER DEFAULT PRIVILEGESstays. It documents what default privileges are in effect for our schemas, and removing it would break restore for already-updated databases, whose dumps carry no explicitGRANT. What changes is that nothing relies on it any more.pg_dependdeptype = 'e'— so it stays correct as objects are added. The extension's own schema is not a member (CREATE EXTENSIONcreates it fromschema =in the control file), so it is unioned in explicitly.ALTER DEFAULT PRIVILEGESconfigured outside cat_tools reaches every object a fresh install creates and nothing an update script touches; without the revoke, whatever it added would be a permanent divergence.PUBLICand the usage role. Grants an administrator made to other roles are left alone — an update must not silently revoke them.PUBLICkeeps theUSAGEPostgreSQL gives it on every type. SchemaUSAGEis what actually gates our types (this is what the permission test's type checks assert), so revoking would tighten the documented model rather than normalize it.USAGEis also the only type privilege, so nothing an external ADP does toPUBLICon types can add anything.REVOKE ... FROM PUBLIC. Which routines the usage role may execute is a per-routine decision made at the creation site, where__cat_tools.create_functionalready issues REVOKE-then-GRANT._cat_tools.catalog_metadatais a real table with seeded data, and the relation branch coversr/p/v/m/f. Sequences are excluded, with a comment, because their privilege set is unlike a table's and cat_tools has none.pg_attribute.attacl) are deliberately not handled. cat_tools grants nothing at column granularity, andALTER DEFAULT PRIVILEGEShas no column granularity either, so the drift this exists to prevent cannot reach them. Since a table-levelREVOKEnever subsumes column-level privileges, the function's comment records that granting at column granularity in future means extending it at the same time.Constraints from #88
All three honoured:
superuser = falseadded to the control file, a genuine non-superuser login role created the extension, normalization ran, and the resulting database has the same 336pg_init_privsrows as the superuser fresh install.role cat_tools__usage does not exist; normalizing PUBLIC privileges onlyatDEBUG, completed, applied thePUBLICrevokes, and left existing grants untouched.c_usage_roleconstant, so whatever Install script requires CREATEROLE, blocking non-superuser install even with superuser = false #88 settles on is a single-point change. Nothing else in the function names a role.