pythia-mssql 0.1.0__tar.gz
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_mssql-0.1.0/LICENSE +21 -0
- pythia_mssql-0.1.0/PKG-INFO +373 -0
- pythia_mssql-0.1.0/README.md +345 -0
- pythia_mssql-0.1.0/pyproject.toml +44 -0
- pythia_mssql-0.1.0/setup.cfg +4 -0
- pythia_mssql-0.1.0/src/pythia_mssql/__init__.py +5 -0
- pythia_mssql-0.1.0/src/pythia_mssql/__main__.py +5 -0
- pythia_mssql-0.1.0/src/pythia_mssql/catalog.py +78 -0
- pythia_mssql-0.1.0/src/pythia_mssql/cli.py +8 -0
- pythia_mssql-0.1.0/src/pythia_mssql/engine.py +218 -0
- pythia_mssql-0.1.0/src/pythia_mssql/guard.py +55 -0
- pythia_mssql-0.1.0/src/pythia_mssql/queries/columns.sql +31 -0
- pythia_mssql-0.1.0/src/pythia_mssql/queries/comment-current.sql +15 -0
- pythia_mssql-0.1.0/src/pythia_mssql/queries/current-schema.sql +4 -0
- pythia_mssql-0.1.0/src/pythia_mssql/queries/dependencies-catalog.sql +37 -0
- pythia_mssql-0.1.0/src/pythia_mssql/queries/dependents-catalog.sql +45 -0
- pythia_mssql-0.1.0/src/pythia_mssql/queries/dependents-text.sql +22 -0
- pythia_mssql-0.1.0/src/pythia_mssql/queries/grep.sql +21 -0
- pythia_mssql-0.1.0/src/pythia_mssql/queries/module-definition.sql +7 -0
- pythia_mssql-0.1.0/src/pythia_mssql/queries/modules.sql +25 -0
- pythia_mssql-0.1.0/src/pythia_mssql/queries/object-names.sql +23 -0
- pythia_mssql-0.1.0/src/pythia_mssql/queries/referenced-check.sql +7 -0
- pythia_mssql-0.1.0/src/pythia_mssql/queries/rehearsal-begin.sql +5 -0
- pythia_mssql-0.1.0/src/pythia_mssql/queries/rehearsal-level-up.sql +6 -0
- pythia_mssql-0.1.0/src/pythia_mssql/queries/rehearsal-settings.sql +7 -0
- pythia_mssql-0.1.0/src/pythia_mssql/queries/resolve.sql +22 -0
- pythia_mssql-0.1.0/src/pythia_mssql/queries/row-set-count.sql +9 -0
- pythia_mssql-0.1.0/src/pythia_mssql/queries/row-set-sample.sql +5 -0
- pythia_mssql-0.1.0/src/pythia_mssql/queries/sequence-ddl.sql +16 -0
- pythia_mssql-0.1.0/src/pythia_mssql/queries/server-version.sql +6 -0
- pythia_mssql-0.1.0/src/pythia_mssql/queries/session-power.sql +6 -0
- pythia_mssql-0.1.0/src/pythia_mssql/queries/source.sql +11 -0
- pythia_mssql-0.1.0/src/pythia_mssql/queries/table-checks.sql +8 -0
- pythia_mssql-0.1.0/src/pythia_mssql/queries/table-columns.sql +37 -0
- pythia_mssql-0.1.0/src/pythia_mssql/queries/table-comments.sql +16 -0
- pythia_mssql-0.1.0/src/pythia_mssql/queries/table-foreign-keys.sql +18 -0
- pythia_mssql-0.1.0/src/pythia_mssql/queries/table-head.sql +7 -0
- pythia_mssql-0.1.0/src/pythia_mssql/queries/table-indexes.sql +20 -0
- pythia_mssql-0.1.0/src/pythia_mssql/queries/table-keys.sql +16 -0
- pythia_mssql-0.1.0/src/pythia_mssql/queries/table-triggers.sql +8 -0
- pythia_mssql-0.1.0/src/pythia_mssql/queries/transaction-state.sql +7 -0
- pythia_mssql-0.1.0/src/pythia_mssql/queries/triggers-enabled.sql +12 -0
- pythia_mssql-0.1.0/src/pythia_mssql/queries/unresolved.sql +12 -0
- pythia_mssql-0.1.0/src/pythia_mssql/read.py +298 -0
- pythia_mssql-0.1.0/src/pythia_mssql/rules.py +156 -0
- pythia_mssql-0.1.0/src/pythia_mssql/skills/pythia-mssql-apply/SKILL.md +104 -0
- pythia_mssql-0.1.0/src/pythia_mssql/skills/pythia-mssql-apply/reference/refusals.md +29 -0
- pythia_mssql-0.1.0/src/pythia_mssql/skills/pythia-mssql-autopilot/SKILL.md +64 -0
- pythia_mssql-0.1.0/src/pythia_mssql/skills/pythia-mssql-deploy/SKILL.md +84 -0
- pythia_mssql-0.1.0/src/pythia_mssql/skills/pythia-mssql-explore/SKILL.md +68 -0
- pythia_mssql-0.1.0/src/pythia_mssql/skills/pythia-mssql-impact/SKILL.md +60 -0
- pythia_mssql-0.1.0/src/pythia_mssql/skills/pythia-mssql-verify/SKILL.md +59 -0
- pythia_mssql-0.1.0/src/pythia_mssql/skills/using-pythia-mssql/SKILL.md +65 -0
- pythia_mssql-0.1.0/src/pythia_mssql/write.py +719 -0
- pythia_mssql-0.1.0/src/pythia_mssql.egg-info/PKG-INFO +373 -0
- pythia_mssql-0.1.0/src/pythia_mssql.egg-info/SOURCES.txt +59 -0
- pythia_mssql-0.1.0/src/pythia_mssql.egg-info/dependency_links.txt +1 -0
- pythia_mssql-0.1.0/src/pythia_mssql.egg-info/entry_points.txt +2 -0
- pythia_mssql-0.1.0/src/pythia_mssql.egg-info/requires.txt +2 -0
- pythia_mssql-0.1.0/src/pythia_mssql.egg-info/top_level.txt +1 -0
- pythia_mssql-0.1.0/tests/test_mssql.py +1247 -0
|
@@ -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,373 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: pythia-mssql
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: pythia for SQL Server: T-SQL development for AI agents over pythia-core - catalog queries, impact analysis and a rehearsed, approved write path.
|
|
5
|
+
License-Expression: MIT
|
|
6
|
+
Project-URL: Repository, https://github.com/thaildhe172591/pythia-mssql
|
|
7
|
+
Project-URL: Changelog, https://github.com/thaildhe172591/pythia-mssql/blob/main/CHANGELOG.md
|
|
8
|
+
Project-URL: Issues, https://github.com/thaildhe172591/pythia-mssql/issues
|
|
9
|
+
Keywords: sql-server,mssql,t-sql,database,agent-skills
|
|
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: pymssql>=2.3
|
|
27
|
+
Dynamic: license-file
|
|
28
|
+
|
|
29
|
+
# pythia-mssql
|
|
30
|
+
|
|
31
|
+
**English** · [Tiếng Việt](https://github.com/thaildhe172591/pythia-mssql/blob/main/README.vi.md)
|
|
32
|
+
|
|
33
|
+
pythia for SQL Server: T-SQL 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 and
|
|
36
|
+
[pythia-postgres](https://github.com/thaildhe172591/pythia-postgres) does on
|
|
37
|
+
PostgreSQL: the agent learns from the live catalog, asks a person before
|
|
38
|
+
any write, and writes only through a gate that snapshots, verifies and
|
|
39
|
+
reports honestly.
|
|
40
|
+
|
|
41
|
+
**Alpha (0.1.0).** It reads everything pythia's Learn movement needs, writes through one gate, and wires a project for an agent: the
|
|
42
|
+
guard, the session brief, the approval doors and the `pythia-mssql-*`
|
|
43
|
+
skills.
|
|
44
|
+
|
|
45
|
+
| Works today |
|
|
46
|
+
|---|
|
|
47
|
+
| reads: `check`, `ls`, `src`, `cols`, `ddl`, `grep`, `deps`, `impact`, `errors`, `connections` |
|
|
48
|
+
| writes: `apply`, `deploy`, `approve`, `journal`, `history`, `policy`, `autopilot`, `lint` |
|
|
49
|
+
| the harness: `install`, `doctor`, `hook`, `mcp`, `skill lint` |
|
|
50
|
+
|
|
51
|
+
## Install
|
|
52
|
+
|
|
53
|
+
```bash
|
|
54
|
+
pip install pythia-mssql
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
That brings pythia-core, and [pymssql](https://pypi.org/project/pymssql/),
|
|
58
|
+
whose wheels carry FreeTDS: no ODBC driver needs installing, on Windows,
|
|
59
|
+
macOS or Linux. The development version is the main branch of both:
|
|
60
|
+
|
|
61
|
+
```bash
|
|
62
|
+
pip install "git+https://github.com/thaildhe172591/pythia-core" \
|
|
63
|
+
"git+https://github.com/thaildhe172591/pythia-mssql"
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
## Set up a project for an agent
|
|
67
|
+
|
|
68
|
+
```bash
|
|
69
|
+
cd your-project
|
|
70
|
+
pythia-mssql install # once; a second run changes nothing
|
|
71
|
+
pythia-mssql doctor # is everything wired? exit 1 names what is not
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
`install` writes, and leaves everything else as it was:
|
|
75
|
+
|
|
76
|
+
- `.pythia/mssql/connections.json`, mode 600, created once from the
|
|
77
|
+
template — the developer fills in the passwords; an agent never opens it;
|
|
78
|
+
- `.gitignore` lines for that file, this machine's approvals
|
|
79
|
+
(`.pythia/mssql/grants/`) and `.claude/settings.local.json`;
|
|
80
|
+
- `.claude/settings.local.json`: the hooks (the guard, the session brief,
|
|
81
|
+
the AskUserQuestion answer), spelled with this interpreter's absolute path
|
|
82
|
+
so they start whether or not a virtual environment is active, and deny
|
|
83
|
+
rules for the engine's files;
|
|
84
|
+
- the MCP approver `pythia_mssql` (tool `pythia_mssql_approve`), in this
|
|
85
|
+
machine's files only: Claude Code's local scope for the project
|
|
86
|
+
(`~/.claude.json`) and Codex's `~/.codex/config.toml`, each when that
|
|
87
|
+
client has run here;
|
|
88
|
+
- a `pythia-mssql` block in CLAUDE.md and AGENTS.md (other tools' blocks,
|
|
89
|
+
and the developer's own text, are left alone);
|
|
90
|
+
- the skills, in `.claude/skills` and `.agents/skills`:
|
|
91
|
+
`using-pythia-mssql` (the router), `pythia-mssql-explore`,
|
|
92
|
+
`pythia-mssql-impact`, `pythia-mssql-apply`, `pythia-mssql-deploy`,
|
|
93
|
+
`pythia-mssql-autopilot`, `pythia-mssql-verify`.
|
|
94
|
+
|
|
95
|
+
Restart the agent's session after installing, so the hooks load. Each
|
|
96
|
+
session then starts with the rules and a `STATE` line (connections, the
|
|
97
|
+
write policy). Before every tool call the guard decides:
|
|
98
|
+
|
|
99
|
+
| The agent runs | The guard |
|
|
100
|
+
|---|---|
|
|
101
|
+
| `sqlcmd`, `osql` or `Invoke-Sqlcmd` given write SQL (DDL, DML, `EXEC`, any `sp_` or `xp_` procedure, `SELECT ... INTO`, `NEXT VALUE FOR`, `:r`), a script (`-i`, `-InputFile`, `< file`, a pipe); `bcp ... in`; `sqlpackage` Publish or Import — also behind `docker exec`, `ssh`, `sudo` or `bash -c` | denies: writes go through `pythia-mssql apply` |
|
|
102
|
+
| an MCP SQL tool given write SQL | denies |
|
|
103
|
+
| reading `connections.json`; editing `policy.json`, the journal or a grant; setting `PYTHIA_MSSQL_CI`; calling `grants.mint` from a one-liner | denies |
|
|
104
|
+
| a `pythia-mssql` read, a preview, `approve --card` | allows without a prompt |
|
|
105
|
+
| `apply --confirm <token>` the developer already approved on this connection | allows; apply re-verifies it |
|
|
106
|
+
| `sqlcmd` reads, `bcp ... queryout`, `approve`, `--yes`, `policy set`, anything compound | stays out: the client asks |
|
|
107
|
+
|
|
108
|
+
The guard reads a command for what it names, not what it means: it can
|
|
109
|
+
deny a harmless command (a `SELECT` that mentions `sp_help`) and miss a
|
|
110
|
+
write spelled in a way it does not list. It sees the commands Claude Code
|
|
111
|
+
hands its Bash tool, not SSMS or Azure Data Studio. The gate behind it
|
|
112
|
+
holds either way (pythia-core's SECURITY.md).
|
|
113
|
+
|
|
114
|
+
pythia-postgres and pythia-mongo install beside it in one project, and
|
|
115
|
+
pythia for Oracle too: each keeps the others' hooks and instruction blocks,
|
|
116
|
+
and each guard and approval hook acts for its own tool only (pythia-core's
|
|
117
|
+
CI checks it with all four installed).
|
|
118
|
+
|
|
119
|
+
## Connections
|
|
120
|
+
|
|
121
|
+
`.pythia/mssql/connections.json` in the project root, owner-readable only
|
|
122
|
+
(`chmod 600`), and out of git. Template:
|
|
123
|
+
[examples/connections.example.json](https://github.com/thaildhe172591/pythia-mssql/blob/main/examples/connections.example.json).
|
|
124
|
+
|
|
125
|
+
```json
|
|
126
|
+
{
|
|
127
|
+
"default": "dev",
|
|
128
|
+
"dev": { "host": "db-dev", "port": 1433, "database": "app",
|
|
129
|
+
"user": "app_agent", "password": "...", "schema": "app_owner",
|
|
130
|
+
"encrypt": "request" }
|
|
131
|
+
}
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
- `host`, `database`, `user` are required; SQL Server authentication only
|
|
135
|
+
(a login and password).
|
|
136
|
+
- `encrypt` is `off`, `request` (the default: TLS when the server offers
|
|
137
|
+
it) or `require`. FreeTDS does not validate the server's certificate.
|
|
138
|
+
- `schema` is spelled as the catalog spells it (default `dbo`). `check`
|
|
139
|
+
warns when the login's default schema — where an unqualified name lands —
|
|
140
|
+
is another one; set it with `ALTER USER app_agent WITH DEFAULT_SCHEMA =
|
|
141
|
+
app_owner`.
|
|
142
|
+
- The entry is picked by `--conn NAME`, then `PYTHIA_MSSQL_CONNECTION`, then
|
|
143
|
+
the only entry, the folder directly under the project root, then
|
|
144
|
+
`"default"`. `PYTHIA_MSSQL_CONFIG` points at another file.
|
|
145
|
+
|
|
146
|
+
pythia for Oracle keeps `.pythia/connections.json`, pythia-postgres
|
|
147
|
+
`.pythia/postgres/` and pythia-mongo `.pythia/mongo/`; pythia-mssql reads
|
|
148
|
+
none of them.
|
|
149
|
+
|
|
150
|
+
## Commands
|
|
151
|
+
|
|
152
|
+
```bash
|
|
153
|
+
pythia-mssql check # server, database, default schema drift
|
|
154
|
+
pythia-mssql ls "t_order%" # objects by LIKE pattern
|
|
155
|
+
pythia-mssql src f_order_total # a module's definition, SQL Server's line numbers
|
|
156
|
+
pythia-mssql cols t_order # columns, types, defaults, identity, comments
|
|
157
|
+
pythia-mssql ddl t_order # CREATE TABLE and its batches, rebuilt from the catalog
|
|
158
|
+
pythia-mssql grep "t_order where" # every module line holding a text
|
|
159
|
+
pythia-mssql deps f_order_total # what it references
|
|
160
|
+
pythia-mssql impact t_order # what references it: run before changing it
|
|
161
|
+
pythia-mssql errors [NAME] # every module bound now, as SQL Server would run it
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
`--json` gives one envelope per command; `--limit`, `--max-lines` and
|
|
165
|
+
`--offset` window long output, and any truncation says how to fetch the
|
|
166
|
+
rest.
|
|
167
|
+
|
|
168
|
+
### What the catalog cannot see, and how pythia-mssql says so
|
|
169
|
+
|
|
170
|
+
- **`impact` asks twice.** `sys.sql_expression_dependencies` records what
|
|
171
|
+
every view, procedure, function and trigger references — by name too,
|
|
172
|
+
when the object did not exist yet. Triggers on a table and foreign keys
|
|
173
|
+
that point at it are added, and a check or default constraint counts as
|
|
174
|
+
its table. Dynamic SQL (`EXEC` of a string, `sp_executesql`) is not in the
|
|
175
|
+
catalog: module text is searched for the name as a whole word and those
|
|
176
|
+
rows are marked `[text]`. Other databases and linked servers are never
|
|
177
|
+
seen. Each blind spot is printed every time.
|
|
178
|
+
- **`errors` binds every module now**, with
|
|
179
|
+
`sys.dm_sql_referenced_entities`: a missing column or object raises the
|
|
180
|
+
error SQL Server would raise when the module runs. A module holding
|
|
181
|
+
dynamic SQL is named as not checked inside its strings.
|
|
182
|
+
- **`ddl` of a table is rebuilt, not dumped**, and ends each batch with
|
|
183
|
+
`GO`; the live check drops the table, runs the DDL, rebuilds it and
|
|
184
|
+
compares. SSMS's Generate Scripts stays the reference for anything it
|
|
185
|
+
does not cover yet.
|
|
186
|
+
- **Reads never commit.** SQL Server has no read-only transaction, so every
|
|
187
|
+
read command runs in a transaction that closing the connection rolls
|
|
188
|
+
back. A read-only login (`db_datareader` plus `VIEW DEFINITION`) is the
|
|
189
|
+
first line.
|
|
190
|
+
|
|
191
|
+
### Writing: the gate
|
|
192
|
+
|
|
193
|
+
```bash
|
|
194
|
+
pythia-mssql apply change.sql # preview: rehearsed, rolled back, nothing written
|
|
195
|
+
pythia-mssql approve 3fa9c1 # the developer approves (or from chat, below)
|
|
196
|
+
pythia-mssql apply change.sql --confirm 3fa9c1 # writes it — only as approved
|
|
197
|
+
pythia-mssql journal restore <entry> # undo is a write too: preview, approve, confirm
|
|
198
|
+
pythia-mssql policy # which groups may be written, and is rollback real?
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
The developer approves through the first door that works:
|
|
202
|
+
|
|
203
|
+
1. **In Claude Code's chat.** The agent asks with AskUserQuestion, its text
|
|
204
|
+
exactly the output of `pythia-mssql approve --card <token>`, options
|
|
205
|
+
Approve / Reject. The answer hook records the approval only when the
|
|
206
|
+
question carried that card and the developer chose Approve themselves —
|
|
207
|
+
not an answer the agent wrote, not a dialog that closed unattended.
|
|
208
|
+
2. **Through the MCP approver** (`pythia_mssql_approve`; Codex starts
|
|
209
|
+
here): the client shows the card, and only an explicit Approve counts.
|
|
210
|
+
3. **At the developer's console:** `pythia-mssql approve <token>`.
|
|
211
|
+
|
|
212
|
+
`pythia-mssql doctor` warns about settings that would answer the chat door
|
|
213
|
+
for the developer (`dontAsk` mode, an allow rule for AskUserQuestion, a hook
|
|
214
|
+
matching every tool).
|
|
215
|
+
|
|
216
|
+
### A release: `deploy`, one transaction
|
|
217
|
+
|
|
218
|
+
```bash
|
|
219
|
+
pythia-mssql deploy 01_schema.sql 02_data.sql # preview the release as one transaction
|
|
220
|
+
pythia-mssql deploy 01_schema.sql 02_data.sql --confirm 9c41e0b2aa # all of it, as approved, or none
|
|
221
|
+
```
|
|
222
|
+
|
|
223
|
+
A release — one or more scripts, cut where SQL Server's tools cut them: at
|
|
224
|
+
`GO` lines, then at each `;`, with a module's body running to the end of
|
|
225
|
+
its batch — is rehearsed in one transaction: each statement in order,
|
|
226
|
+
snapshotted before it runs, its rows and verification taken at its own
|
|
227
|
+
point (an UPDATE of a column the release adds is rehearsed against that
|
|
228
|
+
column), then everything rolls back. One card shows every statement, and
|
|
229
|
+
one approval (a 10-digit token, through the same doors) covers the release;
|
|
230
|
+
a single statement's token cannot be approved alone. The confirm checks
|
|
231
|
+
that every statement is byte-identical to what was approved before anything
|
|
232
|
+
runs, re-runs the release in one transaction, and commits only if every
|
|
233
|
+
statement still matches. A statement that fails, or rows, objects or
|
|
234
|
+
verifications that moved, roll back the whole release. A trigger that
|
|
235
|
+
commits past the rehearsal at statement *n* is reported `WRITTEN?` for
|
|
236
|
+
statements 1 to *n*, with every restore. Refused before anything runs:
|
|
237
|
+
`GO n`, `SET` inside a release, and anything `apply` refuses.
|
|
238
|
+
|
|
239
|
+
### Lint: before any apply
|
|
240
|
+
|
|
241
|
+
```bash
|
|
242
|
+
pythia-mssql lint 01_schema.sql 02_code.sql # a change's files in one run
|
|
243
|
+
pythia-mssql lint --rules # every rule and its effective severity
|
|
244
|
+
```
|
|
245
|
+
|
|
246
|
+
The rules read what SQL Server accepts and a reviewer would not:
|
|
247
|
+
|
|
248
|
+
| Rule | Default | Why |
|
|
249
|
+
|---|---|---|
|
|
250
|
+
| `mssql-trigger-commit` | error | a trigger's `COMMIT` ends its caller's transaction (error 3609), and commits work the caller may still roll back |
|
|
251
|
+
| `mssql-unsized-string` | error | `varchar`, `nvarchar`, `char`, `binary` without a length hold 1 character in a declaration and 30 in `CAST`, and SQL Server cuts the rest silently |
|
|
252
|
+
| `mssql-nolock` | warn | `NOLOCK` / `READ UNCOMMITTED` can skip a row, read one twice, or read what is rolled back |
|
|
253
|
+
| `mssql-select-star` | warn | `SELECT *` in a view keeps the old column list until `sp_refreshview`; `EXISTS (SELECT * ...)` is fine |
|
|
254
|
+
| `mssql-not-in-subquery` | warn | `NOT IN (SELECT ...)` is never true when the subquery returns a NULL |
|
|
255
|
+
| `mssql-deprecated-type` | warn | `text`, `ntext`, `image` — `varchar(max)`, `nvarchar(max)`, `varbinary(max)` |
|
|
256
|
+
| `mssql-datetime` | info | `datetime` rounds to 1/300 s and `smalldatetime` to the minute — `datetime2` |
|
|
257
|
+
| `mssql-sp-prefix` | warn | a procedure named `sp_` is looked for in master first |
|
|
258
|
+
| `mssql-nocount` | info | a procedure without `SET NOCOUNT ON` sends a row count per statement |
|
|
259
|
+
| `mssql-table-comment` | warn | a new table gets its `MS_Description` in the same change |
|
|
260
|
+
|
|
261
|
+
What SQL Server rejects itself is left to the rehearsal. The team sets
|
|
262
|
+
severities and naming patterns in `.pythia/mssql/lint.json`:
|
|
263
|
+
|
|
264
|
+
```json
|
|
265
|
+
{"severity": {"mssql-select-star": "error", "naming": "error"},
|
|
266
|
+
"naming": {"PROCEDURE": "^p_[a-z0-9_]+$", "VIEW": "^v_[a-z0-9_]+$"}}
|
|
267
|
+
```
|
|
268
|
+
|
|
269
|
+
Every preview shows the findings, a statement's errors are on its card, and
|
|
270
|
+
autopilot never lands a lint error.
|
|
271
|
+
|
|
272
|
+
### Autopilot: one lease instead of every approval
|
|
273
|
+
|
|
274
|
+
```bash
|
|
275
|
+
pythia-mssql autopilot request --groups source,comments --hours 2 --max-writes 30
|
|
276
|
+
pythia-mssql autopilot report # every write under the lease, with its restore
|
|
277
|
+
pythia-mssql autopilot off # anyone may revoke
|
|
278
|
+
```
|
|
279
|
+
|
|
280
|
+
When the developer wants the agent to work without approving each write,
|
|
281
|
+
the agent requests a lease. Its card names the connection, the groups, the
|
|
282
|
+
time, the number of writes and whether destructive statements are included,
|
|
283
|
+
and nothing is delegated until the developer approves it through a door (or
|
|
284
|
+
runs `pythia-mssql autopilot on` at a console). While it lasts, `apply` and
|
|
285
|
+
`deploy` land what it covers without `--confirm` — still snapshotted,
|
|
286
|
+
rehearsed, verified and journaled. It never covers another connection, one
|
|
287
|
+
marked `"protected": true` (or an `"env"` of prod / production / prd /
|
|
288
|
+
live), a group or destructive statement the card did not name, or more than
|
|
289
|
+
1000 rows; a write that breaks something, a write that escaped its
|
|
290
|
+
transaction, or five failures in a row pause it.
|
|
291
|
+
|
|
292
|
+
### One statement: `apply`
|
|
293
|
+
|
|
294
|
+
One statement per file. A module (`CREATE [OR ALTER] PROCEDURE`, `FUNCTION`,
|
|
295
|
+
`VIEW`, `TRIGGER`) takes its whole batch, as SQL Server does; anything else
|
|
296
|
+
ends at its `;`. `apply` sorts the statement into a group, and the policy
|
|
297
|
+
decides: `source` and `comments` (an `MS_Description` through
|
|
298
|
+
`sp_addextendedproperty`, `sp_updateextendedproperty` or
|
|
299
|
+
`sp_dropextendedproperty`, its arguments named) need the developer's
|
|
300
|
+
approval; `data_dml`, `structural` (DDL, `sp_rename`) and `grants` (`GRANT`,
|
|
301
|
+
`REVOKE`, `DENY`, users, roles) are denied until the developer runs
|
|
302
|
+
`pythia-mssql policy set <group> confirm` at a console. `session` (`SET`) is
|
|
303
|
+
allowed.
|
|
304
|
+
|
|
305
|
+
What the preview shows, and why it can be trusted:
|
|
306
|
+
|
|
307
|
+
- **A rehearsal, not a prediction.** The statement ran inside a transaction
|
|
308
|
+
and was rolled back. Every module that depends on what changed — and the
|
|
309
|
+
module itself — is bound with `sys.dm_sql_referenced_entities` before and
|
|
310
|
+
after, and what fails only after is what the statement broke: an `ALTER
|
|
311
|
+
TABLE ... DROP COLUMN` names the view it would break. The diff compares
|
|
312
|
+
the definition before and after, both as SQL Server keeps them (it keeps
|
|
313
|
+
`CREATE OR ALTER` as `create`, padded with spaces).
|
|
314
|
+
- **Rows are shown and revalidated.** An UPDATE or DELETE is approved on its
|
|
315
|
+
rows: the count, a sample, and `CHECKSUM_AGG(BINARY_CHECKSUM(*))` over
|
|
316
|
+
them. If any row joins, leaves or changes before the confirm, the confirm
|
|
317
|
+
is refused.
|
|
318
|
+
- **The confirm is the same rehearsal, committed only if it still
|
|
319
|
+
matches.** Same token (the file, the object as it stood, the rows), same
|
|
320
|
+
row count, and nothing broken that the approved preview did not show.
|
|
321
|
+
- **A trigger cannot commit the rehearsal.** In SQL Server a trigger's
|
|
322
|
+
`COMMIT` or `ROLLBACK` ends the caller's transaction. The statement runs
|
|
323
|
+
one transaction level down, so a trigger's single `COMMIT` ends only that
|
|
324
|
+
level and commits nothing (the preview says so: run any other way, the
|
|
325
|
+
statement ends with error 3609). If the transaction ends anyway — a
|
|
326
|
+
trigger that commits twice, or loops — pythia-mssql reads every trigger
|
|
327
|
+
in the database: when one could have committed (it says `COMMIT`, runs
|
|
328
|
+
other code, or cannot be read), or nothing visible explains it, the write
|
|
329
|
+
is reported **`WRITTEN?`** with exit 3 and the restore, never "nothing was
|
|
330
|
+
written". Only a trigger that rolls back, with none able to commit, is a
|
|
331
|
+
plain refusal.
|
|
332
|
+
- **Refused, with the reason:** `EXEC` of a procedure, control flow (`IF`,
|
|
333
|
+
`WHILE`, `DECLARE`, `BEGIN ... END`), `SELECT`, `WITH`, `MERGE`, `USE`,
|
|
334
|
+
transaction control, temporary objects, logins and other server-level
|
|
335
|
+
objects, statements that cannot run in a transaction (`ALTER DATABASE`,
|
|
336
|
+
`BACKUP`, full-text), extended properties other than `MS_Description`,
|
|
337
|
+
`GO`, several statements in one file (a second statement on its own line
|
|
338
|
+
counts, `;` or not), an UPDATE or DELETE whose rows cannot be read
|
|
339
|
+
beforehand (`TOP`, `OUTPUT`, `FROM` or a join, a subquery, `WHERE CURRENT
|
|
340
|
+
OF`, parentheses that do not balance), and a module `WITH ENCRYPTION`
|
|
341
|
+
(nothing to snapshot).
|
|
342
|
+
|
|
343
|
+
A rehearsal waits at most 5 s for a lock (`SET LOCK_TIMEOUT`); SQL Server
|
|
344
|
+
has no session statement timeout. It still fires triggers and draws
|
|
345
|
+
`IDENTITY` values and sequence numbers, and the preview of a DML statement
|
|
346
|
+
says so. Exit codes follow pythia: `0` clean, `1` refused, `3` written but
|
|
347
|
+
broken (with the restore command).
|
|
348
|
+
|
|
349
|
+
### Least privilege
|
|
350
|
+
|
|
351
|
+
The read commands need `db_datareader` on the database and `VIEW
|
|
352
|
+
DEFINITION` (without it, module definitions read as empty, and every
|
|
353
|
+
trigger's text is unknown — so a rehearsal that ends its transaction is
|
|
354
|
+
always reported `WRITTEN?`). Writing needs what the statement needs:
|
|
355
|
+
`db_datawriter` for rows, `db_ddladmin` or `ALTER` on the schema for code
|
|
356
|
+
and DDL. The preview warns when the login is `sysadmin` or `db_owner`. The
|
|
357
|
+
live check runs every read command as a read-only login, and every write
|
|
358
|
+
as one with `db_datawriter` and `db_ddladmin`.
|
|
359
|
+
|
|
360
|
+
## Tests
|
|
361
|
+
|
|
362
|
+
```bash
|
|
363
|
+
python tests/test_mssql.py # no database, no pymssql — mutation-checked
|
|
364
|
+
python tests/live_check.py # a disposable server only (see its docstring)
|
|
365
|
+
```
|
|
366
|
+
|
|
367
|
+
CI runs the suite on Ubuntu, Windows and macOS, Python 3.9 and 3.13, and
|
|
368
|
+
the live check against SQL Server 2022 in a service container. It tests
|
|
369
|
+
against the pythia-core branch of the same name when one exists.
|
|
370
|
+
|
|
371
|
+
## License
|
|
372
|
+
|
|
373
|
+
MIT
|