solunex-ssdo 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.
- solunex_ssdo-0.1.0/PKG-INFO +404 -0
- solunex_ssdo-0.1.0/README.md +386 -0
- solunex_ssdo-0.1.0/pyproject.toml +36 -0
- solunex_ssdo-0.1.0/setup.cfg +4 -0
- solunex_ssdo-0.1.0/solunex_ssdo.egg-info/PKG-INFO +404 -0
- solunex_ssdo-0.1.0/solunex_ssdo.egg-info/SOURCES.txt +90 -0
- solunex_ssdo-0.1.0/solunex_ssdo.egg-info/dependency_links.txt +1 -0
- solunex_ssdo-0.1.0/solunex_ssdo.egg-info/entry_points.txt +2 -0
- solunex_ssdo-0.1.0/solunex_ssdo.egg-info/requires.txt +11 -0
- solunex_ssdo-0.1.0/solunex_ssdo.egg-info/top_level.txt +1 -0
- solunex_ssdo-0.1.0/ssdo/__init__.py +2 -0
- solunex_ssdo-0.1.0/ssdo/api/__init__.py +0 -0
- solunex_ssdo-0.1.0/ssdo/api/auth.py +118 -0
- solunex_ssdo-0.1.0/ssdo/api/main.py +1285 -0
- solunex_ssdo-0.1.0/ssdo/api/models.py +296 -0
- solunex_ssdo-0.1.0/ssdo/api/notify.py +83 -0
- solunex_ssdo-0.1.0/ssdo/api/security.py +207 -0
- solunex_ssdo-0.1.0/ssdo/api/static/index.html +5309 -0
- solunex_ssdo-0.1.0/ssdo/applier.py +94 -0
- solunex_ssdo-0.1.0/ssdo/cli.py +187 -0
- solunex_ssdo-0.1.0/ssdo/core/__init__.py +25 -0
- solunex_ssdo-0.1.0/ssdo/core/appmap.py +453 -0
- solunex_ssdo-0.1.0/ssdo/core/ask.py +1507 -0
- solunex_ssdo-0.1.0/ssdo/core/ask_pair.py +786 -0
- solunex_ssdo-0.1.0/ssdo/core/compare.py +309 -0
- solunex_ssdo-0.1.0/ssdo/core/confidence.py +85 -0
- solunex_ssdo-0.1.0/ssdo/core/ddl.py +909 -0
- solunex_ssdo-0.1.0/ssdo/core/discussion.py +182 -0
- solunex_ssdo-0.1.0/ssdo/core/migration.py +571 -0
- solunex_ssdo-0.1.0/ssdo/core/naming.py +96 -0
- solunex_ssdo-0.1.0/ssdo/core/pair.py +324 -0
- solunex_ssdo-0.1.0/ssdo/core/plainlang.py +264 -0
- solunex_ssdo-0.1.0/ssdo/core/reasoner.py +113 -0
- solunex_ssdo-0.1.0/ssdo/core/render.py +51 -0
- solunex_ssdo-0.1.0/ssdo/core/report.py +352 -0
- solunex_ssdo-0.1.0/ssdo/core/rules.py +217 -0
- solunex_ssdo-0.1.0/ssdo/core/starters.py +74 -0
- solunex_ssdo-0.1.0/ssdo/core/structure.py +138 -0
- solunex_ssdo-0.1.0/ssdo/core/teaching.py +372 -0
- solunex_ssdo-0.1.0/ssdo/core/tuning.py +41 -0
- solunex_ssdo-0.1.0/ssdo/core/watch.py +178 -0
- solunex_ssdo-0.1.0/ssdo/db/__init__.py +26 -0
- solunex_ssdo-0.1.0/ssdo/db/accounts.py +419 -0
- solunex_ssdo-0.1.0/ssdo/db/admin.py +137 -0
- solunex_ssdo-0.1.0/ssdo/db/applies.py +175 -0
- solunex_ssdo-0.1.0/ssdo/db/cards.py +161 -0
- solunex_ssdo-0.1.0/ssdo/db/chat.py +179 -0
- solunex_ssdo-0.1.0/ssdo/db/config.py +35 -0
- solunex_ssdo-0.1.0/ssdo/db/draftdbs.py +131 -0
- solunex_ssdo-0.1.0/ssdo/db/drafts.py +81 -0
- solunex_ssdo-0.1.0/ssdo/db/join.py +247 -0
- solunex_ssdo-0.1.0/ssdo/db/migrate.py +38 -0
- solunex_ssdo-0.1.0/ssdo/db/migrations/env.py +100 -0
- solunex_ssdo-0.1.0/ssdo/db/migrations/script.py.mako +24 -0
- solunex_ssdo-0.1.0/ssdo/db/migrations/versions/0001_baseline.py +93 -0
- solunex_ssdo-0.1.0/ssdo/db/migrations/versions/0002_column_inventory.py +51 -0
- solunex_ssdo-0.1.0/ssdo/db/migrations/versions/0003_accounts.py +68 -0
- solunex_ssdo-0.1.0/ssdo/db/migrations/versions/0004_vault.py +78 -0
- solunex_ssdo-0.1.0/ssdo/db/migrations/versions/0005_private_workspaces.py +74 -0
- solunex_ssdo-0.1.0/ssdo/db/migrations/versions/0006_live_workspace.py +67 -0
- solunex_ssdo-0.1.0/ssdo/db/migrations/versions/0007_feedback.py +38 -0
- solunex_ssdo-0.1.0/ssdo/db/migrations/versions/0008_private_watches.py +70 -0
- solunex_ssdo-0.1.0/ssdo/db/migrations/versions/0009_live_rooms.py +69 -0
- solunex_ssdo-0.1.0/ssdo/db/migrations/versions/0010_usernames.py +62 -0
- solunex_ssdo-0.1.0/ssdo/db/migrations/versions/0011_room_chat.py +42 -0
- solunex_ssdo-0.1.0/ssdo/db/migrations/versions/0012_connection_cards.py +48 -0
- solunex_ssdo-0.1.0/ssdo/db/migrations/versions/0013_draft_databases.py +35 -0
- solunex_ssdo-0.1.0/ssdo/db/migrations/versions/0014_enum_values.py +27 -0
- solunex_ssdo-0.1.0/ssdo/db/migrations/versions/0015_watch_applies.py +48 -0
- solunex_ssdo-0.1.0/ssdo/db/migrations/versions/__init__.py +0 -0
- solunex_ssdo-0.1.0/ssdo/db/models.py +468 -0
- solunex_ssdo-0.1.0/ssdo/db/rooms.py +500 -0
- solunex_ssdo-0.1.0/ssdo/db/seal.py +90 -0
- solunex_ssdo-0.1.0/ssdo/db/store.py +298 -0
- solunex_ssdo-0.1.0/ssdo/db/vault.py +203 -0
- solunex_ssdo-0.1.0/ssdo/db/watches.py +263 -0
- solunex_ssdo-0.1.0/ssdo/ghcomment.py +97 -0
- solunex_ssdo-0.1.0/ssdo/profiler/__init__.py +13 -0
- solunex_ssdo-0.1.0/ssdo/profiler/connection.py +59 -0
- solunex_ssdo-0.1.0/ssdo/profiler/mysql_profiler.py +196 -0
- solunex_ssdo-0.1.0/ssdo/profiler/postgres_profiler.py +258 -0
- solunex_ssdo-0.1.0/ssdo/profiler/tls.py +159 -0
- solunex_ssdo-0.1.0/ssdo/schemadiff.py +106 -0
- solunex_ssdo-0.1.0/ssdo/schemas/__init__.py +0 -0
- solunex_ssdo-0.1.0/ssdo/schemas/profile.py +157 -0
- solunex_ssdo-0.1.0/ssdo/watcher.py +282 -0
- solunex_ssdo-0.1.0/tests/test_applier_live.py +77 -0
- solunex_ssdo-0.1.0/tests/test_cli.py +75 -0
- solunex_ssdo-0.1.0/tests/test_ghcomment.py +57 -0
- solunex_ssdo-0.1.0/tests/test_schemadiff.py +137 -0
- solunex_ssdo-0.1.0/tests/test_watcher.py +111 -0
- solunex_ssdo-0.1.0/tests/test_watcher_apply.py +98 -0
|
@@ -0,0 +1,404 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: solunex-ssdo
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: SSDO, the Smart Data Organizer: understands a database's structure (never its data) and says what it knows, what it infers, and how sure it is.
|
|
5
|
+
Author: Solunex Technologies
|
|
6
|
+
Requires-Python: >=3.10
|
|
7
|
+
Description-Content-Type: text/markdown
|
|
8
|
+
Requires-Dist: fastapi>=0.110
|
|
9
|
+
Requires-Dist: uvicorn>=0.29
|
|
10
|
+
Requires-Dist: pydantic>=2.6
|
|
11
|
+
Requires-Dist: SQLAlchemy>=2.0
|
|
12
|
+
Requires-Dist: alembic>=1.13
|
|
13
|
+
Requires-Dist: PyMySQL>=1.1
|
|
14
|
+
Requires-Dist: psycopg[binary]>=3.1
|
|
15
|
+
Provides-Extra: dev
|
|
16
|
+
Requires-Dist: pytest; extra == "dev"
|
|
17
|
+
Requires-Dist: httpx; extra == "dev"
|
|
18
|
+
|
|
19
|
+
# SSDO — Smart Data Organizer (Solunex Intelligence)
|
|
20
|
+
|
|
21
|
+
SSDO profiles a MySQL or PostgreSQL database's **structure** (`information_schema`
|
|
22
|
+
only — no data rows are read) and returns an explainable understanding of it:
|
|
23
|
+
relationships (declared and inferred, each with confidence and evidence), table
|
|
24
|
+
roles, conventions, and what it honestly could not determine.
|
|
25
|
+
|
|
26
|
+
## Run SSDO on your own machine
|
|
27
|
+
|
|
28
|
+
Use this to profile databases on your laptop or office network (`localhost`) in
|
|
29
|
+
development mode, before anything is hosted. It is the same SSDO: structure only, never
|
|
30
|
+
your data. The console opens at http://127.0.0.1:8000 and only this machine can reach it.
|
|
31
|
+
|
|
32
|
+
**With Python (3.10+):**
|
|
33
|
+
|
|
34
|
+
```bash
|
|
35
|
+
pip install "git+https://github.com/saniabduljabbar619-create/solunex-intelligence.git"
|
|
36
|
+
ssdo # starts SSDO and opens the console in your browser
|
|
37
|
+
ssdo --port 9000 --no-browser # another port, no browser
|
|
38
|
+
ssdo --version
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
SSDO keeps its saved runs in your user data folder (Windows
|
|
42
|
+
`%LOCALAPPDATA%\Solunex\SSDO`, macOS `~/Library/Application Support/Solunex SSDO`,
|
|
43
|
+
Linux `~/.local/share/solunex-ssdo`); `SSDO_DB_URL` overrides it.
|
|
44
|
+
|
|
45
|
+
**With Docker:**
|
|
46
|
+
|
|
47
|
+
```bash
|
|
48
|
+
docker build -t solunex-ssdo .
|
|
49
|
+
docker run --rm -p 127.0.0.1:8000:8000 -v ssdo-data:/data solunex-ssdo
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
- Keep the `127.0.0.1:` in `-p` so only your machine can open the console.
|
|
53
|
+
- Saved runs live in the `ssdo-data` volume.
|
|
54
|
+
- To profile a database running on the same computer, use the host
|
|
55
|
+
**`host.docker.internal`** instead of `localhost`. On Linux, add
|
|
56
|
+
`--add-host=host.docker.internal:host-gateway` to `docker run`.
|
|
57
|
+
- Tagged releases (`vX.Y.Z`) also publish the image to
|
|
58
|
+
`ghcr.io/saniabduljabbar619-create/solunex-ssdo`.
|
|
59
|
+
|
|
60
|
+
## Setup (to work on SSDO itself)
|
|
61
|
+
|
|
62
|
+
```bash
|
|
63
|
+
python -m venv .venv
|
|
64
|
+
.venv\Scripts\activate # Windows (macOS/Linux: source .venv/bin/activate)
|
|
65
|
+
pip install -r requirements-dev.txt
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
## Run the API
|
|
69
|
+
|
|
70
|
+
```bash
|
|
71
|
+
uvicorn ssdo.api.main:app --reload
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
Open http://127.0.0.1:8000 for the console, or http://127.0.0.1:8000/docs for Swagger.
|
|
75
|
+
The console has four views of every run: **Show technical detail**, **Explain simply**
|
|
76
|
+
(plain language), **Teach me** (a six-slide guided walkthrough, also available as
|
|
77
|
+
`GET /v1/understandings/{run_id}/walkthrough`) and **Changes** (drift since an earlier
|
|
78
|
+
run, also `GET /v1/compare` and `GET /v1/understandings/{run_id}/changes`).
|
|
79
|
+
|
|
80
|
+
Above the views, **Ask SSDO** answers questions about the run's structure in plain
|
|
81
|
+
words, such as *"what links to patients?"*, *"how are payments and tags connected?"* or
|
|
82
|
+
*"where is email?"*. It uses fixed templates, no language model and no data access. Every
|
|
83
|
+
answer says how it read the question and how sure SSDO is. Questions about the data
|
|
84
|
+
itself ("how many patients?") are declined with the reason. It is also available as
|
|
85
|
+
`POST /v1/understandings/{run_id}/ask`; see `docs/specs/tier2-ask.md`. It also answers
|
|
86
|
+
*"what changed since the last run?"* and *"what is a junction table?"*, suggests questions
|
|
87
|
+
suited to the portal mode, and in Study mode defines each technical word it uses.
|
|
88
|
+
|
|
89
|
+
The left panel holds the **mode** (Development opens runs in the technical view, Study in
|
|
90
|
+
Teach me, Enterprise in Explain simply; every view stays reachable from every mode), an
|
|
91
|
+
opt-in **Keep this session** (off by default; remembers the mode and the last 3
|
|
92
|
+
connections on this device, never the password, user or API key), and your **run
|
|
93
|
+
history** with a Compare button for any database profiled more than once.
|
|
94
|
+
|
|
95
|
+
Authenticate with the `X-API-Key` header. With no keys configured the server runs in
|
|
96
|
+
dev mode and accepts `dev-local-key`; for real use set
|
|
97
|
+
`SSDO_API_KEYS="key1:tenant-a,key2:tenant-b"`.
|
|
98
|
+
|
|
99
|
+
SSDO stores its own results in `ssdo_platform.db` (SQLite) by default; set
|
|
100
|
+
`SSDO_DB_URL` to use another database. This file is local and not committed.
|
|
101
|
+
|
|
102
|
+
## Production settings
|
|
103
|
+
|
|
104
|
+
| Variable | Purpose | Default |
|
|
105
|
+
|---|---|---|
|
|
106
|
+
| `SSDO_ENV` | `production` refuses to start without API keys and tightens the defaults below | `development` |
|
|
107
|
+
| `SSDO_API_KEYS` | `key:tenant` pairs, comma-separated | none (dev key in development) |
|
|
108
|
+
| `SSDO_ALLOWED_DB_HOSTS` | Private/loopback hosts, IPs or CIDRs `/v1/profile` may reach in production, e.g. `db.internal,10.20.0.0/16` | none |
|
|
109
|
+
| `SSDO_CORS_ORIGINS` | Allowed browser origins | `*` in development, none in production |
|
|
110
|
+
| `SSDO_PROFILE_RATE_LIMIT` | Profiling calls per API key, `calls/seconds`; `0` disables | `10/60` |
|
|
111
|
+
| `SSDO_DB_URL` | SSDO's own store | `sqlite:///ssdo_platform.db` |
|
|
112
|
+
|
|
113
|
+
Link-local addresses (including the cloud metadata address 169.254.169.254) are refused
|
|
114
|
+
in every mode. See `ssdo/api/security.py` for the reasoning.
|
|
115
|
+
|
|
116
|
+
## SSDO's own store and migrations
|
|
117
|
+
|
|
118
|
+
The store's schema is managed by Alembic (`ssdo/db/migrations`). **You normally run
|
|
119
|
+
nothing:** the API and scripts upgrade the store automatically on startup, including
|
|
120
|
+
stores created before migrations existed. Your runs are kept.
|
|
121
|
+
|
|
122
|
+
Everyday commands (safe any time):
|
|
123
|
+
|
|
124
|
+
```bash
|
|
125
|
+
python -m alembic current # which version the store is at
|
|
126
|
+
python -m alembic upgrade head # upgrade now instead of waiting for startup
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
**Only when you change a table in `ssdo/db/models.py`**, draft a migration for it:
|
|
130
|
+
|
|
131
|
+
```bash
|
|
132
|
+
python -m alembic revision --autogenerate -m "add xyz to runs" # then review the file
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
If no model changed, this writes no file. `tests/db/test_migrations.py` fails if the
|
|
136
|
+
models and migrations ever disagree.
|
|
137
|
+
|
|
138
|
+
## Encrypted connections (TLS)
|
|
139
|
+
|
|
140
|
+
Every profile request chooses how the connection to the database is protected (console:
|
|
141
|
+
**Encryption (TLS)**; API: `tls` and `tls_ca`; command line: `--tls`, `--tls-ca`). The same
|
|
142
|
+
five modes apply to MySQL and PostgreSQL:
|
|
143
|
+
|
|
144
|
+
| `tls` | Encrypted | Server certificate checked | Use it for |
|
|
145
|
+
|---|---|---|---|
|
|
146
|
+
| `off` | never | — | a local database without TLS |
|
|
147
|
+
| `prefer` *(default)* | if the server offers it | no | everyday use; falls back to plain |
|
|
148
|
+
| `require` | always | no | stops eavesdropping, **not** impersonation |
|
|
149
|
+
| `verify-ca` | always | signed by *your* CA (`tls_ca` required) | provider CA, IP address as host |
|
|
150
|
+
| `verify-full` | always | signed by a trusted CA **and** names the host | hosted databases, production |
|
|
151
|
+
|
|
152
|
+
Hosted databases (Aiven, DigitalOcean, Azure, …) usually require TLS: download the
|
|
153
|
+
provider's **CA certificate** and paste it into the console with `verify-full`. Without a
|
|
154
|
+
certificate, `verify-full` trusts the public certificate authorities. The certificate is
|
|
155
|
+
used for one connection and never stored. Failures say what to change, e.g. *"The
|
|
156
|
+
server's TLS certificate does not name this host."*
|
|
157
|
+
|
|
158
|
+
## Command line
|
|
159
|
+
|
|
160
|
+
```bash
|
|
161
|
+
python -m scripts.profile_database --database mydb --user readonly --password ... --save
|
|
162
|
+
python -m scripts.profile_database --host db.example.com --database mydb --user readonly \
|
|
163
|
+
--password ... --tls verify-full --tls-ca ca.pem
|
|
164
|
+
python -m scripts.understanding_history --tenant tenant-mydb
|
|
165
|
+
python -m scripts.compare_runs --tenant tenant-mydb
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
Reports are written to `reports/` (local only, not committed).
|
|
169
|
+
|
|
170
|
+
## Deploy on Render
|
|
171
|
+
|
|
172
|
+
`render.yaml` is a Render Blueprint. It creates the web service (API and console) and
|
|
173
|
+
the PostgreSQL database SSDO stores its runs in. A Render service's disk is wiped on
|
|
174
|
+
every deploy, so the store must be Postgres, not SQLite.
|
|
175
|
+
|
|
176
|
+
1. **Create the services.** In Render: **New → Blueprint**, pick this repository, and
|
|
177
|
+
apply. Render builds with `pip install -r requirements.txt`, creates the database
|
|
178
|
+
and wires `SSDO_DB_URL` to it.
|
|
179
|
+
2. **Let people in.** On the `solunex-ssdo` service → **Environment**, set
|
|
180
|
+
`SSDO_ADMIN_EMAILS` (and/or `SSDO_API_KEYS`). The service will not start in
|
|
181
|
+
production with neither.
|
|
182
|
+
- **Accounts (recommended):** `SSDO_ADMIN_EMAILS=you@example.com` (comma-separated
|
|
183
|
+
for more admins). An admin signs in → **Invites** → **Generate code**, once per
|
|
184
|
+
person. A code works **once**, expires (1 to 30 days), can be revoked, and SSDO stores
|
|
185
|
+
only its fingerprint. **Every new account gets its own private workspace**: nobody
|
|
186
|
+
sees another person's runs. Only a **team** invite, chosen on purpose by an admin,
|
|
187
|
+
lets someone into the admin's workspace and its runs. The console shows every
|
|
188
|
+
person whether their workspace is private or shared, and with how many people.
|
|
189
|
+
- **Legacy static codes:** `SSDO_INVITE_CODES=code:tenant,...` still works so nobody is
|
|
190
|
+
locked out, but each sign-up now gets a private workspace (the tenant part is
|
|
191
|
+
ignored). Remove it once admins hand out generated codes.
|
|
192
|
+
- **Operator keys (optional):** `SSDO_API_KEYS=key:tenant,...` for servers or scripts
|
|
193
|
+
you manage yourself. Every entry must have a `:tenant` part, or the service
|
|
194
|
+
refuses to start and says which entry is wrong.
|
|
195
|
+
Generate a strong operator key with:
|
|
196
|
+
```bash
|
|
197
|
+
python -c "import secrets; print(secrets.token_urlsafe(24))"
|
|
198
|
+
```
|
|
199
|
+
The tenant always comes from the credential, never from the request.
|
|
200
|
+
3. **Open it.** Open the service's `https://….onrender.com` URL, then **Create account**
|
|
201
|
+
with a generated invite code (or paste an operator key). The store's tables are created
|
|
202
|
+
and upgraded automatically on start.
|
|
203
|
+
|
|
204
|
+
**Which databases can be profiled from Render.** Only databases reachable from the
|
|
205
|
+
internet, such as cloud or hosted databases. A database on someone's laptop
|
|
206
|
+
(`localhost`) or office network cannot be reached from Render, and SSDO refuses
|
|
207
|
+
private addresses in production anyway. If the database has a firewall, allow
|
|
208
|
+
Render's outbound IP addresses (listed on the service's page in Render). Always use a
|
|
209
|
+
**read-only** database user.
|
|
210
|
+
|
|
211
|
+
**Free plan.** The free web service sleeps when idle, so the first request after a
|
|
212
|
+
break takes a while. Render's free PostgreSQL is time-limited. Check Render's current
|
|
213
|
+
terms, and move the database to a paid plan before relying on the history.
|
|
214
|
+
|
|
215
|
+
## The live workspace: watch your database while you code
|
|
216
|
+
|
|
217
|
+
In Development mode, the **Live** page follows a database while you work on it. Run this
|
|
218
|
+
next to your code (it asks for the database password once and keeps it on your machine):
|
|
219
|
+
|
|
220
|
+
```bash
|
|
221
|
+
SSDO_API_KEY=<your API key> ssdo watch --engine postgres --database myapp_dev --user dev \
|
|
222
|
+
--server https://solunex-ssdo.onrender.com
|
|
223
|
+
```
|
|
224
|
+
|
|
225
|
+
Every few seconds it reads the database's **structure** (never a row) and sends it to SSDO
|
|
226
|
+
**only when it changed**. Each change appears on the Live page and in the terminal: a table
|
|
227
|
+
added, a column's type changed, a link removed. Each is marked Fact or Inferred. SSDO never
|
|
228
|
+
connects to your database, which is why this works for `localhost` databases. See
|
|
229
|
+
`docs/specs/live-workspace.md`.
|
|
230
|
+
|
|
231
|
+
**A watch is private to whoever starts it**, even in a team workspace. Teammates see neither
|
|
232
|
+
the watch nor the runs it saves. Runs started in the console stay shared with the workspace.
|
|
233
|
+
**Live rooms** share a watch with chosen teammates (`docs/specs/team-live-workspace.md`):
|
|
234
|
+
a room lives inside one team workspace; its owner adds a teammate, who must accept; only a
|
|
235
|
+
watch's owner shares it in, from the console or with `ssdo watch … --room "Payments sprint"`.
|
|
236
|
+
Members see one merged feed ("seen on aisha@…'s `clinic_dev`"), each watch's runs, and
|
|
237
|
+
"compare with a teammate"; never a watch's host and port. Unsharing, leaving or closing the
|
|
238
|
+
room ends access at once. In the console: Live → **Rooms** (invitations, the room's
|
|
239
|
+
feed, compare); Live → **My watches** to share or stop sharing a watch. API:
|
|
240
|
+
`/v1/live/rooms…` and `POST /v1/watches/{id}/share`.
|
|
241
|
+
|
|
242
|
+
**Apply a change from your own terminal** (`docs/specs/build-together.md` §7.2). On one of your
|
|
243
|
+
own watches, open **Change this database**, write a structure change (ALTER, CREATE, DROP,
|
|
244
|
+
RENAME: never rows), press **Explain first**, then **Approve for my terminal**. Nothing runs
|
|
245
|
+
on the server: start the watcher with `--allow-apply`,
|
|
246
|
+
|
|
247
|
+
```bash
|
|
248
|
+
SSDO_API_KEY=<your API key> ssdo watch --engine mysql --database shop_dev --user dev \
|
|
249
|
+
--server https://solunex-ssdo.onrender.com --allow-apply
|
|
250
|
+
```
|
|
251
|
+
|
|
252
|
+
and it shows the approved change in your terminal, checks it again against the structure it
|
|
253
|
+
just read, and runs it with its own connection **only after you type `yes`**. Postgres runs
|
|
254
|
+
the whole change in one transaction; MySQL commits each statement, and says how many ran if
|
|
255
|
+
one fails. The console records what your terminal answered (applied, failed with the error's
|
|
256
|
+
code only, or declined), and the feed marks the change "Applied from a draft". Only a watch's
|
|
257
|
+
owner can approve, an approval waits 24 hours, and without a terminal nothing ever runs.
|
|
258
|
+
|
|
259
|
+
## Explain a migration: `ssdo snapshot` and `ssdo diff`
|
|
260
|
+
|
|
261
|
+
Before a change ships, see what it does to the database's structure. Take a snapshot,
|
|
262
|
+
apply your migrations with your own tool, take another, and compare:
|
|
263
|
+
|
|
264
|
+
```bash
|
|
265
|
+
export SSDO_DB_PASSWORD=... # read once, never written to the snapshot
|
|
266
|
+
ssdo snapshot --engine postgres --database myapp_dev --user dev --out before.json --label main
|
|
267
|
+
alembic upgrade head # or: python manage.py migrate, npx prisma migrate deploy, ...
|
|
268
|
+
ssdo snapshot --engine postgres --database myapp_dev --user dev --out after.json --label my-branch
|
|
269
|
+
ssdo diff before.json after.json # --format markdown | json, --out FILE
|
|
270
|
+
```
|
|
271
|
+
|
|
272
|
+
`ssdo diff` is fully offline (no server, no API key, no network). It lists every change as
|
|
273
|
+
Fact or Inferred, what pointed at whatever was removed or retyped, and **needs-care notes**:
|
|
274
|
+
values that a removed column discards, a narrower type, a NOT NULL that fails on existing
|
|
275
|
+
rows, a link that is no longer declared, a changed key, a removed index. Each note says
|
|
276
|
+
what SSDO cannot know, because it never reads data. SSDO never runs a migration.
|
|
277
|
+
|
|
278
|
+
It never fails by default. A team can choose to: `--fail-on can_lose_values,narrower_type`
|
|
279
|
+
(or `any`) exits 1 when the change has those kinds of notes. The kinds are
|
|
280
|
+
`can_lose_values`, `can_fail_on_rows`, `narrower_type`, `type_conversion`,
|
|
281
|
+
`breaks_declared_link`, `breaks_inferred_link`, `key_changed`, `index_removed`,
|
|
282
|
+
`possible_rename`. In CI, `--format markdown --context ci` gives the pull-request comment.
|
|
283
|
+
See `docs/specs/ci-schema-diff.md`.
|
|
284
|
+
|
|
285
|
+
### SSDO in CI: a comment on every pull request
|
|
286
|
+
|
|
287
|
+
Add this workflow to your repository. On each pull request it builds a throwaway database
|
|
288
|
+
with **your** migrations, first at the base and then with the pull request applied on top
|
|
289
|
+
(the path production takes), and posts one comment that it updates on every push:
|
|
290
|
+
|
|
291
|
+
```yaml
|
|
292
|
+
# .github/workflows/schema-diff.yml
|
|
293
|
+
name: schema diff
|
|
294
|
+
on: pull_request
|
|
295
|
+
permissions: { contents: read, pull-requests: write }
|
|
296
|
+
jobs:
|
|
297
|
+
schema:
|
|
298
|
+
runs-on: ubuntu-latest
|
|
299
|
+
services:
|
|
300
|
+
db:
|
|
301
|
+
image: postgres:16
|
|
302
|
+
env: { POSTGRES_PASSWORD: postgres, POSTGRES_DB: app }
|
|
303
|
+
ports: ["5432:5432"]
|
|
304
|
+
options: --health-cmd pg_isready --health-interval 2s --health-retries 30
|
|
305
|
+
steps:
|
|
306
|
+
- uses: saniabduljabbar619-create/solunex-intelligence/.github/actions/schema-diff@v1
|
|
307
|
+
with:
|
|
308
|
+
engine: postgres
|
|
309
|
+
database: app
|
|
310
|
+
user: postgres
|
|
311
|
+
password: postgres # a throwaway database, not a secret
|
|
312
|
+
setup: pip install -r requirements.txt
|
|
313
|
+
migrate: alembic upgrade head
|
|
314
|
+
# fail-on: can_lose_values,narrower_type (optional; off by default)
|
|
315
|
+
```
|
|
316
|
+
|
|
317
|
+
- **Your migrate command** sees `DATABASE_URL`, `SSDO_DB_HOST`/`_PORT`/`_NAME`/`_USER`/`_PASSWORD`,
|
|
318
|
+
and `SSDO_SIDE` (`base` or `head`). Examples: `python manage.py migrate`,
|
|
319
|
+
`npx prisma migrate deploy`, `flyway migrate`, or `psql "$DATABASE_URL" -f schema.sql`.
|
|
320
|
+
- **MySQL:** use a `mysql:8.4` service (`MYSQL_ROOT_PASSWORD`, `MYSQL_DATABASE`) and `engine: mysql`.
|
|
321
|
+
- **Pull requests from forks** get a read-only token, so GitHub refuses the comment. The
|
|
322
|
+
result is always in the job summary too.
|
|
323
|
+
- **Outputs:** `has-change`, `care-count`, `markdown-file` and `json-file`, for your own steps.
|
|
324
|
+
- **Nothing leaves the job:** SSDO is installed from the action itself and runs offline.
|
|
325
|
+
- **Versions:** `@v1` follows every compatible update of the action. Pin a full commit SHA
|
|
326
|
+
instead if your team prefers to review each update.
|
|
327
|
+
|
|
328
|
+
**Other CI systems** use the two commands directly. A GitLab example:
|
|
329
|
+
|
|
330
|
+
```yaml
|
|
331
|
+
schema-diff:
|
|
332
|
+
image: python:3.12
|
|
333
|
+
services: [{ name: postgres:16, alias: db }]
|
|
334
|
+
variables: { POSTGRES_PASSWORD: postgres, POSTGRES_DB: app, SSDO_DB_PASSWORD: postgres,
|
|
335
|
+
DATABASE_URL: "postgresql://postgres:postgres@db:5432/app" }
|
|
336
|
+
rules: [{ if: $CI_PIPELINE_SOURCE == "merge_request_event" }]
|
|
337
|
+
script:
|
|
338
|
+
- pip install "git+https://github.com/saniabduljabbar619-create/solunex-intelligence" -r requirements.txt
|
|
339
|
+
- git fetch origin $CI_MERGE_REQUEST_TARGET_BRANCH_NAME
|
|
340
|
+
- git checkout FETCH_HEAD && alembic upgrade head
|
|
341
|
+
- ssdo snapshot --engine postgres --host db --database app --user postgres --out base.json --label base
|
|
342
|
+
- git checkout $CI_COMMIT_SHA && alembic upgrade head
|
|
343
|
+
- ssdo snapshot --engine postgres --host db --database app --user postgres --out head.json --label head
|
|
344
|
+
- ssdo diff base.json head.json --format markdown --context ci | tee schema-diff.md
|
|
345
|
+
artifacts: { paths: [schema-diff.md] }
|
|
346
|
+
```
|
|
347
|
+
|
|
348
|
+
## Feedback and the Admin page
|
|
349
|
+
|
|
350
|
+
- **Message the Solunex team** (bottom of the left panel) sends an idea, a problem, a
|
|
351
|
+
question or a pricing question to the **people** at Solunex. It is deliberately styled
|
|
352
|
+
unlike Ask SSDO, and says so: a person reads it. Unlike an Ask question it is stored, so
|
|
353
|
+
it can be answered; it is never written to the server log. Five messages an hour per
|
|
354
|
+
workspace.
|
|
355
|
+
- **Admin** (admins only, `SSDO_ADMIN_EMAILS`): sign-ups and runs per day, totals, live
|
|
356
|
+
watches, the latest sign-ups, and the message inbox (mark read, done, reply by email).
|
|
357
|
+
**Counts only**: admins never see another workspace's databases, tables or runs.
|
|
358
|
+
- **Be told when a message arrives** (optional, set on Render → Environment):
|
|
359
|
+
- Telegram: create a bot with @BotFather, then set `SSDO_NOTIFY_TELEGRAM_TOKEN` (the bot
|
|
360
|
+
token) and `SSDO_NOTIFY_TELEGRAM_CHAT` (your chat id: message the bot once, then open
|
|
361
|
+
`https://api.telegram.org/bot<token>/getUpdates` and copy `chat.id`).
|
|
362
|
+
- Email: `SSDO_NOTIFY_EMAIL_TO`, `SSDO_SMTP_HOST`, `SSDO_SMTP_PORT` (587), `SSDO_SMTP_USER`,
|
|
363
|
+
`SSDO_SMTP_PASSWORD` (for Gmail, an app password), optionally `SSDO_SMTP_FROM`.
|
|
364
|
+
With neither set, messages still arrive on the Admin page.
|
|
365
|
+
|
|
366
|
+
## Accounts and API keys
|
|
367
|
+
|
|
368
|
+
- **Sign in** in the left panel. The console then authenticates with your session: no
|
|
369
|
+
key to paste. The session lives in this browser tab only, unless **Keep this
|
|
370
|
+
session** is on; it expires after 14 days, and **Sign out** ends it on the server.
|
|
371
|
+
- **API keys** (signed in → *API keys*) are for scripts and tools: send one as the
|
|
372
|
+
`X-API-Key` header. A key is shown **once** when created. SSDO stores only its SHA-256
|
|
373
|
+
fingerprint, so a lost key is revoked and replaced, never recovered. Keys cannot
|
|
374
|
+
create or revoke other keys; only a signed-in person can.
|
|
375
|
+
- **Passwords** are stored only as salted scrypt hashes. Sign-in and sign-up are
|
|
376
|
+
rate-limited, and an unknown email and a wrong password get the same answer.
|
|
377
|
+
- **Sign out** forgets this person in the browser: the remembered connections are cleared
|
|
378
|
+
and the page reloads, so nothing of theirs is left for the next person. Remembered
|
|
379
|
+
connections belong to one account; a different account signing in never sees them.
|
|
380
|
+
- **Local development** with no `SSDO_ADMIN_EMAILS` and no `SSDO_INVITE_CODES`: sign-up
|
|
381
|
+
is open (each account private), and the dev key `dev-local-key` still works.
|
|
382
|
+
|
|
383
|
+
## Tests
|
|
384
|
+
|
|
385
|
+
```bash
|
|
386
|
+
python -m pytest
|
|
387
|
+
```
|
|
388
|
+
|
|
389
|
+
The Postgres profiler also has live tests against a real server (skipped by default).
|
|
390
|
+
They create a throwaway database, profile it as a read-only user, and drop it:
|
|
391
|
+
|
|
392
|
+
```bash
|
|
393
|
+
SSDO_TEST_PG_ADMIN="host=127.0.0.1 user=postgres password=..." python -m pytest tests/profiler
|
|
394
|
+
```
|
|
395
|
+
|
|
396
|
+
The TLS modes have live tests too, against TLS-enabled servers whose certificate names
|
|
397
|
+
`localhost` and is signed by the CA at `ca=` (see `tests/profiler/test_tls.py`):
|
|
398
|
+
|
|
399
|
+
```bash
|
|
400
|
+
SSDO_TEST_MYSQL_TLS="host=localhost port=3306 user=ro password=... database=shop ca=ca.pem" \
|
|
401
|
+
SSDO_TEST_PG_TLS="host=localhost port=5432 user=ro password=... database=postgres ca=ca.pem" \
|
|
402
|
+
SSDO_TEST_MYSQL_NOTLS="host=127.0.0.1 port=3308 user=ro password=... database=shop" \
|
|
403
|
+
python -m pytest tests/profiler/test_tls.py
|
|
404
|
+
```
|