diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 3a76b38..2791f36 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -147,4 +147,4 @@ jobs: --repo "$GITHUB_REPOSITORY" \ --verify-tag \ --title "substrate_wiki $tag" \ - --notes "Patch release for resilient OAuth device polling across transient hosted maintenance stalls. Targets Hermes 0.20.x with hosted device onboarding and optional durable history import. See COMPATIBILITY.md and SECURITY.md." + --notes "Patch release for compact user/assistant-only capture and batched history upload. Tool calls, tool results, system messages, provider metadata, and duplicate envelope fields are excluded. Targets Hermes 0.20.x. See COMPATIBILITY.md and SECURITY.md." diff --git a/CHANGELOG.md b/CHANGELOG.md index c9c7469..d558658 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,6 @@ # Changelog -## Unreleased +## 2.0.4 - Upload only completed user/assistant text; exclude tool calls, tool results, system messages, memory writes, session boundaries, provider scope, hashes, retention metadata, and duplicate diff --git a/COMPATIBILITY.md b/COMPATIBILITY.md index d45555e..7e77eec 100644 --- a/COMPATIBILITY.md +++ b/COMPATIBILITY.md @@ -6,6 +6,7 @@ Compatibility has three independent axes: Hermes host, hosted Substrate capabili | Plugin | Hermes | Required hosted capabilities | Status | |---|---|---|---| +| 2.0.4 | 0.20.x | Same as 2.0.3; compact user/assistant capture and batched history upload | Contract and lifecycle integration tested | | 2.0.3 | 0.20.x | Same as 2.0.2; resilient approved-device polling across hosted stalls | Contract and lifecycle integration tested | | 2.0.2 | 0.20.x | Same as 2.0.1; bounded retry for hosted tenant cold starts | Contract and lifecycle integration tested | | 2.0.1 | 0.20.x | Same as 2.0.0; complete email authorization URL in agent/headless prompts | Contract and lifecycle integration tested | diff --git a/SECURITY.md b/SECURITY.md index 39f3289..1b922ba 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -2,7 +2,7 @@ ## Supported versions -The standalone `v2.0.0`, `v2.0.1`, `v2.0.2`, and `v2.0.3` releases target Hermes 0.20.x and the hosted service at `https://app.trysubstrate.co`. +The standalone `v2.0.0`, `v2.0.1`, `v2.0.2`, `v2.0.3`, and `v2.0.4` releases target Hermes 0.20.x and the hosted service at `https://app.trysubstrate.co`. Candidate CI artifacts are not supported releases. ## Report a vulnerability diff --git a/docs/extraction-manifest.json b/docs/extraction-manifest.json index 1bdb7ae..40d116b 100644 --- a/docs/extraction-manifest.json +++ b/docs/extraction-manifest.json @@ -41,7 +41,7 @@ "class": "standalone_repository_policy_or_test", "path": ".github/workflows/release.yml", "reason": "Required only by the independent public repository.", - "sha256": "65d10296c23e9a0a993fa674f509d25f7fc9dcc47b35eed51db0a954d1cc3b5b" + "sha256": "73e6a80b853f009f96f1fbc104315f9c4e5561a3f3678ed6836cc684ac971b23" }, { "class": "standalone_repository_policy_or_test", @@ -59,13 +59,13 @@ "class": "standalone_repository_policy_or_test", "path": "CHANGELOG.md", "reason": "Required only by the independent public repository.", - "sha256": "d20f1839056fdc49b98ed686a9a6747267bc62026eb13603577d90a6fbba4f9b" + "sha256": "67642ca88a4d821e32c531c3512c4f3dc4d3e65ae091eea5e7efb1bf6851a8d2" }, { "class": "standalone_repository_policy_or_test", "path": "COMPATIBILITY.md", "reason": "Required only by the independent public repository.", - "sha256": "67bcb14e4988ed5b49379e93f93fa588e31a068b394a8787d1ea81ba8f6a6c70" + "sha256": "53d4873064a5d5b1748950c08c0aeaa76a6c00c435e4a8f534a844e4f90aca87" }, { "class": "standalone_repository_policy_or_test", @@ -83,7 +83,7 @@ "class": "standalone_repository_policy_or_test", "path": "SECURITY.md", "reason": "Required only by the independent public repository.", - "sha256": "d07d0c65e39f0949f91f7a880a4b584b9b57d8ed3c70ca8fd95276d8d525b517" + "sha256": "7ef28d44f7b82b765e870434542b5b67facc703e2105a40a233de2e12783852e" }, { "class": "standalone_repository_policy_or_test", @@ -119,7 +119,7 @@ "class": "standalone_repository_policy_or_test", "path": "pyproject.toml", "reason": "Required only by the independent public repository.", - "sha256": "3187d3621ddb1c227c0db74ee1180ed99c28200f738e02b17e2857f4c9a82e47" + "sha256": "9dc5e75c2aea5a982a6657e3d17b3f39a343ae5d2fe1e9dd5a8a09ebca00ef36" }, { "class": "standalone_repository_policy_or_test", @@ -137,7 +137,7 @@ "class": "standalone_plugin_implementation", "path": "src/substrate_wiki/onboarding.py", "reason": "Added for hosted Hermes 0.20 automatic onboarding.", - "sha256": "a835092092bca7266978609763d36f5767ac363fe7180bda482d713df2ecc439" + "sha256": "73c8c6669b3c052578ce4818cd336b2f871794bcdb28a42131a856174de660b7" }, { "class": "standalone_repository_policy_or_test", @@ -337,7 +337,7 @@ { "class": "build-and-install", "destination": "scripts/install_hermes_plugin.py", - "destination_sha256": "72247d3537140098365350020cce29658c0743fee1aa738d7143db82316acce4", + "destination_sha256": "8e37991db874b306045623c5cba39ef995b53b7b6a1a6c6f0253e2ff12c0ebb8", "source": "scripts/install_hermes_plugin.py", "source_sha256": "7600b2681c3aebcb1b1492b0a04be38bbbec637089cbbcfb1cc26e8c10865b8d", "transformation": "modified_for_standalone" @@ -353,7 +353,7 @@ { "class": "build-and-install", "destination": "scripts/verify_public_plugin_candidate.py", - "destination_sha256": "e0241e4cc230dd50f88a01d23f4c22dac3aff54343e9f3b34ce2ca0906d57e92", + "destination_sha256": "116652c7d6a675d9b140b655b03d9a095603350dbbe99b1f45a13e1c76fc50a1", "source": "scripts/verify_public_plugin_candidate.py", "source_sha256": "4130935d530075fce1758e2e89bd5d973a722e2293b5b1058cfe0d17f326172b", "transformation": "modified_for_standalone" @@ -393,7 +393,7 @@ { "class": "plugin-package", "destination": "src/substrate_wiki/client.py", - "destination_sha256": "59413ab9fa2eef0d943a70a53e2b5d9d5163c0d9e154538735715e8f88585616", + "destination_sha256": "db9eb5043a5c770f155a942459008de684378261e8fd204276d69b0bed3d788f", "source": "hermes-plugin/substrate_wiki/client.py", "source_sha256": "8684b3ecc788ee058c630747b1c72578177cfc948c72ac539ddcc4a1df9e791b", "transformation": "modified_for_standalone" @@ -417,7 +417,7 @@ { "class": "plugin-package", "destination": "src/substrate_wiki/plugin.yaml", - "destination_sha256": "e76721f0d7ba3d37958d15fa398ea47bb7c457388e0ad181df5471a1b2ede61c", + "destination_sha256": "45478259c2e7f584d4a9bbf50d6563b6c57bc41d8f4ed6c6a3e480b87f48b746", "source": "hermes-plugin/substrate_wiki/plugin.yaml", "source_sha256": "5bfc8b20bfe99b8dd4fcb35616724e9a4a93bdfd1f3b270cfea1ccff18eaa116", "transformation": "modified_for_standalone" @@ -481,7 +481,7 @@ { "class": "plugin-tests", "destination": "tests/test_entity_memory.py", - "destination_sha256": "9956679141ec085ab00dabca699fd4afc3638d039f9c567bca7b301929bf595a", + "destination_sha256": "139f51e9fdd91ee813f86281f41d4a150339ed5f6c303c48e7a5ef7ec358adbd", "source": "tests/contract/test_hermes_entity_memory_v13.py", "source_sha256": "297d7dffa2a53dd08cb1737ac024a97eb190c4ce42b338a952d8bec9aeba37f4", "transformation": "modified_for_standalone" @@ -537,7 +537,7 @@ { "class": "plugin-tests", "destination": "tests/test_packaging.py", - "destination_sha256": "9336c62a8011bf4b469574a27dfdee0abe3b1037a7d78baaf95f2840a16c6b51", + "destination_sha256": "5793b5b96793d08060dc7faf3c39d3ee4b0717272a297f865968e84d595f4023", "source": "tests/contract/test_hermes_plugin_packaging.py", "source_sha256": "c54967830788555b8d7bd5d2f871baeb6b98d7804a2bc7410ab297078bc20ee7", "transformation": "modified_for_standalone" diff --git a/pyproject.toml b/pyproject.toml index ce9dcb0..20eeb06 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "hermes-substrate-wiki" -version = "2.0.3" +version = "2.0.4" description = "Substrate memory-provider plugin for Hermes Agent" readme = "README.md" requires-python = ">=3.11" diff --git a/scripts/install_hermes_plugin.py b/scripts/install_hermes_plugin.py index 76daf84..5c8ed22 100644 --- a/scripts/install_hermes_plugin.py +++ b/scripts/install_hermes_plugin.py @@ -19,7 +19,7 @@ from typing import Any, cast PLUGIN_NAME = "substrate_wiki" -EXPECTED_VERSION = "2.0.3" +EXPECTED_VERSION = "2.0.4" EXPECTED_HERMES_VERSION = "0.20.0" LICENSE_FILENAME = "LICENSE" REQUIRED_FILES = { diff --git a/scripts/verify_public_plugin_candidate.py b/scripts/verify_public_plugin_candidate.py index b33fa2d..106df8b 100644 --- a/scripts/verify_public_plugin_candidate.py +++ b/scripts/verify_public_plugin_candidate.py @@ -122,6 +122,7 @@ "a3a24a5c987c939514866e72b70d01a32e34f5771089153e1fd12013829d8baf", "c4033e38f9e832779062381b0d0dcf71eecc25554d8df9af383a2d0c334dcd78", "c7a8e84d116319e62b0b7817c1a049225088c8b3baf59c42ad82cbefc3c172b5", + "5793b5b96793d08060dc7faf3c39d3ee4b0717272a297f865968e84d595f4023", } ), "tests/test_publication_scanner.py": frozenset( @@ -149,6 +150,7 @@ "bb4ba0542582cf3a89a37906651074c52bd1c28754b079bd67fd856c7fe24362", "d07d0c65e39f0949f91f7a880a4b584b9b57d8ed3c70ca8fd95276d8d525b517", "ef0cdf7a6c2fdc4ec7e781be15122f9b20331769929517a0f3c82ab15363480b", + "7ef28d44f7b82b765e870434542b5b67facc703e2105a40a233de2e12783852e", } ), "docs/api-ownership.json": frozenset( @@ -193,6 +195,7 @@ "95de10e9c4e9c37c7bd3bcce1f8b507de977682e869ec4d44386f197a910554f", "33adb95c93f478a91991a97f0b9b6a1c9d2cee77e7894ed37fe331a4403b0bb8", "4b34ee40d0d08ef24d03128e1cfc5ef73c69b39ca77b3fff59f4a4133cef76f2", + "8e37991db874b306045623c5cba39ef995b53b7b6a1a6c6f0253e2ff12c0ebb8", } ), "scripts/verify_public_plugin_candidate.py": frozenset( @@ -213,7 +216,8 @@ "src/substrate_wiki/client.py": frozenset( {"c711b64e496214de3acf5639075bd6bada19b687cd30426058e1b0bb443dce0b", "f98518e2eea1d57e813130822ea95de1fcc5b550adf9b65164347468eadc6818", - "a6e7c18e916057835e8cae9e3aa89bcc7357b397f014b8bc5bb71d995a5aa841"} + "a6e7c18e916057835e8cae9e3aa89bcc7357b397f014b8bc5bb71d995a5aa841", + "db9eb5043a5c770f155a942459008de684378261e8fd204276d69b0bed3d788f"} ), "src/substrate_wiki/redaction.py": frozenset( {"e9bec198aa7ad018da359d2e9aa6df1dab717881bb41b1001348911b23e6439b"} @@ -239,6 +243,7 @@ { "1f18a49d552f2912872c10b0e41e93ff50ccd923d2fa9c108d35084dc5a5b22c", "1f8fc410a88c7b410c4e72f4210226629b573a3c7f0cc410bc35939cd2317cfc", + "53d4873064a5d5b1748950c08c0aeaa76a6c00c435e4a8f534a844e4f90aca87", "67bcb14e4988ed5b49379e93f93fa588e31a068b394a8787d1ea81ba8f6a6c70", "e14a7a057ee64449041a2073590641a44cfe648cff696d4d20c58eed9e4509a3", } @@ -252,6 +257,7 @@ "9cf9406266336996c57227413514cf97a04eb13264d80fa58c54d325fecf3d9f", "d07d0c65e39f0949f91f7a880a4b584b9b57d8ed3c70ca8fd95276d8d525b517", "ef0cdf7a6c2fdc4ec7e781be15122f9b20331769929517a0f3c82ab15363480b", + "7ef28d44f7b82b765e870434542b5b67facc703e2105a40a233de2e12783852e", } ), 'docs/api-ownership.json': frozenset({'f1894c4653c51c6f02d23194617a3a1e9035d2cbe2b7c282f4cd6fbb26d8b65c'}), @@ -265,6 +271,7 @@ "082164ad24c879f6ca6434a8f28c251cd1ee7f4b413c5a788a70b351e2187f2a", "59413ab9fa2eef0d943a70a53e2b5d9d5163c0d9e154538735715e8f88585616", "a6e7c18e916057835e8cae9e3aa89bcc7357b397f014b8bc5bb71d995a5aa841", + "db9eb5043a5c770f155a942459008de684378261e8fd204276d69b0bed3d788f", "f98518e2eea1d57e813130822ea95de1fcc5b550adf9b65164347468eadc6818", } ), @@ -274,6 +281,7 @@ "4ae1938346a5af64b3936913c4b8438891d8170c6caa617b2eb3b5c816079ec0", "52e11806e20017c920d4c0caef394570a8fdfb2cabff8a87cdbde3dc7e5a64fc", "a835092092bca7266978609763d36f5767ac363fe7180bda482d713df2ecc439", + "73c8c6669b3c052578ce4818cd336b2f871794bcdb28a42131a856174de660b7", "d58fc9693f78cb4d5f8d9738c9895f009e098d73400d4e9530e7454662fabff1", "da01090b5f007d9741a06d7ff4a9c8140036e4ef26512665b4802800e0b543a3", } @@ -284,6 +292,7 @@ "4b34ee40d0d08ef24d03128e1cfc5ef73c69b39ca77b3fff59f4a4133cef76f2", "69af75e4240166896031f3a396fd0b2bdc4d00adbc836d1a4f22019bc6713b75", "72247d3537140098365350020cce29658c0743fee1aa738d7143db82316acce4", + "8e37991db874b306045623c5cba39ef995b53b7b6a1a6c6f0253e2ff12c0ebb8", } ), 'tests/test_hardening.py': frozenset({'f5f87125f1edd37bff1d44301d6bb0f44cc7faf3ba6122bdbe7569f349fea7a3', 'bb9825c1889d919e29a90e43c2be48b84d846fcad813fa3896d194491cc8f386'}), @@ -302,7 +311,7 @@ "4b444b2583fbdd340b17d279fd169103c57f87a56dece39988d784b311222920" ) TRUSTED_HISTORICAL_BLOB_POLICY_SHA256 = ( - "ec3a29f27ebe29ce65b7a70ac65311bfb4a31e54095709a7c69d2687e12d50e1" + "88e7743afb748d534413c182eb96e0575942659ce7192c0ea973ef56ed1b2790" ) SCANNER_PATH = "scripts/verify_public_plugin_candidate.py" DESTINATION_MANIFEST_PATH = "docs/extraction-manifest.json" diff --git a/src/substrate_wiki/client.py b/src/substrate_wiki/client.py index 0022ac3..f1f5667 100644 --- a/src/substrate_wiki/client.py +++ b/src/substrate_wiki/client.py @@ -26,7 +26,7 @@ _MAX_SHORT_FIELD_CHARS = 2048 _MAX_TEXT_FIELD_CHARS = 65536 _MAX_MEMORY_CARD_CHARS = 8192 -_USER_AGENT = "substrate_wiki-hermes-plugin/2.0.3" +_USER_AGENT = "substrate_wiki-hermes-plugin/2.0.4" _PLUGIN_VERSION = (2, 0, 0) diff --git a/src/substrate_wiki/onboarding.py b/src/substrate_wiki/onboarding.py index c466117..94450af 100644 --- a/src/substrate_wiki/onboarding.py +++ b/src/substrate_wiki/onboarding.py @@ -29,7 +29,7 @@ SCOPES = "capture retrieve" DEVICE_GRANT = "urn:ietf:params:oauth:grant-type:device_code" _STATE_VERSION = 1 -_PLUGIN_VERSION = "2.0.3" +_PLUGIN_VERSION = "2.0.4" _MAX_RESPONSE = 64 * 1024 _CAPABILITY_TIMEOUT_SECONDS = 60.0 _CAPABILITY_ATTEMPTS = 2 diff --git a/src/substrate_wiki/plugin.yaml b/src/substrate_wiki/plugin.yaml index 9762c1c..ee43d93 100644 --- a/src/substrate_wiki/plugin.yaml +++ b/src/substrate_wiki/plugin.yaml @@ -1,5 +1,5 @@ name: substrate_wiki -version: 2.0.3 +version: 2.0.4 description: "Unified entity-centric Substrate Markdown wiki memory with durable asynchronous capture." hooks: - on_session_switch diff --git a/tests/test_entity_memory.py b/tests/test_entity_memory.py index 1019587..f8fcfe4 100644 --- a/tests/test_entity_memory.py +++ b/tests/test_entity_memory.py @@ -251,6 +251,6 @@ def test_semantic_gate_rejects_non_strict_versions( def test_v141_manifest_and_prompt_describe_one_published_memory() -> None: manifest = (PLUGIN_ROOT / "substrate_wiki" / "plugin.yaml").read_text(encoding="utf-8") prompt = SubstrateWikiProvider().system_prompt_block() - assert "version: 2.0.3" in manifest + assert "version: 2.0.4" in manifest assert "single published memory" in prompt assert "canonical published entity" in prompt diff --git a/tests/test_packaging.py b/tests/test_packaging.py index 5f5757b..4bdf330 100644 --- a/tests/test_packaging.py +++ b/tests/test_packaging.py @@ -148,7 +148,7 @@ def test_archive_provenance_identifies_version_and_source_hashes(tmp_path: Path) assert provenance == { "build_format_version": 3, "license_sha256": hashlib.sha256((REPOSITORY_ROOT / "LICENSE").read_bytes()).hexdigest(), - "plugin_version": "2.0.3", + "plugin_version": "2.0.4", "provider_id": "substrate_wiki", "source_commit": "unknown", "source_files": { @@ -233,8 +233,8 @@ def test_publish_release_creates_exact_current_and_immutable_aliases(tmp_path: P releases_path=releases_path, ) - assert release_archive == releases_path / "2.0.3" / "substrate_wiki.zip" - assert release_installer == releases_path / "2.0.3" / "install_hermes_plugin.py" + assert release_archive == releases_path / "2.0.4" / "substrate_wiki.zip" + assert release_installer == releases_path / "2.0.4" / "install_hermes_plugin.py" assert archive_path.read_bytes() == release_archive.read_bytes() == archive assert release_installer.read_bytes() == INSTALLER_PATH.read_bytes() assert builder.check_release( @@ -246,7 +246,7 @@ def test_publish_release_creates_exact_current_and_immutable_aliases(tmp_path: P def test_publish_release_refuses_to_replace_versioned_bytes(tmp_path: Path) -> None: builder = load_builder() releases_path = tmp_path / "releases" - versioned = releases_path / "2.0.3" / "substrate_wiki.zip" + versioned = releases_path / "2.0.4" / "substrate_wiki.zip" versioned.parent.mkdir(parents=True) versioned.write_bytes(b"different immutable bytes") archive_path = tmp_path / "current" / "substrate_wiki.zip" @@ -265,7 +265,7 @@ def test_publish_release_refuses_to_replace_versioned_bytes(tmp_path: Path) -> N def test_publish_release_preflights_both_immutable_artifacts(tmp_path: Path) -> None: builder = load_builder() releases_path = tmp_path / "releases" - versioned_installer = releases_path / "2.0.3" / "install_hermes_plugin.py" + versioned_installer = releases_path / "2.0.4" / "install_hermes_plugin.py" versioned_installer.parent.mkdir(parents=True) versioned_installer.write_bytes(b"conflicting immutable installer") archive_path = tmp_path / "current" / "substrate_wiki.zip" @@ -277,14 +277,14 @@ def test_publish_release_preflights_both_immutable_artifacts(tmp_path: Path) -> releases_path=releases_path, ) - assert not (releases_path / "2.0.3" / "substrate_wiki.zip").exists() + assert not (releases_path / "2.0.4" / "substrate_wiki.zip").exists() assert not archive_path.exists() def test_publish_release_rejects_symlinked_immutable_artifact(tmp_path: Path) -> None: builder = load_builder() releases_path = tmp_path / "releases" - release_directory = releases_path / "2.0.3" + release_directory = releases_path / "2.0.4" release_directory.mkdir(parents=True) target = tmp_path / "elsewhere.zip" archive = builder.build_archive_bytes(source_commit="a" * 40) @@ -324,7 +324,7 @@ def test_installer_verifies_and_atomically_upgrades_with_rollback(tmp_path: Path assert result["action"] == "upgraded" assert result["source_commit"] == "a" * 40 - assert "version: 2.0.3" in (existing / "plugin.yaml").read_text(encoding="utf-8") + assert "version: 2.0.4" in (existing / "plugin.yaml").read_text(encoding="utf-8") rollback = Path(result["rollback"]) assert "version: 1.0.0" in (rollback / "plugin.yaml").read_text(encoding="utf-8") assert checkpoint.read_bytes() == b"content-free-checkpoint" @@ -382,7 +382,7 @@ def test_plugin_swap_restores_previous_version_when_hardening_fails( ) def fail_hardening(target: Path) -> None: - assert "version: 2.0.3" in (target / "plugin.yaml").read_text(encoding="utf-8") + assert "version: 2.0.4" in (target / "plugin.yaml").read_text(encoding="utf-8") raise OSError("permission hardening failed") monkeypatch.setattr(installer, "_harden_plugin_permissions", fail_hardening) @@ -392,7 +392,7 @@ def fail_hardening(target: Path) -> None: assert "version: 1.3.0" in (existing / "plugin.yaml").read_text(encoding="utf-8") failed = list((hermes_home / "plugins").glob("substrate_wiki.failed-*")) assert len(failed) == 1 - assert "version: 2.0.3" in (failed[0] / "plugin.yaml").read_text(encoding="utf-8") + assert "version: 2.0.4" in (failed[0] / "plugin.yaml").read_text(encoding="utf-8") def test_check_archive_preserves_sha_provenance_without_environment( @@ -588,7 +588,7 @@ def fail_checked_reload( systemctl_calls.append((command, kwargs)) if kwargs.get("check") is True: assert command == ("systemctl", "--user", "daemon-reload") - assert "version: 2.0.3" in (existing / "plugin.yaml").read_text( + assert "version: 2.0.4" in (existing / "plugin.yaml").read_text( encoding="utf-8" ) plugin_rollbacks = list( @@ -620,7 +620,7 @@ def fail_checked_reload( assert (existing / "v13-sentinel.txt").read_text(encoding="utf-8") == "prior plugin" failed_plugins = list((hermes_home / "plugins").glob("substrate_wiki.failed-*")) assert len(failed_plugins) == 1 - assert "version: 2.0.3" in (failed_plugins[0] / "plugin.yaml").read_text( + assert "version: 2.0.4" in (failed_plugins[0] / "plugin.yaml").read_text( encoding="utf-8" ) assert not list((hermes_home / "plugins").glob("substrate_wiki.rollback-*"))