sibyllabs commited on
Commit
e2de4c1
·
1 Parent(s): c5a913d

pre-release: combined patch - cli 0.3.10 (hermes install TypeError) + client 0.4.6 (negative-limit guard)

Browse files
sibyl-memory-cli/CHANGELOG.md CHANGED
@@ -4,6 +4,17 @@ All notable changes to `sibyl-memory-cli` are recorded here. Format follows
4
  [Keep a Changelog](https://keepachangelog.com/en/1.1.0/). Versioning follows
5
  [SemVer](https://semver.org/).
6
 
 
 
 
 
 
 
 
 
 
 
 
7
  ## [0.3.9] — 2026-05-31
8
 
9
  Guided migration plus first-class Codex support and the real fix for Claude
 
4
  [Keep a Changelog](https://keepachangelog.com/en/1.1.0/). Versioning follows
5
  [SemVer](https://semver.org/).
6
 
7
+ ## [0.3.10] — 2026-06-01
8
+
9
+ ### Fixed
10
+
11
+ - **`sibyl setup hermes` raised a TypeError on every first-run wiring.**
12
+ `HermesWirer._install_plugin()` called `install()` with only `hermes_home`
13
+ (as a `str`), but `sibyl_memory_hermes.install_plugin.install()` requires
14
+ `(hermes_home: Path, force: bool, dry_run: bool)` with no defaults, so it
15
+ raised `TypeError` before the plugin could install. Now calls
16
+ `install(hermes_home=Path(self.hermes_home), force=False, dry_run=False)`.
17
+
18
  ## [0.3.9] — 2026-05-31
19
 
20
  Guided migration plus first-class Codex support and the real fix for Claude
sibyl-memory-cli/pyproject.toml CHANGED
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
 
5
  [project]
6
  name = "sibyl-memory-cli"
7
- version = "0.3.9"
8
  description = "Command-line interface for the Sibyl Memory Plugin. `sibyl init` activates, `sibyl upgrade` runs the staker / subscription flow, `sibyl status` shows current tier and DB stats, `sibyl whoami` gives a one-line account summary, `sibyl devices` lists active devices and supports per-device revoke."
9
  authors = [{ name = "SIBYL, Sibyl Labs LLC", email = "sibyl@sibyllabs.org" }]
10
  license = { text = "MIT" }
 
4
 
5
  [project]
6
  name = "sibyl-memory-cli"
7
+ version = "0.3.10"
8
  description = "Command-line interface for the Sibyl Memory Plugin. `sibyl init` activates, `sibyl upgrade` runs the staker / subscription flow, `sibyl status` shows current tier and DB stats, `sibyl whoami` gives a one-line account summary, `sibyl devices` lists active devices and supports per-device revoke."
9
  authors = [{ name = "SIBYL, Sibyl Labs LLC", email = "sibyl@sibyllabs.org" }]
10
  license = { text = "MIT" }
sibyl-memory-cli/src/sibyl_memory_cli/setup.py CHANGED
@@ -206,7 +206,7 @@ class HermesWirer:
206
 
207
  def _install_plugin(self) -> None:
208
  from sibyl_memory_hermes.install_plugin import install
209
- install(hermes_home=str(self.hermes_home))
210
 
211
  def _backup_config(self) -> Optional[Path]:
212
  if not self.config_path.exists():
 
206
 
207
  def _install_plugin(self) -> None:
208
  from sibyl_memory_hermes.install_plugin import install
209
+ install(hermes_home=Path(self.hermes_home), force=False, dry_run=False)
210
 
211
  def _backup_config(self) -> Optional[Path]:
212
  if not self.config_path.exists():
sibyl-memory-cli/tests/test_hermes_install_args.py ADDED
@@ -0,0 +1,31 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Regression: ``HermesWirer._install_plugin`` must call ``install()`` with its
2
+ full required signature ``(hermes_home: Path, force: bool, dry_run: bool)``.
3
+
4
+ cli 0.3.9 shipped a ``TypeError`` here (called with only ``hermes_home`` as a
5
+ ``str``) because the wider setup suite stubs ``_install_plugin`` and masked it.
6
+ This test exercises the real method with ``install`` mocked, so the arg contract
7
+ is enforced without needing the hermes runtime. Source: beta reports
8
+ "sibyl setup hermes fails on fresh install" (2026-06-01).
9
+ """
10
+ from pathlib import Path
11
+
12
+ import sibyl_memory_hermes.install_plugin as ip
13
+ from sibyl_memory_cli import setup as cli_setup
14
+
15
+
16
+ def test_install_plugin_passes_full_signature(monkeypatch):
17
+ calls = []
18
+ monkeypatch.setattr(
19
+ ip, "install",
20
+ lambda hermes_home, force, dry_run: (calls.append((hermes_home, force, dry_run)), 0)[1],
21
+ )
22
+ w = cli_setup.HermesWirer.__new__(cli_setup.HermesWirer)
23
+ w.hermes_home = Path("/tmp/fake-hermes-home")
24
+
25
+ w._install_plugin() # must not raise TypeError
26
+
27
+ assert len(calls) == 1
28
+ hermes_home, force, dry_run = calls[0]
29
+ assert isinstance(hermes_home, Path)
30
+ assert force is False
31
+ assert dry_run is False
sibyl-memory-client/CHANGELOG.md CHANGED
@@ -4,6 +4,16 @@ All notable changes to `sibyl-memory-client` are recorded here. Format
4
  follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/). Versioning
5
  follows [SemVer](https://semver.org/).
6
 
 
 
 
 
 
 
 
 
 
 
7
  ## [0.4.5] - 2026-05-30
8
 
9
  Adversarial QA remediation (Acer stress-test suite): two findings + a review hardening.
 
4
  follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/). Versioning
5
  follows [SemVer](https://semver.org/).
6
 
7
+ ## [0.4.6] - 2026-06-01
8
+
9
+ ### Fixed
10
+
11
+ - **A negative `limit` could broaden search instead of narrowing it.**
12
+ `search()` and `search_entities()` passed `limit` straight into SQLite
13
+ `LIMIT ?`, where `LIMIT -1` means unbounded, so `limit=-1` returned more
14
+ rows rather than fewer. Both methods now clamp `limit` with `max(0, limit)`
15
+ so an invalid negative limit can never broaden results.
16
+
17
  ## [0.4.5] - 2026-05-30
18
 
19
  Adversarial QA remediation (Acer stress-test suite): two findings + a review hardening.
sibyl-memory-client/pyproject.toml CHANGED
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
 
5
  [project]
6
  name = "sibyl-memory-client"
7
- version = "0.4.5"
8
  description = "Local-first agentic memory SDK. SQLite-backed five-tier hierarchical schema, FTS5 search, multi-tenant, with self-learning skill detection and local memory linter. Foundation of the Sibyl Memory Plugin family."
9
  authors = [{ name = "SIBYL, Sibyl Labs LLC", email = "sibyl@sibyllabs.org" }]
10
  license = { text = "MIT" }
 
4
 
5
  [project]
6
  name = "sibyl-memory-client"
7
+ version = "0.4.6"
8
  description = "Local-first agentic memory SDK. SQLite-backed five-tier hierarchical schema, FTS5 search, multi-tenant, with self-learning skill detection and local memory linter. Foundation of the Sibyl Memory Plugin family."
9
  authors = [{ name = "SIBYL, Sibyl Labs LLC", email = "sibyl@sibyllabs.org" }]
10
  license = { text = "MIT" }
sibyl-memory-client/src/sibyl_memory_client/client.py CHANGED
@@ -889,6 +889,7 @@ class MemoryClient:
889
 
890
  Raises: StorageError on backend failure; empty list on empty / invalid query.
891
  """
 
892
  match_q = _sanitize_fts5_query(query, prefix=prefix)
893
  if not match_q:
894
  return []
@@ -934,6 +935,7 @@ class MemoryClient:
934
 
935
  Raises: StorageError on backend failure.
936
  """
 
937
  match_q = _sanitize_fts5_query(query, prefix=prefix)
938
  if not match_q:
939
  return []
 
889
 
890
  Raises: StorageError on backend failure; empty list on empty / invalid query.
891
  """
892
+ limit = max(0, limit) # negative limit must not broaden: SQLite LIMIT -1 = unbounded
893
  match_q = _sanitize_fts5_query(query, prefix=prefix)
894
  if not match_q:
895
  return []
 
935
 
936
  Raises: StorageError on backend failure.
937
  """
938
+ limit = max(0, limit) # negative limit must not broaden: SQLite LIMIT -1 = unbounded
939
  match_q = _sanitize_fts5_query(query, prefix=prefix)
940
  if not match_q:
941
  return []
sibyl-memory-client/tests/test_negative_limit_guard.py ADDED
@@ -0,0 +1,30 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Regression: a negative ``limit`` must never broaden search results.
2
+
3
+ SQLite treats ``LIMIT -1`` as unbounded, so passing ``limit=-1`` previously
4
+ returned MORE rows, not fewer. Both ``search`` and ``search_entities`` now clamp
5
+ with ``max(0, limit)``. Source: adversarial QA finding
6
+ SEARCH-NEGATIVE-LIMIT-CANNOT-BROADEN-RESULTS (2026-06-01).
7
+ """
8
+ from sibyl_memory_client import MemoryClient
9
+
10
+
11
+ def _seed(tmp_path):
12
+ c = MemoryClient.local(tmp_path / "memory.db", tenant_id="qa-sandbox")
13
+ for i in range(6):
14
+ c.set_entity("notes", f"item-{i}", {"text": "alpha beta gamma token"})
15
+ return c
16
+
17
+
18
+ def test_search_negative_limit_does_not_broaden(tmp_path):
19
+ c = _seed(tmp_path)
20
+ bounded = c.search("token", limit=2)
21
+ negative = c.search("token", limit=-1)
22
+ # negative must never return MORE than a small positive limit, and must not
23
+ # fall through to SQLite's unbounded LIMIT -1.
24
+ assert len(negative) <= len(bounded)
25
+ assert len(negative) == 0
26
+
27
+
28
+ def test_search_entities_negative_limit_does_not_broaden(tmp_path):
29
+ c = _seed(tmp_path)
30
+ assert c.search_entities("token", limit=-1) == []