prodedify-memory 0.0.0__tar.gz → 0.2.1__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.
Files changed (132) hide show
  1. prodedify_memory-0.2.1/.github/workflows/ci.yml +46 -0
  2. prodedify_memory-0.2.1/.gitignore +10 -0
  3. prodedify_memory-0.2.1/CLAUDE.md +58 -0
  4. prodedify_memory-0.2.1/LICENSE +89 -0
  5. prodedify_memory-0.2.1/Makefile +37 -0
  6. prodedify_memory-0.2.1/PKG-INFO +109 -0
  7. prodedify_memory-0.2.1/README.md +77 -0
  8. prodedify_memory-0.2.1/SECURITY.md +38 -0
  9. prodedify_memory-0.2.1/alembic/env.py +62 -0
  10. prodedify_memory-0.2.1/alembic/script.py.mako +23 -0
  11. prodedify_memory-0.2.1/alembic/versions/0001_baseline.py +86 -0
  12. prodedify_memory-0.2.1/alembic/versions/0002_kg_rls_policies.py +62 -0
  13. prodedify_memory-0.2.1/alembic/versions/0003_adr005_bitemporal_substrate.py +199 -0
  14. prodedify_memory-0.2.1/alembic/versions/0004_prd05_grounding_audit.py +88 -0
  15. prodedify_memory-0.2.1/alembic/versions/0005_prd12_write_path_policy.py +128 -0
  16. prodedify_memory-0.2.1/alembic/versions/0006_ind1_edge_creator_agent.py +66 -0
  17. prodedify_memory-0.2.1/alembic/versions/0006_prd11b_memory_embeddings.py +112 -0
  18. prodedify_memory-0.2.1/alembic/versions/0007_standalone_app_rw_grants.py +66 -0
  19. prodedify_memory-0.2.1/alembic/versions/0008_sapere_write_idempotency.py +94 -0
  20. prodedify_memory-0.2.1/alembic/versions/0009_uda4_referral_policy.py +154 -0
  21. prodedify_memory-0.2.1/alembic/versions/0010_uda3_fairness_policy.py +134 -0
  22. prodedify_memory-0.2.1/alembic/versions/0011_uda4_assert_event_guard.py +103 -0
  23. prodedify_memory-0.2.1/alembic/versions/0012_decision_conflict_pairs.py +144 -0
  24. prodedify_memory-0.2.1/alembic/versions/0013_project_knowledge_items.py +130 -0
  25. prodedify_memory-0.2.1/alembic/versions/0014_uda1_retrieval_sidecar.py +55 -0
  26. prodedify_memory-0.2.1/alembic/versions/0015_g9_b2_immutability.py +232 -0
  27. prodedify_memory-0.2.1/alembic/versions/0016_g2_entity_resolution.py +285 -0
  28. prodedify_memory-0.2.1/alembic/versions/0017_g2b_merge_event_seq.py +47 -0
  29. prodedify_memory-0.2.1/alembic/versions/0018_g3_contradiction_lifecycle.py +180 -0
  30. prodedify_memory-0.2.1/alembic/versions/0019_g11_api_keys.py +122 -0
  31. prodedify_memory-0.2.1/alembic/versions/2c1b798ce44c_merge_ind1_agent_prd11b_embeddings_heads.py +39 -0
  32. prodedify_memory-0.2.1/alembic.ini +45 -0
  33. prodedify_memory-0.2.1/docker-compose.dev.yml +77 -0
  34. prodedify_memory-0.2.1/prodedify_memory/__init__.py +40 -0
  35. prodedify_memory-0.2.1/prodedify_memory/apikeys.py +58 -0
  36. prodedify_memory-0.2.1/prodedify_memory/benchmark/__init__.py +60 -0
  37. prodedify_memory-0.2.1/prodedify_memory/benchmark/adapters/__init__.py +56 -0
  38. prodedify_memory-0.2.1/prodedify_memory/benchmark/adapters/base.py +103 -0
  39. prodedify_memory-0.2.1/prodedify_memory/benchmark/adapters/hotpotqa.py +194 -0
  40. prodedify_memory-0.2.1/prodedify_memory/benchmark/adapters/longmemeval.py +246 -0
  41. prodedify_memory-0.2.1/prodedify_memory/benchmark/arms.py +204 -0
  42. prodedify_memory-0.2.1/prodedify_memory/benchmark/baselines/fixture_v1__fake-concept-1536.json +40 -0
  43. prodedify_memory-0.2.1/prodedify_memory/benchmark/cli.py +147 -0
  44. prodedify_memory-0.2.1/prodedify_memory/benchmark/corpus.py +470 -0
  45. prodedify_memory-0.2.1/prodedify_memory/benchmark/data/fixture_v1.jsonl +114 -0
  46. prodedify_memory-0.2.1/prodedify_memory/benchmark/data/hotpot_ids_v1.json +112 -0
  47. prodedify_memory-0.2.1/prodedify_memory/benchmark/data/hotpot_sample_v1.json +39 -0
  48. prodedify_memory-0.2.1/prodedify_memory/benchmark/data/longmemeval_ids_v1.json +116 -0
  49. prodedify_memory-0.2.1/prodedify_memory/benchmark/data/longmemeval_sample_v1.json +60 -0
  50. prodedify_memory-0.2.1/prodedify_memory/benchmark/fake_embedder.py +61 -0
  51. prodedify_memory-0.2.1/prodedify_memory/benchmark/fixtures.py +248 -0
  52. prodedify_memory-0.2.1/prodedify_memory/benchmark/judge.py +64 -0
  53. prodedify_memory-0.2.1/prodedify_memory/benchmark/metrics.py +150 -0
  54. prodedify_memory-0.2.1/prodedify_memory/benchmark/report.py +242 -0
  55. prodedify_memory-0.2.1/prodedify_memory/benchmark/runner.py +269 -0
  56. prodedify_memory-0.2.1/prodedify_memory/benchmark/seed.py +282 -0
  57. prodedify_memory-0.2.1/prodedify_memory/benchmark/tokens.py +68 -0
  58. prodedify_memory-0.2.1/prodedify_memory/contradiction/__init__.py +69 -0
  59. prodedify_memory-0.2.1/prodedify_memory/contradiction/lifecycle.py +562 -0
  60. prodedify_memory-0.2.1/prodedify_memory/contradiction/seams.py +138 -0
  61. prodedify_memory-0.2.1/prodedify_memory/contradiction/sweep.py +200 -0
  62. prodedify_memory-0.2.1/prodedify_memory/fairness_policy.py +166 -0
  63. prodedify_memory-0.2.1/prodedify_memory/grounding_audit.py +273 -0
  64. prodedify_memory-0.2.1/prodedify_memory/hooks.py +95 -0
  65. prodedify_memory-0.2.1/prodedify_memory/mcp/__init__.py +1 -0
  66. prodedify_memory-0.2.1/prodedify_memory/mcp/server.py +202 -0
  67. prodedify_memory-0.2.1/prodedify_memory/migrate.py +65 -0
  68. prodedify_memory-0.2.1/prodedify_memory/models.py +740 -0
  69. prodedify_memory-0.2.1/prodedify_memory/provenance.py +36 -0
  70. prodedify_memory-0.2.1/prodedify_memory/referral_policy.py +188 -0
  71. prodedify_memory-0.2.1/prodedify_memory/registry.py +100 -0
  72. prodedify_memory-0.2.1/prodedify_memory/resolution/__init__.py +79 -0
  73. prodedify_memory-0.2.1/prodedify_memory/resolution/fingerprint.py +157 -0
  74. prodedify_memory-0.2.1/prodedify_memory/resolution/residue.py +93 -0
  75. prodedify_memory-0.2.1/prodedify_memory/resolution/resolver.py +392 -0
  76. prodedify_memory-0.2.1/prodedify_memory/resolution/review.py +269 -0
  77. prodedify_memory-0.2.1/prodedify_memory/retrieval/__init__.py +75 -0
  78. prodedify_memory-0.2.1/prodedify_memory/retrieval/backfill.py +323 -0
  79. prodedify_memory-0.2.1/prodedify_memory/retrieval/embedder.py +94 -0
  80. prodedify_memory-0.2.1/prodedify_memory/retrieval/fusion.py +133 -0
  81. prodedify_memory-0.2.1/prodedify_memory/retrieval/signals.py +323 -0
  82. prodedify_memory-0.2.1/prodedify_memory/retrieval/writer.py +180 -0
  83. prodedify_memory-0.2.1/prodedify_memory/services/__init__.py +9 -0
  84. prodedify_memory-0.2.1/prodedify_memory/services/intelligence.py +1713 -0
  85. prodedify_memory-0.2.1/prodedify_memory/services/knowledge_graph.py +1144 -0
  86. prodedify_memory-0.2.1/prodedify_memory/store.py +2205 -0
  87. prodedify_memory-0.2.1/prodedify_memory/temporal.py +91 -0
  88. prodedify_memory-0.2.1/prodedify_memory/write_policy.py +77 -0
  89. prodedify_memory-0.2.1/pyproject.toml +47 -0
  90. prodedify_memory-0.2.1/scripts/check_migration_graph.py +85 -0
  91. prodedify_memory-0.2.1/scripts/dev-initdb.sql +16 -0
  92. prodedify_memory-0.2.1/scripts/rehearse_uda3.sh +50 -0
  93. prodedify_memory-0.2.1/scripts/rehearse_uda4.sh +68 -0
  94. prodedify_memory-0.2.1/tests/test_adr005_substrate.py +267 -0
  95. prodedify_memory-0.2.1/tests/test_b2_immutability.py +413 -0
  96. prodedify_memory-0.2.1/tests/test_benchmark.py +30 -0
  97. prodedify_memory-0.2.1/tests/test_benchmark_adapters.py +396 -0
  98. prodedify_memory-0.2.1/tests/test_benchmark_corpus.py +309 -0
  99. prodedify_memory-0.2.1/tests/test_benchmark_harness.py +180 -0
  100. prodedify_memory-0.2.1/tests/test_benchmark_report.py +163 -0
  101. prodedify_memory-0.2.1/tests/test_benchmark_seed.py +160 -0
  102. prodedify_memory-0.2.1/tests/test_g11_api_keys.py +177 -0
  103. prodedify_memory-0.2.1/tests/test_g2_entity_resolution.py +488 -0
  104. prodedify_memory-0.2.1/tests/test_g2b_merge_review.py +285 -0
  105. prodedify_memory-0.2.1/tests/test_g2g3_post_g4.py +230 -0
  106. prodedify_memory-0.2.1/tests/test_g3_contradiction_lifecycle.py +435 -0
  107. prodedify_memory-0.2.1/tests/test_g3b_sweep.py +238 -0
  108. prodedify_memory-0.2.1/tests/test_g4_traversal.py +401 -0
  109. prodedify_memory-0.2.1/tests/test_ind1_edge_creator.py +20 -0
  110. prodedify_memory-0.2.1/tests/test_kg_conflict_pairs.py +168 -0
  111. prodedify_memory-0.2.1/tests/test_kg_project_knowledge_items.py +126 -0
  112. prodedify_memory-0.2.1/tests/test_migration_graph.py +101 -0
  113. prodedify_memory-0.2.1/tests/test_packaged_migrations.py +105 -0
  114. prodedify_memory-0.2.1/tests/test_prd05_audit_replay.py +564 -0
  115. prodedify_memory-0.2.1/tests/test_prd05_grounding.py +341 -0
  116. prodedify_memory-0.2.1/tests/test_prd11b_retrieval.py +690 -0
  117. prodedify_memory-0.2.1/tests/test_prd12_write_path.py +541 -0
  118. prodedify_memory-0.2.1/tests/test_provenance.py +21 -0
  119. prodedify_memory-0.2.1/tests/test_registry.py +46 -0
  120. prodedify_memory-0.2.1/tests/test_resolution_fingerprint.py +142 -0
  121. prodedify_memory-0.2.1/tests/test_sap_pg2_embed_status.py +158 -0
  122. prodedify_memory-0.2.1/tests/test_store_sql.py +69 -0
  123. prodedify_memory-0.2.1/tests/test_uda3_fairness_policy.py +282 -0
  124. prodedify_memory-0.2.1/tests/test_uda4_referral_policy.py +577 -0
  125. prodedify_memory-0.0.0/PKG-INFO +0 -13
  126. prodedify_memory-0.0.0/README.md +0 -3
  127. prodedify_memory-0.0.0/prodedify_memory.egg-info/PKG-INFO +0 -13
  128. prodedify_memory-0.0.0/prodedify_memory.egg-info/SOURCES.txt +0 -6
  129. prodedify_memory-0.0.0/prodedify_memory.egg-info/dependency_links.txt +0 -1
  130. prodedify_memory-0.0.0/prodedify_memory.egg-info/top_level.txt +0 -1
  131. prodedify_memory-0.0.0/pyproject.toml +0 -18
  132. prodedify_memory-0.0.0/setup.cfg +0 -4
