pythia-mongo 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_mongo-0.1.0/LICENSE +21 -0
- pythia_mongo-0.1.0/PKG-INFO +395 -0
- pythia_mongo-0.1.0/README.md +367 -0
- pythia_mongo-0.1.0/pyproject.toml +43 -0
- pythia_mongo-0.1.0/setup.cfg +4 -0
- pythia_mongo-0.1.0/src/pythia_mongo/__init__.py +5 -0
- pythia_mongo-0.1.0/src/pythia_mongo/__main__.py +5 -0
- pythia_mongo-0.1.0/src/pythia_mongo/catalog.py +124 -0
- pythia_mongo-0.1.0/src/pythia_mongo/cli.py +8 -0
- pythia_mongo-0.1.0/src/pythia_mongo/commands/collection.json +16 -0
- pythia_mongo-0.1.0/src/pythia_mongo/commands/collections.json +29 -0
- pythia_mongo-0.1.0/src/pythia_mongo/commands/connection-status.json +8 -0
- pythia_mongo-0.1.0/src/pythia_mongo/commands/count.json +12 -0
- pythia_mongo-0.1.0/src/pythia_mongo/commands/duplicates.json +43 -0
- pythia_mongo-0.1.0/src/pythia_mongo/commands/indexes.json +12 -0
- pythia_mongo-0.1.0/src/pythia_mongo/commands/invalid.json +45 -0
- pythia_mongo-0.1.0/src/pythia_mongo/commands/pipeline-run.json +18 -0
- pythia_mongo-0.1.0/src/pythia_mongo/commands/probe-sample.json +28 -0
- pythia_mongo-0.1.0/src/pythia_mongo/commands/probe.json +37 -0
- pythia_mongo-0.1.0/src/pythia_mongo/commands/sample.json +23 -0
- pythia_mongo-0.1.0/src/pythia_mongo/commands/server-build.json +9 -0
- pythia_mongo-0.1.0/src/pythia_mongo/commands/server-hello.json +9 -0
- pythia_mongo-0.1.0/src/pythia_mongo/commands/view-run.json +24 -0
- pythia_mongo-0.1.0/src/pythia_mongo/engine.py +301 -0
- pythia_mongo-0.1.0/src/pythia_mongo/guard.py +58 -0
- pythia_mongo-0.1.0/src/pythia_mongo/read.py +346 -0
- pythia_mongo-0.1.0/src/pythia_mongo/rules.py +132 -0
- pythia_mongo-0.1.0/src/pythia_mongo/skills/pythia-mongo-apply/SKILL.md +101 -0
- pythia_mongo-0.1.0/src/pythia_mongo/skills/pythia-mongo-apply/reference/refusals.md +31 -0
- pythia_mongo-0.1.0/src/pythia_mongo/skills/pythia-mongo-autopilot/SKILL.md +62 -0
- pythia_mongo-0.1.0/src/pythia_mongo/skills/pythia-mongo-deploy/SKILL.md +66 -0
- pythia_mongo-0.1.0/src/pythia_mongo/skills/pythia-mongo-explore/SKILL.md +65 -0
- pythia_mongo-0.1.0/src/pythia_mongo/skills/pythia-mongo-impact/SKILL.md +60 -0
- pythia_mongo-0.1.0/src/pythia_mongo/skills/pythia-mongo-verify/SKILL.md +58 -0
- pythia_mongo-0.1.0/src/pythia_mongo/skills/using-pythia-mongo/SKILL.md +68 -0
- pythia_mongo-0.1.0/src/pythia_mongo/write.py +714 -0
- pythia_mongo-0.1.0/src/pythia_mongo.egg-info/PKG-INFO +395 -0
- pythia_mongo-0.1.0/src/pythia_mongo.egg-info/SOURCES.txt +41 -0
- pythia_mongo-0.1.0/src/pythia_mongo.egg-info/dependency_links.txt +1 -0
- pythia_mongo-0.1.0/src/pythia_mongo.egg-info/entry_points.txt +2 -0
- pythia_mongo-0.1.0/src/pythia_mongo.egg-info/requires.txt +2 -0
- pythia_mongo-0.1.0/src/pythia_mongo.egg-info/top_level.txt +1 -0
- pythia_mongo-0.1.0/tests/test_mongo.py +1235 -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,395 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: pythia-mongo
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: pythia for MongoDB: database development for AI agents over pythia-core - validators, views and indexes read live, impact with its blind spots named, and an approved write path.
|
|
5
|
+
License-Expression: MIT
|
|
6
|
+
Project-URL: Repository, https://github.com/thaildhe172591/pythia-mongo
|
|
7
|
+
Project-URL: Changelog, https://github.com/thaildhe172591/pythia-mongo/blob/main/CHANGELOG.md
|
|
8
|
+
Project-URL: Issues, https://github.com/thaildhe172591/pythia-mongo/issues
|
|
9
|
+
Keywords: mongodb,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: pymongo>=4.9
|
|
27
|
+
Dynamic: license-file
|
|
28
|
+
|
|
29
|
+
# pythia-mongo
|
|
30
|
+
|
|
31
|
+
**English** · [Tiếng Việt](https://github.com/thaildhe172591/pythia-mongo/blob/main/README.vi.md)
|
|
32
|
+
|
|
33
|
+
pythia for MongoDB: database 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,
|
|
36
|
+
[pythia-postgres](https://github.com/thaildhe172591/pythia-postgres) on
|
|
37
|
+
PostgreSQL and [pythia-mssql](https://github.com/thaildhe172591/pythia-mssql)
|
|
38
|
+
on SQL Server: the agent learns from the live database, asks a person before
|
|
39
|
+
any write, and writes only through a gate that snapshots, verifies and
|
|
40
|
+
reports honestly.
|
|
41
|
+
|
|
42
|
+
**Alpha (0.1.0).** It reads everything pythia's Learn movement needs, writes through one gate — data rehearsed in a transaction;
|
|
43
|
+
validators, views and indexes predicted, written once, and read back — and
|
|
44
|
+
wires a project for an agent: the guard, the session brief, the approval
|
|
45
|
+
doors and the `pythia-mongo-*` skills.
|
|
46
|
+
|
|
47
|
+
| Works today |
|
|
48
|
+
|---|
|
|
49
|
+
| reads: `check`, `ls`, `src`, `cols`, `ddl`, `grep`, `deps`, `impact`, `errors`, `connections` |
|
|
50
|
+
| writes: `apply`, `deploy`, `approve`, `journal`, `history`, `policy`, `autopilot`, `lint` |
|
|
51
|
+
| the harness: `install`, `doctor`, `hook`, `mcp`, `skill lint` |
|
|
52
|
+
|
|
53
|
+
MongoDB 7.0 and 8.0 are tested; 7.0 is the floor.
|
|
54
|
+
|
|
55
|
+
## Install
|
|
56
|
+
|
|
57
|
+
```bash
|
|
58
|
+
pip install pythia-mongo
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
That brings pythia-core, and [pymongo](https://pypi.org/project/pymongo/),
|
|
62
|
+
the only driver it uses. The development version is the main branch of
|
|
63
|
+
both:
|
|
64
|
+
|
|
65
|
+
```bash
|
|
66
|
+
pip install "git+https://github.com/thaildhe172591/pythia-core" \
|
|
67
|
+
"git+https://github.com/thaildhe172591/pythia-mongo"
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
## Set up a project for an agent
|
|
71
|
+
|
|
72
|
+
```bash
|
|
73
|
+
cd your-project
|
|
74
|
+
pythia-mongo install # once; a second run changes nothing
|
|
75
|
+
pythia-mongo doctor # is everything wired? exit 1 names what is not
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
`install` writes, and leaves everything else as it was:
|
|
79
|
+
|
|
80
|
+
- `.pythia/mongo/connections.json`, mode 600, created once from the
|
|
81
|
+
template — the developer fills in the passwords; an agent never opens it;
|
|
82
|
+
- `.gitignore` lines for that file, this machine's approvals
|
|
83
|
+
(`.pythia/mongo/grants/`) and `.claude/settings.local.json`;
|
|
84
|
+
- `.claude/settings.local.json`: the hooks (the guard, the session brief,
|
|
85
|
+
the AskUserQuestion answer), spelled with this interpreter's absolute path
|
|
86
|
+
so they start whether or not a virtual environment is active, and deny
|
|
87
|
+
rules for the engine's files;
|
|
88
|
+
- the MCP approver `pythia_mongo` (tool `pythia_mongo_approve`), in this
|
|
89
|
+
machine's files only: Claude Code's local scope for the project
|
|
90
|
+
(`~/.claude.json`) and Codex's `~/.codex/config.toml`, each when that
|
|
91
|
+
client has run here;
|
|
92
|
+
- a `pythia-mongo` block in CLAUDE.md and AGENTS.md (other tools' blocks,
|
|
93
|
+
and the developer's own text, are left alone);
|
|
94
|
+
- the skills, in `.claude/skills` and `.agents/skills`:
|
|
95
|
+
`using-pythia-mongo` (the router), `pythia-mongo-explore`,
|
|
96
|
+
`pythia-mongo-impact`, `pythia-mongo-apply`, `pythia-mongo-deploy`,
|
|
97
|
+
`pythia-mongo-autopilot`, `pythia-mongo-verify`.
|
|
98
|
+
|
|
99
|
+
Restart the agent's session after installing, so the hooks load. Each
|
|
100
|
+
session then starts with the rules and a `STATE` line (connections, the
|
|
101
|
+
write policy). Before every tool call the guard decides:
|
|
102
|
+
|
|
103
|
+
| The agent runs | The guard |
|
|
104
|
+
|---|---|
|
|
105
|
+
| `mongosh` or the legacy `mongo` given a write (`insertOne`, `updateMany`, `deleteMany`, `replaceOne`, `drop`, `createIndex`, `createView`, `renameCollection`, `bulkWrite`, `runCommand`, `adminCommand`, `createUser`, a `$out` or `$merge` stage, `load()`), or a script (`-f`, `--file`, a `.js` or `.mjs` file, `< file`, a pipe into the shell); `mongoimport`, `mongorestore`, `mongofiles` — also behind `docker exec`, `ssh`, `sudo` or `bash -c` | denies: writes go through `pythia-mongo apply` |
|
|
106
|
+
| a MongoDB MCP server's tool named for a write (`insert-many`, `update-many`, `delete-many`, `drop-collection`, `create-index`, `rename-collection`, ...), or given a command document that writes (`{"drop": ...}`, `{"collMod": ...}`, a `$merge` or `$out` stage) | denies |
|
|
107
|
+
| reading `connections.json`; editing `policy.json`, the journal or a grant; setting `PYTHIA_MONGO_CI`; calling `grants.mint` from a one-liner | denies |
|
|
108
|
+
| a `pythia-mongo` read, a preview, `approve --card` | allows without a prompt |
|
|
109
|
+
| `apply --confirm <token>` the developer already approved on this connection | allows; apply re-verifies it |
|
|
110
|
+
| `mongosh` reads, `mongodump`, `mongoexport`, `approve`, `--yes`, `policy set`, anything compound | stays out: the client asks |
|
|
111
|
+
|
|
112
|
+
The guard reads a command for what it names, not what it means: it can
|
|
113
|
+
deny a harmless command (a `find` that mentions `.drop(` in a string, or a
|
|
114
|
+
`runCommand` that only reads) and miss a write spelled in a way it does not
|
|
115
|
+
list. It sees the commands Claude Code hands its tools, not Compass or a
|
|
116
|
+
driver inside the application. The gate behind it holds either way
|
|
117
|
+
(pythia-core's SECURITY.md).
|
|
118
|
+
|
|
119
|
+
pythia-postgres and pythia-mssql install beside it in one project, and
|
|
120
|
+
pythia for Oracle too: each keeps the others' hooks and instruction blocks,
|
|
121
|
+
and each guard and approval hook acts for its own tool only (pythia-core's
|
|
122
|
+
CI checks it with all four installed).
|
|
123
|
+
|
|
124
|
+
## Connections
|
|
125
|
+
|
|
126
|
+
`.pythia/mongo/connections.json` in the project root, owner-readable only
|
|
127
|
+
(`chmod 600`), and out of git. Template:
|
|
128
|
+
[examples/connections.example.json](https://github.com/thaildhe172591/pythia-mongo/blob/main/examples/connections.example.json).
|
|
129
|
+
|
|
130
|
+
```json
|
|
131
|
+
{
|
|
132
|
+
"default": "dev",
|
|
133
|
+
"dev": { "host": "db-dev", "port": 27017, "database": "app",
|
|
134
|
+
"user": "app_agent", "password": "...", "auth_source": "admin",
|
|
135
|
+
"tls": false }
|
|
136
|
+
}
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
- `host` and `database` are required. `host` may list several members
|
|
140
|
+
(`"a:27017,b:27017"`).
|
|
141
|
+
- `auth_source` is the database the user lives in (default `admin`).
|
|
142
|
+
- `tls` (default `false`), `replica_set`, and `direct` (`true` talks to the
|
|
143
|
+
one member named, not the set it advertises — for a member reached
|
|
144
|
+
through a container's mapped port or a tunnel).
|
|
145
|
+
- A connection string (`"uri"`) is refused: it carries the password into
|
|
146
|
+
every place it is printed.
|
|
147
|
+
- The entry is picked by `--conn NAME`, then `PYTHIA_MONGO_CONNECTION`,
|
|
148
|
+
then the only entry, the folder directly under the project root, then
|
|
149
|
+
`"default"`. `PYTHIA_MONGO_CONFIG` points at another file.
|
|
150
|
+
|
|
151
|
+
pythia for Oracle keeps `.pythia/connections.json`, and the other engines
|
|
152
|
+
their own folders; pythia-mongo reads none of them.
|
|
153
|
+
|
|
154
|
+
## Commands
|
|
155
|
+
|
|
156
|
+
```bash
|
|
157
|
+
pythia-mongo check # server, replica set or not, database
|
|
158
|
+
pythia-mongo ls "t_order%" # collections and views by LIKE pattern
|
|
159
|
+
pythia-mongo src t_order # a validator, or a view's pipeline, as Extended JSON
|
|
160
|
+
pythia-mongo cols t_order # fields and their types, from a sample of documents
|
|
161
|
+
pythia-mongo ddl t_order # the commands that make it: create, createIndexes
|
|
162
|
+
pythia-mongo grep PAID # every validator and pipeline line holding a text
|
|
163
|
+
pythia-mongo deps v_order_lines # what a view reads
|
|
164
|
+
pythia-mongo impact t_order # what reads it: run before changing it
|
|
165
|
+
pythia-mongo errors [NAME] # documents a validator rejects, views that fail
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
`--json` gives one envelope per command; `--limit`, `--max-lines` and
|
|
169
|
+
`--offset` window long output, and any truncation says how to fetch the
|
|
170
|
+
rest.
|
|
171
|
+
|
|
172
|
+
### What MongoDB cannot see, and how pythia-mongo says so
|
|
173
|
+
|
|
174
|
+
- **There is no catalog of dependencies.** MongoDB records only views:
|
|
175
|
+
`impact` walks every view's `viewOn` and the collections its pipeline
|
|
176
|
+
reads (`$lookup`, `$graphLookup`, `$unionWith`, `$facet`, nested
|
|
177
|
+
pipelines too). The application code that reads and writes a collection
|
|
178
|
+
through a driver is invisible to the database, and so are views in other
|
|
179
|
+
databases, scheduled `$merge` / `$out` jobs and change streams. Each
|
|
180
|
+
blind spot is printed every time.
|
|
181
|
+
- **There is no column list.** `cols` reads up to 1000 sampled documents:
|
|
182
|
+
each field path (`address.city`, `tags[]` for an array's elements), its
|
|
183
|
+
types by how many documents hold them, how many hold it at all, and what
|
|
184
|
+
the `$jsonSchema` validator says (required, description). A field the
|
|
185
|
+
validator declares and no sampled document holds is listed as `0 of N`.
|
|
186
|
+
When the collection holds more than the sample, it says so.
|
|
187
|
+
- **`errors` counts, it does not compile.** For each collection with a
|
|
188
|
+
validator: how many documents break it as it stands, with the first of
|
|
189
|
+
them (`warning` when the validator only warns). Each view runs over its
|
|
190
|
+
first 100 documents, and a view that reads a collection that does not
|
|
191
|
+
exist is named. A collection without a validator has nothing to check,
|
|
192
|
+
and the output says `NOT VERIFIED`.
|
|
193
|
+
- **`ddl` is rebuilt, not dumped:** a JSON array of the command documents
|
|
194
|
+
that make the collection (`create` with its validator and options,
|
|
195
|
+
`createIndexes`) or the view. The live check drops each, runs them,
|
|
196
|
+
rebuilds and compares.
|
|
197
|
+
- **Reads never write, and the server is the second line.** MongoDB has no
|
|
198
|
+
read-only session. Every command a read sends is a file in
|
|
199
|
+
`src/pythia_mongo/commands/`, and the suite holds them to reads only; a
|
|
200
|
+
user holding only the `read` role is what the server enforces.
|
|
201
|
+
|
|
202
|
+
### Writing: the gate
|
|
203
|
+
|
|
204
|
+
```bash
|
|
205
|
+
pythia-mongo apply change.json # preview: rehearsed or predicted, nothing kept
|
|
206
|
+
pythia-mongo approve 3fa9c1 # the developer approves, at their own console
|
|
207
|
+
pythia-mongo apply change.json --confirm 3fa9c1 # writes it — only as approved
|
|
208
|
+
pythia-mongo journal restore <entry> # undo is a write too: preview, approve, confirm
|
|
209
|
+
pythia-mongo policy # which groups may be written, and is rollback real?
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
The developer approves through the first door that works:
|
|
213
|
+
|
|
214
|
+
1. **In Claude Code's chat.** The agent asks with AskUserQuestion, its text
|
|
215
|
+
exactly the output of `pythia-mongo approve --card <token>`, options
|
|
216
|
+
Approve / Reject. The answer hook records the approval only when the
|
|
217
|
+
question carried that card and the developer chose Approve themselves —
|
|
218
|
+
not an answer the agent wrote, not a dialog that closed unattended.
|
|
219
|
+
2. **Through the MCP approver** (`pythia_mongo_approve`; Codex starts
|
|
220
|
+
here): the client shows the card, and only an explicit Approve counts.
|
|
221
|
+
3. **At the developer's console:** `pythia-mongo approve <token>`.
|
|
222
|
+
|
|
223
|
+
`pythia-mongo doctor` warns about settings that would answer the chat door
|
|
224
|
+
for the developer (`dontAsk` mode, an allow rule for AskUserQuestion, a hook
|
|
225
|
+
matching every tool).
|
|
226
|
+
|
|
227
|
+
A file holds **one command document**, in Extended JSON — the document
|
|
228
|
+
MongoDB itself takes, not mongosh JavaScript:
|
|
229
|
+
|
|
230
|
+
```json
|
|
231
|
+
{"update": "t_order",
|
|
232
|
+
"updates": [{"q": {"status": "DRAFT"}, "u": {"$inc": {"total": 1}}, "multi": true}]}
|
|
233
|
+
```
|
|
234
|
+
|
|
235
|
+
`apply` sorts it into a group, and the policy decides: `source` — a view
|
|
236
|
+
(`create` with `viewOn`, `collMod` of `viewOn` and `pipeline`) or a
|
|
237
|
+
validator (`collMod` of `validator`, `validationLevel`,
|
|
238
|
+
`validationAction`) — needs the developer's approval; `structural` (a new
|
|
239
|
+
collection, `createIndexes`, `dropIndexes`, `drop`, other `collMod`
|
|
240
|
+
options) and `data_dml` (`insert`, `update`, `delete`) are denied until the
|
|
241
|
+
developer runs `pythia-mongo policy set <group> confirm` at a console.
|
|
242
|
+
|
|
243
|
+
**Data is rehearsed.** An insert, update or delete runs inside a
|
|
244
|
+
transaction and is aborted: the preview shows how many documents it
|
|
245
|
+
touched, and an UPDATE or DELETE is approved on its documents — the count, a
|
|
246
|
+
sample, and a fingerprint (`$toHashedIndexKey` over each document). If one
|
|
247
|
+
joins, leaves or changes before the confirm, the confirm is refused. A
|
|
248
|
+
document the validator rejects fails the rehearsal (MongoDB reports it in
|
|
249
|
+
the reply, not as an error, and pythia-mongo reads the reply).
|
|
250
|
+
Transactions need a replica set: on a standalone server data writes are
|
|
251
|
+
refused — a single-node replica set is enough for development.
|
|
252
|
+
|
|
253
|
+
**Everything else is predicted.** MongoDB runs a view, a validator, an
|
|
254
|
+
index or a dropped collection outside any transaction, so nothing can be
|
|
255
|
+
run and rolled back. pythia-core's predict tier takes over: nothing runs
|
|
256
|
+
before the approval, and the preview says "predicted — nothing ran".
|
|
257
|
+
|
|
258
|
+
| Command | The prediction | Read back after the write |
|
|
259
|
+
|---|---|---|
|
|
260
|
+
| a validator (`collMod`) | counts the documents the new validator would reject, with the first of them; a validator MongoDB would refuse has nothing to approve | the validator as stored, and the count again — a different count exits 3 |
|
|
261
|
+
| a view (`create`, `collMod`) | runs the pipeline over no documents (would MongoDB accept it?) and until its first 100 documents (does it run?); names what it reads that does not exist; runs every view built on it on top of the new pipeline | the view as stored, and every run again — a failure the prediction did not see exits 3 |
|
|
262
|
+
| a unique index | looks for the keys it would meet twice (its partial filter or sparse rule applied); duplicates mean nothing to approve | each index is there, with its key |
|
|
263
|
+
| an index, a drop | an index name that exists with another key is refused; a drop names every view that reads what it drops | the index is there, or gone |
|
|
264
|
+
|
|
265
|
+
After the approval core runs the command once. MongoDB applies each of
|
|
266
|
+
these commands whole or not at all, so a refusal wrote nothing and keeps
|
|
267
|
+
the approval; a command whose answer never came is reported `WRITTEN?`
|
|
268
|
+
(exit 3). What landed is read back, and anything unlike the prediction is
|
|
269
|
+
reported and exits 3 with the restore. A prediction runs for at most 30
|
|
270
|
+
seconds (`maxTimeMS`).
|
|
271
|
+
|
|
272
|
+
**Refused, with the reason:** mongosh JavaScript, several commands in one
|
|
273
|
+
file, a key written twice, JavaScript anywhere (`$where`, `$function`,
|
|
274
|
+
`$accumulator`, code), `$out` and `$merge` (a prediction runs a pipeline
|
|
275
|
+
before approval: it must never write), reads (`aggregate`, `find`),
|
|
276
|
+
`findAndModify`, an upsert, an update or delete of one document among many
|
|
277
|
+
(`"multi": true` or `"limit": 0`, or name it by `_id`), users and roles,
|
|
278
|
+
`renameCollection`, `dropDatabase`, `bypassDocumentValidation`, session and
|
|
279
|
+
transaction fields (`lsid`, `writeConcern`, ...), and `system.` collections.
|
|
280
|
+
|
|
281
|
+
### A release: `deploy`, one transaction
|
|
282
|
+
|
|
283
|
+
```bash
|
|
284
|
+
pythia-mongo deploy 01_fix.json 02_backfill.json # preview the release as one transaction
|
|
285
|
+
pythia-mongo deploy 01_fix.json 02_backfill.json --confirm 9c41e0b2aa # all of it, as approved, or none
|
|
286
|
+
```
|
|
287
|
+
|
|
288
|
+
A release is data: one or more files, each one command document or a JSON
|
|
289
|
+
array of them, in the order they run. It is rehearsed in one transaction —
|
|
290
|
+
each command in order, its documents counted and fingerprinted at its own
|
|
291
|
+
point (an update of a document the release inserts sees it), then
|
|
292
|
+
everything is aborted. One card shows every command, and one approval (a
|
|
293
|
+
10-digit token, through the same doors) covers the release; a single
|
|
294
|
+
command's token cannot be approved alone. The confirm checks that every
|
|
295
|
+
command is byte-identical to what was approved before anything runs,
|
|
296
|
+
re-runs the release in one transaction, and commits only if every command
|
|
297
|
+
still matches. A command that fails, or documents that moved, abort the
|
|
298
|
+
whole release. A commit that does not answer is reported `WRITTEN?` for the
|
|
299
|
+
whole release.
|
|
300
|
+
|
|
301
|
+
A validator, a view, an index or a new or dropped collection has no place in
|
|
302
|
+
a release: MongoDB runs them outside any transaction, so they are predicted,
|
|
303
|
+
not rehearsed, and could not land all or nothing. `deploy` refuses them
|
|
304
|
+
before anything is written; apply each on its own. Anything `apply` refuses
|
|
305
|
+
is refused here too.
|
|
306
|
+
|
|
307
|
+
### Lint: before any apply
|
|
308
|
+
|
|
309
|
+
```bash
|
|
310
|
+
pythia-mongo lint 01_fix.json 02_view.json # a change's files in one run
|
|
311
|
+
pythia-mongo lint --rules # every rule and its effective severity
|
|
312
|
+
```
|
|
313
|
+
|
|
314
|
+
The rules read what MongoDB accepts and a reviewer would not:
|
|
315
|
+
|
|
316
|
+
| Rule | Default | Why |
|
|
317
|
+
|---|---|---|
|
|
318
|
+
| `mongo-validation-warn` | warn | `validationAction: "warn"` writes every document that breaks the validator anyway, and only logs it |
|
|
319
|
+
| `mongo-validation-off` | warn | `validationLevel: "off"` keeps the validator and enforces nothing |
|
|
320
|
+
| `mongo-validator-required` | info | a `$jsonSchema` with no `required` list accepts a document that holds none of its fields |
|
|
321
|
+
| `mongo-collection-validator` | warn | a new collection gets its validator in the same change (a `collMod` of it that sets one counts) |
|
|
322
|
+
| `mongo-unique-optional` | warn | a unique index counts every document without the field as `null`, and refuses the second — make it partial (`$exists`) or sparse |
|
|
323
|
+
| `mongo-ttl` | warn | a TTL index deletes documents on the server's schedule, unasked and unjournaled |
|
|
324
|
+
| `mongo-update-replacement` | warn | an update whose `u` holds no operator replaces each document, removing every field it does not name |
|
|
325
|
+
|
|
326
|
+
What MongoDB refuses itself is left to the rehearsal or the prediction. The
|
|
327
|
+
team sets severities and naming patterns (a view's name, `VIEW`; a
|
|
328
|
+
validator's collection, `VALIDATOR`) in `.pythia/mongo/lint.json`:
|
|
329
|
+
|
|
330
|
+
```json
|
|
331
|
+
{"severity": {"mongo-validation-warn": "error", "naming": "error"},
|
|
332
|
+
"naming": {"VIEW": "^v_[a-z0-9_]+$"}}
|
|
333
|
+
```
|
|
334
|
+
|
|
335
|
+
Every preview shows the findings, a command's errors are on its card, and
|
|
336
|
+
autopilot never lands a lint error.
|
|
337
|
+
|
|
338
|
+
### Autopilot: one lease instead of every approval
|
|
339
|
+
|
|
340
|
+
```bash
|
|
341
|
+
pythia-mongo autopilot request --groups source --hours 2 --max-writes 30
|
|
342
|
+
pythia-mongo autopilot report # every write under the lease, with its restore
|
|
343
|
+
pythia-mongo autopilot off # anyone may revoke
|
|
344
|
+
```
|
|
345
|
+
|
|
346
|
+
When the developer wants the agent to work without approving each write,
|
|
347
|
+
the agent requests a lease. Its card names the connection, the groups, the
|
|
348
|
+
time, the number of writes and whether destructive commands are included,
|
|
349
|
+
and nothing is delegated until the developer approves it through a door (or
|
|
350
|
+
runs `pythia-mongo autopilot on` at a console). While it lasts, `apply` and
|
|
351
|
+
`deploy` land what it covers without `--confirm` — still snapshotted,
|
|
352
|
+
rehearsed or predicted, read back and journaled. It never covers another
|
|
353
|
+
connection, one marked `"protected": true` (or an `"env"` of prod /
|
|
354
|
+
production / prd / live), a group or destructive command the card did not
|
|
355
|
+
name, or more than 1000 documents; a write that breaks something, lands
|
|
356
|
+
unlike its prediction or is `WRITTEN?`, or five failures in a row pause it.
|
|
357
|
+
|
|
358
|
+
### Least privilege
|
|
359
|
+
|
|
360
|
+
The read commands need the `read` role on the database. Writing needs
|
|
361
|
+
`readWrite` (data, indexes, new collections and views) and `dbAdmin`
|
|
362
|
+
(`collMod` of a validator or a view); the preview warns when the user holds
|
|
363
|
+
far more (`root`, `dbOwner`, any `AnyDatabase` role). The live check runs
|
|
364
|
+
every read as a `read` user, and every write as one holding `readWrite` and
|
|
365
|
+
`dbAdmin`.
|
|
366
|
+
|
|
367
|
+
## Every command is a file
|
|
368
|
+
|
|
369
|
+
Like the SQL engines' query files, every command pythia-mongo sends lives in
|
|
370
|
+
`src/pythia_mongo/commands/` as JSON: what it is for, its binds, what it
|
|
371
|
+
returns, and the command document. A bind is written `{"$bind": "name"}`
|
|
372
|
+
where its value goes, so text from a person or an agent always lands as a
|
|
373
|
+
value — never as an operator, a stage or a command of its own. The suite
|
|
374
|
+
checks that every file declares exactly the binds it uses, holds no
|
|
375
|
+
JavaScript (`$where`, `$function`, `$accumulator`), and holds no write,
|
|
376
|
+
`$out` or `$merge`: every command file reads. The only write pythia-mongo
|
|
377
|
+
ever sends is the developer's own command document — in a rehearsal's
|
|
378
|
+
transaction, or once after the approval.
|
|
379
|
+
|
|
380
|
+
## Tests
|
|
381
|
+
|
|
382
|
+
```bash
|
|
383
|
+
python tests/test_mongo.py # no database, no pymongo — mutation-checked
|
|
384
|
+
python tests/live_check.py # a disposable server only (see its docstring)
|
|
385
|
+
```
|
|
386
|
+
|
|
387
|
+
CI runs the suite on Ubuntu, Windows and macOS, Python 3.9 and 3.13, and
|
|
388
|
+
the live check — every read, every write path, a release, a lint finding
|
|
389
|
+
in a real preview and a write under a lease — against MongoDB 7.0 and 8.0,
|
|
390
|
+
each a single-node replica set with authentication. It tests against the
|
|
391
|
+
pythia-core branch of the same name when one exists.
|
|
392
|
+
|
|
393
|
+
## License
|
|
394
|
+
|
|
395
|
+
MIT
|