diff --git a/docs/FOX_Checklist.pdf b/docs/FOX_Checklist.pdf index 1821eb4..487fe93 100644 Binary files a/docs/FOX_Checklist.pdf and b/docs/FOX_Checklist.pdf differ diff --git a/docs/FOX_Feature_Implemented.html b/docs/FOX_Feature_Implemented.html index a46cbcf..9e2c949 100644 --- a/docs/FOX_Feature_Implemented.html +++ b/docs/FOX_Feature_Implemented.html @@ -119,6 +119,7 @@

Change log

Newest first. Every change to FoxByte adds a row here.

+ diff --git a/docs/FOX_Feature_Implemented.pdf b/docs/FOX_Feature_Implemented.pdf index a5c3180..7df2397 100644 Binary files a/docs/FOX_Feature_Implemented.pdf and b/docs/FOX_Feature_Implemented.pdf differ diff --git a/internal/branch/dblink.go b/internal/branch/dblink.go index e23d98f..ed1ced1 100644 --- a/internal/branch/dblink.go +++ b/internal/branch/dblink.go @@ -24,8 +24,14 @@ package branch // connections from inside the database to anything the container can reach // (audit GW-1). healDblink runs this on every start, so a branch made before // the change loses the grant the next time it starts. +// +// Each REVOKE is issued only while the grant it takes away is there. The +// Blackbox records every GRANT and REVOKE, held or not, so revoking all 41 +// functions' grants again on each start wrote 82 unattributed entries into the +// branch's Blackbox every time it started, and into a branch made from just +// before an entry, after that entry. const ensureDblinkSQL = `DO $do$ -DECLARE s name; f regprocedure; +DECLARE s name; f regprocedure; c oid; BEGIN SELECT n.nspname INTO s FROM pg_extension e JOIN pg_namespace n ON n.oid = e.extnamespace WHERE e.extname = 'dblink'; @@ -40,8 +46,14 @@ BEGIN JOIN pg_proc p ON p.oid = d.objid WHERE d.classid = 'pg_proc'::regclass AND d.refclassid = 'pg_extension'::regclass AND d.deptype = 'e' AND e.extname = 'dblink' LOOP - EXECUTE format('REVOKE EXECUTE ON FUNCTION %s FROM PUBLIC', f); - IF EXISTS (SELECT 1 FROM pg_roles WHERE rolname = 'db_client') THEN + -- PUBLIC is grantee 0; a function with no ACL has the default one, which grants it. + IF EXISTS (SELECT 1 FROM pg_proc p, aclexplode(coalesce(p.proacl, acldefault('f', p.proowner))) a + WHERE p.oid = f AND a.grantee = 0 AND a.privilege_type = 'EXECUTE') THEN + EXECUTE format('REVOKE EXECUTE ON FUNCTION %s FROM PUBLIC', f); + END IF; + SELECT oid INTO c FROM pg_roles WHERE rolname = 'db_client'; + IF c IS NOT NULL AND EXISTS (SELECT 1 FROM pg_proc p, aclexplode(coalesce(p.proacl, acldefault('f', p.proowner))) a + WHERE p.oid = f AND a.grantee = c AND a.privilege_type = 'EXECUTE') THEN EXECUTE format('REVOKE EXECUTE ON FUNCTION %s FROM db_client', f); END IF; END LOOP; diff --git a/internal/branch/import_as_test.go b/internal/branch/import_as_test.go index fdbbc6f..24cd16b 100644 --- a/internal/branch/import_as_test.go +++ b/internal/branch/import_as_test.go @@ -139,3 +139,14 @@ func TestEnsureDblinkPutsItWhereTheTriggersLook(t *testing.T) { } } } + +// Every start runs ensureDblinkSQL, and the Blackbox records each REVOKE, held +// or not: each is issued only while the grant it removes is there, so a start +// that changes nothing records nothing. +func TestEnsureDblinkRevokesOnlyWhatIsHeld(t *testing.T) { + revokes := strings.Count(ensureDblinkSQL, "EXECUTE format('REVOKE") + guarded := strings.Count(ensureDblinkSQL, "aclexplode(coalesce(p.proacl, acldefault('f', p.proowner)))") + if revokes != 2 || guarded != revokes { + t.Errorf("%d REVOKEs, %d of them guarded by a look at the grant", revokes, guarded) + } +}
DateFeature(s)What changedCommit / PR
11 Nov 2026H1Fixed: every branch start wrote 82 entries into the branch’s Blackbox. Since #221, each start of a branch takes dblink away from clients again (a REVOKE of EXECUTE on each of its 41 functions, from PUBLIC and from db_client), and the Blackbox records every REVOKE, held or not: a restarted branch gained 82 entries with no actor, and a branch made from just before an entry held 82 entries made after it. Each REVOKE is now issued only while the grant it removes is there, so a start that changes nothing records nothing, and a grant someone added is still taken away, and recorded, on the next start. Tests: integration-v2 403/403 (it was 401/403 on main), integration 936/936, Go unit tests in both editions (the REVOKEs are guarded by a look at the grant), vet for macOS, Linux and Windows; on the test VM a branch’s restart added 0 entries, and one given two dblink grants lost both on restart and recorded those two REVOKEs.fix/v2-branch-ledger
11 Nov 2026L4Table dialogs (point 12.3, second part, pgAdmin’s basics). + New table… in the Console’s schema rail makes a table with its columns and primary key, and opens on them. A table’s Structure tab lists its columns from the catalog with + Add column…, Edit… (name, type with a USING conversion, NOT NULL, default; only what changed, in one transaction, the rename last) and Drop…, and renames and drops the table; its Constraints tab adds, edits, renames and drops primary, unique, foreign and check keys: columns in the order picked, a foreign key’s referenced table with its primary key filled in, ON DELETE and ON UPDATE, deferrable; an edit replaces the key in one statement under its name and keeps MATCH FULL, NOT VALID and NO INHERIT; a key with parts the dialog cannot write again (INCLUDE, NULLS NOT DISTINCT, an exclusion, an inherited one) is renamed and dropped but not edited. Each dialog shows its SQL and runs it as the person; DROP TABLE meets the guardrail, and Put it in the editor is the way to override it. The engine gives each constraint’s parts (key) on GET …/schema/table/{oid}. Tests: integration 936/936 (§11aq new: what each dialog writes run; a failed rename leaving a column edit undone; each key’s parts, in the key’s order; a key replaced under its name, a failed replacement keeping it, one depended on refused; keys not offered as edits; DROP TABLE refused by the guardrail, then dropped with the override), UI 61/61 plus the upgrade test (a table made, its columns added, edited and dropped, renamed, its drop refused and put in the editor; its four kinds of key added, a foreign key edited, a check renamed and dropped; a refusal shown in the dialog), integration-f5 57/57, f1 70/70, f6 71/71, web unit 294/294, Go unit tests in both editions, vet for macOS, Linux and Windows. v2 401/403: its two failures (a point-in-time branch’s Blackbox holding 82 entries from after the entry it was made before, and a branch’s restart adding them again) are the same on main without this change.feat/p12-tables
10 Nov 2026L4Function and trigger dialogs (point 12.3, first part, pgAdmin’s basics). + New function… in the Console’s schema rail makes a function, a procedure or a trigger function; Edit… changes a body, language, volatility, strict and security definer and keeps what the dialog has no field for (LEAKPROOF, PARALLEL, COST, ROWS, each SET clause); Rename… and Drop… use its identity arguments. A table’s Triggers tab has + New trigger…, Edit, Rename and Drop: timing, events, columns for UPDATE, row or statement, WHEN, the function and its arguments; an edit keeps REFERENCING, and a disabled trigger stays disabled. Each dialog shows the SQL as it is built and runs it as the person, so their rights, the guardrails and the Blackbox apply; what a guardrail refuses can be put in the editor. FoxByte’s own functions and triggers are not offered. The engine gives each function’s attributes (routine) and each trigger’s parts (trigger) on GET …/schema/{kind}/{oid} and marks trigger functions in the list; fox schema’s output is unchanged. Fixed on the way: a slow reply about the branch just left could replace the Console’s catalog of the branch switched to, and a table then said it was not in the catalog; each load now ignores replies to an earlier one. Tests: integration 916/916 (§11ap new: what the dialogs write run, an edit keeping every attribute, a BEGIN ATOMIC function told apart, a disabled trigger staying so, a procedure’s and a trigger’s drop in the Blackbox by the catalog scan, what the role does not own refused), v2 403/403, UI 59/59 plus the upgrade test (a function made, edited, renamed and dropped from its dialogs; a trigger likewise; a late reply about the branch left held back and arriving), integration-f5 57/57, f1 70/70, f6 71/71, web unit 280/280, Go unit tests in both editions, vet for macOS, Linux and Windows.feat/p12-routines
10 Nov 2026L4Run, toggle and plan (point 12.4, pgAdmin’s basics). A function or a procedure opened in the Console’s object browser has a Run form: one field per argument with its type, a NULL box and, where declared, its default; the call is shown as it is built (named arguments when every one has a name, so any default may be left out, otherwise only the last), each value a literal cast to its argument’s type, a procedure’s OUT arguments as NULL; Run puts it in the editor and runs it as the person. The engine now gives each argument’s name, type, mode and default (params on GET …/schema/{kind}/{oid}). A table’s Triggers tab disables (after asking) and enables each of its own triggers, an ALTER TABLE run as the person and recorded in the Blackbox; FoxByte’s own are marked and not offered. Explain, beside Run, shows the selected statement’s plan, or the editor’s only one’s, as a table: each step under the one that uses it, what it reads, its conditions, the estimate and cost, and a bar for its share of the whole, the most expensive marked; with Analyse it runs inside a transaction that is rolled back, adding actual rows, loops, each step’s own time and a warning where the estimate was ten times off. Fixed on the way: a json or jsonb array reached the console with each object inside it as text of its own (["{\"a\":1}"]), as an EXPLAIN plan did; it is now JSON whole. The f6 suite’s measured forensic round wrote its 3.6 GB a minute of audit log to the test VM’s 2 GB in-memory /tmp, and stopped without a word; it now writes to disk. Tests: integration 906/906 (§11ao new: each argument of a function, a set-returning, an argumentless and a variadic one, and a procedure; what the form writes run, with a default left out, NULL, a variadic and a procedure’s INOUT and OUT; a trigger disabled and enabled, firing or not, in the Blackbox, FoxByte’s own marked; a jsonb array whole; a plan read, and with ANALYZE rolled back), v2 403/403, UI 56/56 plus the upgrade test (a function run from its form; a trigger disabled and enabled; a statement explained, and with Analyse measured and rolled back), integration-f5 57/57, f1 70/70, f6 71/71, web unit 269/269, Go unit tests in both editions, vet for macOS, Linux and Windows.feat/p12-run-plan