a-memory 1.7.0__py3-none-any.whl
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.
- .ai-memory.toml +21 -0
- .claude/CLAUDE.md +109 -0
- .codegraph/.gitignore +5 -0
- .coderabbit.yaml +43 -0
- .cursorrules +0 -0
- .dockerignore +19 -0
- .gitattributes +22 -0
- .github/CODEOWNERS +11 -0
- .github/ISSUE_TEMPLATE/bug_report.yml +109 -0
- .github/ISSUE_TEMPLATE/config.yml +8 -0
- .github/ISSUE_TEMPLATE/feature_request.yml +54 -0
- .github/PULL_REQUEST_TEMPLATE.md +19 -0
- .github/codeql-config.yml +14 -0
- .github/coderabbit.yaml +7 -0
- .github/dependabot.yml +11 -0
- .github/labeler.yml +25 -0
- .github/release-drafter.yml +81 -0
- .github/release.yml +22 -0
- .github/workflows/auto-label.yml +20 -0
- .github/workflows/auto-merge-dependabot.yml +28 -0
- .github/workflows/ci.yml +130 -0
- .github/workflows/commit-lint.yml +25 -0
- .github/workflows/detect-secrets.yml +24 -0
- .github/workflows/docs.yml +46 -0
- .github/workflows/release-drafter.yml +22 -0
- .github/workflows/release.yml +34 -0
- .github/workflows/stale.yml +27 -0
- .gitignore +26 -0
- .hypothesis/.gitignore +9 -0
- .hypothesis/constants/0131918d5eaf61a7 +4 -0
- .hypothesis/constants/0217e1bf038fb749 +4 -0
- .hypothesis/constants/025685350222e199 +4 -0
- .hypothesis/constants/08df5bce2c067c67 +4 -0
- .hypothesis/constants/0d08164cc62ee81e +4 -0
- .hypothesis/constants/0d271cf9a853088f +4 -0
- .hypothesis/constants/0f3739f6a4bbceb3 +4 -0
- .hypothesis/constants/0f62f670d7d4556a +4 -0
- .hypothesis/constants/106fec097b637479 +4 -0
- .hypothesis/constants/1176a493ab7e441f +4 -0
- .hypothesis/constants/11a7f35b73b355d5 +4 -0
- .hypothesis/constants/17f994f04fca849d +4 -0
- .hypothesis/constants/185eaa1e42d08ff6 +4 -0
- .hypothesis/constants/189966a34e8bb83c +4 -0
- .hypothesis/constants/19674fb7b6e7fa25 +4 -0
- .hypothesis/constants/1a01b68ac676293a +4 -0
- .hypothesis/constants/1bf1ed0c3b1b2cf2 +4 -0
- .hypothesis/constants/1d15d5b976b2e007 +4 -0
- .hypothesis/constants/1ec990d09f06e8d9 +4 -0
- .hypothesis/constants/203497fc60177f0f +4 -0
- .hypothesis/constants/2075c0643eaa44c8 +4 -0
- .hypothesis/constants/211c68beb3822a01 +4 -0
- .hypothesis/constants/216d171530673e64 +4 -0
- .hypothesis/constants/21f42bb6b24338d5 +4 -0
- .hypothesis/constants/22f64a08a03d6fea +4 -0
- .hypothesis/constants/2713c3dcbfed8a65 +4 -0
- .hypothesis/constants/29e7f9bd76ba7954 +4 -0
- .hypothesis/constants/2a48faca228fb8b8 +4 -0
- .hypothesis/constants/2a720bdb9bcc758b +4 -0
- .hypothesis/constants/2a7d3223dfdacdc7 +4 -0
- .hypothesis/constants/2ab4878d9f8c321b +4 -0
- .hypothesis/constants/2dddcac69bb1261c +4 -0
- .hypothesis/constants/2e7ce396a7f9318d +4 -0
- .hypothesis/constants/31e48df20d82b835 +4 -0
- .hypothesis/constants/366007179f98843a +4 -0
- .hypothesis/constants/371b663ed83124ce +4 -0
- .hypothesis/constants/377b6e3a8331b432 +4 -0
- .hypothesis/constants/3797923938187bee +4 -0
- .hypothesis/constants/3799169d4dc29413 +4 -0
- .hypothesis/constants/3885295d020f5d92 +4 -0
- .hypothesis/constants/39007a22b040288b +4 -0
- .hypothesis/constants/39e3ef3d3b0df762 +4 -0
- .hypothesis/constants/3c661a25646e3c8d +4 -0
- .hypothesis/constants/3d2a9dd03bf5915a +4 -0
- .hypothesis/constants/3d3fff0fd8191af3 +4 -0
- .hypothesis/constants/3d45e3b69897b84b +4 -0
- .hypothesis/constants/3da2cc841c801589 +4 -0
- .hypothesis/constants/3e8b58eee7d8b4d5 +4 -0
- .hypothesis/constants/4348526455301b6f +4 -0
- .hypothesis/constants/447a859c04a7d280 +4 -0
- .hypothesis/constants/44ad6c5a9aca898d +4 -0
- .hypothesis/constants/45e32c74475b5981 +4 -0
- .hypothesis/constants/46b7f518cb403e97 +4 -0
- .hypothesis/constants/48000079506593bc +4 -0
- .hypothesis/constants/48a14ee521dfa3cb +4 -0
- .hypothesis/constants/4b13c601e5d8fa82 +4 -0
- .hypothesis/constants/4bb224fed2cc2464 +4 -0
- .hypothesis/constants/4bba663f9e3a787b +4 -0
- .hypothesis/constants/4c05aaf660e4a3b2 +4 -0
- .hypothesis/constants/4c7b96c079ec1ab7 +4 -0
- .hypothesis/constants/4e5e35c49498c848 +4 -0
- .hypothesis/constants/51621802fe75c4a6 +4 -0
- .hypothesis/constants/525e955b17448061 +4 -0
- .hypothesis/constants/540912285c94e4b0 +4 -0
- .hypothesis/constants/5536ab6571868683 +4 -0
- .hypothesis/constants/562262d029962266 +4 -0
- .hypothesis/constants/5925e89c57abb6ff +4 -0
- .hypothesis/constants/59d133a77d9576e9 +4 -0
- .hypothesis/constants/5b400234d99fd69b +4 -0
- .hypothesis/constants/5e83b4b2ff8af413 +4 -0
- .hypothesis/constants/5fe68c54a53f5ce4 +4 -0
- .hypothesis/constants/6131c8fae3ea4a1c +4 -0
- .hypothesis/constants/61b12d8090da6b04 +4 -0
- .hypothesis/constants/62a13eb5c6ae282e +4 -0
- .hypothesis/constants/632362028177075b +4 -0
- .hypothesis/constants/637fb6fc4de5ab59 +4 -0
- .hypothesis/constants/64581adcc197c14e +4 -0
- .hypothesis/constants/64ae85eda3f4fa14 +4 -0
- .hypothesis/constants/675e6b5dded87234 +4 -0
- .hypothesis/constants/68dd5c4801e6e3fe +4 -0
- .hypothesis/constants/6962c1e17c7d2d6e +4 -0
- .hypothesis/constants/6b5bb9273029269e +4 -0
- .hypothesis/constants/6b76dcda53857290 +4 -0
- .hypothesis/constants/6c479fc827b2cfbe +4 -0
- .hypothesis/constants/6c4ee0da13eebeae +4 -0
- .hypothesis/constants/6d715fa94e03f86b +4 -0
- .hypothesis/constants/6e1df95b5625e965 +4 -0
- .hypothesis/constants/6e275ef4cbf7ac61 +4 -0
- .hypothesis/constants/6fa9fff037e639f7 +4 -0
- .hypothesis/constants/71ace2ebb96b893f +4 -0
- .hypothesis/constants/71c4d835b625450b +4 -0
- .hypothesis/constants/7288eb701eee527d +4 -0
- .hypothesis/constants/7403d3ba4d2427c5 +4 -0
- .hypothesis/constants/75bbc0c2fcedafd3 +4 -0
- .hypothesis/constants/772c40cd27e8744e +4 -0
- .hypothesis/constants/79fa24e4d5da2d33 +4 -0
- .hypothesis/constants/7b6e9f1290db23f7 +4 -0
- .hypothesis/constants/7c6d5f5a208c10c5 +4 -0
- .hypothesis/constants/7dd927a870b92315 +4 -0
- .hypothesis/constants/7f6008018b3bba5d +4 -0
- .hypothesis/constants/8166dc74cd220aec +4 -0
- .hypothesis/constants/82db07106773e27e +4 -0
- .hypothesis/constants/837366e7d032fae3 +4 -0
- .hypothesis/constants/85c89ff18f5bf974 +4 -0
- .hypothesis/constants/865ed168629d6aa5 +4 -0
- .hypothesis/constants/86e6c616c92ab441 +4 -0
- .hypothesis/constants/8a33aed17bd34bab +4 -0
- .hypothesis/constants/8d467b62cb87a27e +4 -0
- .hypothesis/constants/8dd1a0f92678435a +4 -0
- .hypothesis/constants/8e89a1c503109280 +4 -0
- .hypothesis/constants/8fa301e58829e474 +4 -0
- .hypothesis/constants/91857b0d89aad21a +4 -0
- .hypothesis/constants/91a01a53d7020790 +4 -0
- .hypothesis/constants/920ae7e76de44d3f +4 -0
- .hypothesis/constants/924b1a5d241d153a +4 -0
- .hypothesis/constants/9373d27b0b0cf58f +4 -0
- .hypothesis/constants/93785fcb751d6e65 +4 -0
- .hypothesis/constants/939c8629bda15316 +4 -0
- .hypothesis/constants/940ce79a2837ad57 +4 -0
- .hypothesis/constants/94c8a35cd4ebef22 +4 -0
- .hypothesis/constants/94db69cd7ea46848 +4 -0
- .hypothesis/constants/952c7791b4ba4d5a +4 -0
- .hypothesis/constants/95ac562d657f621e +4 -0
- .hypothesis/constants/979151c821a00dcc +4 -0
- .hypothesis/constants/9991e42f1b4b8de3 +4 -0
- .hypothesis/constants/99dd57061d19ac3a +4 -0
- .hypothesis/constants/9c217d6d4d6f2c4a +4 -0
- .hypothesis/constants/9d52c13fb5c16e16 +4 -0
- .hypothesis/constants/9d777dde1f80fdb9 +4 -0
- .hypothesis/constants/9f37a692294d3003 +4 -0
- .hypothesis/constants/9fbe011fca4ea60c +4 -0
- .hypothesis/constants/a003e2c8581f010e +4 -0
- .hypothesis/constants/a01c724a454a8b07 +4 -0
- .hypothesis/constants/a19ddbb5bb485e6d +4 -0
- .hypothesis/constants/a34cec0af3c61213 +4 -0
- .hypothesis/constants/a388424d0fca2721 +4 -0
- .hypothesis/constants/a4a93b033d17cf24 +4 -0
- .hypothesis/constants/a560cce78032950e +4 -0
- .hypothesis/constants/a5a8f7b489f537fc +4 -0
- .hypothesis/constants/a67af5b4aeadcff2 +4 -0
- .hypothesis/constants/a6c8f5eb7163d884 +4 -0
- .hypothesis/constants/a722cf958fac9b3c +4 -0
- .hypothesis/constants/a821981d00e00216 +4 -0
- .hypothesis/constants/a963981095621930 +4 -0
- .hypothesis/constants/aadddacd15f6ac3a +4 -0
- .hypothesis/constants/ac677dd8b8917e26 +4 -0
- .hypothesis/constants/ad88faefd482e804 +4 -0
- .hypothesis/constants/adb5954c6bb49007 +4 -0
- .hypothesis/constants/adc83b19e793491b +4 -0
- .hypothesis/constants/b1c9c22b2744f127 +4 -0
- .hypothesis/constants/b2336f0cf3fc59dd +4 -0
- .hypothesis/constants/b239cb3f0b8646c1 +4 -0
- .hypothesis/constants/b2cacfb0b5256165 +4 -0
- .hypothesis/constants/b57853a780c67716 +4 -0
- .hypothesis/constants/b761cefd2d825687 +4 -0
- .hypothesis/constants/b7654ffb010d2a80 +4 -0
- .hypothesis/constants/b768678447dffe12 +4 -0
- .hypothesis/constants/b9379698f7948f1f +4 -0
- .hypothesis/constants/b9462bd5cd23c3e1 +4 -0
- .hypothesis/constants/bc536f7f8cec8029 +4 -0
- .hypothesis/constants/bc6dc851bd4abd68 +4 -0
- .hypothesis/constants/bd470a969c12eaea +4 -0
- .hypothesis/constants/c195b79b581a5e4a +4 -0
- .hypothesis/constants/c281c878a9cfffe7 +4 -0
- .hypothesis/constants/c32689b74b2df638 +4 -0
- .hypothesis/constants/c563f080b55d5a55 +4 -0
- .hypothesis/constants/c58cb290bf3a6af2 +4 -0
- .hypothesis/constants/c599575389481bca +4 -0
- .hypothesis/constants/c5d3925deea75efd +4 -0
- .hypothesis/constants/c70bdf7090eff52b +4 -0
- .hypothesis/constants/c92583fdbbd8025f +4 -0
- .hypothesis/constants/c9e899c314823e78 +4 -0
- .hypothesis/constants/cbc76d66f0269c37 +4 -0
- .hypothesis/constants/cdb3f019754ca1e6 +4 -0
- .hypothesis/constants/d13022c0afda1563 +4 -0
- .hypothesis/constants/d2610b9162e41d14 +4 -0
- .hypothesis/constants/d47087da8bc398e1 +4 -0
- .hypothesis/constants/d51ac9dbfded2877 +4 -0
- .hypothesis/constants/d83e43c1a00ef966 +4 -0
- .hypothesis/constants/d9b8c8c138bb6b21 +4 -0
- .hypothesis/constants/d9fa1f244511bb57 +4 -0
- .hypothesis/constants/da39a3ee5e6b4b0d +4 -0
- .hypothesis/constants/db1a7096187ad5e3 +4 -0
- .hypothesis/constants/db6f3b1f106a20fe +4 -0
- .hypothesis/constants/dbcc0a55f941df10 +4 -0
- .hypothesis/constants/de59ae4c0e9adc1e +4 -0
- .hypothesis/constants/de7a1e6e64930056 +4 -0
- .hypothesis/constants/df25569e1b33d3f1 +4 -0
- .hypothesis/constants/e05c7ba7895ef945 +4 -0
- .hypothesis/constants/e3e0949ea24f14cb +4 -0
- .hypothesis/constants/e4bebe96ce86ee3a +4 -0
- .hypothesis/constants/e4dbd43376140e77 +4 -0
- .hypothesis/constants/e515b936fbb56d29 +4 -0
- .hypothesis/constants/e55fd534f3c5eef8 +4 -0
- .hypothesis/constants/e59ee7a18d689b65 +4 -0
- .hypothesis/constants/ea468f1fbc755b35 +4 -0
- .hypothesis/constants/eb2261d5ee23ffb7 +4 -0
- .hypothesis/constants/ee936137cd0c418e +4 -0
- .hypothesis/constants/eea2d47be7a11d26 +4 -0
- .hypothesis/constants/eecb26b7b8b77207 +4 -0
- .hypothesis/constants/f05c8ae7929ace98 +4 -0
- .hypothesis/constants/f16bb017e9ee5af9 +4 -0
- .hypothesis/constants/f234592906cfe3fb +4 -0
- .hypothesis/constants/f37e913bdd621102 +4 -0
- .hypothesis/constants/f3e54889b30a59c0 +4 -0
- .hypothesis/constants/f59ffab710c767fd +4 -0
- .hypothesis/constants/f5f4ab83cb6aea25 +4 -0
- .hypothesis/constants/f9356a6ecf0ddc0a +4 -0
- .hypothesis/constants/fbd35b0283486407 +4 -0
- .hypothesis/constants/fc6e2235d01cc617 +4 -0
- .hypothesis/constants/fcf63ec14b415b9b +4 -0
- .hypothesis/constants/fd04c0c4b4afe562 +4 -0
- .hypothesis/constants/fe045c8a1d327a5f +4 -0
- .hypothesis/constants/fee5511b5a452741 +4 -0
- .hypothesis/constants/ffb2784e9f595389 +4 -0
- .hypothesis/constants/ffb8303a5a716659 +4 -0
- .hypothesis/unicode_data/14.0.0/charmap.json.gz +0 -0
- .hypothesis/unicode_data/14.0.0/codec-v2-utf-8.json.gz +0 -0
- .pre-commit-config.yaml +27 -0
- .windsurfrules +88 -0
- ARIEL_RULES.md +28 -0
- AUDIT_REPORT_20260629.md +114 -0
- CHANGELOG.md +400 -0
- CODE_OF_CONDUCT.md +128 -0
- CONTRIBUTING.md +68 -0
- Dockerfile +30 -0
- LICENSE +21 -0
- PKG-INFO +261 -0
- README.md +192 -0
- ROADMAP.md +209 -0
- SECURITY.md +63 -0
- a_memory-1.7.0.dist-info/METADATA +261 -0
- a_memory-1.7.0.dist-info/RECORD +550 -0
- a_memory-1.7.0.dist-info/WHEEL +4 -0
- a_memory-1.7.0.dist-info/entry_points.txt +4 -0
- a_memory-1.7.0.dist-info/licenses/LICENSE +21 -0
- alembic/README +1 -0
- alembic/env.py +94 -0
- alembic/script.py.mako +28 -0
- alembic/versions/4f9d5ab34719_add_audit_log_index_and_optimize.py +34 -0
- alembic/versions/a38d67fcd99e_init_v8_schema.py +291 -0
- alembic/versions/c7e21a94b0d5_layer_isolate_l3_l4.py +59 -0
- alembic/versions/e5b7d2f8a1c3_rag_chunks_float_blobs.py +42 -0
- alembic.ini +149 -0
- config.py +112 -0
- config.yaml +169 -0
- core/__init__.py +100 -0
- core/episodic.py +161 -0
- core/memory.py +232 -0
- core/projects.py +196 -0
- core/reflex.py +75 -0
- core/session.py +106 -0
- data/auth/keys.enc +0 -0
- data/auth/token.enc +1 -0
- docker-compose.yml +14 -0
- docs/CONTROL_MAP.md +109 -0
- docs/api/importance.md +9 -0
- docs/api/memory-types.md +9 -0
- docs/api/secrets.md +11 -0
- docs/architecture/connection.md +44 -0
- docs/architecture/diagrams.md +119 -0
- docs/architecture/layers.md +76 -0
- docs/architecture/overview.md +70 -0
- docs/changelog.md +17 -0
- docs/contributing.md +29 -0
- docs/core/episodic.md +20 -0
- docs/core/memory.md +29 -0
- docs/core/reflex.md +39 -0
- docs/core/session.md +20 -0
- docs/features/auth.md +28 -0
- docs/features/backup.md +28 -0
- docs/features/circuit-breaker.md +35 -0
- docs/features/compression.md +17 -0
- docs/features/dedup.md +25 -0
- docs/features/privacy.md +34 -0
- docs/features/rate-limiting.md +18 -0
- docs/features/secrets.md +48 -0
- docs/features/token-budget.md +27 -0
- docs/getting-started/configuration.md +69 -0
- docs/getting-started/installation.md +52 -0
- docs/getting-started/quickstart.md +42 -0
- docs/hooks/system.md +45 -0
- docs/index.md +90 -0
- docs/lifecycle/overview.md +35 -0
- docs/operations/deployment.md +58 -0
- docs/operations/monitoring.md +25 -0
- docs/operations/testing.md +39 -0
- docs/rag/conflict.md +33 -0
- docs/rag/engine.md +34 -0
- docs/rag/quantize.md +25 -0
- docs/rag/router.md +20 -0
- docs/rag/scoring.md +31 -0
- docs/research/RESEARCH-ideas.md +688 -0
- docs/tools/reference.md +125 -0
- docs/wiki/agent-wiki.md +20 -0
- docs/wiki/file-wiki.md +39 -0
- docs/wiki/overview.md +43 -0
- docs/wiki/user-wiki.md +20 -0
- features/__init__.py +9 -0
- features/audit_trail.py +136 -0
- features/auth/__init__.py +13 -0
- features/auth/api_key.py +82 -0
- features/auth/bearer.py +55 -0
- features/auth/models.py +18 -0
- features/auth/store.py +69 -0
- features/backup.py +99 -0
- features/backup_cron.py +239 -0
- features/compression.py +65 -0
- features/dashboard.py +222 -0
- features/db_maintenance.py +126 -0
- features/import_export.py +98 -0
- features/rate_limiting.py +114 -0
- features/secrets.py +158 -0
- features/typed_export.py +90 -0
- full_test_output.txt +12 -0
- graph/__init__.py +6 -0
- graph/epistemic.py +260 -0
- graph/temporal.py +139 -0
- hooks/__init__.py +11 -0
- hooks/agent_hooks.py +126 -0
- hooks/loader.py +5 -0
- hooks/models.py +18 -0
- hooks/registry.py +101 -0
- hooks/shared.py +112 -0
- hooks/user_hooks.py +163 -0
- lifecycle/__init__.py +7 -0
- lifecycle/consolidation.py +117 -0
- lifecycle/emotion/README.md +30 -0
- lifecycle/emotion/__init__.py +6 -0
- lifecycle/emotion/config.py +16 -0
- lifecycle/emotion/engine.py +86 -0
- lifecycle/emotion/models.py +27 -0
- lifecycle/emotion/trigger.py +58 -0
- lifecycle/forgetting.py +188 -0
- lifecycle/importance_scheduler.py +162 -0
- mcp_server/__init__.py +5 -0
- mcp_server/__main__.py +3 -0
- mcp_server/app.py +57 -0
- mcp_server/constants.py +11 -0
- mcp_server/context.py +55 -0
- mcp_server/endpoints/__init__.py +0 -0
- mcp_server/endpoints/auth.py +30 -0
- mcp_server/endpoints/backup.py +29 -0
- mcp_server/endpoints/common.py +36 -0
- mcp_server/endpoints/dashboard.py +66 -0
- mcp_server/endpoints/system.py +64 -0
- mcp_server/lifespan.py +110 -0
- mcp_server/middlewares.py +32 -0
- mcp_server/models.py +126 -0
- mcp_server/registry.py +32 -0
- mcp_server/schema.py +68 -0
- mcp_server/server.py +153 -0
- mcp_server/tools/base.py +223 -0
- mcp_server/tools/episodic.py +112 -0
- mcp_server/tools/graph.py +151 -0
- mcp_server/tools/memory.py +141 -0
- mcp_server/tools/ops.py +404 -0
- mcp_server/tools/primitives.py +532 -0
- mcp_server/tools/session.py +84 -0
- mcp_server/tools/wiki.py +75 -0
- mcp_server/tools_layer.py +74 -0
- mcp_server/utils/__init__.py +1 -0
- mcp_server/utils/privacy.py +31 -0
- mkdocs.yml +106 -0
- openapi.yaml +314 -0
- py.typed +1 -0
- pyproject.toml +196 -0
- rag/__init__.py +7 -0
- rag/chunking.py +59 -0
- rag/conflict.py +189 -0
- rag/engine.py +180 -0
- rag/ingestor.py +115 -0
- rag/models.py +34 -0
- rag/multi_source.py +175 -0
- rag/quantize.py +232 -0
- rag/router.py +292 -0
- rag/schema.py +75 -0
- rag/scoring.py +165 -0
- rag/search.py +253 -0
- rag/searcher.py +133 -0
- shared/__init__.py +47 -0
- shared/adaptive.py +91 -0
- shared/archived_memories.py +77 -0
- shared/assets/__init__.py +0 -0
- shared/assets/emotions.json +35 -0
- shared/cache.py +57 -0
- shared/connection.py +256 -0
- shared/constants.py +37 -0
- shared/crypto.py +58 -0
- shared/dream_buffer.py +147 -0
- shared/embeddings.py +179 -0
- shared/importance/README.md +33 -0
- shared/importance/__init__.py +4 -0
- shared/importance/models.py +58 -0
- shared/importance/scorer.py +118 -0
- shared/importance/signals/__init__.py +21 -0
- shared/importance/signals/base_signal.py +10 -0
- shared/importance/signals/base_type_signal.py +20 -0
- shared/importance/signals/emotion_signal.py +23 -0
- shared/importance/signals/length_signal.py +15 -0
- shared/importance/signals/noise_signal.py +19 -0
- shared/importance/signals/novelty_signal.py +10 -0
- shared/importance/signals/question_signal.py +10 -0
- shared/importance/signals/retrieval_signal.py +13 -0
- shared/importance/signals/tech_signal.py +26 -0
- shared/memory_types.py +259 -0
- shared/metrics.py +75 -0
- shared/middleware.py +222 -0
- shared/migrations.py +70 -0
- shared/path_safety.py +18 -0
- shared/read_only.py +101 -0
- shared/saga/README.md +36 -0
- shared/saga/__init__.py +36 -0
- shared/saga/engine.py +103 -0
- shared/saga/impl/__init__.py +0 -0
- shared/saga/impl/backup.py +90 -0
- shared/saga/impl/base.py +579 -0
- shared/saga/impl/consolidation.py +88 -0
- shared/saga/impl/crypto.py +91 -0
- shared/saga/persistence.py +84 -0
- shared/saga/schema.py +33 -0
- shared//320/237/320/273/320/260/320/275 /320/277/320/265/321/200/320/265/321/205/320/276/320/264/320/260 /320/275/320/260 aiosqlite.md" +100 -0
- test_output.txt +22 -0
- tests/__init__.py +0 -0
- tests/conftest.py +66 -0
- tests/features/test_api_key_auth.py +75 -0
- tests/features/test_auth_store.py +66 -0
- tests/features/test_bearer_auth.py +53 -0
- tests/shared/__init__.py +0 -0
- tests/shared/test_importance_models.py +30 -0
- tests/shared/test_importance_scorer.py +92 -0
- tests/shared/test_importance_scorer_extra.py +75 -0
- tests/shared/test_importance_signals.py +58 -0
- tests/shared/test_saga_engine.py +160 -0
- tests/shared/test_saga_engine_extra.py +118 -0
- tests/shared/test_saga_impl.py +143 -0
- tests/shared/test_saga_persistence.py +56 -0
- tests/shared/test_saga_schema.py +48 -0
- tests/test_auth_backup.py +84 -0
- tests/test_auth_crypto.py +87 -0
- tests/test_conflict_bm25.py +89 -0
- tests/test_core/__init__.py +0 -0
- tests/test_core/test_core.py +11 -0
- tests/test_db_maintenance.py +74 -0
- tests/test_dream_buffer_flow.py +69 -0
- tests/test_features/__init__.py +0 -0
- tests/test_features/test_backup_path_safety.py +31 -0
- tests/test_features/test_features.py +14 -0
- tests/test_features/test_import_export_path_safety.py +32 -0
- tests/test_features_coverage.py +439 -0
- tests/test_graph/__init__.py +0 -0
- tests/test_hooks/__init__.py +0 -0
- tests/test_hooks/test_registry_core.py +61 -0
- tests/test_hooks/test_registry_fire.py +57 -0
- tests/test_hypothesis.py +1361 -0
- tests/test_importance_middleware.py +74 -0
- tests/test_importance_scheduler.py +122 -0
- tests/test_importance_v2.py +116 -0
- tests/test_infra_overhaul.py +17 -0
- tests/test_integration.py +183 -0
- tests/test_lifecycle/__init__.py +0 -0
- tests/test_lifecycle/test_emotion_engine.py +99 -0
- tests/test_lifecycle/test_emotion_engine_extra.py +52 -0
- tests/test_lifecycle/test_emotion_models.py +39 -0
- tests/test_lifecycle/test_emotion_trigger_integration.py +85 -0
- tests/test_lifecycle/test_forgetting_edge.py +46 -0
- tests/test_mcp/__init__.py +0 -0
- tests/test_mcp/test_primitives.py +120 -0
- tests/test_mcp/test_tools_e2e.py +336 -0
- tests/test_mcp/test_tools_unit.py +167 -0
- tests/test_memory_types.py +72 -0
- tests/test_mib_quantize.py +101 -0
- tests/test_multi_source.py +117 -0
- tests/test_primitives.py +171 -0
- tests/test_project_audit.py +121 -0
- tests/test_projects.py +166 -0
- tests/test_rag/__init__.py +0 -0
- tests/test_rag/test_rag_engine_lifecycle.py +47 -0
- tests/test_rag/test_rag_ingestor.py +84 -0
- tests/test_rag/test_rag_schema.py +59 -0
- tests/test_rag/test_rag_searcher.py +91 -0
- tests/test_rag_binary_search.py +104 -0
- tests/test_rag_chunking.py +27 -0
- tests/test_rag_keep_float.py +49 -0
- tests/test_rag_no_legacy_api.py +40 -0
- tests/test_rag_scoring.py +128 -0
- tests/test_rag_search_facade.py +166 -0
- tests/test_router_improvements.py +106 -0
- tests/test_routing_integration.py +147 -0
- tests/test_saga_behavior.py +185 -0
- tests/test_secrets.py +87 -0
- tests/test_shared/__init__.py +0 -0
- tests/test_shared/test_connection.py +48 -0
- tests/test_shared/test_embeddings_unit.py +17 -0
- tests/test_shared/test_middleware_unit.py +68 -0
- tests/test_shared/test_migrations_coverage.py +43 -0
- tests/test_shared/test_path_safety.py +31 -0
- tests/test_shared/test_read_only_coverage.py +53 -0
- tests/test_shared/test_saga_crypto_coverage.py +108 -0
- tests/test_shared/test_saga_crypto_wiring.py +18 -0
- tests/test_shared/test_saga_unit.py +292 -0
- tests/test_shared/test_shared.py +63 -0
- tests/test_threshold_training.py +132 -0
- tests/test_typed_consolidation.py +57 -0
- tests/test_typed_forgetting.py +87 -0
- tests/test_wiki/__init__.py +0 -0
- tests/test_wiki/test_manager.py +128 -0
- tests/test_wiki/test_path_safety.py +46 -0
- tests/test_wiki/test_wiki.py +41 -0
- tests/test_wiki/test_wiki_index.py +145 -0
- tests/test_wiki/test_wiki_manager.py +125 -0
- tests/test_wiki/test_wiki_manager_extra.py +68 -0
- tests/test_wiki/test_wiki_parser.py +74 -0
- uv.lock +2699 -0
- wiki/README.md +32 -0
- wiki/__init__.py +23 -0
- wiki/index.py +207 -0
- wiki/manager.py +321 -0
- wiki/models.py +14 -0
- wiki/parser.py +72 -0
- wiki/shared.py +41 -0
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
from shared.saga.schema import SagaStatus, SagaStepState, SagaState
|
|
2
|
+
import time
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
def test_saga_status_enum():
|
|
6
|
+
assert SagaStatus.PENDING == "pending"
|
|
7
|
+
assert SagaStatus.STUCK == "stuck"
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def test_saga_step_state_validation():
|
|
11
|
+
step = SagaStepState(name="test_step", status=SagaStatus.PENDING, result={"foo": "bar"})
|
|
12
|
+
assert step.name == "test_step"
|
|
13
|
+
assert step.status == SagaStatus.PENDING
|
|
14
|
+
assert step.result == {"foo": "bar"}
|
|
15
|
+
assert step.error is None
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def test_saga_state_validation():
|
|
19
|
+
saga_id = "abc12345"
|
|
20
|
+
state = SagaState(
|
|
21
|
+
saga_id=saga_id,
|
|
22
|
+
name="test_saga",
|
|
23
|
+
context={"user_id": 1},
|
|
24
|
+
steps=[SagaStepState(name="step1", status=SagaStatus.COMPLETED, result={"ok": True})],
|
|
25
|
+
current_step_index=0,
|
|
26
|
+
status=SagaStatus.RUNNING,
|
|
27
|
+
started_at=time.time(),
|
|
28
|
+
)
|
|
29
|
+
assert state.saga_id == saga_id
|
|
30
|
+
assert len(state.steps) == 1
|
|
31
|
+
assert state.status == SagaStatus.RUNNING
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def test_saga_state_serialization():
|
|
35
|
+
state = SagaState(saga_id="deadbeef", name="test", context={}, steps=[], current_step_index=0, status=SagaStatus.PENDING, started_at=time.time())
|
|
36
|
+
json_data = state.model_dump_json()
|
|
37
|
+
assert "deadbeef" in json_data
|
|
38
|
+
|
|
39
|
+
restored = SagaState.model_validate_json(json_data)
|
|
40
|
+
assert restored.saga_id == "deadbeef"
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
if __name__ == "__main__":
|
|
44
|
+
test_saga_status_enum()
|
|
45
|
+
test_saga_step_state_validation()
|
|
46
|
+
test_saga_state_validation()
|
|
47
|
+
test_saga_state_serialization()
|
|
48
|
+
print("All tests passed!")
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Tests for auth — unique tests only.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import pytest
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
@pytest.mark.asyncio
|
|
9
|
+
async def test_api_key_create():
|
|
10
|
+
from features.auth import APIKeyAuth
|
|
11
|
+
|
|
12
|
+
auth = APIKeyAuth()
|
|
13
|
+
key = auth.create_key("alice", "test key")
|
|
14
|
+
assert key.startswith("ak_")
|
|
15
|
+
assert len(key) > 20
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
@pytest.mark.asyncio
|
|
19
|
+
async def test_api_key_verify():
|
|
20
|
+
from features.auth import APIKeyAuth
|
|
21
|
+
|
|
22
|
+
auth = APIKeyAuth()
|
|
23
|
+
key = auth.create_key("alice", "test key")
|
|
24
|
+
info = auth.verify(key)
|
|
25
|
+
assert info is not None
|
|
26
|
+
assert info["user_id"] == "alice"
|
|
27
|
+
assert info["label"] == "test key"
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
@pytest.mark.asyncio
|
|
31
|
+
async def test_api_key_revoke():
|
|
32
|
+
from features.auth import APIKeyAuth
|
|
33
|
+
|
|
34
|
+
auth = APIKeyAuth()
|
|
35
|
+
key = auth.create_key("alice", "test key")
|
|
36
|
+
assert auth.verify(key) is not None
|
|
37
|
+
revoked = auth.revoke(key)
|
|
38
|
+
assert revoked is True
|
|
39
|
+
assert auth.verify(key) is None
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
@pytest.mark.asyncio
|
|
43
|
+
async def test_api_key_list():
|
|
44
|
+
from features.auth import APIKeyAuth
|
|
45
|
+
|
|
46
|
+
auth = APIKeyAuth()
|
|
47
|
+
auth.create_key("alice", "key1")
|
|
48
|
+
auth.create_key("alice", "key2")
|
|
49
|
+
keys = auth.list_keys()
|
|
50
|
+
assert len(keys) >= 2
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
@pytest.mark.asyncio
|
|
54
|
+
async def test_bearer_auth():
|
|
55
|
+
from features.auth import BearerAuth
|
|
56
|
+
|
|
57
|
+
ba = BearerAuth()
|
|
58
|
+
token = ba.get_token()
|
|
59
|
+
assert token.startswith("mt_")
|
|
60
|
+
assert ba.verify("Bearer " + token) is True
|
|
61
|
+
assert ba.verify("Bearer invalid") is False
|
|
62
|
+
assert ba.verify("") is False
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
@pytest.mark.asyncio
|
|
66
|
+
async def test_bearer_rotate():
|
|
67
|
+
from features.auth import BearerAuth
|
|
68
|
+
|
|
69
|
+
ba = BearerAuth()
|
|
70
|
+
old_token = ba.get_token()
|
|
71
|
+
new_token = ba.rotate()
|
|
72
|
+
assert old_token != new_token
|
|
73
|
+
assert ba.verify("Bearer " + new_token) is True
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
@pytest.mark.asyncio
|
|
77
|
+
async def test_mcp_lifespan():
|
|
78
|
+
from mcp_server.server import lifespan, mcp
|
|
79
|
+
|
|
80
|
+
async with lifespan(mcp) as ctx:
|
|
81
|
+
assert ctx is not None
|
|
82
|
+
assert hasattr(ctx, "mm")
|
|
83
|
+
assert hasattr(ctx, "user_wiki")
|
|
84
|
+
assert hasattr(ctx, "agent_wiki")
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
"""End-to-end tests for auth with encryption."""
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
import os
|
|
5
|
+
|
|
6
|
+
import pytest
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
@pytest.fixture(autouse=True)
|
|
10
|
+
def restore_master_key():
|
|
11
|
+
"""Restore MCP_MASTER_KEY after each test to prevent cross-test contamination."""
|
|
12
|
+
original = os.environ.get("MCP_MASTER_KEY")
|
|
13
|
+
yield
|
|
14
|
+
if original is not None:
|
|
15
|
+
os.environ["MCP_MASTER_KEY"] = original
|
|
16
|
+
else:
|
|
17
|
+
os.environ.pop("MCP_MASTER_KEY", None)
|
|
18
|
+
from features import secrets
|
|
19
|
+
|
|
20
|
+
secrets._master_cache.clear()
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def test_legacy_plain_json_gets_rotated(tmp_path):
|
|
24
|
+
"""If file is plain JSON, it should be auto-encrypted on load."""
|
|
25
|
+
keys_file = tmp_path / "api_keys.json"
|
|
26
|
+
keys_file.write_text(json.dumps({"ak_old": {"user_id": "u1", "label": "old", "enabled": True, "created_at": 1.0}}))
|
|
27
|
+
from features.auth import APIKeyAuth
|
|
28
|
+
|
|
29
|
+
auth = APIKeyAuth(keys_file=str(keys_file))
|
|
30
|
+
keys = auth.list_keys()
|
|
31
|
+
assert len(keys) == 1
|
|
32
|
+
assert keys[0]["user_id"] == "u1"
|
|
33
|
+
|
|
34
|
+
# Verify data survived the rotation — read with fresh instance
|
|
35
|
+
auth2 = APIKeyAuth(keys_file=str(keys_file))
|
|
36
|
+
keys2 = auth2.list_keys()
|
|
37
|
+
assert len(keys2) == 1
|
|
38
|
+
assert keys2[0]["user_id"] == "u1"
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def test_create_then_verify_round_trip(tmp_path):
|
|
42
|
+
keys_file = tmp_path / "api_keys.json"
|
|
43
|
+
from features.auth import APIKeyAuth
|
|
44
|
+
|
|
45
|
+
auth = APIKeyAuth(keys_file=str(keys_file))
|
|
46
|
+
key = auth.create_key("alice", "test-key")
|
|
47
|
+
out = auth.verify(key)
|
|
48
|
+
assert out == {"user_id": "alice", "label": "test-key"}
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def test_wrong_master_key_fails(tmp_path):
|
|
52
|
+
keys_file = tmp_path / "api_keys.json"
|
|
53
|
+
from features.auth import APIKeyAuth
|
|
54
|
+
|
|
55
|
+
auth = APIKeyAuth(keys_file=str(keys_file))
|
|
56
|
+
auth.create_key("bob", "test-label")
|
|
57
|
+
|
|
58
|
+
# Change master key
|
|
59
|
+
os.environ["MCP_MASTER_KEY"] = "different-secret"
|
|
60
|
+
from features import secrets
|
|
61
|
+
|
|
62
|
+
secrets._master_cache.clear()
|
|
63
|
+
|
|
64
|
+
# Should fail to decrypt (returns empty dict, not exception)
|
|
65
|
+
auth2 = APIKeyAuth(keys_file=str(keys_file))
|
|
66
|
+
assert auth2.list_keys() == [] # cannot read old keys
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def test_bearer_token_encrypted(tmp_path):
|
|
70
|
+
token_file = tmp_path / "bearer_token.json"
|
|
71
|
+
from features.auth import BearerAuth
|
|
72
|
+
|
|
73
|
+
auth = BearerAuth(token_file=str(token_file))
|
|
74
|
+
token = auth.get_token()
|
|
75
|
+
assert token.startswith("mt_")
|
|
76
|
+
|
|
77
|
+
# Verify round-trip works
|
|
78
|
+
assert auth.verify(f"Bearer {token}") is True
|
|
79
|
+
assert auth.verify("Bearer invalid") is False
|
|
80
|
+
|
|
81
|
+
# Rotation produces a new token
|
|
82
|
+
old_token = token
|
|
83
|
+
new_token = auth.rotate()
|
|
84
|
+
assert new_token.startswith("mt_")
|
|
85
|
+
assert new_token != old_token
|
|
86
|
+
assert auth.verify(f"Bearer {new_token}") is True
|
|
87
|
+
assert auth.verify(f"Bearer {old_token}") is False
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
"""Tests for B3: BM25 + char-trigram conflict similarity."""
|
|
2
|
+
|
|
3
|
+
import pytest
|
|
4
|
+
|
|
5
|
+
from rag.conflict import (
|
|
6
|
+
ConflictResolver,
|
|
7
|
+
bm25_pair_similarity,
|
|
8
|
+
char_ngram_jaccard,
|
|
9
|
+
smart_similarity,
|
|
10
|
+
)
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def test_jaccard_basic():
|
|
14
|
+
assert char_ngram_jaccard("hello", "hello") == 1.0
|
|
15
|
+
assert char_ngram_jaccard("abc", "xyz") == 0.0
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def test_bm25_pair_basic():
|
|
19
|
+
s = bm25_pair_similarity("redis cluster pipelining", "redis cluster issue")
|
|
20
|
+
assert 0.0 < s < 1.0
|
|
21
|
+
s_unrelated = bm25_pair_similarity("redis cluster", "cats and dogs playing")
|
|
22
|
+
assert s > s_unrelated
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def test_smart_similarity_short_uses_ngrams():
|
|
26
|
+
a = "Python is best"
|
|
27
|
+
b = "Python best"
|
|
28
|
+
s = smart_similarity(a, b)
|
|
29
|
+
char_ngram_jaccard(a, b, 3)
|
|
30
|
+
assert s > 0.5
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def test_smart_similarity_long_uses_both_signals():
|
|
34
|
+
a = "Python multi-paragraph text about multi-threading and memory model"
|
|
35
|
+
b = "Python multi-paragraph text about concurrency and memory model"
|
|
36
|
+
s = smart_similarity(a, b)
|
|
37
|
+
assert s > 0.4
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def test_smart_similarity_rejects_unrelated():
|
|
41
|
+
s = smart_similarity("Python is the best language", "Cats love fish in rivers")
|
|
42
|
+
assert s < 0.15
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def test_smart_similarity_empty_inputs():
|
|
46
|
+
assert smart_similarity("", "abc") == 0.0
|
|
47
|
+
assert smart_similarity("abc", "") == 0.0
|
|
48
|
+
assert smart_similarity("", "") == 0.0
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
@pytest.mark.asyncio
|
|
52
|
+
async def test_resolve_archives_and_audits(tmp_path):
|
|
53
|
+
from shared.connection import AsyncConnectionManager
|
|
54
|
+
|
|
55
|
+
cm = AsyncConnectionManager(base_dir=str(tmp_path))
|
|
56
|
+
cr = ConflictResolver(cm=cm)
|
|
57
|
+
await cr._init_db()
|
|
58
|
+
|
|
59
|
+
# Create conflict group
|
|
60
|
+
conn = await cm.get("memory.db")
|
|
61
|
+
# Init audit table for logging
|
|
62
|
+
from features.audit_trail import AuditTrail
|
|
63
|
+
|
|
64
|
+
at = AuditTrail(cm=cm)
|
|
65
|
+
await at._init_db()
|
|
66
|
+
|
|
67
|
+
await conn.execute(
|
|
68
|
+
"INSERT INTO memory_conflicts (user_id, content, is_conflict, conflict_group_id) VALUES (?, ?, 1, ?)", ("u", "old version", "grp1")
|
|
69
|
+
)
|
|
70
|
+
await conn.execute(
|
|
71
|
+
"INSERT INTO memory_conflicts (user_id, content, is_conflict, conflict_group_id) VALUES (?, ?, 1, ?)", ("u", "new version", "grp1")
|
|
72
|
+
)
|
|
73
|
+
await conn.commit()
|
|
74
|
+
|
|
75
|
+
# Resolve: keep_id=2
|
|
76
|
+
result = await cr.resolve("grp1", keep_id=2)
|
|
77
|
+
assert result is True
|
|
78
|
+
|
|
79
|
+
# Verify only kept entry remains (conflict_group_id set to NULL after resolve)
|
|
80
|
+
cur = await conn.execute("SELECT id, content FROM memory_conflicts WHERE user_id='u'")
|
|
81
|
+
rows = await cur.fetchall()
|
|
82
|
+
assert len(rows) == 1
|
|
83
|
+
assert rows[0][1] == "new version"
|
|
84
|
+
|
|
85
|
+
# Verify audit log
|
|
86
|
+
cur = await conn.execute("SELECT action, details FROM audit_log WHERE action='conflict_resolved'")
|
|
87
|
+
audit_row = await cur.fetchone()
|
|
88
|
+
assert audit_row is not None
|
|
89
|
+
assert "conflict_group_id" in str(audit_row[1])
|
|
File without changes
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
"""Tests for core/ module — unique tests only."""
|
|
2
|
+
|
|
3
|
+
from core.reflex import ReflexBuffer
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def test_reflex_buffer():
|
|
7
|
+
buf = ReflexBuffer(max_size=5)
|
|
8
|
+
buf.add(role="user", content="Hello", tokens=5)
|
|
9
|
+
buf.add(role="assistant", content="Hi", tokens=3)
|
|
10
|
+
assert buf.size() == 2
|
|
11
|
+
assert buf.get_recent(1)[0].content == "Hi"
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
"""features.db_maintenance: size thresholds + auto-VACUUM on fragmentation."""
|
|
2
|
+
|
|
3
|
+
import sqlite3
|
|
4
|
+
|
|
5
|
+
import pytest
|
|
6
|
+
|
|
7
|
+
from features.db_maintenance import run_db_maintenance
|
|
8
|
+
from shared.connection import AsyncConnectionManager
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
@pytest.fixture
|
|
12
|
+
async def cm(tmp_path, monkeypatch):
|
|
13
|
+
manager = AsyncConnectionManager(base_dir=str(tmp_path))
|
|
14
|
+
# maintenance reads connection_manager.base_dir for the file listing —
|
|
15
|
+
# point it at this test's tmp and restore afterwards.
|
|
16
|
+
from shared import connection as conn_mod
|
|
17
|
+
|
|
18
|
+
orig_dir = conn_mod.connection_manager.base_dir
|
|
19
|
+
orig_conns = dict(conn_mod.connection_manager._conns)
|
|
20
|
+
conn_mod.connection_manager.base_dir = tmp_path
|
|
21
|
+
conn_mod.connection_manager._conns.clear()
|
|
22
|
+
yield manager
|
|
23
|
+
conn_mod.connection_manager.base_dir = orig_dir
|
|
24
|
+
conn_mod.connection_manager._conns.clear()
|
|
25
|
+
conn_mod.connection_manager._conns.update(orig_conns)
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
async def _make_fragmented(path, rows: int) -> None:
|
|
29
|
+
"""Create a DB with real dead pages: bulk insert then delete."""
|
|
30
|
+
conn = sqlite3.connect(str(path))
|
|
31
|
+
conn.execute("CREATE TABLE t (v TEXT)")
|
|
32
|
+
conn.executemany("INSERT INTO t VALUES (?)", [("x" * 2000,)] * rows)
|
|
33
|
+
conn.commit()
|
|
34
|
+
conn.execute("DELETE FROM t")
|
|
35
|
+
conn.commit()
|
|
36
|
+
page_count = conn.execute("PRAGMA page_count").fetchone()[0]
|
|
37
|
+
freelist = conn.execute("PRAGMA freelist_count").fetchone()[0]
|
|
38
|
+
assert freelist / max(page_count, 1) > 0.25, "fixture must be fragmented"
|
|
39
|
+
conn.close()
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
@pytest.mark.asyncio
|
|
43
|
+
async def test_vacuum_reclaims_fragmented_db(cm, tmp_path):
|
|
44
|
+
db = tmp_path / "memory.db"
|
|
45
|
+
await _make_fragmented(db, rows=3000)
|
|
46
|
+
|
|
47
|
+
# Direct probe first: the vacuum primitive itself must reclaim space.
|
|
48
|
+
from features.db_maintenance import _vacuum_if_fragmented
|
|
49
|
+
|
|
50
|
+
freed, did = await _vacuum_if_fragmented(cm, db, min_mb=1, ratio_threshold=0.25)
|
|
51
|
+
assert did and freed > 0, f"direct vacuum: did={did} freed={freed}"
|
|
52
|
+
|
|
53
|
+
reports = await run_db_maintenance(cm=cm, vacuum_min_mb=50)
|
|
54
|
+
mem = [r for r in reports if r.name == "memory.db"]
|
|
55
|
+
assert mem, "memory.db must be reported"
|
|
56
|
+
|
|
57
|
+
conn = sqlite3.connect(str(db))
|
|
58
|
+
assert conn.execute("PRAGMA integrity_check").fetchone()[0] == "ok"
|
|
59
|
+
assert conn.execute("PRAGMA freelist_count").fetchone()[0] == 0
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
@pytest.mark.asyncio
|
|
63
|
+
async def test_thresholds_reported(cm):
|
|
64
|
+
"""Small clean DB -> ok level; report carries all data-dir files."""
|
|
65
|
+
import sqlite3 as sq
|
|
66
|
+
|
|
67
|
+
conn = sq.connect(str(cm.base_dir / "projects.db"))
|
|
68
|
+
conn.execute("CREATE TABLE t (v TEXT)")
|
|
69
|
+
conn.commit()
|
|
70
|
+
conn.close()
|
|
71
|
+
reports = await run_db_maintenance(cm=cm)
|
|
72
|
+
names = {r.name for r in reports}
|
|
73
|
+
assert "projects.db" in names
|
|
74
|
+
assert all(r.level in ("ok", "warn", "alert") for r in reports)
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
"""DreamBuffer restore: hook stages output, sweep consolidates into L4."""
|
|
2
|
+
|
|
3
|
+
from unittest.mock import MagicMock
|
|
4
|
+
|
|
5
|
+
import pytest
|
|
6
|
+
|
|
7
|
+
from core.projects import ProjectMemory # noqa: F401 — ensures package import parity
|
|
8
|
+
from shared.connection import AsyncConnectionManager
|
|
9
|
+
from shared.dream_buffer import DreamBuffer
|
|
10
|
+
from shared.migrations import MigrationManager
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
@pytest.fixture
|
|
14
|
+
async def cm(tmp_path):
|
|
15
|
+
manager = AsyncConnectionManager(base_dir=tmp_path)
|
|
16
|
+
await MigrationManager(cm=manager).migrate()
|
|
17
|
+
return manager
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def _make_ctx(app):
|
|
21
|
+
ctx = MagicMock()
|
|
22
|
+
ctx.request_context.lifespan_context = app
|
|
23
|
+
return ctx
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
@pytest.mark.asyncio
|
|
27
|
+
async def test_dream_hook_stages_into_buffer(tmp_path):
|
|
28
|
+
"""The user-layer dream_buffer handler stages summaries via the caller's cm."""
|
|
29
|
+
from hooks.user_hooks import UserHooks
|
|
30
|
+
|
|
31
|
+
cm = AsyncConnectionManager(base_dir=tmp_path)
|
|
32
|
+
|
|
33
|
+
hooks = UserHooks(user_id="du")
|
|
34
|
+
mem = MagicMock()
|
|
35
|
+
mem._cm = cm
|
|
36
|
+
res = await hooks._dream_buffer({"summary": "deployed v2 to prod", "user_id": "du"}, mem=mem)
|
|
37
|
+
assert res["action"] == "add_to_staging"
|
|
38
|
+
|
|
39
|
+
buf = DreamBuffer(cm=cm, layer="user")
|
|
40
|
+
items = await buf.get_staging("du")
|
|
41
|
+
assert len(items) == 1 and "v2" in items[0]["content"], f"staged={items}"
|
|
42
|
+
|
|
43
|
+
# agent layer stays empty — staging is layer-isolated
|
|
44
|
+
agent_buf = DreamBuffer(cm=cm, layer="agent")
|
|
45
|
+
assert await agent_buf.get_staging("du") == []
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
@pytest.mark.asyncio
|
|
49
|
+
async def test_sweep_consolidates_staging_into_l4(cm):
|
|
50
|
+
"""consolidate_staging promotes staged content; clear empties the buffer."""
|
|
51
|
+
from lifecycle.consolidation import ConsolidationEngine
|
|
52
|
+
|
|
53
|
+
buf = DreamBuffer(cm=cm, layer="user")
|
|
54
|
+
await buf.add("su", session_id="dream", content="critical decision: switched to wal mode always", importance=0.9)
|
|
55
|
+
|
|
56
|
+
items = await buf.get_staging("su")
|
|
57
|
+
engine = ConsolidationEngine(cm=cm, layer="user")
|
|
58
|
+
res = await engine.consolidate_staging("su", items, min_importance=0.7)
|
|
59
|
+
assert res["promoted"] == 1
|
|
60
|
+
|
|
61
|
+
await buf.clear_staging("su")
|
|
62
|
+
assert await buf.get_staging("su") == []
|
|
63
|
+
|
|
64
|
+
# landed in L4 as a fact
|
|
65
|
+
from core.memory import CoreMemory
|
|
66
|
+
|
|
67
|
+
l4 = CoreMemory(cm=cm, layer="user")
|
|
68
|
+
hits = await l4.search("su", "wal mode", limit=5)
|
|
69
|
+
assert any("wal mode" in h["value"] for h in hits)
|
|
File without changes
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
"""Tests for backup path traversal prevention."""
|
|
2
|
+
|
|
3
|
+
import asyncio
|
|
4
|
+
import json
|
|
5
|
+
|
|
6
|
+
import pytest
|
|
7
|
+
|
|
8
|
+
from features.backup import BackupManager
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
@pytest.fixture
|
|
12
|
+
def bm(tmp_path):
|
|
13
|
+
data_dir = tmp_path / "data"
|
|
14
|
+
data_dir.mkdir()
|
|
15
|
+
return BackupManager(base_dir=str(data_dir))
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
@pytest.mark.parametrize(
|
|
19
|
+
("manifest_files", "should_reject"),
|
|
20
|
+
[
|
|
21
|
+
(["../../etc/crontab", "memory.db"], True),
|
|
22
|
+
(["/etc/passwd"], True),
|
|
23
|
+
],
|
|
24
|
+
)
|
|
25
|
+
def test_restore_rejects_traversal(bm, manifest_files, should_reject):
|
|
26
|
+
backup_dir = bm.backup_dir / "crafted"
|
|
27
|
+
backup_dir.mkdir()
|
|
28
|
+
manifest = {"files": manifest_files, "created_at": "2026-01-01T00:00:00"}
|
|
29
|
+
(backup_dir / "manifest.json").write_text(json.dumps(manifest), encoding="utf-8")
|
|
30
|
+
with pytest.raises(ValueError, match="escapes base directory"):
|
|
31
|
+
asyncio.run(bm.restore("crafted"))
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
"""Tests for features/ module — unique tests only."""
|
|
2
|
+
|
|
3
|
+
import asyncio
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def test_compression():
|
|
7
|
+
from features.compression import MemoryCompressor
|
|
8
|
+
|
|
9
|
+
async def t():
|
|
10
|
+
mc = MemoryCompressor()
|
|
11
|
+
stats = await mc.get_stats("test_feat")
|
|
12
|
+
assert "core_memory" in stats
|
|
13
|
+
|
|
14
|
+
asyncio.run(t())
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
"""Tests for import_export path traversal prevention."""
|
|
2
|
+
|
|
3
|
+
import pytest
|
|
4
|
+
|
|
5
|
+
from features.import_export import ImportExport
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
@pytest.fixture
|
|
9
|
+
def ie(tmp_path):
|
|
10
|
+
export_dir = tmp_path / "exports"
|
|
11
|
+
export_dir.mkdir()
|
|
12
|
+
|
|
13
|
+
class FakeCM:
|
|
14
|
+
def __init__(self, base):
|
|
15
|
+
self._base = base
|
|
16
|
+
|
|
17
|
+
@property
|
|
18
|
+
def base_dir(self):
|
|
19
|
+
return self._base
|
|
20
|
+
|
|
21
|
+
obj = ImportExport.__new__(ImportExport)
|
|
22
|
+
obj._cm = FakeCM(tmp_path)
|
|
23
|
+
obj.export_dir = export_dir
|
|
24
|
+
return obj
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
@pytest.mark.parametrize("path", ["../../etc/passwd", "/etc/passwd"])
|
|
28
|
+
def test_import_rejects_traversal(ie, path):
|
|
29
|
+
import asyncio
|
|
30
|
+
|
|
31
|
+
with pytest.raises(ValueError, match="escapes base directory"):
|
|
32
|
+
asyncio.run(ie.import_user(path))
|