pythia-plsql 0.3.0__tar.gz → 0.3.2__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_plsql-0.3.0/scripts/pythia_plsql.egg-info → pythia_plsql-0.3.2}/PKG-INFO +1 -1
- {pythia_plsql-0.3.0 → pythia_plsql-0.3.2}/pyproject.toml +1 -1
- {pythia_plsql-0.3.0 → pythia_plsql-0.3.2}/scripts/pythia.py +60 -13
- {pythia_plsql-0.3.0 → pythia_plsql-0.3.2/scripts/pythia_plsql.egg-info}/PKG-INFO +1 -1
- {pythia_plsql-0.3.0 → pythia_plsql-0.3.2}/tests/test_install.py +23 -0
- {pythia_plsql-0.3.0 → pythia_plsql-0.3.2}/tests/test_phase3.py +29 -0
- {pythia_plsql-0.3.0 → pythia_plsql-0.3.2}/LICENSE +0 -0
- {pythia_plsql-0.3.0 → pythia_plsql-0.3.2}/README.md +0 -0
- {pythia_plsql-0.3.0 → pythia_plsql-0.3.2}/queries/compile-errors.sql +0 -0
- {pythia_plsql-0.3.0 → pythia_plsql-0.3.2}/queries/dependencies.sql +0 -0
- {pythia_plsql-0.3.0 → pythia_plsql-0.3.2}/queries/impact.sql +0 -0
- {pythia_plsql-0.3.0 → pythia_plsql-0.3.2}/queries/invalid-objects.sql +0 -0
- {pythia_plsql-0.3.0 → pythia_plsql-0.3.2}/queries/name-occupants.sql +0 -0
- {pythia_plsql-0.3.0 → pythia_plsql-0.3.2}/queries/object-source.sql +0 -0
- {pythia_plsql-0.3.0 → pythia_plsql-0.3.2}/queries/plscope-enabled.sql +0 -0
- {pythia_plsql-0.3.0 → pythia_plsql-0.3.2}/queries/plscope-statements.sql +0 -0
- {pythia_plsql-0.3.0 → pythia_plsql-0.3.2}/queries/plscope-usages.sql +0 -0
- {pythia_plsql-0.3.0 → pythia_plsql-0.3.2}/queries/session-privileges.sql +0 -0
- {pythia_plsql-0.3.0 → pythia_plsql-0.3.2}/queries/similar-candidates.sql +0 -0
- {pythia_plsql-0.3.0 → pythia_plsql-0.3.2}/queries/source.sql +0 -0
- {pythia_plsql-0.3.0 → pythia_plsql-0.3.2}/scripts/pythia_plsql.egg-info/SOURCES.txt +0 -0
- {pythia_plsql-0.3.0 → pythia_plsql-0.3.2}/scripts/pythia_plsql.egg-info/dependency_links.txt +0 -0
- {pythia_plsql-0.3.0 → pythia_plsql-0.3.2}/scripts/pythia_plsql.egg-info/entry_points.txt +0 -0
- {pythia_plsql-0.3.0 → pythia_plsql-0.3.2}/scripts/pythia_plsql.egg-info/requires.txt +0 -0
- {pythia_plsql-0.3.0 → pythia_plsql-0.3.2}/scripts/pythia_plsql.egg-info/top_level.txt +0 -0
- {pythia_plsql-0.3.0 → pythia_plsql-0.3.2}/setup.cfg +0 -0
- {pythia_plsql-0.3.0 → pythia_plsql-0.3.2}/skills/pythia-apply/SKILL.md +0 -0
- {pythia_plsql-0.3.0 → pythia_plsql-0.3.2}/skills/pythia-explore/SKILL.md +0 -0
- {pythia_plsql-0.3.0 → pythia_plsql-0.3.2}/skills/pythia-explore/reference/data-dictionary.md +0 -0
- {pythia_plsql-0.3.0 → pythia_plsql-0.3.2}/skills/pythia-impact/SKILL.md +0 -0
- {pythia_plsql-0.3.0 → pythia_plsql-0.3.2}/skills/pythia-review/SKILL.md +0 -0
- {pythia_plsql-0.3.0 → pythia_plsql-0.3.2}/skills/pythia-review/reference/antipatterns.md +0 -0
- {pythia_plsql-0.3.0 → pythia_plsql-0.3.2}/skills/pythia-setup/SKILL.md +0 -0
- {pythia_plsql-0.3.0 → pythia_plsql-0.3.2}/skills/pythia-skill-author/SKILL.md +0 -0
- {pythia_plsql-0.3.0 → pythia_plsql-0.3.2}/skills/pythia-write/SKILL.md +0 -0
- {pythia_plsql-0.3.0 → pythia_plsql-0.3.2}/skills/pythia-write/reference/patterns.md +0 -0
- {pythia_plsql-0.3.0 → pythia_plsql-0.3.2}/tests/test_phase1.py +0 -0
- {pythia_plsql-0.3.0 → pythia_plsql-0.3.2}/tests/test_phase2.py +0 -0
- {pythia_plsql-0.3.0 → pythia_plsql-0.3.2}/tests/test_phase5.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: pythia-plsql
|
|
3
|
-
Version: 0.3.
|
|
3
|
+
Version: 0.3.2
|
|
4
4
|
Summary: PL/SQL development for AI agents on Oracle Database - expert data-dictionary queries, impact analysis, and a snapshot-verified write path with honest rollback.
|
|
5
5
|
License: MIT
|
|
6
6
|
Project-URL: Homepage, https://github.com/thaildhe172591/pythia
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "pythia-plsql"
|
|
7
|
-
version = "0.3.
|
|
7
|
+
version = "0.3.2"
|
|
8
8
|
description = "PL/SQL development for AI agents on Oracle Database - expert data-dictionary queries, impact analysis, and a snapshot-verified write path with honest rollback."
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
license = { text = "MIT" }
|
|
@@ -201,17 +201,38 @@ def invocation():
|
|
|
201
201
|
return pathlib.Path(prog).name
|
|
202
202
|
|
|
203
203
|
|
|
204
|
+
HEADLESS_YES_MSG = (
|
|
205
|
+
"--yes is the developer's flag, and no terminal is attached to this "
|
|
206
|
+
"session.\nAgents preview, relay the diff and the impact verbatim, "
|
|
207
|
+
"STOP, and pass --confirm <token> only after the developer's explicit "
|
|
208
|
+
"approval in chat.\n(Real pipelines set PYTHIA_CI=1.)")
|
|
209
|
+
|
|
210
|
+
|
|
204
211
|
def human_at_the_keyboard():
|
|
205
|
-
"""True only when a person is typing
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
212
|
+
"""True only when a person is typing at a real console.
|
|
213
|
+
|
|
214
|
+
isatty() alone is not enough on Windows: NUL is a character device, so
|
|
215
|
+
a subprocess launched with stdin=DEVNULL reports isatty() == True and
|
|
216
|
+
would sail straight through this gate. Only a real console answers
|
|
217
|
+
GetConsoleMode, so ask that too. PYTHIA_CI=1 is the documented escape
|
|
218
|
+
for real pipelines."""
|
|
209
219
|
if os.environ.get("PYTHIA_CI"):
|
|
210
220
|
return True
|
|
211
221
|
try:
|
|
212
|
-
|
|
222
|
+
if not sys.stdin.isatty():
|
|
223
|
+
return False
|
|
213
224
|
except (AttributeError, ValueError):
|
|
214
225
|
return False
|
|
226
|
+
if os.name == "nt":
|
|
227
|
+
try:
|
|
228
|
+
import ctypes
|
|
229
|
+
handle = ctypes.windll.kernel32.GetStdHandle(-10) # STD_INPUT
|
|
230
|
+
mode = ctypes.c_ulong()
|
|
231
|
+
return bool(ctypes.windll.kernel32.GetConsoleMode(
|
|
232
|
+
handle, ctypes.byref(mode)))
|
|
233
|
+
except Exception: # noqa: BLE001 — any failure means "not a console"
|
|
234
|
+
return False
|
|
235
|
+
return True
|
|
215
236
|
|
|
216
237
|
|
|
217
238
|
def forbid_write_flag(argv):
|
|
@@ -610,6 +631,12 @@ def load_settings(root):
|
|
|
610
631
|
sys.exit(f"Cannot parse {path}: {e}")
|
|
611
632
|
|
|
612
633
|
|
|
634
|
+
def undo_group_action(ns):
|
|
635
|
+
"""Undoing a CREATE is a DROP, which is `structural` — so the policy on
|
|
636
|
+
that group decides whether the restore command we print can run."""
|
|
637
|
+
return load_policy(ns.project_root)["structural"][0]
|
|
638
|
+
|
|
639
|
+
|
|
613
640
|
def journal_root(root):
|
|
614
641
|
return pathlib.Path(root) / CONFIG_DIR / "journal"
|
|
615
642
|
|
|
@@ -1124,11 +1151,7 @@ def run_apply(conn, schema, ns, file_text, origin=None):
|
|
|
1124
1151
|
db_source = "".join(cell(r[0]) for r in rows)
|
|
1125
1152
|
token = apply_token(otype, name, file_text, db_source)
|
|
1126
1153
|
if ns.yes and not human_at_the_keyboard():
|
|
1127
|
-
sys.exit(
|
|
1128
|
-
"to this session.\nAgents preview, relay the diff and the "
|
|
1129
|
-
"impact verbatim, STOP, and pass --confirm <token> only "
|
|
1130
|
-
"after the developer's explicit approval in chat.\n"
|
|
1131
|
-
"(Real pipelines set PYTHIA_CI=1.)")
|
|
1154
|
+
sys.exit(HEADLESS_YES_MSG)
|
|
1132
1155
|
confirmed = bool(ns.yes) or ns.confirm == token
|
|
1133
1156
|
if ns.confirm and ns.confirm != token:
|
|
1134
1157
|
sys.exit("The confirmation token does not match: the file or the "
|
|
@@ -1234,6 +1257,9 @@ def run_apply(conn, schema, ns, file_text, origin=None):
|
|
|
1234
1257
|
"type": otype, "errors": [list(r) for r in own_errors],
|
|
1235
1258
|
"newly_invalid": [list(x) for x in broke],
|
|
1236
1259
|
"restore": f"{invocation()} journal restore {entry}",
|
|
1260
|
+
"restore_is_drop": created,
|
|
1261
|
+
"restore_blocked_by_policy": (
|
|
1262
|
+
created and undo_group_action(ns) == "deny"),
|
|
1237
1263
|
"exit": 0 if ok else 3}))
|
|
1238
1264
|
else:
|
|
1239
1265
|
en = getattr(ns, "color", False)
|
|
@@ -1254,9 +1280,21 @@ def run_apply(conn, schema, ns, file_text, origin=None):
|
|
|
1254
1280
|
print(f" {len(broke)} objects were VALID before and are INVALID now:")
|
|
1255
1281
|
for n2, t2 in broke:
|
|
1256
1282
|
print(paint(f" {n2} ({t2})", "red", en))
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
|
|
1283
|
+
if created:
|
|
1284
|
+
print("\n To undo — note: undo means DROPPING it "
|
|
1285
|
+
"(it did not exist before):")
|
|
1286
|
+
else:
|
|
1287
|
+
print("\n To undo:")
|
|
1288
|
+
print(" " + paint(f"{invocation()} journal restore {entry}",
|
|
1289
|
+
"cyan", en))
|
|
1290
|
+
if created and undo_group_action(ns) == "deny":
|
|
1291
|
+
# the honest half: that restore is a DROP, DROP is structural,
|
|
1292
|
+
# and structural is deny — so the line above would be refused
|
|
1293
|
+
print(paint(" ...but that restore is a DROP, and structural is "
|
|
1294
|
+
"set to deny, so it will be refused.", "yellow", en))
|
|
1295
|
+
print(paint(" The developer decides: "
|
|
1296
|
+
f"{invocation()} policy set structural confirm",
|
|
1297
|
+
"yellow", en))
|
|
1260
1298
|
return 0 if ok else 3
|
|
1261
1299
|
|
|
1262
1300
|
|
|
@@ -1842,6 +1880,15 @@ def main(argv=None):
|
|
|
1842
1880
|
except AttributeError:
|
|
1843
1881
|
pass
|
|
1844
1882
|
ns = build_parser().parse_args(argv)
|
|
1883
|
+
# refusals that need no database come first — an unreachable or locked
|
|
1884
|
+
# connection must never mask them
|
|
1885
|
+
if ns.command == "sql" and not is_readonly_sql(
|
|
1886
|
+
" ".join(ns.statement).strip().rstrip(";")):
|
|
1887
|
+
sys.exit("Only SELECT/WITH statements are allowed; sql is read-only."
|
|
1888
|
+
f"\nThe write path is `{invocation()} apply <file>` — "
|
|
1889
|
+
"snapshot, preview and verify included.")
|
|
1890
|
+
if getattr(ns, "yes", False) and not human_at_the_keyboard():
|
|
1891
|
+
sys.exit(HEADLESS_YES_MSG)
|
|
1845
1892
|
ns.color = color_enabled() and not ns.json
|
|
1846
1893
|
if ns.color and os.name == "nt":
|
|
1847
1894
|
# Constant empty string, never user input — safe from injection. This
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: pythia-plsql
|
|
3
|
-
Version: 0.3.
|
|
3
|
+
Version: 0.3.2
|
|
4
4
|
Summary: PL/SQL development for AI agents on Oracle Database - expert data-dictionary queries, impact analysis, and a snapshot-verified write path with honest rollback.
|
|
5
5
|
License: MIT
|
|
6
6
|
Project-URL: Homepage, https://github.com/thaildhe172591/pythia
|
|
@@ -122,6 +122,29 @@ def test_agent_user_json_saves_the_same_password_it_prints():
|
|
|
122
122
|
os.chdir(cwd)
|
|
123
123
|
|
|
124
124
|
|
|
125
|
+
def test_devnull_stdin_is_not_a_human():
|
|
126
|
+
"""The gate must hold for a real subprocess, not just in theory. On
|
|
127
|
+
Windows NUL is a character device and isatty() says True for it, so a
|
|
128
|
+
stdin=DEVNULL child would otherwise self-approve writes."""
|
|
129
|
+
import os
|
|
130
|
+
import subprocess
|
|
131
|
+
env = {k: v for k, v in os.environ.items() if k != "PYTHIA_CI"}
|
|
132
|
+
with tempfile.TemporaryDirectory() as td:
|
|
133
|
+
(pathlib.Path(td) / ".pythia").mkdir()
|
|
134
|
+
(pathlib.Path(td) / ".pythia" / "connections.json").write_text(
|
|
135
|
+
json.dumps({"dev": {"host": "h", "user": "U", "password": "p",
|
|
136
|
+
"schema": "U"}}), encoding="utf-8")
|
|
137
|
+
env["PYTHONPATH"] = str(ROOT / "scripts") # -m pythia from the repo
|
|
138
|
+
r = subprocess.run(
|
|
139
|
+
[sys.executable, "-m", "pythia", "policy", "set", "structural",
|
|
140
|
+
"allow"],
|
|
141
|
+
cwd=td, env=env, stdin=subprocess.DEVNULL, text=True,
|
|
142
|
+
capture_output=True)
|
|
143
|
+
assert r.returncode != 0, r.stdout + r.stderr
|
|
144
|
+
assert "developer" in (r.stdout + r.stderr).lower()
|
|
145
|
+
assert not (pathlib.Path(td) / ".pythia" / "policy.json").is_file()
|
|
146
|
+
|
|
147
|
+
|
|
125
148
|
def test_one_version_across_every_manifest():
|
|
126
149
|
"""pyproject, plugin.json, marketplace.json and npm/package.json must all
|
|
127
150
|
carry the same version — the release workflow publishes from one tag."""
|
|
@@ -658,6 +658,35 @@ def test_headless_policy_loosening_is_refused_tightening_allowed():
|
|
|
658
658
|
assert pythia.load_policy(td)["plsql_source"][0] == "deny"
|
|
659
659
|
|
|
660
660
|
|
|
661
|
+
def test_report_admits_a_created_objects_undo_is_blocked():
|
|
662
|
+
"""pythia exists so nothing lies about rollback. Undoing a CREATE is a
|
|
663
|
+
DROP, DROP is structural, and structural is deny by default — so the
|
|
664
|
+
printed restore command would be refused. Say so, in the same breath."""
|
|
665
|
+
import contextlib
|
|
666
|
+
import io
|
|
667
|
+
with tempfile.TemporaryDirectory() as td:
|
|
668
|
+
conn = FakeConn(base_script(db_source="")) # object did not exist
|
|
669
|
+
buf = io.StringIO()
|
|
670
|
+
with contextlib.redirect_stdout(buf):
|
|
671
|
+
pythia.run_apply(conn, "APP", apply_ns(td, file="f.sql", yes=True),
|
|
672
|
+
NEW_FILE)
|
|
673
|
+
out = buf.getvalue().lower()
|
|
674
|
+
assert "dropping it" in out
|
|
675
|
+
assert "will be refused" in out, out[-400:]
|
|
676
|
+
assert "policy set structural confirm" in out
|
|
677
|
+
# and once the developer has loosened it, the warning goes away
|
|
678
|
+
with tempfile.TemporaryDirectory() as td:
|
|
679
|
+
pathlib.Path(td, ".pythia").mkdir()
|
|
680
|
+
pathlib.Path(td, ".pythia", "policy.json").write_text(
|
|
681
|
+
'{"structural": "confirm"}', encoding="utf-8")
|
|
682
|
+
conn = FakeConn(base_script(db_source=""))
|
|
683
|
+
buf = io.StringIO()
|
|
684
|
+
with contextlib.redirect_stdout(buf):
|
|
685
|
+
pythia.run_apply(conn, "APP", apply_ns(td, file="f.sql", yes=True),
|
|
686
|
+
NEW_FILE)
|
|
687
|
+
assert "will be refused" not in buf.getvalue().lower()
|
|
688
|
+
|
|
689
|
+
|
|
661
690
|
def main():
|
|
662
691
|
failed = 0
|
|
663
692
|
for name, fn in sorted(globals().items()):
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{pythia_plsql-0.3.0 → pythia_plsql-0.3.2}/scripts/pythia_plsql.egg-info/dependency_links.txt
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{pythia_plsql-0.3.0 → pythia_plsql-0.3.2}/skills/pythia-explore/reference/data-dictionary.md
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|