sprocguard 0.1.1__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.
@@ -0,0 +1,12 @@
1
+ __pycache__/
2
+ *.pyc
3
+ .venv/
4
+ venv/
5
+ dist/
6
+ build/
7
+ *.egg-info/
8
+ .pytest_cache/
9
+ # Your local config usually holds a connection string; share a sample instead.
10
+ sprocguard.toml
11
+ # Baselines and tSQLt tests generated while trying the demo locally.
12
+ .sprocguard/
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Jheyson Huarauya Riquelme
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,230 @@
1
+ Metadata-Version: 2.5
2
+ Name: sprocguard
3
+ Version: 0.1.1
4
+ Summary: A safety net for SQL Server stored procedures: capture what a procedure does today, then verify an AI agent (or a human) didn't change it by accident.
5
+ Project-URL: Homepage, https://github.com/JheysonJhordyHuarauyaRiquelme/sprocguard
6
+ Project-URL: Repository, https://github.com/JheysonJhordyHuarauyaRiquelme/sprocguard
7
+ Project-URL: Issues, https://github.com/JheysonJhordyHuarauyaRiquelme/sprocguard/issues
8
+ Project-URL: Changelog, https://github.com/JheysonJhordyHuarauyaRiquelme/sprocguard/releases
9
+ Author: Jheyson Huarauya Riquelme
10
+ License-Expression: MIT
11
+ License-File: LICENSE
12
+ Keywords: ai-agents,characterization-tests,golden-master,mcp,sql-server,stored-procedures,t-sql,tsqlt
13
+ Classifier: Development Status :: 3 - Alpha
14
+ Classifier: Environment :: Console
15
+ Classifier: Intended Audience :: Developers
16
+ Classifier: Operating System :: OS Independent
17
+ Classifier: Programming Language :: Python :: 3
18
+ Classifier: Programming Language :: Python :: 3.11
19
+ Classifier: Programming Language :: Python :: 3.12
20
+ Classifier: Programming Language :: Python :: 3.13
21
+ Classifier: Programming Language :: SQL
22
+ Classifier: Topic :: Database
23
+ Classifier: Topic :: Software Development :: Testing
24
+ Requires-Python: >=3.11
25
+ Requires-Dist: mcp>=2.0
26
+ Requires-Dist: pyodbc>=5.0
27
+ Provides-Extra: dev
28
+ Requires-Dist: pytest>=8; extra == 'dev'
29
+ Requires-Dist: sqlglot>=25; extra == 'dev'
30
+ Provides-Extra: pymssql
31
+ Requires-Dist: pymssql>=2.3; extra == 'pymssql'
32
+ Description-Content-Type: text/markdown
33
+
34
+ # sprocguard
35
+
36
+ <!-- mcp-name: io.github.JheysonJhordyHuarauyaRiquelme/sprocguard -->
37
+
38
+ [![PyPI](https://img.shields.io/pypi/v/sprocguard.svg)](https://pypi.org/project/sprocguard/)
39
+ [![CI](https://github.com/JheysonJhordyHuarauyaRiquelme/sprocguard/actions/workflows/ci.yml/badge.svg)](https://github.com/JheysonJhordyHuarauyaRiquelme/sprocguard/actions/workflows/ci.yml)
40
+ [![License: MIT](https://img.shields.io/badge/license-MIT-blue.svg)](https://github.com/JheysonJhordyHuarauyaRiquelme/sprocguard/blob/main/LICENSE)
41
+ [![Python 3.11+](https://img.shields.io/pypi/pyversions/sprocguard.svg)](https://pypi.org/project/sprocguard/)
42
+
43
+ **A safety net for SQL Server stored procedures.**
44
+ Capture what a procedure does *today*, let a human or an AI agent change it, and get a clear
45
+ report of every behavior that changed — before it reaches production.
46
+
47
+ [Leer en español](https://github.com/JheysonJhordyHuarauyaRiquelme/sprocguard/blob/main/README.es.md)
48
+
49
+ ```text
50
+ $ sprocguard verify dbo.usp_PlaceOrder
51
+ FAIL dbo.usp_PlaceOrder: 3/5 case(s) behave exactly as in the baseline.
52
+ (the procedure definition changed since the baseline was captured)
53
+ ok regular customer buys 2 coffees
54
+ XX VIP customer gets 10% off
55
+ - Result set #1, row 1: Total "33.75" -> "33.80"
56
+ - dbo.Orders: no longer inserts {"CustomerId": 1, "Total": "33.75"}
57
+ - dbo.Orders: now inserts {"CustomerId": 1, "Total": "33.80"}
58
+ XX VIP discount with odd cents
59
+ - Result set #1, row 1: Total "78.75" -> "78.80"
60
+ - dbo.Orders: no longer inserts {"CustomerId": 1, "Total": "78.75"}
61
+ - dbo.Orders: now inserts {"CustomerId": 1, "Total": "78.80"}
62
+ ok buys exactly the last units
63
+ ok insufficient stock raises error
64
+ ```
65
+
66
+ ## Why
67
+
68
+ Many companies keep their business rules inside stored procedures, usually without tests.
69
+ AI coding agents are now asked to "clean up" or "optimize" them, and a refactor that looks
70
+ equivalent can quietly change a rounding rule, an error path or a table it updates.
71
+
72
+ sprocguard gives you **characterization tests** (also called *golden master* tests) in minutes:
73
+ it records what the procedure returns and what it changes in the database, then compares any
74
+ new version against that record.
75
+
76
+ ## What it captures
77
+
78
+ For every case (a set of parameter values):
79
+
80
+ - every **result set**, in order when the order is stable;
81
+ - the **return value** and **OUTPUT parameters**;
82
+ - the **error** raised, if any (errors are behavior too);
83
+ - the **rows inserted, updated and deleted** in each table the procedure writes,
84
+ including tables written by procedures it calls and by triggers.
85
+
86
+ Each case runs **twice**. Values that change by themselves (new IDENTITY values, `NEWID()`, and any
87
+ date or time taken while the procedure was running, even at second or day precision) are detected
88
+ automatically and ignored when verifying.
89
+
90
+ ## Safety model
91
+
92
+ sprocguard executes your procedures, so safety comes first:
93
+
94
+ | Protection | How |
95
+ |---|---|
96
+ | Nothing is ever committed | Each execution runs inside a transaction that is always rolled back, with `IMPLICIT_TRANSACTIONS ON` and two nesting levels, so an inner `ROLLBACK` or a stray `COMMIT` in the procedure cannot leak changes. |
97
+ | Only development databases | It refuses to connect to any database not listed in `allowed_databases`. |
98
+ | No irreversible side effects | Procedures that send e-mail, run `xp_cmdshell`, start jobs, use `OPENROWSET`/`OPENQUERY` or linked servers are refused by default: a rollback cannot undo them. |
99
+ | Big tables are not dumped | Tables above `max_rows_per_table` are not snapshotted (reported as skipped). |
100
+ | SQL injection | Parameter values are always sent as query parameters, never concatenated. |
101
+
102
+ Captures hold locks on the tables involved for a moment: use a development database.
103
+
104
+ ## Install
105
+
106
+ Requires Python 3.11+ and the [Microsoft ODBC Driver 17 or 18 for SQL Server](https://learn.microsoft.com/sql/connect/odbc/download-odbc-driver-for-sql-server)
107
+ (usually already installed with SQL Server or SSMS). Check which one you have with
108
+ `Get-OdbcDriver | Where-Object Name -like "*SQL Server*"` in PowerShell.
109
+
110
+ ```bash
111
+ pip install sprocguard
112
+ # SQL authentication without ODBC: pip install "sprocguard[pymssql]"
113
+ ```
114
+
115
+ ## Quick start (5 minutes, with the demo database)
116
+
117
+ The demo scripts live in the [`examples`](https://github.com/JheysonJhordyHuarauyaRiquelme/sprocguard/tree/main/examples) folder of the repository:
118
+
119
+ ```bash
120
+ git clone https://github.com/JheysonJhordyHuarauyaRiquelme/sprocguard.git
121
+ cd sprocguard
122
+ ```
123
+
124
+ ```bash
125
+ sqlcmd -S localhost -E -i examples/demo.sql # creates DemoShop with two procedures
126
+ sprocguard init # writes sprocguard.toml; edit the connection
127
+ sprocguard inspect dbo.usp_PlaceOrder # parameters, tables, risks
128
+ sprocguard capture dbo.usp_PlaceOrder --cases examples/cases/usp_PlaceOrder.json
129
+ sqlcmd -S localhost -E -i examples/regression.sql # a "harmless" refactor
130
+ sprocguard verify dbo.usp_PlaceOrder # catches the rounding change (exit code 2)
131
+ ```
132
+
133
+ `sprocguard.toml` (use `ODBC Driver 17 for SQL Server` if that is the one you have):
134
+
135
+ ```toml
136
+ [connection]
137
+ odbc = "Driver={ODBC Driver 18 for SQL Server};Server=localhost;Database=DemoShop;Trusted_Connection=yes;TrustServerCertificate=yes"
138
+
139
+ [safety]
140
+ allowed_databases = ["DemoShop"]
141
+ max_rows_per_table = 5000
142
+ allow_external_side_effects = false
143
+
144
+ [storage]
145
+ baseline_dir = ".sprocguard/baselines" # commit this folder
146
+ tsqlt_dir = ".sprocguard/tsqlt"
147
+ ```
148
+
149
+ ## Use it with AI agents (MCP)
150
+
151
+ sprocguard ships an MCP server and an [Agent Skill](https://github.com/JheysonJhordyHuarauyaRiquelme/sprocguard/blob/main/skills/sprocguard/SKILL.md) that teaches
152
+ agents the workflow: *inspect → design cases → capture → change → verify*.
153
+
154
+ **Claude Code** (plugin with the MCP server and the skill):
155
+
156
+ ```text
157
+ /plugin marketplace add JheysonJhordyHuarauyaRiquelme/sprocguard
158
+ /plugin install sprocguard@sprocguard
159
+ ```
160
+
161
+ **Any MCP client** (VS Code, Cursor, Claude Desktop...):
162
+
163
+ ```json
164
+ { "mcpServers": { "sprocguard": { "command": "sprocguard", "args": ["serve"] } } }
165
+ ```
166
+
167
+ With [uv](https://docs.astral.sh/uv/) you can skip the install step: use `"command": "uvx", "args": ["sprocguard", "serve"]`.
168
+ sprocguard is also listed in the [MCP Registry](https://registry.modelcontextprotocol.io) as
169
+ `io.github.JheysonJhordyHuarauyaRiquelme/sprocguard`.
170
+
171
+ | Tool | What it does | Read-only |
172
+ |---|---|---|
173
+ | `inspect_procedure` | Parameters, tables read/written, procedures and triggers involved, risks | yes |
174
+ | `suggest_cases` | Real parameter values from related tables plus edge values | yes |
175
+ | `capture_baseline` | Records the current behavior (refuses to overwrite unless asked) | writes a file |
176
+ | `verify_procedure` | Re-runs the cases and lists every difference | yes |
177
+ | `generate_tsqlt_tests` | Turns a baseline into tSQLt tests | writes a file |
178
+ | `list_baselines` | Lists captured baselines | yes |
179
+
180
+ ## CLI
181
+
182
+ ```text
183
+ sprocguard init create sprocguard.toml
184
+ sprocguard inspect <proc> [--definition] describe a procedure and its risks
185
+ sprocguard suggest <proc> suggest parameter values
186
+ sprocguard capture <proc> --cases cases.json capture a baseline [--watch schema.table ...] [--overwrite]
187
+ sprocguard verify [<proc> ...] verify one, several or all baselines (exit 2 if behavior changed)
188
+ sprocguard tsqlt <proc> generate tSQLt tests (read-only procedures)
189
+ sprocguard list list baselines
190
+ sprocguard serve run the MCP server (stdio)
191
+ ```
192
+
193
+ Add `--json` for machine-readable output. In CI, run `sprocguard verify` against a test
194
+ database restored from a known backup: the job fails when any procedure changed behavior.
195
+
196
+ ## tSQLt export
197
+
198
+ For read-only procedures, `sprocguard tsqlt <proc>` writes a `.sql` file with one
199
+ [tSQLt](https://tsqlt.org) test per case, using `tSQLt.AssertEqualsTable` and
200
+ `tSQLt.ExpectException`. Volatile columns are left out of the comparison.
201
+
202
+ ## Limitations (0.1)
203
+
204
+ - Table-valued parameters are not supported yet.
205
+ - Tables changed only through dynamic SQL are not detected automatically; add them with `--watch`.
206
+ - If the procedure fails, result sets returned before the error are not recorded (the error is).
207
+ - tSQLt export covers read-only procedures; procedures that modify data use `verify`.
208
+ - Comparisons ignore the order of rows when the order was not stable between the two capture runs.
209
+
210
+ ## Roadmap
211
+
212
+ - [ ] tSQLt export with `FakeTable` for procedures that modify data
213
+ - [ ] Table-valued parameters
214
+ - [ ] `sprocguard impact <table>`: verify every procedure that uses a table you are changing
215
+ - [ ] GitHub Action
216
+ - [x] Publish to PyPI and the MCP Registry
217
+
218
+ ## Development
219
+
220
+ ```bash
221
+ pip install -e ".[dev]"
222
+ pytest
223
+ ```
224
+
225
+ The test suite uses an in-memory imitation of SQL Server (`tests/fakedb.py`), so it runs
226
+ anywhere; real-database testing uses `examples/demo.sql`.
227
+
228
+ ## License
229
+
230
+ MIT
@@ -0,0 +1,197 @@
1
+ # sprocguard
2
+
3
+ <!-- mcp-name: io.github.JheysonJhordyHuarauyaRiquelme/sprocguard -->
4
+
5
+ [![PyPI](https://img.shields.io/pypi/v/sprocguard.svg)](https://pypi.org/project/sprocguard/)
6
+ [![CI](https://github.com/JheysonJhordyHuarauyaRiquelme/sprocguard/actions/workflows/ci.yml/badge.svg)](https://github.com/JheysonJhordyHuarauyaRiquelme/sprocguard/actions/workflows/ci.yml)
7
+ [![License: MIT](https://img.shields.io/badge/license-MIT-blue.svg)](https://github.com/JheysonJhordyHuarauyaRiquelme/sprocguard/blob/main/LICENSE)
8
+ [![Python 3.11+](https://img.shields.io/pypi/pyversions/sprocguard.svg)](https://pypi.org/project/sprocguard/)
9
+
10
+ **A safety net for SQL Server stored procedures.**
11
+ Capture what a procedure does *today*, let a human or an AI agent change it, and get a clear
12
+ report of every behavior that changed — before it reaches production.
13
+
14
+ [Leer en español](https://github.com/JheysonJhordyHuarauyaRiquelme/sprocguard/blob/main/README.es.md)
15
+
16
+ ```text
17
+ $ sprocguard verify dbo.usp_PlaceOrder
18
+ FAIL dbo.usp_PlaceOrder: 3/5 case(s) behave exactly as in the baseline.
19
+ (the procedure definition changed since the baseline was captured)
20
+ ok regular customer buys 2 coffees
21
+ XX VIP customer gets 10% off
22
+ - Result set #1, row 1: Total "33.75" -> "33.80"
23
+ - dbo.Orders: no longer inserts {"CustomerId": 1, "Total": "33.75"}
24
+ - dbo.Orders: now inserts {"CustomerId": 1, "Total": "33.80"}
25
+ XX VIP discount with odd cents
26
+ - Result set #1, row 1: Total "78.75" -> "78.80"
27
+ - dbo.Orders: no longer inserts {"CustomerId": 1, "Total": "78.75"}
28
+ - dbo.Orders: now inserts {"CustomerId": 1, "Total": "78.80"}
29
+ ok buys exactly the last units
30
+ ok insufficient stock raises error
31
+ ```
32
+
33
+ ## Why
34
+
35
+ Many companies keep their business rules inside stored procedures, usually without tests.
36
+ AI coding agents are now asked to "clean up" or "optimize" them, and a refactor that looks
37
+ equivalent can quietly change a rounding rule, an error path or a table it updates.
38
+
39
+ sprocguard gives you **characterization tests** (also called *golden master* tests) in minutes:
40
+ it records what the procedure returns and what it changes in the database, then compares any
41
+ new version against that record.
42
+
43
+ ## What it captures
44
+
45
+ For every case (a set of parameter values):
46
+
47
+ - every **result set**, in order when the order is stable;
48
+ - the **return value** and **OUTPUT parameters**;
49
+ - the **error** raised, if any (errors are behavior too);
50
+ - the **rows inserted, updated and deleted** in each table the procedure writes,
51
+ including tables written by procedures it calls and by triggers.
52
+
53
+ Each case runs **twice**. Values that change by themselves (new IDENTITY values, `NEWID()`, and any
54
+ date or time taken while the procedure was running, even at second or day precision) are detected
55
+ automatically and ignored when verifying.
56
+
57
+ ## Safety model
58
+
59
+ sprocguard executes your procedures, so safety comes first:
60
+
61
+ | Protection | How |
62
+ |---|---|
63
+ | Nothing is ever committed | Each execution runs inside a transaction that is always rolled back, with `IMPLICIT_TRANSACTIONS ON` and two nesting levels, so an inner `ROLLBACK` or a stray `COMMIT` in the procedure cannot leak changes. |
64
+ | Only development databases | It refuses to connect to any database not listed in `allowed_databases`. |
65
+ | No irreversible side effects | Procedures that send e-mail, run `xp_cmdshell`, start jobs, use `OPENROWSET`/`OPENQUERY` or linked servers are refused by default: a rollback cannot undo them. |
66
+ | Big tables are not dumped | Tables above `max_rows_per_table` are not snapshotted (reported as skipped). |
67
+ | SQL injection | Parameter values are always sent as query parameters, never concatenated. |
68
+
69
+ Captures hold locks on the tables involved for a moment: use a development database.
70
+
71
+ ## Install
72
+
73
+ Requires Python 3.11+ and the [Microsoft ODBC Driver 17 or 18 for SQL Server](https://learn.microsoft.com/sql/connect/odbc/download-odbc-driver-for-sql-server)
74
+ (usually already installed with SQL Server or SSMS). Check which one you have with
75
+ `Get-OdbcDriver | Where-Object Name -like "*SQL Server*"` in PowerShell.
76
+
77
+ ```bash
78
+ pip install sprocguard
79
+ # SQL authentication without ODBC: pip install "sprocguard[pymssql]"
80
+ ```
81
+
82
+ ## Quick start (5 minutes, with the demo database)
83
+
84
+ The demo scripts live in the [`examples`](https://github.com/JheysonJhordyHuarauyaRiquelme/sprocguard/tree/main/examples) folder of the repository:
85
+
86
+ ```bash
87
+ git clone https://github.com/JheysonJhordyHuarauyaRiquelme/sprocguard.git
88
+ cd sprocguard
89
+ ```
90
+
91
+ ```bash
92
+ sqlcmd -S localhost -E -i examples/demo.sql # creates DemoShop with two procedures
93
+ sprocguard init # writes sprocguard.toml; edit the connection
94
+ sprocguard inspect dbo.usp_PlaceOrder # parameters, tables, risks
95
+ sprocguard capture dbo.usp_PlaceOrder --cases examples/cases/usp_PlaceOrder.json
96
+ sqlcmd -S localhost -E -i examples/regression.sql # a "harmless" refactor
97
+ sprocguard verify dbo.usp_PlaceOrder # catches the rounding change (exit code 2)
98
+ ```
99
+
100
+ `sprocguard.toml` (use `ODBC Driver 17 for SQL Server` if that is the one you have):
101
+
102
+ ```toml
103
+ [connection]
104
+ odbc = "Driver={ODBC Driver 18 for SQL Server};Server=localhost;Database=DemoShop;Trusted_Connection=yes;TrustServerCertificate=yes"
105
+
106
+ [safety]
107
+ allowed_databases = ["DemoShop"]
108
+ max_rows_per_table = 5000
109
+ allow_external_side_effects = false
110
+
111
+ [storage]
112
+ baseline_dir = ".sprocguard/baselines" # commit this folder
113
+ tsqlt_dir = ".sprocguard/tsqlt"
114
+ ```
115
+
116
+ ## Use it with AI agents (MCP)
117
+
118
+ sprocguard ships an MCP server and an [Agent Skill](https://github.com/JheysonJhordyHuarauyaRiquelme/sprocguard/blob/main/skills/sprocguard/SKILL.md) that teaches
119
+ agents the workflow: *inspect → design cases → capture → change → verify*.
120
+
121
+ **Claude Code** (plugin with the MCP server and the skill):
122
+
123
+ ```text
124
+ /plugin marketplace add JheysonJhordyHuarauyaRiquelme/sprocguard
125
+ /plugin install sprocguard@sprocguard
126
+ ```
127
+
128
+ **Any MCP client** (VS Code, Cursor, Claude Desktop...):
129
+
130
+ ```json
131
+ { "mcpServers": { "sprocguard": { "command": "sprocguard", "args": ["serve"] } } }
132
+ ```
133
+
134
+ With [uv](https://docs.astral.sh/uv/) you can skip the install step: use `"command": "uvx", "args": ["sprocguard", "serve"]`.
135
+ sprocguard is also listed in the [MCP Registry](https://registry.modelcontextprotocol.io) as
136
+ `io.github.JheysonJhordyHuarauyaRiquelme/sprocguard`.
137
+
138
+ | Tool | What it does | Read-only |
139
+ |---|---|---|
140
+ | `inspect_procedure` | Parameters, tables read/written, procedures and triggers involved, risks | yes |
141
+ | `suggest_cases` | Real parameter values from related tables plus edge values | yes |
142
+ | `capture_baseline` | Records the current behavior (refuses to overwrite unless asked) | writes a file |
143
+ | `verify_procedure` | Re-runs the cases and lists every difference | yes |
144
+ | `generate_tsqlt_tests` | Turns a baseline into tSQLt tests | writes a file |
145
+ | `list_baselines` | Lists captured baselines | yes |
146
+
147
+ ## CLI
148
+
149
+ ```text
150
+ sprocguard init create sprocguard.toml
151
+ sprocguard inspect <proc> [--definition] describe a procedure and its risks
152
+ sprocguard suggest <proc> suggest parameter values
153
+ sprocguard capture <proc> --cases cases.json capture a baseline [--watch schema.table ...] [--overwrite]
154
+ sprocguard verify [<proc> ...] verify one, several or all baselines (exit 2 if behavior changed)
155
+ sprocguard tsqlt <proc> generate tSQLt tests (read-only procedures)
156
+ sprocguard list list baselines
157
+ sprocguard serve run the MCP server (stdio)
158
+ ```
159
+
160
+ Add `--json` for machine-readable output. In CI, run `sprocguard verify` against a test
161
+ database restored from a known backup: the job fails when any procedure changed behavior.
162
+
163
+ ## tSQLt export
164
+
165
+ For read-only procedures, `sprocguard tsqlt <proc>` writes a `.sql` file with one
166
+ [tSQLt](https://tsqlt.org) test per case, using `tSQLt.AssertEqualsTable` and
167
+ `tSQLt.ExpectException`. Volatile columns are left out of the comparison.
168
+
169
+ ## Limitations (0.1)
170
+
171
+ - Table-valued parameters are not supported yet.
172
+ - Tables changed only through dynamic SQL are not detected automatically; add them with `--watch`.
173
+ - If the procedure fails, result sets returned before the error are not recorded (the error is).
174
+ - tSQLt export covers read-only procedures; procedures that modify data use `verify`.
175
+ - Comparisons ignore the order of rows when the order was not stable between the two capture runs.
176
+
177
+ ## Roadmap
178
+
179
+ - [ ] tSQLt export with `FakeTable` for procedures that modify data
180
+ - [ ] Table-valued parameters
181
+ - [ ] `sprocguard impact <table>`: verify every procedure that uses a table you are changing
182
+ - [ ] GitHub Action
183
+ - [x] Publish to PyPI and the MCP Registry
184
+
185
+ ## Development
186
+
187
+ ```bash
188
+ pip install -e ".[dev]"
189
+ pytest
190
+ ```
191
+
192
+ The test suite uses an in-memory imitation of SQL Server (`tests/fakedb.py`), so it runs
193
+ anywhere; real-database testing uses `examples/demo.sql`.
194
+
195
+ ## License
196
+
197
+ MIT
@@ -0,0 +1,5 @@
1
+ [
2
+ { "name": "customer with orders", "params": { "@CustomerId": 1 } },
3
+ { "name": "customer without orders", "params": { "@CustomerId": 2 } },
4
+ { "name": "unknown customer -> error", "params": { "@CustomerId": 99 } }
5
+ ]
@@ -0,0 +1,7 @@
1
+ [
2
+ { "name": "regular customer buys 2 coffees", "params": { "@CustomerId": 2, "@ProductId": 10, "@Quantity": 2 } },
3
+ { "name": "VIP customer gets 10% off", "params": { "@CustomerId": 1, "@ProductId": 10, "@Quantity": 3 } },
4
+ { "name": "VIP discount with odd cents", "params": { "@CustomerId": 1, "@ProductId": 10, "@Quantity": 7 } },
5
+ { "name": "buys exactly the last units", "params": { "@CustomerId": 2, "@ProductId": 11, "@Quantity": 2 } },
6
+ { "name": "insufficient stock raises error", "params": { "@CustomerId": 2, "@ProductId": 11, "@Quantity": 5 } }
7
+ ]
@@ -0,0 +1,95 @@
1
+ /*
2
+ sprocguard demo database.
3
+ Run it in SSMS, Azure Data Studio or sqlcmd against a DEVELOPMENT SQL Server (2016 or later):
4
+ sqlcmd -S localhost -E -i examples\demo.sql
5
+ */
6
+ IF DB_ID(N'DemoShop') IS NULL
7
+ CREATE DATABASE DemoShop;
8
+ GO
9
+ USE DemoShop;
10
+ GO
11
+
12
+ DROP PROCEDURE IF EXISTS dbo.usp_PlaceOrder;
13
+ DROP PROCEDURE IF EXISTS dbo.usp_GetCustomerOrders;
14
+ DROP TABLE IF EXISTS dbo.AuditLog;
15
+ DROP TABLE IF EXISTS dbo.Orders;
16
+ DROP TABLE IF EXISTS dbo.Products;
17
+ DROP TABLE IF EXISTS dbo.Customers;
18
+ GO
19
+
20
+ CREATE TABLE dbo.Customers (
21
+ CustomerId int NOT NULL PRIMARY KEY,
22
+ Name nvarchar(100) NOT NULL,
23
+ Segment varchar(20) NOT NULL
24
+ );
25
+ CREATE TABLE dbo.Products (
26
+ ProductId int NOT NULL PRIMARY KEY,
27
+ Name nvarchar(100) NOT NULL,
28
+ Price decimal(10,2) NOT NULL,
29
+ Stock int NOT NULL
30
+ );
31
+ CREATE TABLE dbo.Orders (
32
+ OrderId int IDENTITY(1,1) NOT NULL PRIMARY KEY,
33
+ CustomerId int NOT NULL REFERENCES dbo.Customers (CustomerId),
34
+ OrderDate datetime2(0) NOT NULL,
35
+ Total decimal(10,2) NOT NULL
36
+ );
37
+ CREATE TABLE dbo.AuditLog ( -- no primary key on purpose: sprocguard compares it as a multiset
38
+ Action varchar(50) NOT NULL,
39
+ At datetime2(3) NOT NULL
40
+ );
41
+ GO
42
+
43
+ INSERT dbo.Customers (CustomerId, Name, Segment) VALUES (1, N'Ana', 'VIP'), (2, N'Luis', 'Regular');
44
+ INSERT dbo.Products (ProductId, Name, Price, Stock) VALUES (10, N'Café', 12.50, 100), (11, N'Cacao', 8.00, 2);
45
+ SET IDENTITY_INSERT dbo.Orders ON;
46
+ INSERT dbo.Orders (OrderId, CustomerId, OrderDate, Total)
47
+ VALUES (1, 1, '2026-01-05T09:00:00', 25.00), (2, 1, '2026-02-07T15:30:00', 8.00);
48
+ SET IDENTITY_INSERT dbo.Orders OFF;
49
+ GO
50
+
51
+ CREATE OR ALTER PROCEDURE dbo.usp_GetCustomerOrders
52
+ @CustomerId int
53
+ AS
54
+ BEGIN
55
+ SET NOCOUNT ON;
56
+ IF NOT EXISTS (SELECT 1 FROM dbo.Customers WHERE CustomerId = @CustomerId)
57
+ THROW 50001, 'Customer not found', 1;
58
+
59
+ SELECT OrderId, OrderDate, Total
60
+ FROM dbo.Orders
61
+ WHERE CustomerId = @CustomerId
62
+ ORDER BY OrderId;
63
+ END;
64
+ GO
65
+
66
+ CREATE OR ALTER PROCEDURE dbo.usp_PlaceOrder
67
+ @CustomerId int,
68
+ @ProductId int,
69
+ @Quantity int,
70
+ @OrderId int OUTPUT
71
+ AS
72
+ BEGIN
73
+ SET NOCOUNT ON;
74
+ DECLARE @Price decimal(10,2), @Stock int, @Total decimal(10,2);
75
+
76
+ SELECT @Price = Price, @Stock = Stock FROM dbo.Products WHERE ProductId = @ProductId;
77
+ IF @Stock < @Quantity
78
+ THROW 50002, 'Insufficient stock', 1;
79
+
80
+ SET @Total = @Price * @Quantity;
81
+ IF EXISTS (SELECT 1 FROM dbo.Customers WHERE CustomerId = @CustomerId AND Segment = 'VIP')
82
+ SET @Total = ROUND(@Total * 0.90, 2); -- VIP customers get 10% off
83
+
84
+ INSERT INTO dbo.Orders (CustomerId, OrderDate, Total) VALUES (@CustomerId, SYSDATETIME(), @Total);
85
+ SET @OrderId = SCOPE_IDENTITY();
86
+
87
+ UPDATE dbo.Products SET Stock = Stock - @Quantity WHERE ProductId = @ProductId;
88
+ INSERT INTO dbo.AuditLog (Action, At) VALUES ('order', SYSDATETIME());
89
+
90
+ SELECT @OrderId AS OrderId, @Total AS Total;
91
+ RETURN 0;
92
+ END;
93
+ GO
94
+
95
+ PRINT 'DemoShop is ready.';
@@ -0,0 +1,38 @@
1
+ /*
2
+ A "harmless refactor" of dbo.usp_PlaceOrder, the kind an AI agent might write.
3
+ It looks cleaner, but it changes how the VIP discount is rounded.
4
+ Deploy it after capturing the baseline, then run: sprocguard verify dbo.usp_PlaceOrder
5
+ */
6
+ USE DemoShop;
7
+ GO
8
+
9
+ CREATE OR ALTER PROCEDURE dbo.usp_PlaceOrder
10
+ @CustomerId int,
11
+ @ProductId int,
12
+ @Quantity int,
13
+ @OrderId int OUTPUT
14
+ AS
15
+ BEGIN
16
+ SET NOCOUNT ON;
17
+ DECLARE @Price decimal(10,2), @Stock int, @Total decimal(10,2);
18
+
19
+ SELECT @Price = Price, @Stock = Stock FROM dbo.Products WHERE ProductId = @ProductId;
20
+ IF @Stock < @Quantity
21
+ THROW 50002, 'Insufficient stock', 1;
22
+
23
+ -- "Simplified": one expression instead of two steps.
24
+ SET @Total = CAST(@Price * @Quantity
25
+ * CASE WHEN EXISTS (SELECT 1 FROM dbo.Customers
26
+ WHERE CustomerId = @CustomerId AND Segment = 'VIP')
27
+ THEN 0.9 ELSE 1 END AS decimal(10,1));
28
+
29
+ INSERT INTO dbo.Orders (CustomerId, OrderDate, Total) VALUES (@CustomerId, SYSDATETIME(), @Total);
30
+ SET @OrderId = SCOPE_IDENTITY();
31
+
32
+ UPDATE dbo.Products SET Stock = Stock - @Quantity WHERE ProductId = @ProductId;
33
+ INSERT INTO dbo.AuditLog (Action, At) VALUES ('order', SYSDATETIME());
34
+
35
+ SELECT @OrderId AS OrderId, @Total AS Total;
36
+ RETURN 0;
37
+ END;
38
+ GO
@@ -0,0 +1,56 @@
1
+ [build-system]
2
+ requires = ["hatchling>=1.24"]
3
+ build-backend = "hatchling.build"
4
+
5
+ [project]
6
+ name = "sprocguard"
7
+ dynamic = ["version"]
8
+ description = "A safety net for SQL Server stored procedures: capture what a procedure does today, then verify an AI agent (or a human) didn't change it by accident."
9
+ readme = "README.md"
10
+ license = "MIT"
11
+ requires-python = ">=3.11"
12
+ authors = [{ name = "Jheyson Huarauya Riquelme" }]
13
+ keywords = ["sql-server", "t-sql", "stored-procedures", "tsqlt", "mcp", "ai-agents", "characterization-tests", "golden-master"]
14
+ classifiers = [
15
+ "Development Status :: 3 - Alpha",
16
+ "Intended Audience :: Developers",
17
+ "Environment :: Console",
18
+ "Operating System :: OS Independent",
19
+ "Programming Language :: Python :: 3",
20
+ "Programming Language :: Python :: 3.11",
21
+ "Programming Language :: Python :: 3.12",
22
+ "Programming Language :: Python :: 3.13",
23
+ "Programming Language :: SQL",
24
+ "Topic :: Database",
25
+ "Topic :: Software Development :: Testing",
26
+ ]
27
+ dependencies = [
28
+ "mcp>=2.0",
29
+ "pyodbc>=5.0",
30
+ ]
31
+
32
+ [project.optional-dependencies]
33
+ pymssql = ["pymssql>=2.3"]
34
+ dev = ["pytest>=8", "sqlglot>=25"]
35
+
36
+ [project.scripts]
37
+ sprocguard = "sprocguard.cli:main"
38
+
39
+ [project.urls]
40
+ Homepage = "https://github.com/JheysonJhordyHuarauyaRiquelme/sprocguard"
41
+ Repository = "https://github.com/JheysonJhordyHuarauyaRiquelme/sprocguard"
42
+ Issues = "https://github.com/JheysonJhordyHuarauyaRiquelme/sprocguard/issues"
43
+ Changelog = "https://github.com/JheysonJhordyHuarauyaRiquelme/sprocguard/releases"
44
+
45
+ [tool.hatch.version]
46
+ path = "src/sprocguard/__init__.py"
47
+
48
+ [tool.hatch.build.targets.wheel]
49
+ packages = ["src/sprocguard"]
50
+
51
+ [tool.hatch.build.targets.sdist]
52
+ include = ["src", "tests", "examples", "README.md", "LICENSE", "pyproject.toml"]
53
+
54
+ [tool.pytest.ini_options]
55
+ testpaths = ["tests"]
56
+ pythonpath = ["src"]
@@ -0,0 +1,3 @@
1
+ """sprocguard: a safety net for SQL Server stored procedures."""
2
+
3
+ __version__ = "0.1.1"