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
pythia_postgres/rules.py
ADDED
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
"""PostgreSQL's lint rules: what PostgreSQL accepts and a reviewer would not.
|
|
2
|
+
|
|
3
|
+
Each rule reads the statement as the lexer does — strings and comments
|
|
4
|
+
blanked, function bodies read as code of their own — so a keyword inside a
|
|
5
|
+
literal never fires one. A rule that PostgreSQL itself enforces when the
|
|
6
|
+
statement runs (a RAISE with the wrong number of arguments, say) does not
|
|
7
|
+
belong here: the rehearsal already reports it.
|
|
8
|
+
"""
|
|
9
|
+
import re
|
|
10
|
+
|
|
11
|
+
from .write import _DOLLAR, lex
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def _line(text, pos):
|
|
15
|
+
return text.count("\n", 0, pos) + 1
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def _bodies(stmt):
|
|
19
|
+
"""(offset, body) of every dollar-quoted body: a function's code."""
|
|
20
|
+
out, i, code = [], 0, lex(stmt).uncommented
|
|
21
|
+
while True:
|
|
22
|
+
m = _DOLLAR.search(code, i)
|
|
23
|
+
if not m:
|
|
24
|
+
return out
|
|
25
|
+
end = code.find(m.group(0), m.end())
|
|
26
|
+
if end < 0:
|
|
27
|
+
return out
|
|
28
|
+
out.append((m.end(), stmt[m.end():end]))
|
|
29
|
+
i = end + len(m.group(0))
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def _code_parts(stmt):
|
|
33
|
+
"""(offset, masked code) of the statement and of each body inside it."""
|
|
34
|
+
return [(0, lex(stmt).masked.lower())] + [
|
|
35
|
+
(off, lex(body).masked.lower()) for off, body in _bodies(stmt)]
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def _hits(stmt, pattern, message, parts=None):
|
|
39
|
+
out = []
|
|
40
|
+
for off, code in parts if parts is not None else [(0, lex(stmt).masked.lower())]:
|
|
41
|
+
for m in re.finditer(pattern, code):
|
|
42
|
+
out.append((_line(stmt, off + m.start()), message))
|
|
43
|
+
return out
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def definer_search_path(stmt, group, run):
|
|
47
|
+
"""CREATE FUNCTION ... SECURITY DEFINER, or ALTER FUNCTION ... SECURITY
|
|
48
|
+
DEFINER, with no SET search_path in the same statement."""
|
|
49
|
+
code = lex(stmt).masked.lower()
|
|
50
|
+
m = re.search(r"\bsecurity\s+definer\b", code)
|
|
51
|
+
if not m or re.search(r"\bset\s+search_path\b", code):
|
|
52
|
+
return []
|
|
53
|
+
return [(_line(stmt, m.start()),
|
|
54
|
+
"SECURITY DEFINER runs with its owner's rights, and without SET "
|
|
55
|
+
"search_path any role that can create objects in a schema on the "
|
|
56
|
+
"caller's path can put its own function in the way. Add "
|
|
57
|
+
"SET search_path = <schema>, pg_temp")]
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def _ddl(pattern, message):
|
|
61
|
+
def check(stmt, group, run):
|
|
62
|
+
return _hits(stmt, pattern, message) if group == "structural" else []
|
|
63
|
+
return check
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def select_star(stmt, group, run):
|
|
67
|
+
if group != "source":
|
|
68
|
+
return []
|
|
69
|
+
return _hits(stmt, r"\bselect\s+(?:distinct\s+)?\*", "SELECT * in stored code: a "
|
|
70
|
+
"view freezes the column list when it is created, and a "
|
|
71
|
+
"function breaks when a column is added — name the columns",
|
|
72
|
+
_code_parts(stmt))
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
def not_in_subquery(stmt, group, run):
|
|
76
|
+
return _hits(stmt, r"\bnot\s+in\s*\(\s*select\b", "NOT IN (SELECT ...) is "
|
|
77
|
+
"never true when the subquery returns a NULL — use NOT EXISTS",
|
|
78
|
+
_code_parts(stmt))
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
_NAME = r'((?:"[^"]+"|[a-z_][\w$]*)\s*\.\s*)?("[^"]+"|[a-z_][\w$]*)'
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
def _fold(name):
|
|
85
|
+
return name[1:-1] if name.startswith('"') else name.lower()
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
def table_comment(stmt, group, run):
|
|
89
|
+
if group != "structural":
|
|
90
|
+
return []
|
|
91
|
+
code = lex(stmt).uncommented
|
|
92
|
+
m = re.match(r"\s*create\s+(?:(?:global|local)\s+)?(?:unlogged\s+)?table\s+"
|
|
93
|
+
r"(?:if\s+not\s+exists\s+)?" + _NAME, code, re.I)
|
|
94
|
+
if not m:
|
|
95
|
+
return []
|
|
96
|
+
table = _fold(m.group(2))
|
|
97
|
+
for other in run:
|
|
98
|
+
c = re.match(r"\s*comment\s+on\s+table\s+" + _NAME, lex(other).uncommented, re.I)
|
|
99
|
+
if c and _fold(c.group(2)) == table:
|
|
100
|
+
return []
|
|
101
|
+
return [(_line(stmt, m.start(2)), f"table {table} has no COMMENT ON TABLE in this "
|
|
102
|
+
"change — say what a row is, in the same change that creates it")]
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
RULES = [
|
|
106
|
+
{"id": "pg-definer-search-path", "severity": "error",
|
|
107
|
+
"summary": "SECURITY DEFINER functions set their search_path",
|
|
108
|
+
"check": definer_search_path},
|
|
109
|
+
{"id": "pg-timestamp-without-tz", "severity": "warn",
|
|
110
|
+
"summary": "timestamp without time zone stores a wall-clock time, not a moment",
|
|
111
|
+
"check": _ddl(r"\btimestamp\b(?!\s*(?:\(\s*\d+\s*\))?\s*with\s+time\s+zone)",
|
|
112
|
+
"timestamp without time zone records a wall-clock time and "
|
|
113
|
+
"loses the moment — use timestamptz")},
|
|
114
|
+
{"id": "pg-char-n", "severity": "warn",
|
|
115
|
+
"summary": "char(n) pads with spaces",
|
|
116
|
+
"check": _ddl(r"\b(?:char|character)\s*\(\s*\d+\s*\)",
|
|
117
|
+
"char(n) pads with spaces and compares surprisingly — use "
|
|
118
|
+
"text or varchar(n)")},
|
|
119
|
+
{"id": "pg-money", "severity": "warn",
|
|
120
|
+
"summary": "money depends on lc_monetary",
|
|
121
|
+
"check": _ddl(r"\bmoney\b", "money is formatted by lc_monetary and rounds "
|
|
122
|
+
"to it — use numeric")},
|
|
123
|
+
{"id": "pg-serial", "severity": "info",
|
|
124
|
+
"summary": "identity columns over serial",
|
|
125
|
+
"check": _ddl(r"\b(?:small|big)?serial[248]?\b",
|
|
126
|
+
"serial is a sequence with loose ownership — prefer "
|
|
127
|
+
"GENERATED ALWAYS AS IDENTITY")},
|
|
128
|
+
{"id": "pg-select-star", "severity": "warn",
|
|
129
|
+
"summary": "no SELECT * in views and functions", "check": select_star},
|
|
130
|
+
{"id": "pg-not-in-subquery", "severity": "warn",
|
|
131
|
+
"summary": "NOT EXISTS over NOT IN (SELECT ...)", "check": not_in_subquery},
|
|
132
|
+
{"id": "pg-table-comment", "severity": "warn",
|
|
133
|
+
"summary": "every new table carries a comment in the same change",
|
|
134
|
+
"check": table_comment},
|
|
135
|
+
]
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: pythia-pg-apply
|
|
3
|
+
description: Use when landing any change in PostgreSQL - creating or replacing a function, procedure, view or trigger, a COMMENT ON, DDL, a GRANT, an INSERT/UPDATE/DELETE, or restoring a previous version. Every write goes through pythia-pg apply - one statement, rehearsed, previewed, approved by the developer, then committed.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Apply through the gate
|
|
7
|
+
|
|
8
|
+
**Announce at start:** "Using pythia-pg-apply — preview first, then the developer approves."
|
|
9
|
+
|
|
10
|
+
**Phase:** Ask → Do — the preview is the Ask, the confirm is the Do
|
|
11
|
+
|
|
12
|
+
PostgreSQL runs DDL inside a transaction, so pythia-pg rehearses every
|
|
13
|
+
statement for real — runs it, checks what broke, rolls it back — and shows
|
|
14
|
+
the developer the result before anything is kept. psql, a driver script or
|
|
15
|
+
an MCP SQL tool would write with no snapshot, no rehearsal and no journal;
|
|
16
|
+
the guard denies them.
|
|
17
|
+
|
|
18
|
+
## The Iron Law
|
|
19
|
+
|
|
20
|
+
```
|
|
21
|
+
NO WRITE WITHOUT A PREVIEW THE DEVELOPER SAW AND APPROVED
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
## The Workflow
|
|
25
|
+
|
|
26
|
+
1. **Impact first** (`pythia-pg-impact`) — the number was known before the
|
|
27
|
+
code was written.
|
|
28
|
+
2. **One statement per file**, linted first: `pythia-pg lint <files>` — a
|
|
29
|
+
change's files in one run, since a COMMENT ON in one counts for the
|
|
30
|
+
table in another. Errors are fixed, not argued. `CREATE OR REPLACE
|
|
31
|
+
FUNCTION ...;` goes in its own `.sql` file. Several statements, DO blocks, CALL, psql meta-commands and
|
|
32
|
+
anything that cannot run in a transaction are refused — see
|
|
33
|
+
`reference/refusals.md` for each refusal and its way forward.
|
|
34
|
+
3. **Preview:** `pythia-pg apply change.sql`. Nothing is kept. It prints:
|
|
35
|
+
- the diff of the definition before and after, as PostgreSQL prints both;
|
|
36
|
+
- the rehearsal: `nothing broke`, what it **would break**, or
|
|
37
|
+
`NOT VERIFIED` with the reason;
|
|
38
|
+
- the rows a DML statement touched, with a sample;
|
|
39
|
+
- warnings: another overload with the same name, `! DESTRUCTIVE`;
|
|
40
|
+
- a token, and the approval doors.
|
|
41
|
+
4. **Relay the preview verbatim** — diff, rehearsal, rows, warnings. Do not
|
|
42
|
+
summarise away a broken dependent or a NOT VERIFIED line.
|
|
43
|
+
5. **Ask, through the first door that works** — the next one unasked when
|
|
44
|
+
a door fails (the hook or the tool result says which):
|
|
45
|
+
1. **AskUserQuestion** (Claude Code): run
|
|
46
|
+
`pythia-pg approve --card <token>` and ask with exactly its output,
|
|
47
|
+
options Approve / Reject, never with answers filled in. The answer
|
|
48
|
+
hook records the approval.
|
|
49
|
+
2. **The `pythia_pg_approve` tool** (MCP; Codex starts here), with the
|
|
50
|
+
token. The developer answers its prompt.
|
|
51
|
+
3. **The developer's terminal:** relay `pythia-pg approve <token>`.
|
|
52
|
+
|
|
53
|
+
Never mint a grant, pipe anything into `approve --hook`, or set
|
|
54
|
+
`PYTHIA_PG_CI` — the guard denies all three. A Reject means stop and ask
|
|
55
|
+
what should change.
|
|
56
|
+
6. **Confirm:** `pythia-pg apply change.sql --confirm <token>`. It rehearses
|
|
57
|
+
again and commits only if the file, the object, the rows and the
|
|
58
|
+
verification still match what was approved; otherwise it rolls back and
|
|
59
|
+
names what moved — preview again and get the new token approved.
|
|
60
|
+
7. **Verify** (`pythia-pg-verify`) before any "done".
|
|
61
|
+
|
|
62
|
+
## Exit codes are the verdict
|
|
63
|
+
|
|
64
|
+
| Exit | Meaning | You say |
|
|
65
|
+
|---|---|---|
|
|
66
|
+
| 0 | written and verified (or NOT VERIFIED, said plainly) | what landed, and what was not checked |
|
|
67
|
+
| 1 | refused, nothing written | the reason, verbatim; never route around it |
|
|
68
|
+
| 3 | written **but broken** | "broken" first: the errors and the printed restore command; the developer chooses fix-forward or restore |
|
|
69
|
+
|
|
70
|
+
Never read the exit code through a pipe: `pythia-pg apply ... | tail` returns
|
|
71
|
+
tail's code.
|
|
72
|
+
|
|
73
|
+
## Policy
|
|
74
|
+
|
|
75
|
+
`data_dml`, `structural` and `grants` are `deny` by default; `source` and
|
|
76
|
+
`comments` need the developer's approval. A deny is relayed with its reason.
|
|
77
|
+
`pythia-pg policy` shows the policy and whether rollback is real per group.
|
|
78
|
+
Loosening it (`policy set`) and `--yes` are the developer's acts, at a
|
|
79
|
+
console — never yours.
|
|
80
|
+
|
|
81
|
+
## Restore is a write
|
|
82
|
+
|
|
83
|
+
`pythia-pg history <name>` lists every version the journal holds;
|
|
84
|
+
`pythia-pg journal restore <entry>` previews putting one back, and goes
|
|
85
|
+
through the same approval and `--confirm <token>`. Only definitions and
|
|
86
|
+
comments have a restore: rows, a dropped column or a revoked grant do not
|
|
87
|
+
come back.
|
|
88
|
+
|
|
89
|
+
## Red Flags — STOP if you catch yourself thinking
|
|
90
|
+
|
|
91
|
+
| Thought | Reality |
|
|
92
|
+
|---------|---------|
|
|
93
|
+
| "It's a one-line fix, psql is faster" | Faster to write, and the only copy of what it replaced is gone. |
|
|
94
|
+
| "The developer said go ahead earlier" | They approve a token, after seeing its preview. |
|
|
95
|
+
| "Exit 3, but it mostly worked" | Exit 3 is broken. Say so first. |
|
|
96
|
+
| "I'll split the script and apply each part quietly" | Each part is previewed and approved on its own. |
|
|
97
|
+
| "The preview is long, I'll summarise" | The developer approves what they saw. Relay it. |
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
# Refusals and the way forward
|
|
2
|
+
|
|
3
|
+
`pythia-pg apply` refuses what it cannot rehearse, verify or undo honestly.
|
|
4
|
+
Each refusal exits 1 and writes nothing. Relay the reason, then offer the
|
|
5
|
+
way forward below — never run the statement another way.
|
|
6
|
+
|
|
7
|
+
| Refused | Why | Way forward |
|
|
8
|
+
|---|---|---|
|
|
9
|
+
| Several statements in one file | each is previewed, approved and journaled on its own | one file per statement, applied in order |
|
|
10
|
+
| `DO $$ ... $$` | an anonymous block can run anything; no policy group fits | put the logic in a function and apply that |
|
|
11
|
+
| `CALL proc(...)` | runs a body that can do anything | apply the statements it would run, or the developer runs it by hand |
|
|
12
|
+
| `WITH ... INSERT/UPDATE/DELETE` | hides its writes inside a query | a plain INSERT, UPDATE or DELETE |
|
|
13
|
+
| `SELECT`, `EXPLAIN`, `SHOW` | apply takes writes; a SELECT can call a function that writes | reads go through `ls`, `src`, `cols`, `grep` |
|
|
14
|
+
| `COPY` | reads or writes files outside the database | the developer runs it by hand |
|
|
15
|
+
| `CREATE INDEX CONCURRENTLY`, `VACUUM`, `ALTER SYSTEM`, `CREATE DATABASE` | cannot run inside a transaction: no rehearsal, no rollback | the developer runs it by hand, with a DBA watching |
|
|
16
|
+
| `CREATE TEMP ...` | dies with the connection, nothing left to verify | not needed through apply |
|
|
17
|
+
| `SET ROLE`, `SET SESSION AUTHORIZATION` | changes who verifies the write | connect as the right account |
|
|
18
|
+
| `BEGIN`, `COMMIT`, `SAVEPOINT`, `LOCK` | pythia-pg owns the transaction | drop the transaction control |
|
|
19
|
+
| psql meta-commands (`\i`, `\set`, `\gexec`) | pythia-pg runs SQL, not psql scripts | plain SQL in the file |
|
|
20
|
+
| a file that ends inside a quote or `$$` body | cannot be read safely | close the quote |
|
|
21
|
+
| a schema other than the connection's | writing across schemas is refused | `--conn` for the connection that owns it |
|
|
22
|
+
| a group set to `deny` | the policy | relay it; loosening is the developer's `policy set`, at a console |
|
|
23
|
+
|
|
24
|
+
When the gate refuses, the developer may still decide to run the statement
|
|
25
|
+
by hand. Hand them the file and, for a definition, the restore from
|
|
26
|
+
`pythia-pg history <name>` — never run it for them through psql.
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: pythia-pg-autopilot
|
|
3
|
+
description: Use when the developer wants the agent to work on PostgreSQL without approving every write - "do it all without asking me", "just apply them", "tự động hoàn toàn". Asks once for a lease that names exactly what it covers, then reports every write landed under it.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Autopilot — one lease, never a blank cheque
|
|
7
|
+
|
|
8
|
+
**Announce at start:** "Using pythia-pg-autopilot — one request, covering only what the task needs."
|
|
9
|
+
|
|
10
|
+
**Phase:** Ask → Do — the lease card is the one Ask; every covered write is a Do that is reported
|
|
11
|
+
|
|
12
|
+
A lease is the developer's delegation, not yours. It covers one connection,
|
|
13
|
+
the statement groups it names, a time and a number of writes. Inside it,
|
|
14
|
+
covered writes land without a per-write approval — still snapshotted,
|
|
15
|
+
rehearsed, verified and journaled. Outside it, the normal gate applies.
|
|
16
|
+
|
|
17
|
+
## The Iron Law
|
|
18
|
+
|
|
19
|
+
```
|
|
20
|
+
ASK FOR THE NARROWEST LEASE THAT FITS, AND REPORT EVERY WRITE UNDER IT
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
## The Workflow
|
|
24
|
+
|
|
25
|
+
1. **Settle the task first.** Open decisions are asked before any lease
|
|
26
|
+
(`using-pythia-pg`).
|
|
27
|
+
2. **Request the narrowest lease that fits:**
|
|
28
|
+
`pythia-pg autopilot request --groups source,comments --hours 2
|
|
29
|
+
--max-writes 30 --note "rename the order functions"`. Name only the
|
|
30
|
+
groups the task needs; add `--destructive` only when the developer asked
|
|
31
|
+
for drops.
|
|
32
|
+
3. **Ask once, through the doors** — exactly as `pythia-pg-apply` does, with
|
|
33
|
+
the request's 8-digit token: AskUserQuestion with the output of
|
|
34
|
+
`pythia-pg approve --card <token>`, then the `pythia_pg_approve` tool,
|
|
35
|
+
then the developer's terminal. `pythia-pg autopilot on` is the
|
|
36
|
+
developer's own command, at a console — never yours.
|
|
37
|
+
4. **Work:** `pythia-pg apply <file>` and `pythia-pg deploy <files>` land
|
|
38
|
+
what the lease covers, with no `--confirm`. When one prints
|
|
39
|
+
`! not landed under autopilot — <why>`, that write needs its own
|
|
40
|
+
approval: relay its preview and ask, as `pythia-pg-apply` does.
|
|
41
|
+
5. **Stop when the lease pauses.** A write that breaks something, or five
|
|
42
|
+
failures in a row, pauses it: show the developer the result before
|
|
43
|
+
anything else. Exit 3 is still "broken", never "done".
|
|
44
|
+
6. **Report:** `pythia-pg autopilot report` lists every write with its
|
|
45
|
+
verdict and its restore. Relay it at the end, and `pythia-pg autopilot
|
|
46
|
+
off` when the task is done.
|
|
47
|
+
|
|
48
|
+
## What a lease never covers
|
|
49
|
+
|
|
50
|
+
Another connection, or one marked `"protected": true` (or an `"env"` of
|
|
51
|
+
prod / production / prd / live) in the connections file; a group the card
|
|
52
|
+
did not name; a destructive statement the card did not include; an UPDATE
|
|
53
|
+
or DELETE of more than 1000 rows; anything after it expired, was spent,
|
|
54
|
+
revoked or paused.
|
|
55
|
+
|
|
56
|
+
## Red Flags — STOP if you catch yourself thinking
|
|
57
|
+
|
|
58
|
+
| Thought | Reality |
|
|
59
|
+
|---------|---------|
|
|
60
|
+
| "I'll ask for all groups, to be safe" | Safe for whom? Ask for what the task needs. |
|
|
61
|
+
| "It paused, but the next write is unrelated" | Paused means stop and show the developer. |
|
|
62
|
+
| "Covered writes don't need reporting" | Every one is reported: `autopilot report`. |
|
|
63
|
+
| "I'll approve my own request" | Approval is the developer's, through a door. The guard denies the rest. |
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: pythia-pg-deploy
|
|
3
|
+
description: Use when landing several PostgreSQL statements together - a release script, a migration, DDL followed by the data change that needs it, or more than one file of changes. pythia-pg deploy rehearses the whole release in one transaction and lands all of it or none of it, under one approval.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Deploy a release as one transaction
|
|
7
|
+
|
|
8
|
+
**Announce at start:** "Using pythia-pg-deploy — the release is rehearsed as one, then approved as one."
|
|
9
|
+
|
|
10
|
+
**Phase:** Ask → Do — one card for the whole release is the Ask; the confirm is the Do
|
|
11
|
+
|
|
12
|
+
PostgreSQL runs DDL inside a transaction, so a whole release can be
|
|
13
|
+
rehearsed and committed as one: if statement 7 fails, statements 1–6 are
|
|
14
|
+
not left half-landed. Running a migration through psql gives that up, and
|
|
15
|
+
the snapshot, the rehearsal and the journal with it.
|
|
16
|
+
|
|
17
|
+
## The Iron Law
|
|
18
|
+
|
|
19
|
+
```
|
|
20
|
+
A RELEASE LANDS WHOLE, AS APPROVED, OR NOT AT ALL
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
## The Workflow
|
|
24
|
+
|
|
25
|
+
1. **Impact first** (`pythia-pg-impact`) for every object the release
|
|
26
|
+
changes.
|
|
27
|
+
2. **The scripts:** plain SQL, statements ending in `;`, in the order they
|
|
28
|
+
must run — one file or several, named in that order on the command line.
|
|
29
|
+
No `BEGIN`/`COMMIT` (pythia-pg owns the transaction), no `SET`, no psql
|
|
30
|
+
meta-commands, nothing that cannot run in a transaction
|
|
31
|
+
(`pythia-pg-apply` lists each refusal and its way forward).
|
|
32
|
+
3. **Preview:** `pythia-pg deploy 01_schema.sql 02_data.sql`. Every
|
|
33
|
+
statement runs in order inside one transaction — an UPDATE of a column
|
|
34
|
+
added two statements earlier is rehearsed against that column — and
|
|
35
|
+
then everything rolls back. It prints one card: every statement, the rows
|
|
36
|
+
each DML statement touches, what the rehearsal of the whole broke or
|
|
37
|
+
could not verify, and one 10-digit token.
|
|
38
|
+
4. **Relay the card verbatim**, then ask through the doors exactly as
|
|
39
|
+
`pythia-pg-apply` does — AskUserQuestion with the output of
|
|
40
|
+
`pythia-pg approve --card <token>`, then the `pythia_pg_approve` tool,
|
|
41
|
+
then `pythia-pg approve <token>` in the developer's terminal. A
|
|
42
|
+
statement's own token is refused: a release is approved as a whole.
|
|
43
|
+
5. **Confirm:** `pythia-pg deploy 01_schema.sql 02_data.sql --confirm
|
|
44
|
+
<token>`, with the same files in the same order. It checks every
|
|
45
|
+
statement is byte-identical to what was approved before anything runs,
|
|
46
|
+
then runs the release again in one transaction and commits only if every
|
|
47
|
+
statement still matches — its object, its rows, its verification.
|
|
48
|
+
Otherwise the whole release rolls back, says which statement moved, and
|
|
49
|
+
the approval stays unspent.
|
|
50
|
+
6. **Verify** (`pythia-pg-verify`): exit 0 clean, 3 written but broken.
|
|
51
|
+
|
|
52
|
+
## Policy and undo
|
|
53
|
+
|
|
54
|
+
Every group in the release must be allowed: `structural` and `data_dml`
|
|
55
|
+
are `deny` until the developer runs `pythia-pg policy set <group> confirm`
|
|
56
|
+
at a console. After commit, functions, views and comments are restorable
|
|
57
|
+
statement by statement (`pythia-pg journal restore <entry>`, newest first,
|
|
58
|
+
as the report lists them); added columns and changed rows are not.
|
|
59
|
+
|
|
60
|
+
## Red Flags — STOP if you catch yourself thinking
|
|
61
|
+
|
|
62
|
+
| Thought | Reality |
|
|
63
|
+
|---------|---------|
|
|
64
|
+
| "psql -f is how migrations run" | Not here: no rehearsal, no snapshot, no all-or-nothing. The guard denies it. |
|
|
65
|
+
| "I'll apply the statements one by one" | Then a failure halfway leaves a half release. Deploy them together. |
|
|
66
|
+
| "Just one more statement after approval" | The scripts changed: nothing runs. Preview again. |
|
|
67
|
+
| "It rolled back, so I'll retry with --yes" | `--yes` is the developer's flag. Find what moved, preview again. |
|
|
68
|
+
|
|
69
|
+
## When NOT to use this skill
|
|
70
|
+
|
|
71
|
+
- One statement → `pythia-pg-apply`.
|
|
72
|
+
- A statement that cannot run in a transaction (`CREATE INDEX
|
|
73
|
+
CONCURRENTLY`, `VACUUM`) → the developer runs it by hand.
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: pythia-pg-explore
|
|
3
|
+
description: Use when explaining, investigating or reading PostgreSQL objects - what a function does, where a table is used, why something fails, what a schema holds. Asks the live database through pythia-pg read commands, never a dump or memory.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Explore the live database
|
|
7
|
+
|
|
8
|
+
**Announce at start:** "Using pythia-pg-explore to read the live database."
|
|
9
|
+
|
|
10
|
+
**Phase:** Learn — facts from the catalog, before any opinion
|
|
11
|
+
|
|
12
|
+
A repository's SQL files drift from the database: a function edited in
|
|
13
|
+
place, an index added by hand, a view nobody exported. An explanation built
|
|
14
|
+
on the dump is an explanation of a database that does not exist.
|
|
15
|
+
|
|
16
|
+
## The Iron Law
|
|
17
|
+
|
|
18
|
+
```
|
|
19
|
+
EVERY FACT COMES FROM THE LIVE CATALOG, WITH THE COMMAND THAT SHOWED IT
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
## The commands
|
|
23
|
+
|
|
24
|
+
| Question | Command |
|
|
25
|
+
|---|---|
|
|
26
|
+
| Where am I, and where do unqualified names land? | `pythia-pg check` |
|
|
27
|
+
| Which objects match a name? | `pythia-pg ls "t_order%"` |
|
|
28
|
+
| What does this function / view / trigger say? | `pythia-pg src <name>` |
|
|
29
|
+
| What columns does this table or view have? | `pythia-pg cols <name>` |
|
|
30
|
+
| What statement would create it? | `pythia-pg ddl <name>` |
|
|
31
|
+
| Where is this text used in source? | `pythia-pg grep <text>` |
|
|
32
|
+
| What does it use? | `pythia-pg deps <name>` |
|
|
33
|
+
| What uses it? | `pythia-pg impact <name>` (see `pythia-pg-impact`) |
|
|
34
|
+
| What does the checker say is broken? | `pythia-pg errors [<name>]` |
|
|
35
|
+
|
|
36
|
+
`--json` gives structured output on every command. Line numbers in `src`
|
|
37
|
+
are PostgreSQL's own, so they match the line in an error message.
|
|
38
|
+
|
|
39
|
+
## Reading the output honestly
|
|
40
|
+
|
|
41
|
+
- **Truncation is announced.** No `-- truncated` line means you saw
|
|
42
|
+
everything. With one, say so, or fetch the rest with `--offset` /
|
|
43
|
+
`--limit 0`.
|
|
44
|
+
- **`[text]` rows are leads, not proofs.** PostgreSQL's catalog does not
|
|
45
|
+
record what a PL/pgSQL body calls; pythia-pg finds those by name in the
|
|
46
|
+
source and marks them. Say which rows are which.
|
|
47
|
+
- **`-- blind spot:` lines are part of the answer.** Relay them; never call
|
|
48
|
+
a dependency list complete when one is printed.
|
|
49
|
+
- **`NOT VERIFIED` means nothing was checked there** (no `plpgsql_check`, or
|
|
50
|
+
a language it cannot read). It is never "no errors".
|
|
51
|
+
|
|
52
|
+
## Red flags
|
|
53
|
+
|
|
54
|
+
| Thought | Reality |
|
|
55
|
+
|---------|---------|
|
|
56
|
+
| "The migration file shows the table" | The database may not. `pythia-pg cols`. |
|
|
57
|
+
| "`errors` printed nothing, so it is clean" | Only if no NOT VERIFIED line was printed. |
|
|
58
|
+
| "I'll open connections.json to see the host" | Never: `pythia-pg connections` lists it without passwords. |
|
|
59
|
+
|
|
60
|
+
## Hand-off
|
|
61
|
+
|
|
62
|
+
Explaining only → done, with the commands quoted. A change follows →
|
|
63
|
+
`pythia-pg-impact` before any code is written.
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: pythia-pg-impact
|
|
3
|
+
description: Use before proposing or writing any change to a PostgreSQL object - a function, procedure, trigger, view, table, column or type. Impact analysis comes first, because what depends on an object decides how careful the change must be.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Impact Before Change
|
|
7
|
+
|
|
8
|
+
**Announce at start:** "Using pythia-pg-impact to measure the blast radius first."
|
|
9
|
+
|
|
10
|
+
**Phase:** Learn — and the first Ask gate: a large blast radius reaches the developer before any code is written
|
|
11
|
+
|
|
12
|
+
A dropped column breaks every function that reads it — at call time, not
|
|
13
|
+
when the column goes, because PostgreSQL does not record what a PL/pgSQL
|
|
14
|
+
body uses. The cost of knowing first is one command.
|
|
15
|
+
|
|
16
|
+
## The Iron Law
|
|
17
|
+
|
|
18
|
+
```
|
|
19
|
+
NO CHANGE PROPOSED WITHOUT ITS IMPACT MEASURED FIRST
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
## The Workflow
|
|
23
|
+
|
|
24
|
+
1. `pythia-pg impact <name> --depth 2` — everything that depends on it, as
|
|
25
|
+
a tree, ending with the summary:
|
|
26
|
+
`-- impact: N dependent objects (... matched by name as text ...)`.
|
|
27
|
+
2. `pythia-pg deps <name> --depth 2` — the other direction, what it uses;
|
|
28
|
+
read it when the change touches calls or table access.
|
|
29
|
+
3. `pythia-pg errors` — the baseline. Anything broken before the change
|
|
30
|
+
must not be blamed on it later.
|
|
31
|
+
|
|
32
|
+
## Reading the numbers
|
|
33
|
+
|
|
34
|
+
| Result | What to do |
|
|
35
|
+
|---|---|
|
|
36
|
+
| 0 dependents | Say so, with the output as evidence, and proceed. |
|
|
37
|
+
| 1–9 dependents | List them to the developer alongside the proposal. |
|
|
38
|
+
| `! show this list to the developer` (10+, or one in another schema) | Show the tree and get an explicit go-ahead **before writing any code**. |
|
|
39
|
+
| `[text]` rows | Leads found by name in function source: name them as such. |
|
|
40
|
+
| `-- blind spot:` lines | Relay them. Never call the list complete. |
|
|
41
|
+
|
|
42
|
+
Overloads count separately: `f_order_total(integer)` and
|
|
43
|
+
`f_order_total(integer, boolean)` are two functions, and impact walks each.
|
|
44
|
+
|
|
45
|
+
## Red Flags — STOP if you catch yourself thinking
|
|
46
|
+
|
|
47
|
+
| Thought | Reality |
|
|
48
|
+
|---------|---------|
|
|
49
|
+
| "It's just a small helper" | Helpers are the most-depended-on objects there are. |
|
|
50
|
+
| "I'll check impact after writing the code" | Then the number cannot change the design. Too late. |
|
|
51
|
+
| "The catalog shows no dependents, so nothing calls it" | Function bodies are not in the catalog — read the `[text]` rows and blind spots. |
|
|
52
|
+
| "apply's preview shows what breaks anyway" | That confirms a number you should already know; it is not discovery. |
|
|
53
|
+
|
|
54
|
+
## Hand-off
|
|
55
|
+
|
|
56
|
+
Impact known and acceptable → write the change (one statement per file) →
|
|
57
|
+
`pythia-pg-apply` to land it through the gate.
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: pythia-pg-verify
|
|
3
|
+
description: Use before claiming any PostgreSQL change is done, fixed, working or safe - after an apply, a restore, or a fix to a broken function. Evidence first - exit codes, the checker, the dependents - and "broken" or "not verified" said plainly.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Evidence before "done"
|
|
7
|
+
|
|
8
|
+
**Announce at start:** "Using pythia-pg-verify — evidence before done."
|
|
9
|
+
|
|
10
|
+
**Phase:** Do — the last step of every change
|
|
11
|
+
|
|
12
|
+
PostgreSQL accepts a PL/pgSQL function that references a column that does
|
|
13
|
+
not exist; it fails when called. "It applied" is not "it works", and a
|
|
14
|
+
claim of done without evidence costs the developer the debugging later.
|
|
15
|
+
|
|
16
|
+
## The Iron Law
|
|
17
|
+
|
|
18
|
+
```
|
|
19
|
+
NO COMPLETION CLAIM WITHOUT THE EVIDENCE PRINTED IN THIS SESSION
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
## The Checklist
|
|
23
|
+
|
|
24
|
+
1. **Every write's exit code, unpiped.** 0 clean, 3 written-but-broken.
|
|
25
|
+
A 3 means the report starts with "broken".
|
|
26
|
+
2. **`pythia-pg errors <name>`** for each object you touched, and for each
|
|
27
|
+
dependent that `impact` listed. Compare with the baseline taken before
|
|
28
|
+
the change: only new findings are yours.
|
|
29
|
+
3. **`NOT VERIFIED` is reported as such.** Without `plpgsql_check`, or for a
|
|
30
|
+
language it cannot read, nothing was checked: say "applied, not
|
|
31
|
+
verified", never "clean".
|
|
32
|
+
4. **The dependents.** If the change altered a signature, a column or a
|
|
33
|
+
return type, read the `[text]` rows from `impact` again — they are where
|
|
34
|
+
a call-time failure hides.
|
|
35
|
+
5. **The journal.** `pythia-pg history <name>` shows the entry; quote its
|
|
36
|
+
restore command in the report for a change the developer may want back.
|
|
37
|
+
|
|
38
|
+
## How to report
|
|
39
|
+
|
|
40
|
+
```
|
|
41
|
+
Applied f_order_total (FUNCTION) — exit 0.
|
|
42
|
+
errors f_order_total: none (plpgsql_check).
|
|
43
|
+
Dependents: v_order_summary [catalog], f_order_paid [text] — errors: none.
|
|
44
|
+
Restore if needed: pythia-pg journal restore <entry>
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
Say "broken" first when it is; say "not verified" plainly when it is.
|
|
48
|
+
|
|
49
|
+
## Red Flags — STOP if you catch yourself thinking
|
|
50
|
+
|
|
51
|
+
| Thought | Reality |
|
|
52
|
+
|---------|---------|
|
|
53
|
+
| "apply printed Applied" | Read the exit code and the rehearsal lines, not the word. |
|
|
54
|
+
| "errors printed nothing" | With no NOT VERIFIED line, that is evidence. With one, it is not. |
|
|
55
|
+
| "The dependents compiled before" | PL/pgSQL is checked at call time; check them now. |
|
|
56
|
+
| "I'll verify after reporting" | Then the report is a guess. |
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: using-pythia-pg
|
|
3
|
+
description: Use when starting any conversation that touches PostgreSQL, PL/pgSQL, a schema, a function, a trigger or a view - it picks the right pythia-pg skill BEFORE any response or action, including clarifying questions. Fires on plain requests in any language - "build/add/fix/explain/review X", "lam/them/sua/tao/giai thich/kiem tra X" (Vietnamese, with or without diacritics). Also use whenever unsure which pythia-pg skill applies.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Using pythia-pg — the pocket handbook
|
|
7
|
+
|
|
8
|
+
**Phase:** Learn → Ask → Do — this page opens the right one
|
|
9
|
+
|
|
10
|
+
pythia-pg is pythia for PostgreSQL. Its one job here is consistency:
|
|
11
|
+
whether a session measures the blast radius and asks before it writes
|
|
12
|
+
should not depend on the agent's mood that day.
|
|
13
|
+
|
|
14
|
+
## The Rule
|
|
15
|
+
|
|
16
|
+
If there is even a small chance a pythia-pg skill applies, open it **before
|
|
17
|
+
acting** — before answering, before exploring, before clarifying questions.
|
|
18
|
+
Opened the wrong one? Close it, no harm done.
|
|
19
|
+
|
|
20
|
+
## Route by what the developer said
|
|
21
|
+
|
|
22
|
+
| The request sounds like | Open first | Then |
|
|
23
|
+
|---|---|---|
|
|
24
|
+
| "explain X / why / how does it work" ("giải thích") | `pythia-pg-explore` | |
|
|
25
|
+
| "is it safe to touch X" | `pythia-pg-impact` | |
|
|
26
|
+
| "change / fix / add X" ("sửa", "thêm") | `pythia-pg-impact` | write, then `pythia-pg-apply` |
|
|
27
|
+
| "land / apply this" — one statement | `pythia-pg-apply` | `pythia-pg-verify` |
|
|
28
|
+
| "run this migration / script / release" ("chạy script") | `pythia-pg-deploy` | `pythia-pg-verify` |
|
|
29
|
+
| "do it all without asking me" ("tự động hoàn toàn") | `pythia-pg-autopilot` — one lease, never a blank cheque | the task's own skills |
|
|
30
|
+
| "undo / restore X" | `pythia-pg-apply` (a restore is a write) | `pythia-pg-verify` |
|
|
31
|
+
| an error from PostgreSQL, a broken function | `pythia-pg-explore` (`errors`, `src`) | |
|
|
32
|
+
| about to say done / fixed / works | `pythia-pg-verify` — evidence first | |
|
|
33
|
+
| setup, connection failures, the hooks not running | `pythia-pg doctor`, then `pythia-pg check` | |
|
|
34
|
+
|
|
35
|
+
Priority when several apply: **process before construction** — impact
|
|
36
|
+
before writing, apply's preview before any write, verify before "done".
|
|
37
|
+
Announce the skill you opened ("Using pythia-pg-impact — ...") so the
|
|
38
|
+
developer sees which page you are on.
|
|
39
|
+
|
|
40
|
+
## A request with open decisions
|
|
41
|
+
|
|
42
|
+
A change that leaves real decisions open — data kept or deleted, a column's
|
|
43
|
+
type, who may call a function, a security or audit trade-off — becomes
|
|
44
|
+
questions with options and a recommendation, asked **before** any code is
|
|
45
|
+
written. A new decision found mid-build stops the build.
|
|
46
|
+
|
|
47
|
+
## Red flags
|
|
48
|
+
|
|
49
|
+
| Thought | Reality |
|
|
50
|
+
|---------|---------|
|
|
51
|
+
| "This task is simple" | Simple tasks hide decisions; ask the open ones first. |
|
|
52
|
+
| "I'll just run it in psql" | psql has no snapshot, no rehearsal, no journal. The guard denies it. |
|
|
53
|
+
| "I remember the schema" | The live catalog is the truth; a dump drifts. `pythia-pg ls`. |
|
|
54
|
+
| "I remember what that skill says" | Open the current page. |
|
|
55
|
+
|
|
56
|
+
## Session state
|
|
57
|
+
|
|
58
|
+
The session-start brief ends with `STATE` lines: the connections (the
|
|
59
|
+
default is starred) and the write policy per group. Read them before
|
|
60
|
+
deciding whether a write is even allowed — `data_dml`, `structural` and
|
|
61
|
+
`grants` are `deny` until the developer says otherwise.
|
|
62
|
+
|
|
63
|
+
No STATE line this session means the hooks are not running and the guard
|
|
64
|
+
is off: say so to the developer before any write and ask them to run
|
|
65
|
+
`pythia-pg doctor`. Every rule still binds.
|