pythia-plsql 0.1.2__tar.gz → 0.1.3__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.1.2/scripts/pythia_plsql.egg-info → pythia_plsql-0.1.3}/PKG-INFO +2 -2
- {pythia_plsql-0.1.2 → pythia_plsql-0.1.3}/README.md +1 -1
- {pythia_plsql-0.1.2 → pythia_plsql-0.1.3}/pyproject.toml +1 -1
- {pythia_plsql-0.1.2 → pythia_plsql-0.1.3}/scripts/pythia.py +81 -2
- {pythia_plsql-0.1.2 → pythia_plsql-0.1.3/scripts/pythia_plsql.egg-info}/PKG-INFO +2 -2
- {pythia_plsql-0.1.2 → pythia_plsql-0.1.3}/tests/test_phase1.py +39 -0
- {pythia_plsql-0.1.2 → pythia_plsql-0.1.3}/LICENSE +0 -0
- {pythia_plsql-0.1.2 → pythia_plsql-0.1.3}/queries/compile-errors.sql +0 -0
- {pythia_plsql-0.1.2 → pythia_plsql-0.1.3}/queries/dependencies.sql +0 -0
- {pythia_plsql-0.1.2 → pythia_plsql-0.1.3}/queries/impact.sql +0 -0
- {pythia_plsql-0.1.2 → pythia_plsql-0.1.3}/queries/invalid-objects.sql +0 -0
- {pythia_plsql-0.1.2 → pythia_plsql-0.1.3}/queries/name-occupants.sql +0 -0
- {pythia_plsql-0.1.2 → pythia_plsql-0.1.3}/queries/object-source.sql +0 -0
- {pythia_plsql-0.1.2 → pythia_plsql-0.1.3}/queries/plscope-enabled.sql +0 -0
- {pythia_plsql-0.1.2 → pythia_plsql-0.1.3}/queries/plscope-statements.sql +0 -0
- {pythia_plsql-0.1.2 → pythia_plsql-0.1.3}/queries/plscope-usages.sql +0 -0
- {pythia_plsql-0.1.2 → pythia_plsql-0.1.3}/queries/session-privileges.sql +0 -0
- {pythia_plsql-0.1.2 → pythia_plsql-0.1.3}/queries/similar-candidates.sql +0 -0
- {pythia_plsql-0.1.2 → pythia_plsql-0.1.3}/queries/source.sql +0 -0
- {pythia_plsql-0.1.2 → pythia_plsql-0.1.3}/scripts/pythia_plsql.egg-info/SOURCES.txt +0 -0
- {pythia_plsql-0.1.2 → pythia_plsql-0.1.3}/scripts/pythia_plsql.egg-info/dependency_links.txt +0 -0
- {pythia_plsql-0.1.2 → pythia_plsql-0.1.3}/scripts/pythia_plsql.egg-info/entry_points.txt +0 -0
- {pythia_plsql-0.1.2 → pythia_plsql-0.1.3}/scripts/pythia_plsql.egg-info/requires.txt +0 -0
- {pythia_plsql-0.1.2 → pythia_plsql-0.1.3}/scripts/pythia_plsql.egg-info/top_level.txt +0 -0
- {pythia_plsql-0.1.2 → pythia_plsql-0.1.3}/setup.cfg +0 -0
- {pythia_plsql-0.1.2 → pythia_plsql-0.1.3}/skills/plsql-apply/SKILL.md +0 -0
- {pythia_plsql-0.1.2 → pythia_plsql-0.1.3}/skills/plsql-explore/SKILL.md +0 -0
- {pythia_plsql-0.1.2 → pythia_plsql-0.1.3}/skills/plsql-explore/reference/data-dictionary.md +0 -0
- {pythia_plsql-0.1.2 → pythia_plsql-0.1.3}/skills/plsql-impact/SKILL.md +0 -0
- {pythia_plsql-0.1.2 → pythia_plsql-0.1.3}/skills/plsql-review/SKILL.md +0 -0
- {pythia_plsql-0.1.2 → pythia_plsql-0.1.3}/skills/plsql-review/reference/antipatterns.md +0 -0
- {pythia_plsql-0.1.2 → pythia_plsql-0.1.3}/skills/plsql-setup/SKILL.md +0 -0
- {pythia_plsql-0.1.2 → pythia_plsql-0.1.3}/skills/plsql-skill-author/SKILL.md +0 -0
- {pythia_plsql-0.1.2 → pythia_plsql-0.1.3}/skills/plsql-write/SKILL.md +0 -0
- {pythia_plsql-0.1.2 → pythia_plsql-0.1.3}/skills/plsql-write/reference/patterns.md +0 -0
- {pythia_plsql-0.1.2 → pythia_plsql-0.1.3}/tests/test_install.py +0 -0
- {pythia_plsql-0.1.2 → pythia_plsql-0.1.3}/tests/test_phase2.py +0 -0
- {pythia_plsql-0.1.2 → pythia_plsql-0.1.3}/tests/test_phase3.py +0 -0
- {pythia_plsql-0.1.2 → pythia_plsql-0.1.3}/tests/test_phase5.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: pythia-plsql
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.3
|
|
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
|
|
@@ -103,7 +103,7 @@ and WSL are all CI-tested.
|
|
|
103
103
|
| `ls` find objects | `impact` what depends on it | `journal` list · diff · export · restore |
|
|
104
104
|
| `src` source, compiler line numbers | `errors` compile errors, line:col | `policy` show · set |
|
|
105
105
|
| `args` signatures | `invalid` everything broken | `unistr` exact non-ASCII literals |
|
|
106
|
-
| `ddl` via DBMS_METADATA | `plscope` exact identifier usages | |
|
|
106
|
+
| `ddl` via DBMS_METADATA | `plscope` exact identifier usages | `agent-user` least-privilege setup |
|
|
107
107
|
| `cols` columns + types | `similar` programs named like this | |
|
|
108
108
|
| `grep` search all source | | |
|
|
109
109
|
| `sql` free query (SELECT/WITH only) | | |
|
|
@@ -89,7 +89,7 @@ and WSL are all CI-tested.
|
|
|
89
89
|
| `ls` find objects | `impact` what depends on it | `journal` list · diff · export · restore |
|
|
90
90
|
| `src` source, compiler line numbers | `errors` compile errors, line:col | `policy` show · set |
|
|
91
91
|
| `args` signatures | `invalid` everything broken | `unistr` exact non-ASCII literals |
|
|
92
|
-
| `ddl` via DBMS_METADATA | `plscope` exact identifier usages | |
|
|
92
|
+
| `ddl` via DBMS_METADATA | `plscope` exact identifier usages | `agent-user` least-privilege setup |
|
|
93
93
|
| `cols` columns + types | `similar` programs named like this | |
|
|
94
94
|
| `grep` search all source | | |
|
|
95
95
|
| `sql` free query (SELECT/WITH only) | | |
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "pythia-plsql"
|
|
7
|
-
version = "0.1.
|
|
7
|
+
version = "0.1.3"
|
|
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" }
|
|
@@ -43,6 +43,7 @@ you always know whether you saw everything.
|
|
|
43
43
|
pythia policy
|
|
44
44
|
pythia install
|
|
45
45
|
pythia unistr "Nhóm không được để trống"
|
|
46
|
+
pythia agent-user --save
|
|
46
47
|
"""
|
|
47
48
|
import argparse
|
|
48
49
|
import json
|
|
@@ -1301,6 +1302,76 @@ def cmd_conventions(conn, schema, ns):
|
|
|
1301
1302
|
print(f"\nProse rules for agents: {md}")
|
|
1302
1303
|
|
|
1303
1304
|
|
|
1305
|
+
AGENT_USER_SQL = """\
|
|
1306
|
+
-- Least-privilege agent credential for schema {owner} — run as a DBA.
|
|
1307
|
+
-- Proxy authentication: the agent logs in with its OWN password but works
|
|
1308
|
+
-- inside {owner}; it never learns the owner password, owns nothing, and
|
|
1309
|
+
-- revocation is one statement. Deliberately absent: DBA, RESOURCE, ANY
|
|
1310
|
+
-- privileges, utility grants — the agent needs none of them to develop
|
|
1311
|
+
-- PL/SQL, and every extra grant widens the blast radius.
|
|
1312
|
+
|
|
1313
|
+
CREATE USER {agent} IDENTIFIED BY "{password}";
|
|
1314
|
+
GRANT CREATE SESSION TO {agent};
|
|
1315
|
+
ALTER USER {owner} GRANT CONNECT THROUGH {agent};
|
|
1316
|
+
|
|
1317
|
+
-- Cut the agent off later (owner untouched):
|
|
1318
|
+
-- ALTER USER {owner} REVOKE CONNECT THROUGH {agent};
|
|
1319
|
+
-- Fresh owner schema instead? See examples/agent-user-setup.example.sql.
|
|
1320
|
+
"""
|
|
1321
|
+
|
|
1322
|
+
|
|
1323
|
+
def agent_password():
|
|
1324
|
+
"""Random password satisfying common Oracle verify functions: letter
|
|
1325
|
+
first, upper+lower+digit+special, 16+ chars. Always quoted in SQL."""
|
|
1326
|
+
import secrets
|
|
1327
|
+
import string
|
|
1328
|
+
body = "".join(secrets.choice(string.ascii_letters + string.digits)
|
|
1329
|
+
for _ in range(12))
|
|
1330
|
+
return "Ag" + body + "#7"
|
|
1331
|
+
|
|
1332
|
+
|
|
1333
|
+
def save_agent_connection(root, base_name, base, agent, password):
|
|
1334
|
+
"""Add <base>_agent alongside the owner entry (never overwrite it) and
|
|
1335
|
+
make it the default. Returns the new entry name."""
|
|
1336
|
+
path = pathlib.Path(root) / CONFIG_DIR / CONFIG_NAME
|
|
1337
|
+
cfg = json.loads(path.read_text(encoding="utf-8"))
|
|
1338
|
+
owner = (base.get("schema") or base["user"]).upper()
|
|
1339
|
+
entry = dict(base)
|
|
1340
|
+
entry["user"] = f"{agent.lower()}[{owner.lower()}]"
|
|
1341
|
+
entry["password"] = password
|
|
1342
|
+
entry["schema"] = owner
|
|
1343
|
+
name = f"{base_name}_agent"
|
|
1344
|
+
cfg[name] = entry
|
|
1345
|
+
cfg["default"] = name
|
|
1346
|
+
path.write_text(json.dumps(cfg, indent=2, ensure_ascii=False) + "\n",
|
|
1347
|
+
encoding="utf-8")
|
|
1348
|
+
return name
|
|
1349
|
+
|
|
1350
|
+
|
|
1351
|
+
def cmd_agent_user(conn, schema, ns):
|
|
1352
|
+
cwd = pathlib.Path.cwd()
|
|
1353
|
+
cfg, root = find_config(cwd, os.environ)
|
|
1354
|
+
base_name, base = resolve_connection(cfg, ns.conn, os.environ, cwd, root)
|
|
1355
|
+
owner = (base.get("schema") or base["user"]).upper()
|
|
1356
|
+
if "[" in base["user"]:
|
|
1357
|
+
sys.exit(f"Connection {base_name} already uses a proxy user "
|
|
1358
|
+
f"({base['user']}) — nothing to set up.")
|
|
1359
|
+
agent = (ns.name or f"{owner}_AGENT").upper()
|
|
1360
|
+
password = agent_password()
|
|
1361
|
+
print(AGENT_USER_SQL.format(owner=owner, agent=agent, password=password))
|
|
1362
|
+
if ns.save:
|
|
1363
|
+
name = save_agent_connection(root, base_name, base, agent, password)
|
|
1364
|
+
print(f"Saved connection '{name}' (now the default) in "
|
|
1365
|
+
f"{pathlib.Path(root) / CONFIG_DIR / CONFIG_NAME};\n"
|
|
1366
|
+
f"the owner entry '{base_name}' is untouched — switch back "
|
|
1367
|
+
f"any time with --conn {base_name}.")
|
|
1368
|
+
print(f"\nNext: run the SQL above as a DBA, then: {invocation()} check")
|
|
1369
|
+
else:
|
|
1370
|
+
print(f"-- Rerun with --save to add this credential to "
|
|
1371
|
+
f"connections.json as '{base_name}_agent' and make it the "
|
|
1372
|
+
f"default once the DBA has run it.")
|
|
1373
|
+
|
|
1374
|
+
|
|
1304
1375
|
# unistr escapes. Single quote doubles to '' per SQL (a \' is ORA-01756 for
|
|
1305
1376
|
# Oracle); backslash doubles for unistr itself.
|
|
1306
1377
|
UNISTR_ESC = {0: r"\0", 8: r"\b", 9: r"\t", 10: r"\n", 13: r"\r",
|
|
@@ -1430,9 +1501,10 @@ COMMANDS = {"check": cmd_check, "ls": cmd_ls, "src": cmd_src, "args": cmd_args,
|
|
|
1430
1501
|
"impact": cmd_impact, "similar": cmd_similar, "plscope": cmd_plscope,
|
|
1431
1502
|
"policy": cmd_policy, "journal": cmd_journal, "apply": cmd_apply,
|
|
1432
1503
|
"conventions": cmd_conventions, "install": cmd_install,
|
|
1433
|
-
"unistr": cmd_unistr}
|
|
1504
|
+
"unistr": cmd_unistr, "agent-user": cmd_agent_user}
|
|
1434
1505
|
|
|
1435
|
-
NO_DB_COMMANDS = {"policy", "journal", "conventions", "install", "unistr"
|
|
1506
|
+
NO_DB_COMMANDS = {"policy", "journal", "conventions", "install", "unistr",
|
|
1507
|
+
"agent-user"}
|
|
1436
1508
|
|
|
1437
1509
|
|
|
1438
1510
|
# --- CLI ---------------------------------------------------------------------
|
|
@@ -1531,6 +1603,13 @@ def build_parser():
|
|
|
1531
1603
|
help="impact depth for the preview (default 3)")
|
|
1532
1604
|
sub.add_parser("conventions", parents=[common()],
|
|
1533
1605
|
help="show the project's house-style naming patterns")
|
|
1606
|
+
s = sub.add_parser("agent-user", parents=[common()],
|
|
1607
|
+
help="SQL for a least-privilege proxy agent user; "
|
|
1608
|
+
"--save adds it to connections.json")
|
|
1609
|
+
s.add_argument("--name", help="agent user name (default <OWNER>_AGENT)")
|
|
1610
|
+
s.add_argument("--save", action="store_true",
|
|
1611
|
+
help="add the new credential as <conn>_agent and make it "
|
|
1612
|
+
"the default connection")
|
|
1534
1613
|
s = sub.add_parser("unistr", parents=[common()],
|
|
1535
1614
|
help="Oracle unistr('...') literal for non-ASCII text "
|
|
1536
1615
|
"(Vietnamese messages stay exact)")
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: pythia-plsql
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.3
|
|
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
|
|
@@ -103,7 +103,7 @@ and WSL are all CI-tested.
|
|
|
103
103
|
| `ls` find objects | `impact` what depends on it | `journal` list · diff · export · restore |
|
|
104
104
|
| `src` source, compiler line numbers | `errors` compile errors, line:col | `policy` show · set |
|
|
105
105
|
| `args` signatures | `invalid` everything broken | `unistr` exact non-ASCII literals |
|
|
106
|
-
| `ddl` via DBMS_METADATA | `plscope` exact identifier usages | |
|
|
106
|
+
| `ddl` via DBMS_METADATA | `plscope` exact identifier usages | `agent-user` least-privilege setup |
|
|
107
107
|
| `cols` columns + types | `similar` programs named like this | |
|
|
108
108
|
| `grep` search all source | | |
|
|
109
109
|
| `sql` free query (SELECT/WITH only) | | |
|
|
@@ -253,6 +253,45 @@ def test_to_unistr():
|
|
|
253
253
|
assert u("ABC 123") == "unistr('ABC 123')"
|
|
254
254
|
|
|
255
255
|
|
|
256
|
+
def test_agent_user_sql_is_least_privilege():
|
|
257
|
+
sql = pythia.AGENT_USER_SQL.format(owner="APP", agent="APP_AGENT",
|
|
258
|
+
password="x")
|
|
259
|
+
assert "CREATE USER APP_AGENT" in sql
|
|
260
|
+
assert "GRANT CREATE SESSION TO APP_AGENT" in sql
|
|
261
|
+
assert "GRANT CONNECT THROUGH APP_AGENT" in sql
|
|
262
|
+
for bad in ("DBA TO", "RESOURCE TO", "ANY "): # the whole point
|
|
263
|
+
assert bad not in sql.upper().replace("-- ", ""), bad
|
|
264
|
+
|
|
265
|
+
|
|
266
|
+
def test_agent_password_shape():
|
|
267
|
+
for _ in range(20):
|
|
268
|
+
pw = pythia.agent_password()
|
|
269
|
+
assert len(pw) >= 14 and pw[0].isalpha() and "#" in pw
|
|
270
|
+
assert '"' not in pw and "'" not in pw # safe inside quotes
|
|
271
|
+
|
|
272
|
+
|
|
273
|
+
def test_save_agent_connection_keeps_owner_and_sets_default():
|
|
274
|
+
with tempfile.TemporaryDirectory() as td:
|
|
275
|
+
root = pathlib.Path(td)
|
|
276
|
+
(root / ".pythia").mkdir()
|
|
277
|
+
cfg = {"default": "dev",
|
|
278
|
+
"dev": {"host": "h", "port": 1521, "service_name": "svc",
|
|
279
|
+
"user": "PYTHIA", "password": "ownerpw",
|
|
280
|
+
"schema": "PYTHIA"}}
|
|
281
|
+
(root / ".pythia" / "connections.json").write_text(
|
|
282
|
+
json.dumps(cfg), encoding="utf-8")
|
|
283
|
+
name = pythia.save_agent_connection(root, "dev", cfg["dev"],
|
|
284
|
+
"PYTHIA_AGENT", "agpw")
|
|
285
|
+
out = json.loads((root / ".pythia" / "connections.json")
|
|
286
|
+
.read_text(encoding="utf-8"))
|
|
287
|
+
assert name == "dev_agent" and out["default"] == "dev_agent"
|
|
288
|
+
assert out["dev"]["user"] == "PYTHIA" # owner untouched
|
|
289
|
+
assert out["dev"]["password"] == "ownerpw"
|
|
290
|
+
assert out["dev_agent"]["user"] == "pythia_agent[pythia]"
|
|
291
|
+
assert out["dev_agent"]["password"] == "agpw"
|
|
292
|
+
assert out["dev_agent"]["host"] == "h" # connect details copied
|
|
293
|
+
|
|
294
|
+
|
|
256
295
|
def test_json_envelope():
|
|
257
296
|
s = pythia.json_envelope("ls", "DEV", "OWNER1", ["A", "B"],
|
|
258
297
|
[(1, datetime.date(2026, 1, 2))], truncated=True)
|
|
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.1.2 → pythia_plsql-0.1.3}/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
|
|
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
|