@@ -0,0 +1,46 @@
1
+ # Engine CI (prodedify/memory).
2
+ #
3
+ # The engine's migration history is its most fragile shared surface — parallel
4
+ # worktrees that branch off the same commit can each pick the same revision number
5
+ # and fork the history into two heads, which silently breaks `alembic upgrade head`
6
+ # and the standalone-independence bootstrap. Nothing ran at engine-PR time to catch
7
+ # it (the SAP-IND-1 / PRD-11b two-`0006`-heads fork slipped through to rehearsal),
8
+ # so this is the engine's first CI: a BLOCKING migration-graph guard + ruff + the
9
+ # test suite. It mirrors the Supabase duplicate-timestamp guard (mono #129), one
10
+ # layer up — colliding timestamps there, a forked history here.
11
+ name: engine-ci
12
+
13
+ on:
14
+ pull_request:
15
+ push:
16
+ branches: [main]
17
+
18
+ permissions:
19
+ contents: read
20
+
21
+ jobs:
22
+ ci:
23
+ runs-on: [self-hosted, linux, x64]
24
+ steps:
25
+ - uses: actions/checkout@v4
26
+
27
+ - uses: actions/setup-python@v5
28
+ with:
29
+ python-version: "3.11"
30
+
31
+ - name: Install engine (migrations + mcp + dev)
32
+ run: pip install -e '.[migrations,mcp,dev]'
33
+
34
+ # The headline gate: fail the build on a forked history (>1 head) or an
35
+ # unresolvable revision graph (orphan down_revision / cycle).
36
+ - name: Migration-graph guard — single head + integrity (BLOCKING)
37
+ run: python scripts/check_migration_graph.py
38
+
39
+ - name: ruff
40
+ run: ruff check .
41
+
42
+ # DB-backed suites skip without SAPERE_TEST_* env; the migration-graph test
43
+ # and the pure-Python suites run. `-m pytest` puts the repo root on sys.path
44
+ # so the guard's `scripts` package imports.
45
+ - name: pytest
46
+ run: python -m pytest -q
@@ -0,0 +1,10 @@
1
+ __pycache__/
2
+ *.pyc
3
+ .venv/
4
+ dist/
5
+ .pytest_cache/
6
+ .ruff_cache/
7
+ *.egg-info/
8
+ .env
9
+ # raw public-benchmark dataset cache (spec §2.3) — never committed
10
+ prodedify_memory/benchmark/.cache/
@@ -0,0 +1,58 @@
1
+ # CLAUDE.md — prodedify_memory (Engine Repo)
2
+
3
+ This file governs work **inside the engine repo** (`prodedify_memory`, vendored at `backend/vendor/memory/`). It is its own repository with its own authority — the monorepo CLAUDE.md does not cover the engine-specific rules below. When working here, these rules take precedence for engine concerns.
4
+
5
+ ## What this repo is
6
+
7
+ `prodedify_memory` is the **Sapere memory engine** — the bitemporal, governed, replayable decision/fact/grounding substrate. It is consumed by the monorepo backend as a vendored submodule and is pinned by SHA. Two properties define everything below:
8
+
9
+ 1. **It is the Alembic migration authority.** The engine's `alembic/` migrations are the canonical, standalone schema definition. The monorepo's Supabase migrations are dual-authored *mirrors* — but the Alembic authority is what must stand the schema up alone.
10
+ 2. **It must be standalone-installable.** Sapere ships independently (door-3/self-hosted/BYOC). The engine must stand up with **zero Plinth tables**, no imports from the monorepo backend, no foreign keys into any sibling-product schema, and `workspace_id` as an **opaque tenant key** (not an FK). This is the independence guarantee — it is load-bearing, not aspirational.
11
+
12
+ ## Migration discipline (the most dangerous surface — read first)
13
+
14
+ The engine's migration history is the single most fragile shared surface in the whole system, because its failures reach **deploy-time and fresh-bootstrap, never CI**. Three near-misses have already occurred (duplicate Supabase timestamp, submodule-pointer regression, two-heads fork). Therefore:
15
+
16
+ - **Single head, always.** Before opening any PR that adds a migration, run `alembic heads` and confirm it returns **exactly one**. Parallel worktrees branching off the same engine commit *will* pick colliding revision numbers — if you find two heads, STOP and surface it (the fix is an `alembic merge` revision; do not paper over it).
17
+ - **Every `down_revision` must resolve.** No orphan revisions. The history graph must be a single connected lineage.
18
+ - **Fresh-DB verification is mandatory for any migration.** Verify `alembic upgrade head` runs clean and deterministically on a **fresh ephemeral DB with zero Plinth tables** — not just against an already-migrated DB (which hides forks and ordering bugs). This is the standalone-independence test; it is the real proof, not a formality.
19
+ - **Dual-authoring contract:** schema changes are authored in BOTH this repo (Alembic) AND the monorepo (Supabase). The engine Alembic migration is **FK-free** (the independence gate); the Supabase mirror may carry FKs to monorepo tables. The `prodedify/memory` PR **merges before** the monorepo bumps the submodule pointer — never pin the monorepo to an unmerged engine commit.
20
+ - **Merge revisions are history-only.** An `alembic merge` revision has no DDL and no Supabase counterpart (prod already has the merged migrations applied). Don't author a phantom Supabase mirror for a merge.
21
+ - **`pgvector`/extensions:** vector ops use bare `vector`/`vector_cosine_ops`/`<=>` (extension lives in `public`, on the migration role's search_path). Anything in the `extensions` schema (e.g. pgcrypto) must be schema-qualified — local search_path includes `extensions`, prod's does not (this caused a prod migration failure). Confirm the extension's schema before using it unqualified.
22
+
23
+ ## Self-verification (no false greens)
24
+
25
+ - **Verify under `app_rw` ONLY** on ephemeral databases — never owner, superuser, or `service_role`. A migration or RLS test that passes under a privileged role is a **false green**: privileged roles bypass RLS, so isolation/policy tests must run as the least-privileged runtime role to mean anything.
26
+ - Spin up an ephemeral DB, apply the full chain, run the suite, tear it down. Do not verify against a shared/persistent DB.
27
+ - The existing engine test suite must stay green; `ruff` clean. New schema → its RLS/isolation must be covered (registered in the policy-source/invariant mechanism, even when the table has no ORM model — model-less tables still need isolation coverage).
28
+
29
+ ## Tenancy & RLS (engine-side)
30
+
31
+ - Every tenant-scoped table carries `workspace_id` and is protected by **FORCE ROW LEVEL SECURITY** with the GUC-based fail-closed policy template: `workspace_id = NULLIF(current_setting('app.workspace_id', true), '')::uuid`. The empty-string→NULL coercion is what makes it fail closed (an unset GUC matches nothing, never everything).
32
+ - The engine self-scopes: it sets `app.workspace_id` from the `workspace_id` argument passed in (it does not depend on the monorepo's request middleware). Engine ops are RLS-scoped **by argument**, independent of any request context.
33
+ - Derived caches (e.g. embeddings) may be mutable (`app_rw` gets full CRUD) — that is NOT a governed-record deletion. The no-hard-delete rule binds **governed records** (decisions, facts, grounding, authority/audit rows), not their derived caches.
34
+
35
+ ## Vendor-neutrality / independence (Sapere ships alone)
36
+
37
+ - **No sibling-product names in the engine schema or code.** The graph-edge-creator triad is `system / agent / user` — `kilnai` was removed (SAP-IND-1) and must never reappear. Any AI/agent author is `agent`; never a product name.
38
+ - **Truthful provenance:** agent-authored writes record `created_by_ai = True` (→ `agent`), never defaulted to `user`. The engine is a system-of-record; the provenance it stores must be true.
39
+ - **No imports from the monorepo backend, no FK into Plinth or any sibling schema.** If a change seems to need either, STOP — it breaks the standalone guarantee.
40
+ - The package namespace `prodedify_*` is fine (company namespace, not a sibling product). The line is **sibling-product coupling**, not company branding.
41
+
42
+ ## Authority & governed writes
43
+
44
+ - The substrate is bitemporal: `recorded_at`/`invalidated_recorded_at` (transaction-time, system-owned, never caller-set) and `valid_from`/`valid_until` (valid-time, caller-assertable). Transaction-time is the basis of tamper-evidence — nothing but the record/supersede path writes `recorded_at`.
45
+ - Authority states: `ASSERTED` / `PROPOSED` / `QUARANTINED`. Writes resolve through the policy engine (`resolve_write_authority`); unknown sources fail closed to `PROPOSED`. Promotion is attributed, append-only, never a silent flip.
46
+ - Audit (grounding/authority events) is append-only and hash-chained — no UPDATE/DELETE under FORCE RLS. "Forget" is governed demotion, never a hard delete.
47
+
48
+ ## Human levers (never performed by an agent session)
49
+
50
+ - Bumping the monorepo's submodule pointer to an engine commit (done in the monorepo, after the engine PR merges — and the pointer diff is eyeballed before push).
51
+ - Rotating signing keys / changing the JWT alg (would invalidate live tokens).
52
+ - Issuing real credentials, registering real providers.
53
+
54
+ End engine work at **"rehearsed green (fresh ephemeral DB, app_rw) + PR open."** Do not apply, deploy, or move pointers.
55
+
56
+ ## The loop
57
+
58
+ branch off the engine repo's `main` → Step 0 (inspect/decide, STOP for confirm on anything touching the migration graph or a shared decision) → build → fresh-ephemeral-`app_rw` rehearsal incl. `alembic heads == 1` and zero-Plinth-tables bootstrap → open PR → stop.
@@ -0,0 +1,89 @@
1
+ Business Source License 1.1
2
+
3
+ Parameters
4
+
5
+ Licensor: Prodedify
6
+ Licensed Work: prodedify-memory
7
+ The Licensed Work is (c) 2026 Prodedify.
8
+ Additional Use Grant: You may use the Licensed Work for non-production
9
+ purposes only: development, testing, and evaluation.
10
+ Any production use, and any use of the Licensed Work
11
+ to provide a product or service to third parties,
12
+ requires a separate commercial license from the
13
+ Licensor (hello@prodedify.com).
14
+ Change Date: 2030-07-06
15
+ Change License: Apache License, Version 2.0
16
+
17
+ For information about alternative licensing arrangements for the Licensed
18
+ Work, please contact hello@prodedify.com.
19
+
20
+ Notice
21
+
22
+ Business Source License 1.1
23
+
24
+ Terms
25
+
26
+ The Licensor hereby grants you the right to copy, modify, create derivative
27
+ works, redistribute, and make non-production use of the Licensed Work. The
28
+ Licensor may make an Additional Use Grant, above, permitting limited
29
+ production use.
30
+
31
+ Effective on the Change Date, or the fourth anniversary of the first publicly
32
+ available distribution of a specific version of the Licensed Work under this
33
+ License, whichever comes first, the Licensor hereby grants you rights under
34
+ the terms of the Change License, and the rights granted in the paragraph
35
+ above terminate.
36
+
37
+ If your use of the Licensed Work does not comply with the requirements
38
+ currently in effect as described in this License, you must purchase a
39
+ commercial license from the Licensor, its affiliated entities, or authorized
40
+ resellers, or you must refrain from using the Licensed Work.
41
+
42
+ All copies of the original and modified Licensed Work, and derivative works
43
+ of the Licensed Work, are subject to this License. This License applies
44
+ separately for each version of the Licensed Work and the Change Date may vary
45
+ for each version of the Licensed Work released by Licensor.
46
+
47
+ You must conspicuously display this License on each original or modified copy
48
+ of the Licensed Work. If you receive the Licensed Work in original or
49
+ modified form from a third party, the terms and conditions set forth in this
50
+ License apply to your use of that work.
51
+
52
+ Any use of the Licensed Work in violation of this License will automatically
53
+ terminate your rights under this License for the current and all other
54
+ versions of the Licensed Work.
55
+
56
+ This License does not grant you any right in any trademark or logo of
57
+ Licensor or its affiliates (provided that you may use a trademark or logo of
58
+ Licensor as expressly required by this License).
59
+
60
+ TO THE EXTENT PERMITTED BY APPLICABLE LAW, THE LICENSED WORK IS PROVIDED ON
61
+ AN "AS IS" BASIS. LICENSOR HEREBY DISCLAIMS ALL WARRANTIES AND CONDITIONS,
62
+ EXPRESS OR IMPLIED, INCLUDING (WITHOUT LIMITATION) WARRANTIES OF
63
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, NON-INFRINGEMENT, AND
64
+ TITLE.
65
+
66
+ MariaDB hereby grants you permission to use this License's text to license
67
+ your works, and to refer to it using the trademark "Business Source License",
68
+ as long as you comply with the Covenants of Licensor below.
69
+
70
+ Covenants of Licensor
71
+
72
+ In consideration of the right to use this License's text and the "Business
73
+ Source License" name and trademark, Licensor covenants to MariaDB, and to all
74
+ other recipients of the licensed work to be provided by Licensor:
75
+
76
+ 1. To specify as the Change License the GPL Version 2.0 or any later version,
77
+ or a license that is compatible with GPL Version 2.0 or any later version,
78
+ where "compatible" means that software provided under the Change License
79
+ can be included in a program with software provided under GPL Version 2.0
80
+ or a later version. Licensor may specify additional Change Licenses
81
+ without limitation.
82
+
83
+ 2. To either: (a) specify an additional grant of rights to use that does not
84
+ impose any additional restriction on the right granted in this License, as
85
+ the Additional Use Grant; or (b) insert the text "None".
86
+
87
+ 3. To specify a Change Date.
88
+
89
+ 4. Not to modify this License in any other way.
@@ -0,0 +1,37 @@
1
+ # Sapere standalone dev data-plane (ADR-008). See docker-compose.dev.yml.
2
+ COMPOSE := docker compose -f docker-compose.dev.yml
3
+ SAPERE_DATABASE_URL ?= postgresql://postgres:postgres@127.0.0.1:5434/sapere
4
+
5
+ .PHONY: dev-up dev-studio dev-bootstrap dev-down dev-reset eval eval-full
6
+
7
+ # G5 retrieval-eval harness. `eval` is the reproducible one-command deliverable:
8
+ # stands up the standalone PG + schema, then runs the fixture through every arm,
9
+ # prints the quality + latency tables, and gates against the committed baseline.
10
+ BENCH_OWNER ?= postgresql://postgres:postgres@127.0.0.1:5434/sapere
11
+ BENCH_APP_RW ?= postgresql://app_rw:app_rw@127.0.0.1:5434/sapere
12
+
13
+ eval: dev-up dev-bootstrap ## run the fixture benchmark + gate (prints both tables)
14
+ SAPERE_TEST_OWNER_URL=$(BENCH_OWNER) SAPERE_TEST_APP_RW_URL=$(BENCH_APP_RW) \
15
+ python -m prodedify_memory.benchmark.cli --corpus fixture --embedder fake --judge scripted --gate
16
+
17
+ eval-full: ## full-scale run — CORPUS= ADAPTER= LIMIT= INFLATE= JUDGE= REPEATS= pass-through, no gate
18
+ SAPERE_TEST_OWNER_URL=$(BENCH_OWNER) SAPERE_TEST_APP_RW_URL=$(BENCH_APP_RW) \
19
+ python -m prodedify_memory.benchmark.cli --corpus $(or $(CORPUS),fixture) \
20
+ --adapter $(or $(ADAPTER),none) --inflate $(or $(INFLATE),0) \
21
+ --repeats $(or $(REPEATS),20) --judge $(or $(JUDGE),none) --embedder $(or $(EMBEDDER),fake)
22
+
23
+ dev-up: ## standalone Postgres + pgweb viewer (http://localhost:8081)
24
+ $(COMPOSE) up -d sapere-db pgweb
25
+
26
+ dev-studio: ## also bring up Supabase Studio (http://localhost:54330)
27
+ $(COMPOSE) --profile studio up -d
28
+
29
+ dev-bootstrap: ## apply the engine schema (incl. 0007 app_rw grants; needs an installed '.[migrations]' env)
30
+ SAPERE_DATABASE_URL=$(SAPERE_DATABASE_URL) alembic upgrade head
31
+ @echo "bootstrapped head: $$($(COMPOSE) exec -T sapere-db psql -U postgres -d sapere -tAc 'select version_num from alembic_version')"
32
+
33
+ dev-down: ## stop the stack (keeps data)
34
+ $(COMPOSE) --profile studio down
35
+
36
+ dev-reset: ## stop + delete the volume (fresh DB on next up)
37
+ $(COMPOSE) --profile studio down -v
@@ -0,0 +1,109 @@
1
+ Metadata-Version: 2.4
2
+ Name: prodedify-memory
3
+ Version: 0.2.1
4
+ Summary: Organization memory engine: a compounding knowledge graph of decisions, entities, and their relationships — queryable by humans and agents (MCP).
5
+ License: Business Source License 1.1
6
+
7
+ Parameters
8
+
9
+ Licensor: Prodedify
10
+ Licensed Work: prodedify-memory
11
+ The Licensed Work is (c) 2026 Prodedify.
12
+ Additional Use Grant: You may use the Licensed Work for non-production
13
+ purposes only: development, testing, and evaluation.
14
+ Any production use, and any use of the Licensed Work
15
+ to provide a product or service to third parties,
16
+ requires a separate commercial license from the
17
+ Licensor (hello@prodedify.com).
18
+ Change Date: 2030-07-06
19
+ Change License: Apache License, Version 2.0
20
+
21
+ For information about alternative licensing arrangements for the Licensed
22
+ Work, please contact hello@prodedify.com.
23
+
24
+ Notice
25
+
26
+ Business Source License 1.1
27
+
28
+ Terms
29
+
30
+ The Licensor hereby grants you the right to copy, modify, create derivative
31
+ works, redistribute, and make non-production use of the Licensed Work. The
32
+ Licensor may make an Additional Use Grant, above, permitting limited
33
+ production use.
34
+
35
+ Effective on the Change Date, or the fourth anniversary of the first publicly
36
+ available distribution of a specific version of the Licensed Work under this
37
+ License, whichever comes first, the Licensor hereby grants you rights under
38
+ the terms of the Change License, and the rights granted in the paragraph
39
+ above terminate.
40
+
41
+ If your use of the Licensed Work does not comply with the requirements
42
+ currently in effect as described in this License, you must purchase a
43
+ commercial license from the Licensor, its affiliated entities, or authorized
44
+ resellers, or you must refrain from using the Licensed Work.
45
+
46
+ All copies of the original and modified Licensed Work, and derivative works
47
+ of the Licensed Work, are subject to this License. This License applies
48
+ separately for each version of the Licensed Work and the Change Date may vary
49
+ for each version of the Licensed Work released by Licensor.
50
+
51
+ You must conspicuously display this License on each original or modified copy
52
+ of the Licensed Work. If you receive the Licensed Work in original or
53
+ modified form from a third party, the terms and conditions set forth in this
54
+ License apply to your use of that work.
55
+
56
+ Any use of the Licensed Work in violation of this License will automatically
57
+ terminate your rights under this License for the current and all other
58
+ versions of the Licensed Work.
59
+
60
+ This License does not grant you any right in any trademark or logo of
61
+ Licensor or its affiliates (provided that you may use a trademark or logo of
62
+ Licensor as expressly required by this License).
63
+
64
+ TO THE EXTENT PERMITTED BY APPLICABLE LAW, THE LICENSED WORK IS PROVIDED ON
65
+ AN "AS IS" BASIS. LICENSOR HEREBY DISCLAIMS ALL WARRANTIES AND CONDITIONS,
66
+ EXPRESS OR IMPLIED, INCLUDING (WITHOUT LIMITATION) WARRANTIES OF
67
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, NON-INFRINGEMENT, AND
68
+ TITLE.
69
+
70
+ MariaDB hereby grants you permission to use this License's text to license
71
+ your works, and to refer to it using the trademark "Business Source License",
72
+ as long as you comply with the Covenants of Licensor below.
73
+
74
+ Covenants of Licensor
75
+
76
+ In consideration of the right to use this License's text and the "Business
77
+ Source License" name and trademark, Licensor covenants to MariaDB, and to all
78
+ other recipients of the licensed work to be provided by Licensor:
79
+
80
+ 1. To specify as the Change License the GPL Version 2.0 or any later version,
81
+ or a license that is compatible with GPL Version 2.0 or any later version,
82
+ where "compatible" means that software provided under the Change License
83
+ can be included in a program with software provided under GPL Version 2.0
84
+ or a later version. Licensor may specify additional Change Licenses
85
+ without limitation.
86
+
87
+ 2. To either: (a) specify an additional grant of rights to use that does not
88
+ impose any additional restriction on the right granted in this License, as
89
+ the Additional Use Grant; or (b) insert the text "None".
90
+
91
+ 3. To specify a Change Date.
92
+
93
+ 4. Not to modify this License in any other way.
94
+ License-File: LICENSE
95
+ Classifier: License :: Other/Proprietary License
96
+ Requires-Python: >=3.11
97
+ Requires-Dist: asyncpg>=0.29
98
+ Requires-Dist: networkx>=3.2
99
+ Requires-Dist: pydantic>=2.6
100
+ Requires-Dist: sqlalchemy[asyncio]>=2.0
101
+ Provides-Extra: dev
102
+ Requires-Dist: pytest-asyncio>=0.23; extra == 'dev'
103
+ Requires-Dist: pytest>=8; extra == 'dev'
104
+ Requires-Dist: ruff>=0.4; extra == 'dev'
105
+ Provides-Extra: mcp
106
+ Requires-Dist: mcp>=1.2; extra == 'mcp'
107
+ Provides-Extra: migrations
108
+ Requires-Dist: alembic>=1.13; extra == 'migrations'
109
+ Requires-Dist: psycopg[binary]>=3.1; extra == 'migrations'
@@ -0,0 +1,77 @@
1
+ # prodedify-memory
2
+
3
+ **The organization memory engine.** A compounding knowledge graph of
4
+ decisions, entities, and relationships — industry-agnostic via an
5
+ injectable `EntityRegistry`, queryable by humans and agents over MCP.
6
+
7
+ Extracted from the Prodedify platform (KG_EXTRACTION_SPEC, 2026-06-11);
8
+ the Prodedify monorepo is the first consumer. **Private — proprietary.**
9
+
10
+ ## Standalone install (owns its schema, not a database)
11
+
12
+ The engine owns its schema but takes an **injected** Postgres connection — no
13
+ bundled database. The schema is self-contained (no foreign keys to host tables;
14
+ `workspace_id` is a plain tenant partition key), so it bootstraps on an empty
15
+ Postgres:
16
+
17
+ ```bash
18
+ pip install -e '.[migrations]'
19
+
20
+ # Fresh standalone database:
21
+ export SAPERE_DATABASE_URL=postgresql://user:pass@host:5432/sapere
22
+ alembic upgrade head # creates the engine's tables + enums + indexes
23
+
24
+ # Existing install whose tables already exist (e.g. a hosted Supabase created
25
+ # by the monorepo migrations) — adopt, don't recreate:
26
+ alembic stamp head
27
+ ```
28
+
29
+ The same engine embeds in Plinth (inject the shared platform Postgres) or runs
30
+ standalone (inject a dedicated one). Migrations run on a sync driver; the app
31
+ connects however it likes (asyncpg).
32
+
33
+ ## What's here (KG-0)
34
+
35
+ | Module | Purpose |
36
+ |---|---|
37
+ | `registry` | `EntityRegistry` — the deployment's vocabulary (entity/relationship types, decision categories). `PRODEDIFY_REGISTRY` mirrors production. |
38
+ | `store` | `MemoryStore` — read-only, **workspace-bound** query layer (search, decisions, conflicts, timeline, neighbors, shortest path, communities). |
39
+ | `provenance` | `EXTRACTED / INFERRED / AMBIGUOUS` trust labels + default classifier. |
40
+ | `mcp` | The memory as an MCP server (stdio + Streamable HTTP). |
41
+ | `benchmark` | Memory-ROI metric: graph-grounded context vs raw corpus tokens. |
42
+
43
+ KG-1 moves the write/intelligence core (graph service, clustering,
44
+ temporal analysis) here from the monorepo. See the spec for phases.
45
+
46
+ ## Quickstart (against a Prodedify-schema database)
47
+
48
+ ```bash
49
+ pip install -e '.[mcp,dev]'
50
+
51
+ python -m prodedify_memory.mcp.server \
52
+ --database-url postgresql+asyncpg://user:pass@host:5432/db \
53
+ --workspace-id <workspace-uuid>
54
+ ```
55
+
56
+ Register with Claude Code:
57
+
58
+ ```bash
59
+ claude mcp add prodedify-memory -- python -m prodedify_memory.mcp.server \
60
+ --database-url $MEMORY_DATABASE_URL --workspace-id $WORKSPACE_ID
61
+ ```
62
+
63
+ Tools exposed: `memory_stats`, `query_memory`, `get_decisions`,
64
+ `get_conflicts`, `get_timeline`, `get_neighbors`, `shortest_path`,
65
+ `get_communities`.
66
+
67
+ ## Security
68
+
69
+ One server = one workspace (tenant isolation is structural). Read-only.
70
+ Parameterized SQL only. HTTP transport refuses non-loopback binds without
71
+ an API key. See SECURITY.md.
72
+
73
+ ## Tests
74
+
75
+ ```bash
76
+ pytest -q
77
+ ```
@@ -0,0 +1,38 @@
1
+ # Security model
2
+
3
+ ## Tenant isolation is structural
4
+
5
+ A `MemoryStore` (and therefore an MCP server instance) is bound to exactly
6
+ one `workspace_id` at construction. There is no tool parameter, header, or
7
+ query that can address another tenant's data — cross-tenant access is not
8
+ an authorization failure mode, it is unrepresentable.
9
+
10
+ ## Read-only by default; one opt-in write
11
+
12
+ The default tool surface never mutates the graph. The single write tool
13
+ (`record_decision`) exists only when the operator starts the server with
14
+ `--allow-writes` AND `--default-project-id`; it records operator-stated
15
+ decisions with `provenance=EXTRACTED`, remains workspace-bound, validates
16
+ category against the registry, and sanitizes all text. All other writes
17
+ (extraction, conflict scanning) remain with the consuming application.
18
+
19
+ ## Input handling
20
+
21
+ - All SQL is parameterized (SQLAlchemy Core `text()` with bound params).
22
+ - Free-text queries are control-character-stripped and capped at 500
23
+ chars before use.
24
+ - Limits are clamped server-side (max 50 rows; path depth max 5).
25
+ - Entity/relationship types are validated against the deployment's
26
+ EntityRegistry before touching the graph.
27
+
28
+ ## Transports
29
+
30
+ - stdio (default): inherits the parent process's security context.
31
+ - Streamable HTTP: binds loopback by default; refuses to bind a
32
+ non-loopback host unless an API key is configured. Always run behind
33
+ TLS termination in shared environments.
34
+
35
+ ## Data classification
36
+
37
+ Graph contents are customer organizational memory — treat every value as
38
+ confidential. Logs must never include row contents (IDs and counts only).
@@ -0,0 +1,62 @@
1
+ """Alembic environment for the standalone memory engine.
2
+
3
+ Schema comes from the engine's own models (`prodedify_memory.models.Base`); the
4
+ database connection is injected via SAPERE_DATABASE_URL or DATABASE_URL. No
5
+ bundled database — the engine owns its schema, the host owns the server.
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ import os
11
+
12
+ from alembic import context
13
+ from sqlalchemy import engine_from_config, pool
14
+
15
+ from prodedify_memory.models import Base
16
+
17
+ config = context.config
18
+
19
+ target_metadata = Base.metadata
20
+
21
+
22
+ def _db_url() -> str:
23
+ url = os.environ.get("SAPERE_DATABASE_URL") or os.environ.get("DATABASE_URL")
24
+ if not url:
25
+ raise RuntimeError(
26
+ "Set SAPERE_DATABASE_URL (or DATABASE_URL) to the target Postgres."
27
+ )
28
+ # Migrations run on a sync driver regardless of how the app connects.
29
+ return (
30
+ url.replace("postgresql+asyncpg://", "postgresql+psycopg://")
31
+ .replace("postgres://", "postgresql+psycopg://")
32
+ .replace("postgresql://", "postgresql+psycopg://")
33
+ )
34
+
35
+
36
+ def run_migrations_offline() -> None:
37
+ context.configure(
38
+ url=_db_url(),
39
+ target_metadata=target_metadata,
40
+ literal_binds=True,
41
+ dialect_opts={"paramstyle": "named"},
42
+ )
43
+ with context.begin_transaction():
44
+ context.run_migrations()
45
+
46
+
47
+ def run_migrations_online() -> None:
48
+ section = config.get_section(config.config_ini_section) or {}
49
+ section["sqlalchemy.url"] = _db_url()
50
+ connectable = engine_from_config(
51
+ section, prefix="sqlalchemy.", poolclass=pool.NullPool
52
+ )
53
+ with connectable.connect() as connection:
54
+ context.configure(connection=connection, target_metadata=target_metadata)
55
+ with context.begin_transaction():
56
+ context.run_migrations()
57
+
58
+
59
+ if context.is_offline_mode():
60
+ run_migrations_offline()
61
+ else:
62
+ run_migrations_online()
@@ -0,0 +1,23 @@
1
+ """${message}
2
+
3
+ Revision ID: ${up_revision}
4
+ Revises: ${down_revision | comma,n}
5
+ Create Date: ${create_date}
6
+ """
7
+
8
+ from alembic import op
9
+ import sqlalchemy as sa
10
+ ${imports if imports else ""}
11
+
12
+ revision = ${repr(up_revision)}
13
+ down_revision = ${repr(down_revision)}
14
+ branch_labels = ${repr(branch_labels)}
15
+ depends_on = ${repr(depends_on)}
16
+
17
+
18
+ def upgrade() -> None:
19
+ ${upgrades if upgrades else "pass"}
20
+
21
+
22
+ def downgrade() -> None:
23
+ ${downgrades if downgrades else "pass"}
@@ -0,0 +1,86 @@
1
+ """baseline — the memory engine's self-contained graph schema
2
+
3
+ Creates the engine's own tables from its models (knowledge_graph_edges,
4
+ product_decisions, knowledge_graph_cluster_runs, knowledge_graph_communities,
5
+ knowledge_graph_temporal_snapshots), including bi-temporal validity columns.
6
+ The schema has no foreign keys to any host tables — workspace_id is a plain
7
+ partition key — so a standalone Sapere bootstraps against an empty Postgres.
8
+
9
+ Existing installs whose tables already exist (e.g. the hosted Supabase, created
10
+ by the monorepo migrations) should `alembic stamp head` instead of upgrading.
11
+
12
+ Revision ID: 0001_baseline
13
+ Revises:
14
+ Create Date: 2026-06-13
15
+ """
16
+
17
+ from alembic import op
18
+
19
+ from prodedify_memory.models import Base
20
+
21
+ revision = "0001_baseline"
22
+ down_revision = None
23
+ branch_labels = None
24
+ depends_on = None
25
+
26
+
27
+ def upgrade() -> None:
28
+ bind = op.get_bind()
29
+
30
+ # 1. The two fixed-vocabulary enums that survived KG-2 (the ontology enums
31
+ # are varchar now). create_type=False on the models means create_all
32
+ # won't make these — so we make them first.
33
+ op.execute(
34
+ "DO $$ BEGIN IF NOT EXISTS (SELECT 1 FROM pg_type WHERE typname="
35
+ "'graph_edge_creator') THEN CREATE TYPE graph_edge_creator AS ENUM "
36
+ "('system','kilnai','user'); END IF; END $$;"
37
+ )
38
+ op.execute(
39
+ "DO $$ BEGIN IF NOT EXISTS (SELECT 1 FROM pg_type WHERE typname="
40
+ "'decision_status') THEN CREATE TYPE decision_status AS ENUM "
41
+ "('active','superseded','reversed','under_review'); END IF; END $$;"
42
+ )
43
+
44
+ # 2. Tables from the engine's own models (single source of truth, includes
45
+ # valid_from/valid_until).
46
+ Base.metadata.create_all(bind, checkfirst=True)
47
+
48
+ # 3. Correctness + hot-path indexes the bare models don't declare.
49
+ op.execute(
50
+ "CREATE UNIQUE INDEX IF NOT EXISTS idx_kge_unique_edge "
51
+ "ON knowledge_graph_edges "
52
+ "(source_type, source_id, target_type, target_id, relationship_type) "
53
+ "WHERE deleted_at IS NULL"
54
+ )
55
+ op.execute(
56
+ "CREATE UNIQUE INDEX IF NOT EXISTS idx_kgc_unique "
57
+ "ON knowledge_graph_communities "
58
+ "(entity_type, entity_id, project_id, cluster_run_id)"
59
+ )
60
+ op.execute(
61
+ "CREATE INDEX IF NOT EXISTS idx_pd_live ON product_decisions "
62
+ "(workspace_id, valid_from) "
63
+ "WHERE valid_until IS NULL AND status = 'active' AND deleted_at IS NULL"
64
+ )
65
+ op.execute(
66
+ "CREATE INDEX IF NOT EXISTS idx_kge_source "
67
+ "ON knowledge_graph_edges (source_type, source_id) "
68
+ "WHERE deleted_at IS NULL"
69
+ )
70
+ op.execute(
71
+ "CREATE INDEX IF NOT EXISTS idx_kge_target "
72
+ "ON knowledge_graph_edges (target_type, target_id) "
73
+ "WHERE deleted_at IS NULL"
74
+ )
75
+ op.execute(
76
+ "CREATE INDEX IF NOT EXISTS idx_pd_project_active "
77
+ "ON product_decisions (project_id) "
78
+ "WHERE status = 'active' AND deleted_at IS NULL"
79
+ )
80
+
81
+
82
+ def downgrade() -> None:
83
+ bind = op.get_bind()
84
+ Base.metadata.drop_all(bind, checkfirst=True)
85
+ op.execute("DROP TYPE IF EXISTS decision_status")
86
+ op.execute("DROP TYPE IF EXISTS graph_edge_creator")