holoscript-holorepo 0.2.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.
- holoscript_holorepo-0.2.0/LICENSE +21 -0
- holoscript_holorepo-0.2.0/PKG-INFO +69 -0
- holoscript_holorepo-0.2.0/README.md +49 -0
- holoscript_holorepo-0.2.0/holoscript_holorepo/__init__.py +225 -0
- holoscript_holorepo-0.2.0/holoscript_holorepo/cli.py +58 -0
- holoscript_holorepo-0.2.0/holoscript_holorepo.egg-info/PKG-INFO +69 -0
- holoscript_holorepo-0.2.0/holoscript_holorepo.egg-info/SOURCES.txt +11 -0
- holoscript_holorepo-0.2.0/holoscript_holorepo.egg-info/dependency_links.txt +1 -0
- holoscript_holorepo-0.2.0/holoscript_holorepo.egg-info/entry_points.txt +2 -0
- holoscript_holorepo-0.2.0/holoscript_holorepo.egg-info/top_level.txt +1 -0
- holoscript_holorepo-0.2.0/pyproject.toml +35 -0
- holoscript_holorepo-0.2.0/setup.cfg +4 -0
- holoscript_holorepo-0.2.0/tests/test_holorepo.py +66 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 HoloScript ecosystem (Joseph, founder).
|
|
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,69 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: holoscript-holorepo
|
|
3
|
+
Version: 0.2.0
|
|
4
|
+
Summary: Python companion for HoloRepo: plug-and-play sovereign DB + HoloScript KS for agent frameworks.
|
|
5
|
+
Author: HoloScript ecosystem
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/brianonbased-dev/ai-ecosystem/tree/main/packages/holorepo-py
|
|
8
|
+
Project-URL: Source, https://github.com/brianonbased-dev/ai-ecosystem
|
|
9
|
+
Project-URL: Doctrine, https://github.com/brianonbased-dev/ai-ecosystem/blob/main/docs/handbooks/holorepo-operating-model.md
|
|
10
|
+
Keywords: holoscript,holorepo,sovereign,postgres,knowledge-store,agent-framework
|
|
11
|
+
Classifier: Development Status :: 3 - Alpha
|
|
12
|
+
Classifier: Programming Language :: Python :: 3
|
|
13
|
+
Classifier: Operating System :: OS Independent
|
|
14
|
+
Classifier: Topic :: Software Development :: Version Control :: Git
|
|
15
|
+
Classifier: Topic :: Database
|
|
16
|
+
Requires-Python: >=3.9
|
|
17
|
+
Description-Content-Type: text/markdown
|
|
18
|
+
License-File: LICENSE
|
|
19
|
+
Dynamic: license-file
|
|
20
|
+
|
|
21
|
+
# holoscript-holorepo
|
|
22
|
+
|
|
23
|
+
Python companion for HoloRepo: a plug-and-play sovereign GitHub + database +
|
|
24
|
+
HoloScript knowledge store for high-volume agent frameworks.
|
|
25
|
+
|
|
26
|
+
The Python client covers the DB/KS guest surface. It reads any Postgres reachable
|
|
27
|
+
by `psql`, or an explicitly configured owned node over SSH + Docker. No host, IP,
|
|
28
|
+
or container is assumed by default.
|
|
29
|
+
|
|
30
|
+
## Install
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
pip install holoscript-holorepo
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
## Config
|
|
37
|
+
|
|
38
|
+
Use `holorepo.config.json` or env:
|
|
39
|
+
|
|
40
|
+
```bash
|
|
41
|
+
export HOLOREPO_DATABASE_URL="postgres://user@host:5432/app"
|
|
42
|
+
export PGPASSWORD="..."
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
For an owned node over SSH, use the `jetsonExample` profile as an example and
|
|
46
|
+
set `HOLOREPO_SSH`, `HOLOREPO_SSH_KEY`, and `HOLOREPO_POSTGRES_CONTAINER`.
|
|
47
|
+
|
|
48
|
+
## Use
|
|
49
|
+
|
|
50
|
+
```bash
|
|
51
|
+
holorepo-py db read knowledge "select count(*) from memory_entries"
|
|
52
|
+
holorepo-py ks query "storage lanes" 8
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
```python
|
|
56
|
+
from holoscript_holorepo import query_ks, read_db
|
|
57
|
+
|
|
58
|
+
hits = query_ks("sovereign backbone", limit=5)["rows"]
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
Reads refuse mutations. Writes are staged by the npm client as
|
|
62
|
+
`holorepo.storage-proof.v1` and admitted by the configured operator route.
|
|
63
|
+
|
|
64
|
+
## Release Boundary
|
|
65
|
+
|
|
66
|
+
`holoscript-holorepo` is distributed under the MIT license. The compatibility
|
|
67
|
+
label is still `v0-preview`: the Python companion supports config/env-driven
|
|
68
|
+
DB/KS reads for agent frameworks, while write admission remains in the npm
|
|
69
|
+
client and HoloGate/HoloKey operator lane.
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
# holoscript-holorepo
|
|
2
|
+
|
|
3
|
+
Python companion for HoloRepo: a plug-and-play sovereign GitHub + database +
|
|
4
|
+
HoloScript knowledge store for high-volume agent frameworks.
|
|
5
|
+
|
|
6
|
+
The Python client covers the DB/KS guest surface. It reads any Postgres reachable
|
|
7
|
+
by `psql`, or an explicitly configured owned node over SSH + Docker. No host, IP,
|
|
8
|
+
or container is assumed by default.
|
|
9
|
+
|
|
10
|
+
## Install
|
|
11
|
+
|
|
12
|
+
```bash
|
|
13
|
+
pip install holoscript-holorepo
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
## Config
|
|
17
|
+
|
|
18
|
+
Use `holorepo.config.json` or env:
|
|
19
|
+
|
|
20
|
+
```bash
|
|
21
|
+
export HOLOREPO_DATABASE_URL="postgres://user@host:5432/app"
|
|
22
|
+
export PGPASSWORD="..."
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
For an owned node over SSH, use the `jetsonExample` profile as an example and
|
|
26
|
+
set `HOLOREPO_SSH`, `HOLOREPO_SSH_KEY`, and `HOLOREPO_POSTGRES_CONTAINER`.
|
|
27
|
+
|
|
28
|
+
## Use
|
|
29
|
+
|
|
30
|
+
```bash
|
|
31
|
+
holorepo-py db read knowledge "select count(*) from memory_entries"
|
|
32
|
+
holorepo-py ks query "storage lanes" 8
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
```python
|
|
36
|
+
from holoscript_holorepo import query_ks, read_db
|
|
37
|
+
|
|
38
|
+
hits = query_ks("sovereign backbone", limit=5)["rows"]
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
Reads refuse mutations. Writes are staged by the npm client as
|
|
42
|
+
`holorepo.storage-proof.v1` and admitted by the configured operator route.
|
|
43
|
+
|
|
44
|
+
## Release Boundary
|
|
45
|
+
|
|
46
|
+
`holoscript-holorepo` is distributed under the MIT license. The compatibility
|
|
47
|
+
label is still `v0-preview`: the Python companion supports config/env-driven
|
|
48
|
+
DB/KS reads for agent frameworks, while write admission remains in the npm
|
|
49
|
+
client and HoloGate/HoloKey operator lane.
|
|
@@ -0,0 +1,225 @@
|
|
|
1
|
+
"""holoscript-holorepo: Python companion for HoloRepo.
|
|
2
|
+
|
|
3
|
+
The client is config/env driven. It can read any Postgres reachable by `psql`,
|
|
4
|
+
or an explicitly configured owned node over SSH + docker exec. No host, IP, or
|
|
5
|
+
container is assumed by default.
|
|
6
|
+
"""
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
import hashlib
|
|
10
|
+
import json
|
|
11
|
+
import os
|
|
12
|
+
import re
|
|
13
|
+
import subprocess
|
|
14
|
+
from pathlib import Path
|
|
15
|
+
from urllib.parse import urlsplit, urlunsplit
|
|
16
|
+
|
|
17
|
+
__version__ = "0.2.0"
|
|
18
|
+
RELEASE_LANE = "v0-preview"
|
|
19
|
+
CONFIG_SCHEMA = "holorepo.config.v1"
|
|
20
|
+
|
|
21
|
+
_MUTATION = re.compile(r"\b(insert|update|delete|drop|alter|create|truncate|grant|revoke|copy|merge|call)\b", re.I)
|
|
22
|
+
_READ_HEAD = re.compile(r"^(select|with|explain|show|table)\b", re.I)
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def default_config() -> dict:
|
|
26
|
+
return {
|
|
27
|
+
"schema": CONFIG_SCHEMA,
|
|
28
|
+
"productName": "HoloRepo",
|
|
29
|
+
"activeProfile": "local",
|
|
30
|
+
"storage": {"kind": "filesystem", "root": ".holorepo/storage"},
|
|
31
|
+
"git": {"backend": "local-bare", "root": ".holorepo/git"},
|
|
32
|
+
"database": {"transport": "direct-psql", "connectionEnv": "HOLOREPO_DATABASE_URL"},
|
|
33
|
+
"knowledgeStore": {"database": "knowledge", "table": "memory_entries"},
|
|
34
|
+
"agents": {"maxConcurrentWriters": 100},
|
|
35
|
+
"profiles": {
|
|
36
|
+
"jetsonExample": {
|
|
37
|
+
"note": "Example only. HoloRepo no longer assumes this host, container, or path.",
|
|
38
|
+
"database": {
|
|
39
|
+
"transport": "ssh-docker-psql",
|
|
40
|
+
"ssh": {"hostEnv": "HOLOREPO_SSH", "keyEnv": "HOLOREPO_SSH_KEY"},
|
|
41
|
+
"docker": {"containerEnv": "HOLOREPO_POSTGRES_CONTAINER"},
|
|
42
|
+
"userEnv": "HOLOREPO_DATABASE_USER",
|
|
43
|
+
},
|
|
44
|
+
}
|
|
45
|
+
},
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def _merge(a: dict, b: dict) -> dict:
|
|
50
|
+
out = dict(a)
|
|
51
|
+
for key, value in (b or {}).items():
|
|
52
|
+
if isinstance(value, dict) and isinstance(out.get(key), dict):
|
|
53
|
+
out[key] = _merge(out[key], value)
|
|
54
|
+
else:
|
|
55
|
+
out[key] = value
|
|
56
|
+
return out
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def load_config(path: str | None = None, env: dict | None = None) -> dict:
|
|
60
|
+
env = os.environ if env is None else env
|
|
61
|
+
target = Path(path or env.get("HOLOREPO_CONFIG", "holorepo.config.json"))
|
|
62
|
+
cfg = json.loads(target.read_text()) if target.exists() else default_config()
|
|
63
|
+
if "$schema" in cfg and "schema" not in cfg:
|
|
64
|
+
cfg["schema"] = cfg["$schema"]
|
|
65
|
+
profile = env.get("HOLOREPO_CONFIG_PROFILE") or cfg.get("activeProfile") or cfg.get("profile")
|
|
66
|
+
if profile and cfg.get("profiles", {}).get(profile):
|
|
67
|
+
cfg = _merge(cfg, cfg["profiles"][profile])
|
|
68
|
+
cfg["activeProfile"] = profile
|
|
69
|
+
if env.get("HOLOREPO_DATABASE_URL"):
|
|
70
|
+
cfg["database"] = _merge(cfg.get("database", {}), {"transport": "direct-psql", "connectionEnv": "HOLOREPO_DATABASE_URL"})
|
|
71
|
+
elif env.get("DATABASE_URL"):
|
|
72
|
+
cfg["database"] = _merge(cfg.get("database", {}), {"transport": "direct-psql", "connectionEnv": "DATABASE_URL"})
|
|
73
|
+
return cfg
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
def _sha256(text: str) -> str:
|
|
77
|
+
return "sha256:" + hashlib.sha256(text.encode("utf-8")).hexdigest()
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def is_read_only_sql(sql: str) -> bool:
|
|
81
|
+
s = (sql or "").strip().rstrip(";").strip()
|
|
82
|
+
if not s:
|
|
83
|
+
return False
|
|
84
|
+
if ";" in s:
|
|
85
|
+
return False
|
|
86
|
+
if s.startswith("\\"):
|
|
87
|
+
return True
|
|
88
|
+
return bool(_READ_HEAD.match(s)) and not _MUTATION.search(s)
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
def sanitize_term(term: str) -> str:
|
|
92
|
+
return re.sub(r"\s+", " ", re.sub(r"[%_\\']", " ", term or "")).strip()[:120]
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
def safe_table(name: str = "memory_entries") -> str:
|
|
96
|
+
if not re.match(r"^[a-z_][a-z0-9_]*$", name or "", re.I):
|
|
97
|
+
raise ValueError(f"unsafe SQL identifier: {name}")
|
|
98
|
+
return name
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
def build_ks_query(term: str, domain: str | None = None, limit: int = 10, table: str = "memory_entries") -> str:
|
|
102
|
+
t = sanitize_term(term)
|
|
103
|
+
tbl = safe_table(table)
|
|
104
|
+
dom = f" and domain = '{sanitize_term(domain)}'" if domain else ""
|
|
105
|
+
lim = max(1, min(50, int(limit) if str(limit).isdigit() else 10))
|
|
106
|
+
return (
|
|
107
|
+
"select id, domain, type, coalesce(confidence,0), "
|
|
108
|
+
"left(regexp_replace(content, '\\s+', ' ', 'g'), 150) "
|
|
109
|
+
f"from {tbl} where content ILIKE '%{t}%'{dom} and coalesce(is_stale,false)=false "
|
|
110
|
+
f"order by coalesce(reuse_count,0) desc, updated_at desc limit {lim}"
|
|
111
|
+
)
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
def redact_connection_string(value: str | None) -> str | None:
|
|
115
|
+
if not value:
|
|
116
|
+
return value
|
|
117
|
+
try:
|
|
118
|
+
parts = urlsplit(value)
|
|
119
|
+
host = parts.hostname or "host"
|
|
120
|
+
port = f":{parts.port}" if parts.port else ""
|
|
121
|
+
user = "redacted@" if parts.username else ""
|
|
122
|
+
return urlunsplit((parts.scheme, f"{user}{host}{port}", parts.path, parts.query, parts.fragment))
|
|
123
|
+
except Exception:
|
|
124
|
+
return re.sub(r"//[^:@/\s]+:[^@/\s]+@", "//redacted:redacted@", value)
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
def _split_password_from_url(value: str):
|
|
128
|
+
if not value:
|
|
129
|
+
return value, None
|
|
130
|
+
try:
|
|
131
|
+
parts = urlsplit(value)
|
|
132
|
+
if not parts.password:
|
|
133
|
+
return value, None
|
|
134
|
+
host = parts.hostname or ""
|
|
135
|
+
port = f":{parts.port}" if parts.port else ""
|
|
136
|
+
netloc = f"{parts.username}@{host}{port}" if parts.username else f"{host}{port}"
|
|
137
|
+
return urlunsplit((parts.scheme, netloc, parts.path, parts.query, parts.fragment)), parts.password
|
|
138
|
+
except Exception:
|
|
139
|
+
return value, None
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
def redact_db_summary(db: str, config: dict | None = None, env: dict | None = None) -> str:
|
|
143
|
+
env = os.environ if env is None else env
|
|
144
|
+
config = load_config(env=env) if config is None else config
|
|
145
|
+
db_cfg = config.get("database", {})
|
|
146
|
+
transport = db_cfg.get("transport", "direct-psql")
|
|
147
|
+
env_name = db_cfg.get("connectionEnv")
|
|
148
|
+
if env_name and env.get(env_name):
|
|
149
|
+
return f"{redact_connection_string(env[env_name])} db={db} ({transport}, redacted)"
|
|
150
|
+
return f"postgres://<{env_name or 'HOLOREPO_DATABASE_URL'}>/{db} ({transport}, redacted)"
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
def _run_psql(db: str, sql: str, meta: bool = False, config: dict | None = None, env: dict | None = None):
|
|
154
|
+
env = os.environ if env is None else env
|
|
155
|
+
config = load_config(env=env) if config is None else config
|
|
156
|
+
db_cfg = config.get("database", {})
|
|
157
|
+
transport = db_cfg.get("transport", "direct-psql")
|
|
158
|
+
password = None
|
|
159
|
+
if transport == "direct-psql":
|
|
160
|
+
env_name = db_cfg.get("connectionEnv", "HOLOREPO_DATABASE_URL")
|
|
161
|
+
conn = env.get(env_name)
|
|
162
|
+
if not conn:
|
|
163
|
+
return False, "", f"direct-psql needs {env_name}"
|
|
164
|
+
conn, password = _split_password_from_url(conn)
|
|
165
|
+
args = ["psql", "-X", conn, "-c" if meta else "-tAc", sql]
|
|
166
|
+
elif transport == "ssh-docker-psql":
|
|
167
|
+
ssh = db_cfg.get("ssh", {})
|
|
168
|
+
docker = db_cfg.get("docker", {})
|
|
169
|
+
host = ssh.get("host") or env.get(ssh.get("hostEnv", "HOLOREPO_SSH"))
|
|
170
|
+
key = ssh.get("keyPath") or env.get(ssh.get("keyEnv", "HOLOREPO_SSH_KEY"))
|
|
171
|
+
container = docker.get("container") or env.get(docker.get("containerEnv", "HOLOREPO_POSTGRES_CONTAINER"))
|
|
172
|
+
user = db_cfg.get("user") or env.get(db_cfg.get("userEnv", "HOLOREPO_DATABASE_USER"), "postgres")
|
|
173
|
+
if not host:
|
|
174
|
+
return False, "", "ssh-docker-psql needs database.ssh.host or hostEnv"
|
|
175
|
+
if not container:
|
|
176
|
+
return False, "", "ssh-docker-psql needs database.docker.container or containerEnv"
|
|
177
|
+
esc = sql.replace("'", "'\\''")
|
|
178
|
+
flag = "" if meta else "-tA"
|
|
179
|
+
remote = f"sudo docker exec '{container}' psql -U '{user}' -d '{db}' {flag} -c '{esc}'"
|
|
180
|
+
args = ["ssh", "-o", "StrictHostKeyChecking=no", "-o", "ConnectTimeout=15"]
|
|
181
|
+
if key:
|
|
182
|
+
args.extend(["-i", os.path.expanduser(key)])
|
|
183
|
+
args.extend([host, remote])
|
|
184
|
+
else:
|
|
185
|
+
return False, "", f"unsupported database transport: {transport}"
|
|
186
|
+
child_env = dict(os.environ)
|
|
187
|
+
child_env.update(env)
|
|
188
|
+
if transport == "direct-psql" and password and not child_env.get("PGPASSWORD"):
|
|
189
|
+
child_env["PGPASSWORD"] = password
|
|
190
|
+
proc = subprocess.run(args, capture_output=True, text=True, timeout=30, env=child_env)
|
|
191
|
+
return proc.returncode == 0, proc.stdout.strip(), proc.stderr.strip()
|
|
192
|
+
|
|
193
|
+
|
|
194
|
+
def read_db(db: str, sql: str, config: dict | None = None, env: dict | None = None) -> dict:
|
|
195
|
+
if not is_read_only_sql(sql):
|
|
196
|
+
return {"ok": False, "refused": True}
|
|
197
|
+
ok, out, err = _run_psql(db, sql, meta=sql.strip().startswith("\\"), config=config, env=env)
|
|
198
|
+
if not ok:
|
|
199
|
+
return {"ok": False, "err": err}
|
|
200
|
+
rows = out.split("\n") if out else []
|
|
201
|
+
receipt = {
|
|
202
|
+
"schema": "holorepo.db-read-receipt.v1",
|
|
203
|
+
"backend": (config or load_config(env=env)).get("database", {}).get("transport", "direct-psql"),
|
|
204
|
+
"db": db,
|
|
205
|
+
"connection": redact_db_summary(db, config=config, env=env),
|
|
206
|
+
"query_digest": _sha256(sql),
|
|
207
|
+
"result_digest": _sha256(out),
|
|
208
|
+
"row_count": len(rows),
|
|
209
|
+
"read_only": True,
|
|
210
|
+
}
|
|
211
|
+
return {"ok": True, "out": out, "rows": len(rows), "receipt": receipt}
|
|
212
|
+
|
|
213
|
+
|
|
214
|
+
def query_ks(term: str, domain: str | None = None, limit: int = 10, config: dict | None = None, env: dict | None = None) -> dict:
|
|
215
|
+
config = load_config(env=env) if config is None else config
|
|
216
|
+
ks = config.get("knowledgeStore", {})
|
|
217
|
+
ok, out, err = _run_psql(ks.get("database", "knowledge"), build_ks_query(term, domain, limit, ks.get("table", "memory_entries")), config=config, env=env)
|
|
218
|
+
if not ok:
|
|
219
|
+
return {"ok": False, "err": err}
|
|
220
|
+
rows = []
|
|
221
|
+
for line in filter(None, out.split("\n")):
|
|
222
|
+
parts = line.split("|")
|
|
223
|
+
if len(parts) >= 5:
|
|
224
|
+
rows.append({"id": parts[0], "domain": parts[1], "type": parts[2], "confidence": parts[3], "snippet": parts[4]})
|
|
225
|
+
return {"ok": True, "term": sanitize_term(term), "rows": rows}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
"""holorepo-py CLI."""
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
import json
|
|
5
|
+
import sys
|
|
6
|
+
|
|
7
|
+
from . import default_config, load_config, query_ks, read_db
|
|
8
|
+
|
|
9
|
+
HELP = """holorepo-py - Python companion for HoloRepo
|
|
10
|
+
|
|
11
|
+
holorepo-py config print resolved config
|
|
12
|
+
holorepo-py db read <db> "<sql>" guest read (SELECT/meta only) + receipt
|
|
13
|
+
holorepo-py ks query "<text>" [limit] configured HoloScript KS read
|
|
14
|
+
|
|
15
|
+
Config: holorepo.config.json + env. No host, IP, or container is assumed.
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def main(argv=None) -> int:
|
|
20
|
+
argv = list(sys.argv[1:] if argv is None else argv)
|
|
21
|
+
if not argv or argv[0] in ("-h", "--help"):
|
|
22
|
+
print(HELP)
|
|
23
|
+
return 0
|
|
24
|
+
group = argv[0]
|
|
25
|
+
if group == "config":
|
|
26
|
+
print(json.dumps(load_config(), indent=2))
|
|
27
|
+
return 0
|
|
28
|
+
if group == "sample":
|
|
29
|
+
print(json.dumps(default_config(), indent=2))
|
|
30
|
+
return 0
|
|
31
|
+
if group == "db" and len(argv) >= 4 and argv[1] == "read":
|
|
32
|
+
result = read_db(argv[2], argv[3])
|
|
33
|
+
if result.get("refused"):
|
|
34
|
+
print("[holorepo-py] REFUSED: read accepts SELECT/meta only; mutations go through stage-write.", file=sys.stderr)
|
|
35
|
+
return 3
|
|
36
|
+
if not result["ok"]:
|
|
37
|
+
print(f"[holorepo-py] query failed: {result['err'][:200]}", file=sys.stderr)
|
|
38
|
+
return 1
|
|
39
|
+
print(f"read {argv[2]} ({result['rows']} rows) {result['receipt']['result_digest'][:24]}")
|
|
40
|
+
print(result["out"][:1600])
|
|
41
|
+
return 0
|
|
42
|
+
if group == "ks" and len(argv) >= 3 and argv[1] == "query":
|
|
43
|
+
limit = int(argv[3]) if len(argv) > 3 and argv[3].isdigit() else 10
|
|
44
|
+
result = query_ks(argv[2], limit=limit)
|
|
45
|
+
if not result["ok"]:
|
|
46
|
+
print(f"[holorepo-py] query failed: {result['err'][:200]}", file=sys.stderr)
|
|
47
|
+
return 1
|
|
48
|
+
print(f'"{result["term"]}" - {len(result["rows"])} hit(s)')
|
|
49
|
+
for row in result["rows"]:
|
|
50
|
+
print(f" [{row['domain']}/{row['type']}] {row['snippet']}")
|
|
51
|
+
print(f" {row['id']} conf={row['confidence']}")
|
|
52
|
+
return 0
|
|
53
|
+
print(HELP, file=sys.stderr)
|
|
54
|
+
return 2
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
if __name__ == "__main__":
|
|
58
|
+
raise SystemExit(main())
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: holoscript-holorepo
|
|
3
|
+
Version: 0.2.0
|
|
4
|
+
Summary: Python companion for HoloRepo: plug-and-play sovereign DB + HoloScript KS for agent frameworks.
|
|
5
|
+
Author: HoloScript ecosystem
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/brianonbased-dev/ai-ecosystem/tree/main/packages/holorepo-py
|
|
8
|
+
Project-URL: Source, https://github.com/brianonbased-dev/ai-ecosystem
|
|
9
|
+
Project-URL: Doctrine, https://github.com/brianonbased-dev/ai-ecosystem/blob/main/docs/handbooks/holorepo-operating-model.md
|
|
10
|
+
Keywords: holoscript,holorepo,sovereign,postgres,knowledge-store,agent-framework
|
|
11
|
+
Classifier: Development Status :: 3 - Alpha
|
|
12
|
+
Classifier: Programming Language :: Python :: 3
|
|
13
|
+
Classifier: Operating System :: OS Independent
|
|
14
|
+
Classifier: Topic :: Software Development :: Version Control :: Git
|
|
15
|
+
Classifier: Topic :: Database
|
|
16
|
+
Requires-Python: >=3.9
|
|
17
|
+
Description-Content-Type: text/markdown
|
|
18
|
+
License-File: LICENSE
|
|
19
|
+
Dynamic: license-file
|
|
20
|
+
|
|
21
|
+
# holoscript-holorepo
|
|
22
|
+
|
|
23
|
+
Python companion for HoloRepo: a plug-and-play sovereign GitHub + database +
|
|
24
|
+
HoloScript knowledge store for high-volume agent frameworks.
|
|
25
|
+
|
|
26
|
+
The Python client covers the DB/KS guest surface. It reads any Postgres reachable
|
|
27
|
+
by `psql`, or an explicitly configured owned node over SSH + Docker. No host, IP,
|
|
28
|
+
or container is assumed by default.
|
|
29
|
+
|
|
30
|
+
## Install
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
pip install holoscript-holorepo
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
## Config
|
|
37
|
+
|
|
38
|
+
Use `holorepo.config.json` or env:
|
|
39
|
+
|
|
40
|
+
```bash
|
|
41
|
+
export HOLOREPO_DATABASE_URL="postgres://user@host:5432/app"
|
|
42
|
+
export PGPASSWORD="..."
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
For an owned node over SSH, use the `jetsonExample` profile as an example and
|
|
46
|
+
set `HOLOREPO_SSH`, `HOLOREPO_SSH_KEY`, and `HOLOREPO_POSTGRES_CONTAINER`.
|
|
47
|
+
|
|
48
|
+
## Use
|
|
49
|
+
|
|
50
|
+
```bash
|
|
51
|
+
holorepo-py db read knowledge "select count(*) from memory_entries"
|
|
52
|
+
holorepo-py ks query "storage lanes" 8
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
```python
|
|
56
|
+
from holoscript_holorepo import query_ks, read_db
|
|
57
|
+
|
|
58
|
+
hits = query_ks("sovereign backbone", limit=5)["rows"]
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
Reads refuse mutations. Writes are staged by the npm client as
|
|
62
|
+
`holorepo.storage-proof.v1` and admitted by the configured operator route.
|
|
63
|
+
|
|
64
|
+
## Release Boundary
|
|
65
|
+
|
|
66
|
+
`holoscript-holorepo` is distributed under the MIT license. The compatibility
|
|
67
|
+
label is still `v0-preview`: the Python companion supports config/env-driven
|
|
68
|
+
DB/KS reads for agent frameworks, while write admission remains in the npm
|
|
69
|
+
client and HoloGate/HoloKey operator lane.
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
LICENSE
|
|
2
|
+
README.md
|
|
3
|
+
pyproject.toml
|
|
4
|
+
holoscript_holorepo/__init__.py
|
|
5
|
+
holoscript_holorepo/cli.py
|
|
6
|
+
holoscript_holorepo.egg-info/PKG-INFO
|
|
7
|
+
holoscript_holorepo.egg-info/SOURCES.txt
|
|
8
|
+
holoscript_holorepo.egg-info/dependency_links.txt
|
|
9
|
+
holoscript_holorepo.egg-info/entry_points.txt
|
|
10
|
+
holoscript_holorepo.egg-info/top_level.txt
|
|
11
|
+
tests/test_holorepo.py
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
holoscript_holorepo
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=77"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "holoscript-holorepo"
|
|
7
|
+
version = "0.2.0"
|
|
8
|
+
description = "Python companion for HoloRepo: plug-and-play sovereign DB + HoloScript KS for agent frameworks."
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.9"
|
|
11
|
+
license = "MIT"
|
|
12
|
+
license-files = ["LICENSE"]
|
|
13
|
+
keywords = ["holoscript", "holorepo", "sovereign", "postgres", "knowledge-store", "agent-framework"]
|
|
14
|
+
authors = [{ name = "HoloScript ecosystem" }]
|
|
15
|
+
classifiers = [
|
|
16
|
+
"Development Status :: 3 - Alpha",
|
|
17
|
+
"Programming Language :: Python :: 3",
|
|
18
|
+
"Operating System :: OS Independent",
|
|
19
|
+
"Topic :: Software Development :: Version Control :: Git",
|
|
20
|
+
"Topic :: Database",
|
|
21
|
+
]
|
|
22
|
+
dependencies = []
|
|
23
|
+
|
|
24
|
+
[project.scripts]
|
|
25
|
+
holorepo-py = "holoscript_holorepo.cli:main"
|
|
26
|
+
|
|
27
|
+
[project.urls]
|
|
28
|
+
Homepage = "https://github.com/brianonbased-dev/ai-ecosystem/tree/main/packages/holorepo-py"
|
|
29
|
+
Source = "https://github.com/brianonbased-dev/ai-ecosystem"
|
|
30
|
+
Doctrine = "https://github.com/brianonbased-dev/ai-ecosystem/blob/main/docs/handbooks/holorepo-operating-model.md"
|
|
31
|
+
|
|
32
|
+
[tool.holorepo]
|
|
33
|
+
release_lane = "v0-preview"
|
|
34
|
+
npm_companion = "@holoscript/holorepo"
|
|
35
|
+
auth_boundary = "Config/env-driven Postgres or explicit owned-node SSH. Reads + stages proofs; never write authority."
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
"""Pure-function guards for holoscript-holorepo."""
|
|
2
|
+
import sys
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
|
|
5
|
+
sys.path.insert(0, str(Path(__file__).resolve().parents[1]))
|
|
6
|
+
|
|
7
|
+
from holoscript_holorepo import (
|
|
8
|
+
build_ks_query,
|
|
9
|
+
default_config,
|
|
10
|
+
is_read_only_sql,
|
|
11
|
+
load_config,
|
|
12
|
+
redact_connection_string,
|
|
13
|
+
redact_db_summary,
|
|
14
|
+
safe_table,
|
|
15
|
+
sanitize_term,
|
|
16
|
+
)
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def test_default_config_has_no_private_node_assumption():
|
|
20
|
+
cfg = default_config()
|
|
21
|
+
operational = str({k: cfg[k] for k in ("storage", "git", "database", "knowledgeStore")})
|
|
22
|
+
assert "192.168.0.119" not in operational
|
|
23
|
+
assert "holo-app-pg" not in operational
|
|
24
|
+
assert "JETSON" not in operational
|
|
25
|
+
assert "Example only" in str(cfg["profiles"]["jetsonExample"])
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def test_env_load_uses_database_url_without_jetson_aliases():
|
|
29
|
+
cfg = load_config(env={"HOLOREPO_DATABASE_URL": "postgres://u:p@example.test/db"})
|
|
30
|
+
assert cfg["database"]["transport"] == "direct-psql"
|
|
31
|
+
assert cfg["database"]["connectionEnv"] == "HOLOREPO_DATABASE_URL"
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def test_read_only_guard():
|
|
35
|
+
for ok in ("select 1", "WITH x as (select 1) select * from x", "explain select 1", "\\dt"):
|
|
36
|
+
assert is_read_only_sql(ok), ok
|
|
37
|
+
for no in ("insert into t values (1)", "update t set a=1", "drop table t", "truncate t",
|
|
38
|
+
"copy t from stdin", "select 1; drop table t", ""):
|
|
39
|
+
assert not is_read_only_sql(no), no
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def test_redacted_summary_has_no_credentials():
|
|
43
|
+
redacted = redact_connection_string("postgres://alice:secret@example.test/knowledge")
|
|
44
|
+
assert "secret" not in redacted
|
|
45
|
+
summary = redact_db_summary("knowledge", env={"HOLOREPO_DATABASE_URL": "postgres://alice:secret@example.test/knowledge"})
|
|
46
|
+
assert "secret" not in summary
|
|
47
|
+
assert "knowledge" in summary
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def test_ks_query_sanitizes_injection():
|
|
51
|
+
assert "'" not in sanitize_term("a' or '1'='1")
|
|
52
|
+
assert all(c not in sanitize_term("50%_off\\x") for c in "%_\\")
|
|
53
|
+
assert len(sanitize_term("x" * 300)) <= 120
|
|
54
|
+
benign = build_ks_query("x").count("'")
|
|
55
|
+
evil = build_ks_query("'; drop table memory_entries; --").count("'")
|
|
56
|
+
assert evil == benign, "a quote-injection term adds zero quotes"
|
|
57
|
+
assert "limit 50" in build_ks_query("x", limit=9999)
|
|
58
|
+
assert "domain = 'identity'" in build_ks_query("a", domain="identity")
|
|
59
|
+
assert safe_table("memory_entries") == "memory_entries"
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
if __name__ == "__main__":
|
|
63
|
+
for name, fn in list(globals().items()):
|
|
64
|
+
if name.startswith("test_"):
|
|
65
|
+
fn()
|
|
66
|
+
print("PASS holoscript-holorepo: config + read-only + KS guards")
|