pythia-plsql 0.3.1__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.
Files changed (39) hide show
  1. {pythia_plsql-0.3.1/scripts/pythia_plsql.egg-info → pythia_plsql-0.3.2}/PKG-INFO +1 -1
  2. {pythia_plsql-0.3.1 → pythia_plsql-0.3.2}/pyproject.toml +1 -1
  3. {pythia_plsql-0.3.1 → pythia_plsql-0.3.2}/scripts/pythia.py +43 -8
  4. {pythia_plsql-0.3.1 → pythia_plsql-0.3.2/scripts/pythia_plsql.egg-info}/PKG-INFO +1 -1
  5. {pythia_plsql-0.3.1 → pythia_plsql-0.3.2}/tests/test_install.py +23 -0
  6. {pythia_plsql-0.3.1 → pythia_plsql-0.3.2}/tests/test_phase3.py +29 -0
  7. {pythia_plsql-0.3.1 → pythia_plsql-0.3.2}/LICENSE +0 -0
  8. {pythia_plsql-0.3.1 → pythia_plsql-0.3.2}/README.md +0 -0
  9. {pythia_plsql-0.3.1 → pythia_plsql-0.3.2}/queries/compile-errors.sql +0 -0
  10. {pythia_plsql-0.3.1 → pythia_plsql-0.3.2}/queries/dependencies.sql +0 -0
  11. {pythia_plsql-0.3.1 → pythia_plsql-0.3.2}/queries/impact.sql +0 -0
  12. {pythia_plsql-0.3.1 → pythia_plsql-0.3.2}/queries/invalid-objects.sql +0 -0
  13. {pythia_plsql-0.3.1 → pythia_plsql-0.3.2}/queries/name-occupants.sql +0 -0
  14. {pythia_plsql-0.3.1 → pythia_plsql-0.3.2}/queries/object-source.sql +0 -0
  15. {pythia_plsql-0.3.1 → pythia_plsql-0.3.2}/queries/plscope-enabled.sql +0 -0
  16. {pythia_plsql-0.3.1 → pythia_plsql-0.3.2}/queries/plscope-statements.sql +0 -0
  17. {pythia_plsql-0.3.1 → pythia_plsql-0.3.2}/queries/plscope-usages.sql +0 -0
  18. {pythia_plsql-0.3.1 → pythia_plsql-0.3.2}/queries/session-privileges.sql +0 -0
  19. {pythia_plsql-0.3.1 → pythia_plsql-0.3.2}/queries/similar-candidates.sql +0 -0
  20. {pythia_plsql-0.3.1 → pythia_plsql-0.3.2}/queries/source.sql +0 -0
  21. {pythia_plsql-0.3.1 → pythia_plsql-0.3.2}/scripts/pythia_plsql.egg-info/SOURCES.txt +0 -0
  22. {pythia_plsql-0.3.1 → pythia_plsql-0.3.2}/scripts/pythia_plsql.egg-info/dependency_links.txt +0 -0
  23. {pythia_plsql-0.3.1 → pythia_plsql-0.3.2}/scripts/pythia_plsql.egg-info/entry_points.txt +0 -0
  24. {pythia_plsql-0.3.1 → pythia_plsql-0.3.2}/scripts/pythia_plsql.egg-info/requires.txt +0 -0
  25. {pythia_plsql-0.3.1 → pythia_plsql-0.3.2}/scripts/pythia_plsql.egg-info/top_level.txt +0 -0
  26. {pythia_plsql-0.3.1 → pythia_plsql-0.3.2}/setup.cfg +0 -0
  27. {pythia_plsql-0.3.1 → pythia_plsql-0.3.2}/skills/pythia-apply/SKILL.md +0 -0
  28. {pythia_plsql-0.3.1 → pythia_plsql-0.3.2}/skills/pythia-explore/SKILL.md +0 -0
  29. {pythia_plsql-0.3.1 → pythia_plsql-0.3.2}/skills/pythia-explore/reference/data-dictionary.md +0 -0
  30. {pythia_plsql-0.3.1 → pythia_plsql-0.3.2}/skills/pythia-impact/SKILL.md +0 -0
  31. {pythia_plsql-0.3.1 → pythia_plsql-0.3.2}/skills/pythia-review/SKILL.md +0 -0
  32. {pythia_plsql-0.3.1 → pythia_plsql-0.3.2}/skills/pythia-review/reference/antipatterns.md +0 -0
  33. {pythia_plsql-0.3.1 → pythia_plsql-0.3.2}/skills/pythia-setup/SKILL.md +0 -0
  34. {pythia_plsql-0.3.1 → pythia_plsql-0.3.2}/skills/pythia-skill-author/SKILL.md +0 -0
  35. {pythia_plsql-0.3.1 → pythia_plsql-0.3.2}/skills/pythia-write/SKILL.md +0 -0
  36. {pythia_plsql-0.3.1 → pythia_plsql-0.3.2}/skills/pythia-write/reference/patterns.md +0 -0
  37. {pythia_plsql-0.3.1 → pythia_plsql-0.3.2}/tests/test_phase1.py +0 -0
  38. {pythia_plsql-0.3.1 → pythia_plsql-0.3.2}/tests/test_phase2.py +0 -0
  39. {pythia_plsql-0.3.1 → 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.1
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.1"
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" }
@@ -209,16 +209,30 @@ HEADLESS_YES_MSG = (
209
209
 
210
210
 
211
211
  def human_at_the_keyboard():
212
- """True only when a person is typing: stdin is a TTY. An agent driving
213
- the CLI through a subprocess has no TTY and cannot fake one — which is
214
- exactly why the loosening actions below key on this. PYTHIA_CI=1 is the
215
- documented escape for real pipelines."""
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."""
216
219
  if os.environ.get("PYTHIA_CI"):
217
220
  return True
218
221
  try:
219
- return sys.stdin.isatty()
222
+ if not sys.stdin.isatty():
223
+ return False
220
224
  except (AttributeError, ValueError):
221
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
222
236
 
223
237
 
224
238
  def forbid_write_flag(argv):
@@ -617,6 +631,12 @@ def load_settings(root):
617
631
  sys.exit(f"Cannot parse {path}: {e}")
618
632
 
619
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
+
620
640
  def journal_root(root):
621
641
  return pathlib.Path(root) / CONFIG_DIR / "journal"
622
642
 
@@ -1237,6 +1257,9 @@ def run_apply(conn, schema, ns, file_text, origin=None):
1237
1257
  "type": otype, "errors": [list(r) for r in own_errors],
1238
1258
  "newly_invalid": [list(x) for x in broke],
1239
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"),
1240
1263
  "exit": 0 if ok else 3}))
1241
1264
  else:
1242
1265
  en = getattr(ns, "color", False)
@@ -1257,9 +1280,21 @@ def run_apply(conn, schema, ns, file_text, origin=None):
1257
1280
  print(f" {len(broke)} objects were VALID before and are INVALID now:")
1258
1281
  for n2, t2 in broke:
1259
1282
  print(paint(f" {n2} ({t2})", "red", en))
1260
- undo = "dropping it (it did not exist before)" if created else None
1261
- print(f"\n To undo{' — note: undo means ' + undo if undo else ''}:")
1262
- print(" " + paint(f"{invocation()} journal restore {entry}", "cyan", en))
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))
1263
1298
  return 0 if ok else 3
1264
1299
 
1265
1300
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pythia-plsql
3
- Version: 0.3.1
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