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.
- prodedify_memory-0.2.1/.github/workflows/ci.yml +46 -0
- prodedify_memory-0.2.1/.gitignore +10 -0
- prodedify_memory-0.2.1/CLAUDE.md +58 -0
- prodedify_memory-0.2.1/LICENSE +89 -0
- prodedify_memory-0.2.1/Makefile +37 -0
- prodedify_memory-0.2.1/PKG-INFO +109 -0
- prodedify_memory-0.2.1/README.md +77 -0
- prodedify_memory-0.2.1/SECURITY.md +38 -0
- prodedify_memory-0.2.1/alembic/env.py +62 -0
- prodedify_memory-0.2.1/alembic/script.py.mako +23 -0
- prodedify_memory-0.2.1/alembic/versions/0001_baseline.py +86 -0
- prodedify_memory-0.2.1/alembic/versions/0002_kg_rls_policies.py +62 -0
- prodedify_memory-0.2.1/alembic/versions/0003_adr005_bitemporal_substrate.py +199 -0
- prodedify_memory-0.2.1/alembic/versions/0004_prd05_grounding_audit.py +88 -0
- prodedify_memory-0.2.1/alembic/versions/0005_prd12_write_path_policy.py +128 -0
- prodedify_memory-0.2.1/alembic/versions/0006_ind1_edge_creator_agent.py +66 -0
- prodedify_memory-0.2.1/alembic/versions/0006_prd11b_memory_embeddings.py +112 -0
- prodedify_memory-0.2.1/alembic/versions/0007_standalone_app_rw_grants.py +66 -0
- prodedify_memory-0.2.1/alembic/versions/0008_sapere_write_idempotency.py +94 -0
- prodedify_memory-0.2.1/alembic/versions/0009_uda4_referral_policy.py +154 -0
- prodedify_memory-0.2.1/alembic/versions/0010_uda3_fairness_policy.py +134 -0
- prodedify_memory-0.2.1/alembic/versions/0011_uda4_assert_event_guard.py +103 -0
- prodedify_memory-0.2.1/alembic/versions/0012_decision_conflict_pairs.py +144 -0
- prodedify_memory-0.2.1/alembic/versions/0013_project_knowledge_items.py +130 -0
- prodedify_memory-0.2.1/alembic/versions/0014_uda1_retrieval_sidecar.py +55 -0
- prodedify_memory-0.2.1/alembic/versions/0015_g9_b2_immutability.py +232 -0
- prodedify_memory-0.2.1/alembic/versions/0016_g2_entity_resolution.py +285 -0
- prodedify_memory-0.2.1/alembic/versions/0017_g2b_merge_event_seq.py +47 -0
- prodedify_memory-0.2.1/alembic/versions/0018_g3_contradiction_lifecycle.py +180 -0
- prodedify_memory-0.2.1/alembic/versions/0019_g11_api_keys.py +122 -0
- prodedify_memory-0.2.1/alembic/versions/2c1b798ce44c_merge_ind1_agent_prd11b_embeddings_heads.py +39 -0
- prodedify_memory-0.2.1/alembic.ini +45 -0
- prodedify_memory-0.2.1/docker-compose.dev.yml +77 -0
- prodedify_memory-0.2.1/prodedify_memory/__init__.py +40 -0
- prodedify_memory-0.2.1/prodedify_memory/apikeys.py +58 -0
- prodedify_memory-0.2.1/prodedify_memory/benchmark/__init__.py +60 -0
- prodedify_memory-0.2.1/prodedify_memory/benchmark/adapters/__init__.py +56 -0
- prodedify_memory-0.2.1/prodedify_memory/benchmark/adapters/base.py +103 -0
- prodedify_memory-0.2.1/prodedify_memory/benchmark/adapters/hotpotqa.py +194 -0
- prodedify_memory-0.2.1/prodedify_memory/benchmark/adapters/longmemeval.py +246 -0
- prodedify_memory-0.2.1/prodedify_memory/benchmark/arms.py +204 -0
- prodedify_memory-0.2.1/prodedify_memory/benchmark/baselines/fixture_v1__fake-concept-1536.json +40 -0
- prodedify_memory-0.2.1/prodedify_memory/benchmark/cli.py +147 -0
- prodedify_memory-0.2.1/prodedify_memory/benchmark/corpus.py +470 -0
- prodedify_memory-0.2.1/prodedify_memory/benchmark/data/fixture_v1.jsonl +114 -0
- prodedify_memory-0.2.1/prodedify_memory/benchmark/data/hotpot_ids_v1.json +112 -0
- prodedify_memory-0.2.1/prodedify_memory/benchmark/data/hotpot_sample_v1.json +39 -0
- prodedify_memory-0.2.1/prodedify_memory/benchmark/data/longmemeval_ids_v1.json +116 -0
- prodedify_memory-0.2.1/prodedify_memory/benchmark/data/longmemeval_sample_v1.json +60 -0
- prodedify_memory-0.2.1/prodedify_memory/benchmark/fake_embedder.py +61 -0
- prodedify_memory-0.2.1/prodedify_memory/benchmark/fixtures.py +248 -0
- prodedify_memory-0.2.1/prodedify_memory/benchmark/judge.py +64 -0
- prodedify_memory-0.2.1/prodedify_memory/benchmark/metrics.py +150 -0
- prodedify_memory-0.2.1/prodedify_memory/benchmark/report.py +242 -0
- prodedify_memory-0.2.1/prodedify_memory/benchmark/runner.py +269 -0
- prodedify_memory-0.2.1/prodedify_memory/benchmark/seed.py +282 -0
- prodedify_memory-0.2.1/prodedify_memory/benchmark/tokens.py +68 -0
- prodedify_memory-0.2.1/prodedify_memory/contradiction/__init__.py +69 -0
- prodedify_memory-0.2.1/prodedify_memory/contradiction/lifecycle.py +562 -0
- prodedify_memory-0.2.1/prodedify_memory/contradiction/seams.py +138 -0
- prodedify_memory-0.2.1/prodedify_memory/contradiction/sweep.py +200 -0
- prodedify_memory-0.2.1/prodedify_memory/fairness_policy.py +166 -0
- prodedify_memory-0.2.1/prodedify_memory/grounding_audit.py +273 -0
- prodedify_memory-0.2.1/prodedify_memory/hooks.py +95 -0
- prodedify_memory-0.2.1/prodedify_memory/mcp/__init__.py +1 -0
- prodedify_memory-0.2.1/prodedify_memory/mcp/server.py +202 -0
- prodedify_memory-0.2.1/prodedify_memory/migrate.py +65 -0
- prodedify_memory-0.2.1/prodedify_memory/models.py +740 -0
- prodedify_memory-0.2.1/prodedify_memory/provenance.py +36 -0
- prodedify_memory-0.2.1/prodedify_memory/referral_policy.py +188 -0
- prodedify_memory-0.2.1/prodedify_memory/registry.py +100 -0
- prodedify_memory-0.2.1/prodedify_memory/resolution/__init__.py +79 -0
- prodedify_memory-0.2.1/prodedify_memory/resolution/fingerprint.py +157 -0
- prodedify_memory-0.2.1/prodedify_memory/resolution/residue.py +93 -0
- prodedify_memory-0.2.1/prodedify_memory/resolution/resolver.py +392 -0
- prodedify_memory-0.2.1/prodedify_memory/resolution/review.py +269 -0
- prodedify_memory-0.2.1/prodedify_memory/retrieval/__init__.py +75 -0
- prodedify_memory-0.2.1/prodedify_memory/retrieval/backfill.py +323 -0
- prodedify_memory-0.2.1/prodedify_memory/retrieval/embedder.py +94 -0
- prodedify_memory-0.2.1/prodedify_memory/retrieval/fusion.py +133 -0
- prodedify_memory-0.2.1/prodedify_memory/retrieval/signals.py +323 -0
- prodedify_memory-0.2.1/prodedify_memory/retrieval/writer.py +180 -0
- prodedify_memory-0.2.1/prodedify_memory/services/__init__.py +9 -0
- prodedify_memory-0.2.1/prodedify_memory/services/intelligence.py +1713 -0
- prodedify_memory-0.2.1/prodedify_memory/services/knowledge_graph.py +1144 -0
- prodedify_memory-0.2.1/prodedify_memory/store.py +2205 -0
- prodedify_memory-0.2.1/prodedify_memory/temporal.py +91 -0
- prodedify_memory-0.2.1/prodedify_memory/write_policy.py +77 -0
- prodedify_memory-0.2.1/pyproject.toml +47 -0
- prodedify_memory-0.2.1/scripts/check_migration_graph.py +85 -0
- prodedify_memory-0.2.1/scripts/dev-initdb.sql +16 -0
- prodedify_memory-0.2.1/scripts/rehearse_uda3.sh +50 -0
- prodedify_memory-0.2.1/scripts/rehearse_uda4.sh +68 -0
- prodedify_memory-0.2.1/tests/test_adr005_substrate.py +267 -0
- prodedify_memory-0.2.1/tests/test_b2_immutability.py +413 -0
- prodedify_memory-0.2.1/tests/test_benchmark.py +30 -0
- prodedify_memory-0.2.1/tests/test_benchmark_adapters.py +396 -0
- prodedify_memory-0.2.1/tests/test_benchmark_corpus.py +309 -0
- prodedify_memory-0.2.1/tests/test_benchmark_harness.py +180 -0
- prodedify_memory-0.2.1/tests/test_benchmark_report.py +163 -0
- prodedify_memory-0.2.1/tests/test_benchmark_seed.py +160 -0
- prodedify_memory-0.2.1/tests/test_g11_api_keys.py +177 -0
- prodedify_memory-0.2.1/tests/test_g2_entity_resolution.py +488 -0
- prodedify_memory-0.2.1/tests/test_g2b_merge_review.py +285 -0
- prodedify_memory-0.2.1/tests/test_g2g3_post_g4.py +230 -0
- prodedify_memory-0.2.1/tests/test_g3_contradiction_lifecycle.py +435 -0
- prodedify_memory-0.2.1/tests/test_g3b_sweep.py +238 -0
- prodedify_memory-0.2.1/tests/test_g4_traversal.py +401 -0
- prodedify_memory-0.2.1/tests/test_ind1_edge_creator.py +20 -0
- prodedify_memory-0.2.1/tests/test_kg_conflict_pairs.py +168 -0
- prodedify_memory-0.2.1/tests/test_kg_project_knowledge_items.py +126 -0
- prodedify_memory-0.2.1/tests/test_migration_graph.py +101 -0
- prodedify_memory-0.2.1/tests/test_packaged_migrations.py +105 -0
- prodedify_memory-0.2.1/tests/test_prd05_audit_replay.py +564 -0
- prodedify_memory-0.2.1/tests/test_prd05_grounding.py +341 -0
- prodedify_memory-0.2.1/tests/test_prd11b_retrieval.py +690 -0
- prodedify_memory-0.2.1/tests/test_prd12_write_path.py +541 -0
- prodedify_memory-0.2.1/tests/test_provenance.py +21 -0
- prodedify_memory-0.2.1/tests/test_registry.py +46 -0
- prodedify_memory-0.2.1/tests/test_resolution_fingerprint.py +142 -0
- prodedify_memory-0.2.1/tests/test_sap_pg2_embed_status.py +158 -0
- prodedify_memory-0.2.1/tests/test_store_sql.py +69 -0
- prodedify_memory-0.2.1/tests/test_uda3_fairness_policy.py +282 -0
- prodedify_memory-0.2.1/tests/test_uda4_referral_policy.py +577 -0
- prodedify_memory-0.0.0/PKG-INFO +0 -13
- prodedify_memory-0.0.0/README.md +0 -3
- prodedify_memory-0.0.0/prodedify_memory.egg-info/PKG-INFO +0 -13
- prodedify_memory-0.0.0/prodedify_memory.egg-info/SOURCES.txt +0 -6
- prodedify_memory-0.0.0/prodedify_memory.egg-info/dependency_links.txt +0 -1
- prodedify_memory-0.0.0/prodedify_memory.egg-info/top_level.txt +0 -1
- prodedify_memory-0.0.0/pyproject.toml +0 -18
- 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,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")
|