pythia-postgres 0.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- pythia_postgres/__init__.py +5 -0
- pythia_postgres/__main__.py +5 -0
- pythia_postgres/catalog.py +59 -0
- pythia_postgres/cli.py +8 -0
- pythia_postgres/engine.py +213 -0
- pythia_postgres/guard.py +54 -0
- pythia_postgres/queries/code-objects.sql +28 -0
- pythia_postgres/queries/columns.sql +15 -0
- pythia_postgres/queries/comment-current.sql +17 -0
- pythia_postgres/queries/current-schema.sql +5 -0
- pythia_postgres/queries/dependencies-catalog.sql +66 -0
- pythia_postgres/queries/dependents-catalog.sql +66 -0
- pythia_postgres/queries/dependents-text.sql +22 -0
- pythia_postgres/queries/function-body.sql +8 -0
- pythia_postgres/queries/grep.sql +30 -0
- pythia_postgres/queries/name-candidates.sql +32 -0
- pythia_postgres/queries/object-names.sql +56 -0
- pythia_postgres/queries/plpgsql-check-installed.sql +7 -0
- pythia_postgres/queries/plpgsql-check.sql +27 -0
- pythia_postgres/queries/plpgsql-unchecked.sql +16 -0
- pythia_postgres/queries/rehearsal-limits.sql +7 -0
- pythia_postgres/queries/resolve.sql +34 -0
- pythia_postgres/queries/row-set-count.sql +11 -0
- pythia_postgres/queries/row-set-sample.sql +8 -0
- pythia_postgres/queries/search-path-catalog.sql +7 -0
- pythia_postgres/queries/search-path-first.sql +13 -0
- pythia_postgres/queries/sequence-ddl.sql +16 -0
- pythia_postgres/queries/server-version.sql +6 -0
- pythia_postgres/queries/session-power.sql +13 -0
- pythia_postgres/queries/source.sql +28 -0
- pythia_postgres/queries/table-columns.sql +16 -0
- pythia_postgres/queries/table-constraints.sql +13 -0
- pythia_postgres/queries/table-extras.sql +43 -0
- pythia_postgres/queries/table-head.sql +18 -0
- pythia_postgres/read.py +299 -0
- pythia_postgres/rules.py +135 -0
- pythia_postgres/skills/pythia-pg-apply/SKILL.md +97 -0
- pythia_postgres/skills/pythia-pg-apply/reference/refusals.md +26 -0
- pythia_postgres/skills/pythia-pg-autopilot/SKILL.md +63 -0
- pythia_postgres/skills/pythia-pg-deploy/SKILL.md +73 -0
- pythia_postgres/skills/pythia-pg-explore/SKILL.md +63 -0
- pythia_postgres/skills/pythia-pg-impact/SKILL.md +57 -0
- pythia_postgres/skills/pythia-pg-verify/SKILL.md +56 -0
- pythia_postgres/skills/using-pythia-pg/SKILL.md +65 -0
- pythia_postgres/write.py +583 -0
- pythia_postgres-0.1.0.dist-info/METADATA +360 -0
- pythia_postgres-0.1.0.dist-info/RECORD +51 -0
- pythia_postgres-0.1.0.dist-info/WHEEL +5 -0
- pythia_postgres-0.1.0.dist-info/entry_points.txt +2 -0
- pythia_postgres-0.1.0.dist-info/licenses/LICENSE +21 -0
- pythia_postgres-0.1.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,360 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: pythia-postgres
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: pythia for PostgreSQL: PL/pgSQL development for AI agents over pythia-core - catalog queries, impact analysis and a snapshot-verified write path.
|
|
5
|
+
License-Expression: MIT
|
|
6
|
+
Project-URL: Repository, https://github.com/thaildhe172591/pythia-postgres
|
|
7
|
+
Project-URL: Changelog, https://github.com/thaildhe172591/pythia-postgres/blob/main/CHANGELOG.md
|
|
8
|
+
Project-URL: Issues, https://github.com/thaildhe172591/pythia-postgres/issues
|
|
9
|
+
Keywords: postgresql,plpgsql,database,agent-skills,sql
|
|
10
|
+
Classifier: Development Status :: 3 - Alpha
|
|
11
|
+
Classifier: Environment :: Console
|
|
12
|
+
Classifier: Intended Audience :: Developers
|
|
13
|
+
Classifier: Operating System :: OS Independent
|
|
14
|
+
Classifier: Programming Language :: Python :: 3
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
20
|
+
Classifier: Topic :: Database
|
|
21
|
+
Classifier: Topic :: Software Development
|
|
22
|
+
Requires-Python: >=3.9
|
|
23
|
+
Description-Content-Type: text/markdown
|
|
24
|
+
License-File: LICENSE
|
|
25
|
+
Requires-Dist: pythia-core<0.2,>=0.1.0
|
|
26
|
+
Requires-Dist: psycopg[binary]>=3.1
|
|
27
|
+
Dynamic: license-file
|
|
28
|
+
|
|
29
|
+
# pythia-postgres
|
|
30
|
+
|
|
31
|
+
**English** · [Tiếng Việt](https://github.com/thaildhe172591/pythia-postgres/blob/main/README.vi.md)
|
|
32
|
+
|
|
33
|
+
pythia for PostgreSQL: PL/pgSQL development for AI agents, built on
|
|
34
|
+
[pythia-core](https://github.com/thaildhe172591/pythia-core). It aims to do
|
|
35
|
+
what [pythia](https://github.com/thaildhe172591/pythia) does on Oracle: the
|
|
36
|
+
agent learns from the live catalog, asks a person before any write, and
|
|
37
|
+
writes only through a gate that snapshots, verifies and reports honestly.
|
|
38
|
+
|
|
39
|
+
**Alpha (0.1.0).** It reads everything pythia's Learn movement needs, and
|
|
40
|
+
it writes through one gate. Every write is rehearsed inside a
|
|
41
|
+
transaction, shown, approved by the developer — in the agent's chat or at
|
|
42
|
+
their console — and committed only as approved. `pythia-pg install` wires a project for an
|
|
43
|
+
agent: a guard that denies `psql` writing around the gate, the rules in
|
|
44
|
+
every session, and the `pythia-pg-*` skills. The road and how each pythia idea maps onto
|
|
45
|
+
PostgreSQL are in pythia-core's
|
|
46
|
+
[DESIGN.md](https://github.com/thaildhe172591/pythia-core/blob/main/DESIGN.md).
|
|
47
|
+
What each gate stops, and what it does not, is in its
|
|
48
|
+
[SECURITY.md](https://github.com/thaildhe172591/pythia-core/blob/main/SECURITY.md).
|
|
49
|
+
|
|
50
|
+
| Works today |
|
|
51
|
+
|---|
|
|
52
|
+
| reads: `check`, `ls`, `src`, `cols`, `ddl`, `grep`, `deps`, `impact`, `errors`, `connections` |
|
|
53
|
+
| writes: `apply`, `deploy`, `approve` (console, chat, MCP), `journal`, `history`, `policy`, `autopilot`, `lint` |
|
|
54
|
+
| the harness: `install`, `doctor`, `hook`, `mcp`, `skill lint` |
|
|
55
|
+
|
|
56
|
+
## Install
|
|
57
|
+
|
|
58
|
+
```bash
|
|
59
|
+
pip install pythia-postgres
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
That brings pythia-core, and psycopg 3 with its own libpq
|
|
63
|
+
(`psycopg[binary]`), so no PostgreSQL client needs installing. The
|
|
64
|
+
development version is the main branch of both:
|
|
65
|
+
|
|
66
|
+
```bash
|
|
67
|
+
pip install "git+https://github.com/thaildhe172591/pythia-core" \
|
|
68
|
+
"git+https://github.com/thaildhe172591/pythia-postgres"
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
## Set up a project for an agent
|
|
72
|
+
|
|
73
|
+
```bash
|
|
74
|
+
cd your-project
|
|
75
|
+
pythia-pg install # once; a second run changes nothing
|
|
76
|
+
pythia-pg doctor # is everything wired? exit 1 names what is not
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
`install` writes, and leaves everything else as it was:
|
|
80
|
+
|
|
81
|
+
- `.pythia/postgres/connections.json`, mode 600, created once from the
|
|
82
|
+
template — the developer fills in the passwords; an agent never opens it;
|
|
83
|
+
- `.gitignore` lines for that file, this machine's approvals
|
|
84
|
+
(`.pythia/postgres/grants/`) and `.claude/settings.local.json`;
|
|
85
|
+
- `.claude/settings.local.json`: the hooks (the guard, the session brief,
|
|
86
|
+
the AskUserQuestion answer), spelled with this interpreter's absolute path
|
|
87
|
+
so they start whether or not a virtual environment is active, and deny
|
|
88
|
+
rules for the engine's files;
|
|
89
|
+
- the MCP approver `pythia_pg` (tool `pythia_pg_approve`), in this
|
|
90
|
+
machine's files only: Claude Code's local scope for the project
|
|
91
|
+
(`~/.claude.json`) and Codex's `~/.codex/config.toml`, each when that
|
|
92
|
+
client has run here;
|
|
93
|
+
- a `pythia-pg` block in CLAUDE.md and AGENTS.md (pythia for Oracle's block,
|
|
94
|
+
and the developer's own text, are left alone);
|
|
95
|
+
- the skills, in `.claude/skills` and `.agents/skills`: `using-pythia-pg`
|
|
96
|
+
(the router), `pythia-pg-explore`, `pythia-pg-impact`, `pythia-pg-apply`,
|
|
97
|
+
`pythia-pg-deploy`, `pythia-pg-autopilot`, `pythia-pg-verify`.
|
|
98
|
+
|
|
99
|
+
Restart the agent's session after installing, so the hooks load. Each
|
|
100
|
+
session then starts with the rules and a `STATE` line (connections, the
|
|
101
|
+
write policy). Before every tool call the guard decides:
|
|
102
|
+
|
|
103
|
+
| The agent runs | The guard |
|
|
104
|
+
|---|---|
|
|
105
|
+
| `psql` given write SQL, `-f`, `< file`, a pipe, `\i`; `pg_restore` — also behind `docker exec`, `ssh`, `sudo` or `bash -c` | denies: writes go through `pythia-pg apply` |
|
|
106
|
+
| an MCP SQL tool given write SQL | denies |
|
|
107
|
+
| reading `connections.json`; editing `policy.json`, the journal or a grant; setting `PYTHIA_PG_CI`; calling `grants.mint` from a one-liner | denies |
|
|
108
|
+
| a `pythia-pg` read, a preview, `approve --card` | allows without a prompt |
|
|
109
|
+
| `apply --confirm <token>` the developer already approved on this connection | allows; apply re-verifies it |
|
|
110
|
+
| `psql` reads, `approve`, `--yes`, `policy set`, anything compound | stays out: the client asks |
|
|
111
|
+
|
|
112
|
+
The guard reads a command for what it names, not what it means: it can
|
|
113
|
+
deny a harmless command and miss a write spelled in a way it does not
|
|
114
|
+
list. The gate behind it holds either way (pythia-core's SECURITY.md).
|
|
115
|
+
|
|
116
|
+
pythia-mssql and pythia-mongo install beside it in one project, and pythia
|
|
117
|
+
for Oracle too: each keeps the others' hooks and instruction blocks, and
|
|
118
|
+
each guard and approval hook acts for its own tool only (pythia-core's CI
|
|
119
|
+
checks it with all four installed).
|
|
120
|
+
|
|
121
|
+
## Connections
|
|
122
|
+
|
|
123
|
+
`.pythia/postgres/connections.json` in the project root, owner-readable only
|
|
124
|
+
(`chmod 600`), and out of git — `pythia-pg install` does all three. Template:
|
|
125
|
+
[examples/connections.example.json](https://github.com/thaildhe172591/pythia-postgres/blob/main/examples/connections.example.json).
|
|
126
|
+
|
|
127
|
+
```json
|
|
128
|
+
{
|
|
129
|
+
"default": "dev",
|
|
130
|
+
"dev": { "host": "db-dev", "port": 5432, "dbname": "app",
|
|
131
|
+
"user": "app_agent", "password": "...", "schema": "APP_OWNER" }
|
|
132
|
+
}
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
- `host`, `dbname`, `user` are required. With no `password`, libpq's own
|
|
136
|
+
sources apply (`PGPASSWORD`, `~/.pgpass`). `sslmode` and
|
|
137
|
+
`connect_timeout` (seconds, default 10) are optional.
|
|
138
|
+
- `schema` is folded to lower case, as PostgreSQL folds an unquoted name;
|
|
139
|
+
the default is `public`. `check` warns when the session's `search_path`
|
|
140
|
+
puts unqualified names somewhere else.
|
|
141
|
+
- The entry is picked by `--conn NAME`, then `PYTHIA_PG_CONNECTION`, then
|
|
142
|
+
the only entry, the folder directly under the project root, then
|
|
143
|
+
`"default"`. Anything ambiguous is an error. `PYTHIA_PG_CONFIG` points at
|
|
144
|
+
another file.
|
|
145
|
+
|
|
146
|
+
**Alongside pythia for Oracle:** pythia keeps its connections in
|
|
147
|
+
`.pythia/connections.json`, and pythia-pg never reads that file. Its
|
|
148
|
+
command (`pythia-pg`) and its variables (`PYTHIA_PG_*`) never collide with
|
|
149
|
+
pythia's either.
|
|
150
|
+
|
|
151
|
+
## Commands
|
|
152
|
+
|
|
153
|
+
Every command opens a read-only session: PostgreSQL itself refuses a write
|
|
154
|
+
in it.
|
|
155
|
+
|
|
156
|
+
```bash
|
|
157
|
+
pythia-pg check # server, database, schema, search_path drift
|
|
158
|
+
pythia-pg ls "t_order%" # objects by LIKE pattern, any case
|
|
159
|
+
pythia-pg src f_order_total # source as PostgreSQL prints it, one unit per overload
|
|
160
|
+
pythia-pg cols t_order # columns, defaults, comments
|
|
161
|
+
pythia-pg ddl t_order # CREATE statement: tables rebuilt from the catalog
|
|
162
|
+
pythia-pg grep "t_order where" # every source line holding a text
|
|
163
|
+
pythia-pg deps f_order_paid # what it depends on
|
|
164
|
+
pythia-pg impact t_order # what depends on it: run before changing it
|
|
165
|
+
pythia-pg errors [NAME] # plpgsql_check's findings, or why there are none
|
|
166
|
+
pythia-pg connections # entries without their secrets
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
Names fold like PostgreSQL's: `T_ORDER` is `t_order`, `'"MixedCase"'` is
|
|
170
|
+
taken exactly. `--json` gives one envelope per command. `--limit`,
|
|
171
|
+
`--max-lines` and `--offset` window long output, and any truncation says how
|
|
172
|
+
to fetch the rest.
|
|
173
|
+
|
|
174
|
+
### What the catalog cannot see, and how pythia-pg says so
|
|
175
|
+
|
|
176
|
+
- **`deps` and `impact` ask twice.** `pg_depend` records views,
|
|
177
|
+
materialized views, SQL-standard function bodies (`BEGIN ATOMIC`),
|
|
178
|
+
triggers, constraints, column defaults and policies. It does not see into
|
|
179
|
+
a PL/pgSQL body, or a SQL function whose body is a string. Those are found
|
|
180
|
+
by a whole-word search of function source and marked `[text]`: a lead, not
|
|
181
|
+
a proof. Every run prints these blind spots: names built at run time
|
|
182
|
+
(`EXECUTE format(...)`), and other databases and servers.
|
|
183
|
+
- **`impact` says when to stop.** At 10 or more dependents, or any in
|
|
184
|
+
another schema, it says to show the list to the developer before writing
|
|
185
|
+
code.
|
|
186
|
+
- **`errors` never reads as clean when it is not.** PostgreSQL checks only
|
|
187
|
+
syntax when a PL/pgSQL function is created. Without the `plpgsql_check`
|
|
188
|
+
extension, `errors` prints `NOT VERIFIED` and says how to add it. With the
|
|
189
|
+
extension, unqualified names are resolved with the configured schema first
|
|
190
|
+
on `search_path`, as the schema's application would. If the account has no
|
|
191
|
+
`USAGE` on the schema, `errors` reports nothing and names the `GRANT`
|
|
192
|
+
needed. Without `USAGE`, every name would look missing.
|
|
193
|
+
- **`ddl` of a table is rebuilt, not dumped.** It is rebuilt from the
|
|
194
|
+
catalog: columns, identity, generated columns, constraints, indexes,
|
|
195
|
+
triggers and comments. The test suite proves the round trip: drop the
|
|
196
|
+
table, run the DDL, rebuild it, compare. `pg_dump --schema-only` stays the
|
|
197
|
+
reference for anything it does not cover yet, and the output says which.
|
|
198
|
+
|
|
199
|
+
### Writing: the gate
|
|
200
|
+
|
|
201
|
+
```bash
|
|
202
|
+
pythia-pg apply change.sql # preview: rehearsed, rolled back, nothing written
|
|
203
|
+
pythia-pg approve 3fa9c1 # the developer approves (or from chat, below)
|
|
204
|
+
pythia-pg apply change.sql --confirm 3fa9c1 # writes it — only as approved
|
|
205
|
+
pythia-pg journal restore <entry> # undo is a write too: preview, approve, confirm
|
|
206
|
+
pythia-pg policy # which groups may be written, and is rollback real?
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
The developer approves through the first door that works:
|
|
210
|
+
|
|
211
|
+
1. **In Claude Code's chat.** The agent asks with AskUserQuestion, its text
|
|
212
|
+
exactly the output of `pythia-pg approve --card <token>`, options
|
|
213
|
+
Approve / Reject. The answer hook records the approval only when the
|
|
214
|
+
question carried that card and the developer chose Approve themselves —
|
|
215
|
+
not an answer the agent wrote, not a dialog that closed unattended.
|
|
216
|
+
2. **Through the MCP approver** (`pythia_pg_approve`; Codex starts here):
|
|
217
|
+
the client shows the card, and only an explicit Approve counts.
|
|
218
|
+
3. **At the developer's console:** `pythia-pg approve <token>`.
|
|
219
|
+
|
|
220
|
+
`pythia-pg doctor` warns about settings that would answer the chat door for
|
|
221
|
+
the developer (`dontAsk` mode, an allow rule for AskUserQuestion, a hook
|
|
222
|
+
matching every tool).
|
|
223
|
+
|
|
224
|
+
### A release: `deploy`, one transaction
|
|
225
|
+
|
|
226
|
+
```bash
|
|
227
|
+
pythia-pg deploy 01_schema.sql 02_data.sql # preview the release as one transaction
|
|
228
|
+
pythia-pg deploy 01_schema.sql 02_data.sql --confirm 9c41e0b2aa # all of it, as approved, or none
|
|
229
|
+
```
|
|
230
|
+
|
|
231
|
+
A release — one or more scripts, statements ending in `;` — is rehearsed in
|
|
232
|
+
one transaction: each statement in order, snapshotted before it runs, its
|
|
233
|
+
rows and verification taken at its own point (an UPDATE of a column the
|
|
234
|
+
release adds is rehearsed against that column), then everything rolls back.
|
|
235
|
+
One card shows every statement, and one approval (a 10-digit token, through
|
|
236
|
+
the same doors) covers the release; a single statement's token cannot be
|
|
237
|
+
approved alone. The confirm checks that every statement is byte-identical
|
|
238
|
+
to what was approved before anything runs, re-runs the release in one
|
|
239
|
+
transaction, and commits only if every statement still matches. A statement
|
|
240
|
+
that fails, or rows, objects or verifications that moved, roll back the
|
|
241
|
+
whole release: nothing of it is left half-landed. Refused before anything
|
|
242
|
+
runs: `SET` inside a release, and anything `apply` refuses.
|
|
243
|
+
|
|
244
|
+
### Lint: before any apply
|
|
245
|
+
|
|
246
|
+
```bash
|
|
247
|
+
pythia-pg lint 01_schema.sql 02_code.sql # a change's files in one run
|
|
248
|
+
pythia-pg lint --rules # every rule and its effective severity
|
|
249
|
+
```
|
|
250
|
+
|
|
251
|
+
The rules read what PostgreSQL accepts and a reviewer would not:
|
|
252
|
+
|
|
253
|
+
| Rule | Default | Why |
|
|
254
|
+
|---|---|---|
|
|
255
|
+
| `pg-definer-search-path` | error | a `SECURITY DEFINER` function (created or altered) without `SET search_path` can be hijacked by anyone who can create objects on the caller's path |
|
|
256
|
+
| `pg-timestamp-without-tz` | warn | `timestamp` stores a wall-clock time, not a moment — `timestamptz` |
|
|
257
|
+
| `pg-char-n` | warn | `char(n)` pads with spaces |
|
|
258
|
+
| `pg-money` | warn | `money` depends on `lc_monetary` — `numeric` |
|
|
259
|
+
| `pg-serial` | info | prefer `GENERATED ALWAYS AS IDENTITY` |
|
|
260
|
+
| `pg-select-star` | warn | `SELECT *` in a view or function freezes or breaks on a new column |
|
|
261
|
+
| `pg-not-in-subquery` | warn | `NOT IN (SELECT ...)` is never true when the subquery returns a NULL |
|
|
262
|
+
| `pg-table-comment` | warn | a new table gets its `COMMENT ON TABLE` in the same change |
|
|
263
|
+
|
|
264
|
+
What PostgreSQL rejects itself (a RAISE with the wrong number of arguments)
|
|
265
|
+
is left to the rehearsal. The team sets severities and naming patterns in
|
|
266
|
+
`.pythia/postgres/lint.json`:
|
|
267
|
+
|
|
268
|
+
```json
|
|
269
|
+
{"severity": {"pg-select-star": "error", "naming": "error"},
|
|
270
|
+
"naming": {"FUNCTION": "^f_[a-z0-9_]+$", "VIEW": "^v_[a-z0-9_]+$"}}
|
|
271
|
+
```
|
|
272
|
+
|
|
273
|
+
Every preview shows the findings, a statement's errors are on its card, and
|
|
274
|
+
autopilot never lands a lint error.
|
|
275
|
+
|
|
276
|
+
### Autopilot: one lease instead of every approval
|
|
277
|
+
|
|
278
|
+
```bash
|
|
279
|
+
pythia-pg autopilot request --groups source,comments --hours 2 --max-writes 30
|
|
280
|
+
pythia-pg autopilot report # every write under the lease, with its restore
|
|
281
|
+
pythia-pg autopilot off # anyone may revoke
|
|
282
|
+
```
|
|
283
|
+
|
|
284
|
+
When the developer wants the agent to work without approving each write,
|
|
285
|
+
the agent requests a lease. Its card names the connection, the groups, the
|
|
286
|
+
time, the number of writes and whether destructive statements are included,
|
|
287
|
+
and nothing is delegated until the developer approves it through a door (or
|
|
288
|
+
runs `pythia-pg autopilot on` at a console). While it lasts, `apply` and
|
|
289
|
+
`deploy` land what it covers without `--confirm` — still snapshotted,
|
|
290
|
+
rehearsed, verified and journaled. It never covers another connection, one
|
|
291
|
+
marked `"protected": true` (or an `"env"` of prod / production / prd / live),
|
|
292
|
+
a group or destructive statement the card did not name, or more than 1000
|
|
293
|
+
rows; a write that breaks something, or five failures in a row, pause it.
|
|
294
|
+
|
|
295
|
+
### One statement: `apply`
|
|
296
|
+
|
|
297
|
+
One statement per file. `apply` sorts it into a group, and the policy
|
|
298
|
+
decides: `source` (functions, procedures, views) and `comments` need the
|
|
299
|
+
developer's approval; `data_dml`, `structural` and `grants` are denied
|
|
300
|
+
until the developer runs `pythia-pg policy set <group> confirm` at a
|
|
301
|
+
console. `session` (`SET`) is allowed.
|
|
302
|
+
|
|
303
|
+
What the preview shows, and why it can be trusted:
|
|
304
|
+
|
|
305
|
+
- **A rehearsal, not a prediction.** The statement ran inside a transaction
|
|
306
|
+
and was rolled back. The diff compares the definition before and after,
|
|
307
|
+
both as PostgreSQL prints them, so formatting is no noise. With
|
|
308
|
+
`plpgsql_check` installed, it says what the change broke compared with
|
|
309
|
+
just before; an `ALTER TABLE ... DROP COLUMN` names the function it would
|
|
310
|
+
break. Without it, the preview says `NOT VERIFIED`.
|
|
311
|
+
- **Overloads are named.** `CREATE OR REPLACE FUNCTION f(int)` next to an
|
|
312
|
+
existing `f(text)` creates a second function; the preview says so.
|
|
313
|
+
- **Rows are shown and revalidated.** An UPDATE or DELETE is approved on
|
|
314
|
+
its rows: the count, a sample, and a fingerprint of their ctids. If any
|
|
315
|
+
row joins, leaves or changes before the confirm, the confirm is refused.
|
|
316
|
+
- **The confirm is the same rehearsal, committed only if it still
|
|
317
|
+
matches.** Same token (the file, the object as it stood, the rows), same
|
|
318
|
+
row count, and nothing broken that the approved preview did not show.
|
|
319
|
+
Otherwise it rolls back and says what moved.
|
|
320
|
+
- **Refused, with the reason:** DO, CALL, SELECT, WITH, transaction
|
|
321
|
+
control, SET ROLE, temporary objects, psql meta-commands, several
|
|
322
|
+
statements in one file, statements that cannot run in a transaction
|
|
323
|
+
(`CREATE INDEX CONCURRENTLY`, `VACUUM`, ...), and an unqualified name that
|
|
324
|
+
`search_path` would send to another schema than the configured one.
|
|
325
|
+
|
|
326
|
+
A rehearsal is bounded (`statement_timeout` 30 s, `lock_timeout` 5 s). It
|
|
327
|
+
still runs triggers and advances sequences, and the preview of a DML
|
|
328
|
+
statement says so. Exit codes follow pythia: `0` clean, `1` refused,
|
|
329
|
+
`3` written but broken (with the restore command).
|
|
330
|
+
|
|
331
|
+
### Least privilege
|
|
332
|
+
|
|
333
|
+
The read commands need only `LOGIN`: PostgreSQL's catalog is readable by
|
|
334
|
+
every role. `errors` also needs `USAGE` on the schema. Writing needs what
|
|
335
|
+
the statement needs (to replace a function, owning it), and the preview
|
|
336
|
+
warns when the account is a superuser, can create roles, or owns the
|
|
337
|
+
schema. Set the agent role's search path to the schema it works in:
|
|
338
|
+
`ALTER ROLE app_agent IN DATABASE app SET search_path = app_owner, public`.
|
|
339
|
+
|
|
340
|
+
## Tests
|
|
341
|
+
|
|
342
|
+
```bash
|
|
343
|
+
python tests/test_postgres.py # no database, no psycopg: a stand-in catalog answers by query file; the statement reader; the guard's patterns; the skills
|
|
344
|
+
python tests/live_check.py # a disposable server only (see its docstring)
|
|
345
|
+
```
|
|
346
|
+
|
|
347
|
+
`test_postgres.py` finds pythia-core installed, in a checkout next to this
|
|
348
|
+
one (`../pythia-core`), or at `PYTHIA_CORE_SRC`. CI runs it on Ubuntu,
|
|
349
|
+
Windows and macOS, Python 3.9 and 3.13. CI also runs the live check against
|
|
350
|
+
a `postgres:16` service container. The live check loads
|
|
351
|
+
[tests/fixtures/app_owner.sql](https://github.com/thaildhe172591/pythia-postgres/blob/main/tests/fixtures/app_owner.sql) and checks
|
|
352
|
+
every command against it: the reads, then the write gate end to end
|
|
353
|
+
(preview, approve, confirm, restore, rows and verifications that moved).
|
|
354
|
+
All of it runs with and without `plpgsql_check`. CI tests against the
|
|
355
|
+
pythia-core branch of the same name when one exists, so a change spanning
|
|
356
|
+
both repositories is tested whole.
|
|
357
|
+
|
|
358
|
+
## License
|
|
359
|
+
|
|
360
|
+
MIT
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
pythia_postgres/__init__.py,sha256=u-_PeS4rxcX1fYLL_f2c-yRFSck6qn_B4zTSko8mp0I,294
|
|
2
|
+
pythia_postgres/__main__.py,sha256=E6Gls0DNz8GQK2K-kOUIx8cYhgANW_CH54VKrfCfs14,52
|
|
3
|
+
pythia_postgres/catalog.py,sha256=OM9F6b_v4XYMHDyVtnX7Lgnwlj4lAZonVGqxhbkPQow,2061
|
|
4
|
+
pythia_postgres/cli.py,sha256=1RhWgeqlGIptPBGuQv21X9hDZwjdZLgIPrFQO6-kmaM,210
|
|
5
|
+
pythia_postgres/engine.py,sha256=pKedXZh_OpMuySzvERt6kLZDoRURLuKUvgrn1o7c190,8547
|
|
6
|
+
pythia_postgres/guard.py,sha256=rpkAByPwNCf-sQWdRVJcz7HlH2wCIenSY-kygJJfpQs,2191
|
|
7
|
+
pythia_postgres/read.py,sha256=p2m38eI7qN3TFuGW98W6pgZUItBr7W3vZqiRya5Ydbw,12054
|
|
8
|
+
pythia_postgres/rules.py,sha256=Mi1_gkfRcqL-QhnP00D5Cja9qoDnWsgUqNQWv-WCDWA,5413
|
|
9
|
+
pythia_postgres/write.py,sha256=kRot2I-E2EUu9483DsxHUaqKLNoPsHLQE-bZqGy6zJw,25905
|
|
10
|
+
pythia_postgres/queries/code-objects.sql,sha256=lvXgIkiIAPXa1I0zPEcKpvS_uaG_Hh-cn3GYvvmOnuE,1244
|
|
11
|
+
pythia_postgres/queries/columns.sql,sha256=F8vLojmkvYC_Ff-i3htKfUWte5Fbc60oX9THutrZiwQ,802
|
|
12
|
+
pythia_postgres/queries/comment-current.sql,sha256=uqYdhi-Kdd1QnbrWEcnInh_uk2tweMi-UJHkXqmV-_M,776
|
|
13
|
+
pythia_postgres/queries/current-schema.sql,sha256=TU6C9YGKnLo28_ia3Q-4Oa-9HG0O3GmHwAOdmNFpD_s,235
|
|
14
|
+
pythia_postgres/queries/dependencies-catalog.sql,sha256=0PAXpulvGDJizkmv4OmDOyYSflGGHaCVRVDlSvB0_w0,2698
|
|
15
|
+
pythia_postgres/queries/dependents-catalog.sql,sha256=0BacIWqI59YMEhYbhkwsA4fgtIm95zZTvi0mbbFh7Ok,2906
|
|
16
|
+
pythia_postgres/queries/dependents-text.sql,sha256=f0uHmMNs_2HuSrQ8bSnYjBaUm3Ub0OHgHKluLH54HBw,1103
|
|
17
|
+
pythia_postgres/queries/function-body.sql,sha256=UHetyarMoeQceEQZ1pJOKjP5b-fNOM92gjxRXi7wFR8,352
|
|
18
|
+
pythia_postgres/queries/grep.sql,sha256=vQz_446dkwLODzChkustl_A4FrGHAioh-TLXZdoFy7A,1459
|
|
19
|
+
pythia_postgres/queries/name-candidates.sql,sha256=7UZXMVfiupfmXwA0szwwSe8WPk-4uqrYGFhRqnTEmKw,1347
|
|
20
|
+
pythia_postgres/queries/object-names.sql,sha256=ET2Gn5GUbc7qeMucX7zWBoQWLTicespAtwtE6w9BiSk,2541
|
|
21
|
+
pythia_postgres/queries/plpgsql-check-installed.sql,sha256=RaHMZ-oOBpda7okD-ljpEQn0WErZm_5mpRkpB9G-YuQ,354
|
|
22
|
+
pythia_postgres/queries/plpgsql-check.sql,sha256=g-F_tUSvEaZuaS5oRNns7NPqq9qgwat7FcuQkGIC7FY,1345
|
|
23
|
+
pythia_postgres/queries/plpgsql-unchecked.sql,sha256=TgIP2FgLDdsuGB0zw-snaHKje3r41VNiFf0uvNoMazM,784
|
|
24
|
+
pythia_postgres/queries/rehearsal-limits.sql,sha256=WZPLg7-bcn2xG0WmKCQ9oPEf5meUsVRvUiYGKf7F22A,481
|
|
25
|
+
pythia_postgres/queries/resolve.sql,sha256=YAvp2s1DHu6qBAuWpvw7cgHi5kfblIPSkFY3h15Wguc,1459
|
|
26
|
+
pythia_postgres/queries/row-set-count.sql,sha256=LMw5JnpUVy8kclEOQSk3gNSo30ERPzkFY02g3mLV4XA,563
|
|
27
|
+
pythia_postgres/queries/row-set-sample.sql,sha256=Vth0a3VtVWscx4154P5_JXzjCGle0dGvhtw-LVad3c0,300
|
|
28
|
+
pythia_postgres/queries/search-path-catalog.sql,sha256=mDg5l8Qt0-JP3bnGLyocL5y6o8jv3Oqfd-B6aRfswUE,386
|
|
29
|
+
pythia_postgres/queries/search-path-first.sql,sha256=c-ldrYfWosQpXIiNBqcXmllljlQQck-5x2bYIoyrA8I,795
|
|
30
|
+
pythia_postgres/queries/sequence-ddl.sql,sha256=EVUGXzFM88FV_lNsigZKqKHfP1Xobim-sx9Qpd3EaQ0,690
|
|
31
|
+
pythia_postgres/queries/server-version.sql,sha256=g0oo6qG2_ycfGrIBklbWHzOyYsJa74MlMWbyrnxTCHM,285
|
|
32
|
+
pythia_postgres/queries/session-power.sql,sha256=0PIQ1U86cGOzK7cObYaFytlK1KElrDnkIBGWbeC2LVA,548
|
|
33
|
+
pythia_postgres/queries/source.sql,sha256=-VGVyLs8KVH0FXvODt-T8Tjx_N-KHwjpFavS5GUsq-g,1184
|
|
34
|
+
pythia_postgres/queries/table-columns.sql,sha256=h7oz7tXupAGD0QaZ2T-91sWRmRJ8mgUzJ23NaH6FRrM,820
|
|
35
|
+
pythia_postgres/queries/table-constraints.sql,sha256=xrsIG_JJe-0Z_liQ3FAJXYmhCenSxbBRLFF2mZGbPjM,645
|
|
36
|
+
pythia_postgres/queries/table-extras.sql,sha256=lUz7DXEcHE1sYq9bRMxyURfsxBu3aVcXa8-JmZk5KZY,2033
|
|
37
|
+
pythia_postgres/queries/table-head.sql,sha256=j-Z7nbmV11z42283xzl5i-gr2LKSUjESKKzjpGIxIkw,972
|
|
38
|
+
pythia_postgres/skills/pythia-pg-apply/SKILL.md,sha256=aPYeWemCacwEl8s9_XF0N2b8GYq2qCCF1sG04TH-ITQ,4853
|
|
39
|
+
pythia_postgres/skills/pythia-pg-apply/reference/refusals.md,sha256=_7WGmq_xCwhLtS3YhassHfmy4VLz1QcyTdHsmHaTdlA,2152
|
|
40
|
+
pythia_postgres/skills/pythia-pg-autopilot/SKILL.md,sha256=wWPz5qhcwkoV6S9fZjJrQLlsf7rkjJI5-TlMpVLdk8Q,3165
|
|
41
|
+
pythia_postgres/skills/pythia-pg-deploy/SKILL.md,sha256=WyIfrUDJVpwtuZTsIMWMDG3l2dusRJOtpTZ-fRYhmXA,3780
|
|
42
|
+
pythia_postgres/skills/pythia-pg-explore/SKILL.md,sha256=xG5_IypBo5uCNd4xNwu5QsZq11CFIb2nCc5RsFmwfJE,2666
|
|
43
|
+
pythia_postgres/skills/pythia-pg-impact/SKILL.md,sha256=L5uyBL5wkScoPFo-zTx2ilQhp0fvrezAH7x4XWrZbZA,2544
|
|
44
|
+
pythia_postgres/skills/pythia-pg-verify/SKILL.md,sha256=8_78Zs18NPdbJ1MywrtW9uFXcYVUYtXOwMGJXId-SRM,2327
|
|
45
|
+
pythia_postgres/skills/using-pythia-pg/SKILL.md,sha256=-OiymyILHbA1lvbcOJ5nfecXM2aQiYro6c-LQCN3Il8,3452
|
|
46
|
+
pythia_postgres-0.1.0.dist-info/licenses/LICENSE,sha256=fY6GEoq3pNfFbCVGoUQulGzhdL5NrJCnoLdc7dOA4dQ,1063
|
|
47
|
+
pythia_postgres-0.1.0.dist-info/METADATA,sha256=g2ok8Lor54kxMrkYzZ2kL4oa18dyLExBwlA6VhvJ1sI,18347
|
|
48
|
+
pythia_postgres-0.1.0.dist-info/WHEEL,sha256=mOdXR9C3wMVov7cSY8PY-XCzo3SmmWCHvGQr5csJoao,91
|
|
49
|
+
pythia_postgres-0.1.0.dist-info/entry_points.txt,sha256=R95iia0URlssevrUnXpfh9BUrSd161xh4q39Xs4TpzY,55
|
|
50
|
+
pythia_postgres-0.1.0.dist-info/top_level.txt,sha256=2sUM9qCwJDZ40ga1HPpi9q7tM722Hl_OgyWm6StQQZQ,16
|
|
51
|
+
pythia_postgres-0.1.0.dist-info/RECORD,,
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 thaild
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
pythia_postgres
|