lorebase-store 0.2.3__tar.gz → 0.3.0__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.
- {lorebase_store-0.2.3 → lorebase_store-0.3.0}/PKG-INFO +3 -3
- {lorebase_store-0.2.3 → lorebase_store-0.3.0}/migrations/versions/0001_initial_schema.py +19 -2
- lorebase_store-0.3.0/migrations/versions/0038_reconciliation_candidates.py +131 -0
- lorebase_store-0.3.0/migrations/versions/0039_reconciliation_lookups.py +42 -0
- lorebase_store-0.3.0/migrations/versions/0040_source_written.py +148 -0
- lorebase_store-0.3.0/migrations/versions/0041_document_versions.py +121 -0
- lorebase_store-0.3.0/migrations/versions/0042_proposal_kinds.py +37 -0
- lorebase_store-0.3.0/migrations/versions/0043_source_last_seen.py +36 -0
- lorebase_store-0.3.0/migrations/versions/0044_mat_records_object_hash_index.py +85 -0
- lorebase_store-0.3.0/migrations/versions/0045_value_index.py +459 -0
- lorebase_store-0.3.0/migrations/versions/0046_vocabulary_and_coverage.py +159 -0
- lorebase_store-0.3.0/migrations/versions/0047_value_index_limits.py +261 -0
- {lorebase_store-0.2.3 → lorebase_store-0.3.0}/pyproject.toml +3 -3
- lorebase_store-0.3.0/src/lorebase_store/__init__.py +1 -0
- lorebase_store-0.3.0/src/lorebase_store/approval_rules.py +114 -0
- {lorebase_store-0.2.3 → lorebase_store-0.3.0}/src/lorebase_store/auth/principals.py +16 -0
- {lorebase_store-0.2.3 → lorebase_store-0.3.0}/src/lorebase_store/blobs.py +23 -3
- {lorebase_store-0.2.3 → lorebase_store-0.3.0}/src/lorebase_store/bundle.py +22 -11
- {lorebase_store-0.2.3 → lorebase_store-0.3.0}/src/lorebase_store/commit/changes.py +2 -1
- {lorebase_store-0.2.3 → lorebase_store-0.3.0}/src/lorebase_store/commit/pipeline.py +6 -0
- {lorebase_store-0.2.3 → lorebase_store-0.3.0}/src/lorebase_store/commit/schema.py +3 -0
- lorebase_store-0.3.0/src/lorebase_store/coverage.py +351 -0
- {lorebase_store-0.2.3 → lorebase_store-0.3.0}/src/lorebase_store/definer.py +3 -1
- lorebase_store-0.3.0/src/lorebase_store/duplicates.py +98 -0
- {lorebase_store-0.2.3 → lorebase_store-0.3.0}/src/lorebase_store/embeddings.py +155 -1
- {lorebase_store-0.2.3 → lorebase_store-0.3.0}/src/lorebase_store/extraction/materialize.py +7 -3
- lorebase_store-0.3.0/src/lorebase_store/extraction/modes.py +318 -0
- {lorebase_store-0.2.3 → lorebase_store-0.3.0}/src/lorebase_store/extraction/proposal.py +9 -2
- {lorebase_store-0.2.3 → lorebase_store-0.3.0}/src/lorebase_store/gc.py +10 -4
- {lorebase_store-0.2.3 → lorebase_store-0.3.0}/src/lorebase_store/jobs.py +63 -0
- {lorebase_store-0.2.3 → lorebase_store-0.3.0}/src/lorebase_store/merging/__init__.py +8 -0
- lorebase_store-0.3.0/src/lorebase_store/merging/preserved.py +62 -0
- {lorebase_store-0.2.3 → lorebase_store-0.3.0}/src/lorebase_store/merging/service.py +226 -9
- {lorebase_store-0.2.3 → lorebase_store-0.3.0}/src/lorebase_store/models/__init__.py +18 -0
- {lorebase_store-0.2.3 → lorebase_store-0.3.0}/src/lorebase_store/models/ingestion.py +163 -10
- {lorebase_store-0.2.3 → lorebase_store-0.3.0}/src/lorebase_store/models/materialized.py +112 -1
- {lorebase_store-0.2.3 → lorebase_store-0.3.0}/src/lorebase_store/models/proposals.py +5 -0
- lorebase_store-0.3.0/src/lorebase_store/models/reconciliation.py +62 -0
- {lorebase_store-0.2.3 → lorebase_store-0.3.0}/src/lorebase_store/models/versioning.py +4 -1
- {lorebase_store-0.2.3 → lorebase_store-0.3.0}/src/lorebase_store/objectstore.py +1 -0
- {lorebase_store-0.2.3 → lorebase_store-0.3.0}/src/lorebase_store/profiling/__init__.py +4 -0
- {lorebase_store-0.2.3 → lorebase_store-0.3.0}/src/lorebase_store/profiling/sql_databases.py +31 -19
- lorebase_store-0.3.0/src/lorebase_store/reconciliation.py +237 -0
- {lorebase_store-0.2.3 → lorebase_store-0.3.0}/src/lorebase_store/repositories/__init__.py +32 -0
- lorebase_store-0.3.0/src/lorebase_store/repositories/documents.py +637 -0
- {lorebase_store-0.2.3 → lorebase_store-0.3.0}/src/lorebase_store/repositories/domains.py +5 -1
- {lorebase_store-0.2.3 → lorebase_store-0.3.0}/src/lorebase_store/repositories/materialized.py +84 -0
- {lorebase_store-0.2.3 → lorebase_store-0.3.0}/src/lorebase_store/repositories/proposal_items.py +63 -2
- {lorebase_store-0.2.3 → lorebase_store-0.3.0}/src/lorebase_store/repositories/proposals.py +34 -3
- {lorebase_store-0.2.3 → lorebase_store-0.3.0}/src/lorebase_store/repositories/provenance.py +12 -0
- lorebase_store-0.3.0/src/lorebase_store/repositories/quotes.py +40 -0
- lorebase_store-0.3.0/src/lorebase_store/repositories/reconciliation_candidates.py +575 -0
- lorebase_store-0.3.0/src/lorebase_store/repositories/source_written.py +351 -0
- {lorebase_store-0.2.3 → lorebase_store-0.3.0}/src/lorebase_store/repositories/suggestions.py +187 -10
- lorebase_store-0.3.0/src/lorebase_store/resolve_misses.py +257 -0
- {lorebase_store-0.2.3 → lorebase_store-0.3.0}/src/lorebase_store/settings.py +223 -0
- {lorebase_store-0.2.3 → lorebase_store-0.3.0}/src/lorebase_store/storage.py +34 -5
- lorebase_store-0.3.0/src/lorebase_store/values.py +931 -0
- lorebase_store-0.3.0/tests/test_approval_rules.py +78 -0
- {lorebase_store-0.2.3 → lorebase_store-0.3.0}/tests/test_bundle.py +27 -1
- {lorebase_store-0.2.3 → lorebase_store-0.3.0}/tests/test_definer_checks.py +29 -2
- lorebase_store-0.3.0/tests/test_document_quotes.py +43 -0
- lorebase_store-0.3.0/tests/test_ef_search.py +64 -0
- lorebase_store-0.3.0/tests/test_extraction_modes.py +140 -0
- {lorebase_store-0.2.3 → lorebase_store-0.3.0}/tests/test_gc.py +3 -1
- {lorebase_store-0.2.3 → lorebase_store-0.3.0}/tests/test_models.py +14 -1
- {lorebase_store-0.2.3 → lorebase_store-0.3.0}/tests/test_profiling_sql.py +19 -19
- {lorebase_store-0.2.3 → lorebase_store-0.3.0}/tests/test_reads.py +6 -1
- lorebase_store-0.3.0/tests/test_reconciliation_candidates_integration.py +398 -0
- lorebase_store-0.3.0/tests/test_reconciliation_wrapping.py +44 -0
- {lorebase_store-0.2.3 → lorebase_store-0.3.0}/tests/test_storage.py +21 -0
- {lorebase_store-0.2.3 → lorebase_store-0.3.0}/tests/test_suggestion_fold.py +50 -1
- lorebase_store-0.3.0/tests/test_values.py +810 -0
- lorebase_store-0.2.3/src/lorebase_store/__init__.py +0 -1
- lorebase_store-0.2.3/src/lorebase_store/repositories/documents.py +0 -305
- {lorebase_store-0.2.3 → lorebase_store-0.3.0}/.gitignore +0 -0
- {lorebase_store-0.2.3 → lorebase_store-0.3.0}/alembic.ini +0 -0
- {lorebase_store-0.2.3 → lorebase_store-0.3.0}/migrations/env.py +0 -0
- {lorebase_store-0.2.3 → lorebase_store-0.3.0}/migrations/script.py.mako +0 -0
- {lorebase_store-0.2.3 → lorebase_store-0.3.0}/migrations/versions/0002_merge_attempt_details.py +0 -0
- {lorebase_store-0.2.3 → lorebase_store-0.3.0}/migrations/versions/0003_webhooks.py +0 -0
- {lorebase_store-0.2.3 → lorebase_store-0.3.0}/migrations/versions/0004_defer_revision_references.py +0 -0
- {lorebase_store-0.2.3 → lorebase_store-0.3.0}/migrations/versions/0005_security_definer_reads.py +0 -0
- {lorebase_store-0.2.3 → lorebase_store-0.3.0}/migrations/versions/0006_api_key_usage.py +0 -0
- {lorebase_store-0.2.3 → lorebase_store-0.3.0}/migrations/versions/0007_document_extraction.py +0 -0
- {lorebase_store-0.2.3 → lorebase_store-0.3.0}/migrations/versions/0008_definer_argument_checks.py +0 -0
- {lorebase_store-0.2.3 → lorebase_store-0.3.0}/migrations/versions/0009_api_key_networks.py +0 -0
- {lorebase_store-0.2.3 → lorebase_store-0.3.0}/migrations/versions/0010_overlay_branches.py +0 -0
- {lorebase_store-0.2.3 → lorebase_store-0.3.0}/migrations/versions/0011_branch_scoped_search_index.py +0 -0
- {lorebase_store-0.2.3 → lorebase_store-0.3.0}/migrations/versions/0012_overlay_neighborhood_lookups.py +0 -0
- {lorebase_store-0.2.3 → lorebase_store-0.3.0}/migrations/versions/0013_web_sessions.py +0 -0
- {lorebase_store-0.2.3 → lorebase_store-0.3.0}/migrations/versions/0014_embedding_jobs.py +0 -0
- {lorebase_store-0.2.3 → lorebase_store-0.3.0}/migrations/versions/0015_append_only_audit_log.py +0 -0
- {lorebase_store-0.2.3 → lorebase_store-0.3.0}/migrations/versions/0016_sealed_webhook_keys.py +0 -0
- {lorebase_store-0.2.3 → lorebase_store-0.3.0}/migrations/versions/0017_background_branch_copies.py +0 -0
- {lorebase_store-0.2.3 → lorebase_store-0.3.0}/migrations/versions/0018_search_lexicon.py +0 -0
- {lorebase_store-0.2.3 → lorebase_store-0.3.0}/migrations/versions/0019_vault_bindings.py +0 -0
- {lorebase_store-0.2.3 → lorebase_store-0.3.0}/migrations/versions/0020_outbox_consumers.py +0 -0
- {lorebase_store-0.2.3 → lorebase_store-0.3.0}/migrations/versions/0021_schema_suggestions.py +0 -0
- {lorebase_store-0.2.3 → lorebase_store-0.3.0}/migrations/versions/0022_read_audit_and_shared_rate_limits.py +0 -0
- {lorebase_store-0.2.3 → lorebase_store-0.3.0}/migrations/versions/0023_agent_trace.py +0 -0
- {lorebase_store-0.2.3 → lorebase_store-0.3.0}/migrations/versions/0024_embedding_template_keys.py +0 -0
- {lorebase_store-0.2.3 → lorebase_store-0.3.0}/migrations/versions/0025_snapshot_refs.py +0 -0
- {lorebase_store-0.2.3 → lorebase_store-0.3.0}/migrations/versions/0026_plain_reverse_link_index.py +0 -0
- {lorebase_store-0.2.3 → lorebase_store-0.3.0}/migrations/versions/0027_vault_rotation_and_tombstones.py +0 -0
- {lorebase_store-0.2.3 → lorebase_store-0.3.0}/migrations/versions/0028_operation_deferred_events.py +0 -0
- {lorebase_store-0.2.3 → lorebase_store-0.3.0}/migrations/versions/0029_agent_trace_sessions.py +0 -0
- {lorebase_store-0.2.3 → lorebase_store-0.3.0}/migrations/versions/0030_path_fanout.py +0 -0
- {lorebase_store-0.2.3 → lorebase_store-0.3.0}/migrations/versions/0031_branch_builds.py +0 -0
- {lorebase_store-0.2.3 → lorebase_store-0.3.0}/migrations/versions/0032_lineage_revisions.py +0 -0
- {lorebase_store-0.2.3 → lorebase_store-0.3.0}/migrations/versions/0033_vault_moved_tokens.py +0 -0
- {lorebase_store-0.2.3 → lorebase_store-0.3.0}/migrations/versions/0034_inference_endpoint_modes.py +0 -0
- {lorebase_store-0.2.3 → lorebase_store-0.3.0}/migrations/versions/0035_search_truncation.py +0 -0
- {lorebase_store-0.2.3 → lorebase_store-0.3.0}/migrations/versions/0036_lineage_maps.py +0 -0
- {lorebase_store-0.2.3 → lorebase_store-0.3.0}/migrations/versions/0037_bm25_search.py +0 -0
- {lorebase_store-0.2.3 → lorebase_store-0.3.0}/src/lorebase_store/auth/__init__.py +0 -0
- {lorebase_store-0.2.3 → lorebase_store-0.3.0}/src/lorebase_store/auth/api_keys.py +0 -0
- {lorebase_store-0.2.3 → lorebase_store-0.3.0}/src/lorebase_store/auth/audit.py +0 -0
- {lorebase_store-0.2.3 → lorebase_store-0.3.0}/src/lorebase_store/auth/checkpoints.py +0 -0
- {lorebase_store-0.2.3 → lorebase_store-0.3.0}/src/lorebase_store/auth/grants.py +0 -0
- {lorebase_store-0.2.3 → lorebase_store-0.3.0}/src/lorebase_store/auth/scope.py +0 -0
- {lorebase_store-0.2.3 → lorebase_store-0.3.0}/src/lorebase_store/auth/sessions.py +0 -0
- {lorebase_store-0.2.3 → lorebase_store-0.3.0}/src/lorebase_store/auth/unit.py +0 -0
- {lorebase_store-0.2.3 → lorebase_store-0.3.0}/src/lorebase_store/auth/usage.py +0 -0
- {lorebase_store-0.2.3 → lorebase_store-0.3.0}/src/lorebase_store/bm25.py +0 -0
- {lorebase_store-0.2.3 → lorebase_store-0.3.0}/src/lorebase_store/bulk.py +0 -0
- {lorebase_store-0.2.3 → lorebase_store-0.3.0}/src/lorebase_store/changefeed.py +0 -0
- {lorebase_store-0.2.3 → lorebase_store-0.3.0}/src/lorebase_store/commit/__init__.py +0 -0
- {lorebase_store-0.2.3 → lorebase_store-0.3.0}/src/lorebase_store/commit/errors.py +0 -0
- {lorebase_store-0.2.3 → lorebase_store-0.3.0}/src/lorebase_store/commit/migrations.py +0 -0
- {lorebase_store-0.2.3 → lorebase_store-0.3.0}/src/lorebase_store/db.py +0 -0
- {lorebase_store-0.2.3 → lorebase_store-0.3.0}/src/lorebase_store/digests.py +0 -0
- {lorebase_store-0.2.3 → lorebase_store-0.3.0}/src/lorebase_store/endpoint_modes.py +0 -0
- {lorebase_store-0.2.3 → lorebase_store-0.3.0}/src/lorebase_store/extraction/__init__.py +0 -0
- {lorebase_store-0.2.3 → lorebase_store-0.3.0}/src/lorebase_store/extraction/items.py +0 -0
- {lorebase_store-0.2.3 → lorebase_store-0.3.0}/src/lorebase_store/extraction/review.py +0 -0
- {lorebase_store-0.2.3 → lorebase_store-0.3.0}/src/lorebase_store/indexes.py +0 -0
- {lorebase_store-0.2.3 → lorebase_store-0.3.0}/src/lorebase_store/keyring.py +0 -0
- {lorebase_store-0.2.3 → lorebase_store-0.3.0}/src/lorebase_store/layout.py +0 -0
- {lorebase_store-0.2.3 → lorebase_store-0.3.0}/src/lorebase_store/materialize/__init__.py +0 -0
- {lorebase_store-0.2.3 → lorebase_store-0.3.0}/src/lorebase_store/materialize/apply.py +0 -0
- {lorebase_store-0.2.3 → lorebase_store-0.3.0}/src/lorebase_store/materialize/background.py +0 -0
- {lorebase_store-0.2.3 → lorebase_store-0.3.0}/src/lorebase_store/materialize/branch.py +0 -0
- {lorebase_store-0.2.3 → lorebase_store-0.3.0}/src/lorebase_store/materialize/build.py +0 -0
- {lorebase_store-0.2.3 → lorebase_store-0.3.0}/src/lorebase_store/materialize/derived.py +0 -0
- {lorebase_store-0.2.3 → lorebase_store-0.3.0}/src/lorebase_store/materialize/lineage_backfill.py +0 -0
- {lorebase_store-0.2.3 → lorebase_store-0.3.0}/src/lorebase_store/materialize/links.py +0 -0
- {lorebase_store-0.2.3 → lorebase_store-0.3.0}/src/lorebase_store/materialize/overlay.py +0 -0
- {lorebase_store-0.2.3 → lorebase_store-0.3.0}/src/lorebase_store/materialize/paths.py +0 -0
- {lorebase_store-0.2.3 → lorebase_store-0.3.0}/src/lorebase_store/materialize/rebuild.py +0 -0
- {lorebase_store-0.2.3 → lorebase_store-0.3.0}/src/lorebase_store/materialize/rows.py +0 -0
- {lorebase_store-0.2.3 → lorebase_store-0.3.0}/src/lorebase_store/materialize/sensitivity.py +0 -0
- {lorebase_store-0.2.3 → lorebase_store-0.3.0}/src/lorebase_store/materialize/snapshot.py +0 -0
- {lorebase_store-0.2.3 → lorebase_store-0.3.0}/src/lorebase_store/materialize/stale.py +0 -0
- {lorebase_store-0.2.3 → lorebase_store-0.3.0}/src/lorebase_store/materialize/verify.py +0 -0
- {lorebase_store-0.2.3 → lorebase_store-0.3.0}/src/lorebase_store/merging/attempts.py +0 -0
- {lorebase_store-0.2.3 → lorebase_store-0.3.0}/src/lorebase_store/merging/lineage.py +0 -0
- {lorebase_store-0.2.3 → lorebase_store-0.3.0}/src/lorebase_store/migrations.py +0 -0
- {lorebase_store-0.2.3 → lorebase_store-0.3.0}/src/lorebase_store/models/base.py +0 -0
- {lorebase_store-0.2.3 → lorebase_store-0.3.0}/src/lorebase_store/models/enums.py +0 -0
- {lorebase_store-0.2.3 → lorebase_store-0.3.0}/src/lorebase_store/models/governance.py +0 -0
- {lorebase_store-0.2.3 → lorebase_store-0.3.0}/src/lorebase_store/models/queues.py +0 -0
- {lorebase_store-0.2.3 → lorebase_store-0.3.0}/src/lorebase_store/models/tracing.py +0 -0
- {lorebase_store-0.2.3 → lorebase_store-0.3.0}/src/lorebase_store/models/webhooks.py +0 -0
- {lorebase_store-0.2.3 → lorebase_store-0.3.0}/src/lorebase_store/nodestore.py +0 -0
- {lorebase_store-0.2.3 → lorebase_store-0.3.0}/src/lorebase_store/outbox_events.py +0 -0
- {lorebase_store-0.2.3 → lorebase_store-0.3.0}/src/lorebase_store/profiling/delimited.py +0 -0
- {lorebase_store-0.2.3 → lorebase_store-0.3.0}/src/lorebase_store/profiling/postgres.py +0 -0
- {lorebase_store-0.2.3 → lorebase_store-0.3.0}/src/lorebase_store/profiling/record_files.py +0 -0
- {lorebase_store-0.2.3 → lorebase_store-0.3.0}/src/lorebase_store/profiling/records.py +0 -0
- {lorebase_store-0.2.3 → lorebase_store-0.3.0}/src/lorebase_store/ratelimit.py +0 -0
- {lorebase_store-0.2.3 → lorebase_store-0.3.0}/src/lorebase_store/reads.py +0 -0
- {lorebase_store-0.2.3 → lorebase_store-0.3.0}/src/lorebase_store/replicas.py +0 -0
- {lorebase_store-0.2.3 → lorebase_store-0.3.0}/src/lorebase_store/repositories/changesets.py +0 -0
- {lorebase_store-0.2.3 → lorebase_store-0.3.0}/src/lorebase_store/repositories/deferred_events.py +0 -0
- {lorebase_store-0.2.3 → lorebase_store-0.3.0}/src/lorebase_store/repositories/events.py +0 -0
- {lorebase_store-0.2.3 → lorebase_store-0.3.0}/src/lorebase_store/repositories/index_view.py +0 -0
- {lorebase_store-0.2.3 → lorebase_store-0.3.0}/src/lorebase_store/repositories/invocations.py +0 -0
- {lorebase_store-0.2.3 → lorebase_store-0.3.0}/src/lorebase_store/repositories/materializations.py +0 -0
- {lorebase_store-0.2.3 → lorebase_store-0.3.0}/src/lorebase_store/repositories/merges.py +0 -0
- {lorebase_store-0.2.3 → lorebase_store-0.3.0}/src/lorebase_store/repositories/refs.py +0 -0
- {lorebase_store-0.2.3 → lorebase_store-0.3.0}/src/lorebase_store/repositories/revisions.py +0 -0
- {lorebase_store-0.2.3 → lorebase_store-0.3.0}/src/lorebase_store/repositories/scope.py +0 -0
- {lorebase_store-0.2.3 → lorebase_store-0.3.0}/src/lorebase_store/repositories/source_runs.py +0 -0
- {lorebase_store-0.2.3 → lorebase_store-0.3.0}/src/lorebase_store/runtime.py +0 -0
- {lorebase_store-0.2.3 → lorebase_store-0.3.0}/src/lorebase_store/schema_cache.py +0 -0
- {lorebase_store-0.2.3 → lorebase_store-0.3.0}/src/lorebase_store/telemetry.py +0 -0
- {lorebase_store-0.2.3 → lorebase_store-0.3.0}/src/lorebase_store/testing.py +0 -0
- {lorebase_store-0.2.3 → lorebase_store-0.3.0}/src/lorebase_store/tracing.py +0 -0
- {lorebase_store-0.2.3 → lorebase_store-0.3.0}/src/lorebase_store/traverse.py +0 -0
- {lorebase_store-0.2.3 → lorebase_store-0.3.0}/src/lorebase_store/vault/__init__.py +0 -0
- {lorebase_store-0.2.3 → lorebase_store-0.3.0}/src/lorebase_store/vault/keys.py +0 -0
- {lorebase_store-0.2.3 → lorebase_store-0.3.0}/src/lorebase_store/vault/quotes.py +0 -0
- {lorebase_store-0.2.3 → lorebase_store-0.3.0}/src/lorebase_store/vault/rotation.py +0 -0
- {lorebase_store-0.2.3 → lorebase_store-0.3.0}/src/lorebase_store/vault/settings.py +0 -0
- {lorebase_store-0.2.3 → lorebase_store-0.3.0}/src/lorebase_store/vault/store.py +0 -0
- {lorebase_store-0.2.3 → lorebase_store-0.3.0}/src/lorebase_store/vault/transfer.py +0 -0
- {lorebase_store-0.2.3 → lorebase_store-0.3.0}/src/lorebase_store/webhooks/__init__.py +0 -0
- {lorebase_store-0.2.3 → lorebase_store-0.3.0}/src/lorebase_store/webhooks/delivery.py +0 -0
- {lorebase_store-0.2.3 → lorebase_store-0.3.0}/src/lorebase_store/webhooks/keys.py +0 -0
- {lorebase_store-0.2.3 → lorebase_store-0.3.0}/src/lorebase_store/webhooks/repository.py +0 -0
- {lorebase_store-0.2.3 → lorebase_store-0.3.0}/src/lorebase_store/webhooks/settings.py +0 -0
- {lorebase_store-0.2.3 → lorebase_store-0.3.0}/src/lorebase_store/webhooks/signing.py +0 -0
- {lorebase_store-0.2.3 → lorebase_store-0.3.0}/tests/auth/conftest.py +0 -0
- {lorebase_store-0.2.3 → lorebase_store-0.3.0}/tests/auth/test_audit_append_only_integration.py +0 -0
- {lorebase_store-0.2.3 → lorebase_store-0.3.0}/tests/auth/test_audit_integration.py +0 -0
- {lorebase_store-0.2.3 → lorebase_store-0.3.0}/tests/auth/test_audit_streams_integration.py +0 -0
- {lorebase_store-0.2.3 → lorebase_store-0.3.0}/tests/auth/test_principals_api_keys_grants_integration.py +0 -0
- {lorebase_store-0.2.3 → lorebase_store-0.3.0}/tests/auth/test_rls_scope.py +0 -0
- {lorebase_store-0.2.3 → lorebase_store-0.3.0}/tests/auth/test_sessions_integration.py +0 -0
- {lorebase_store-0.2.3 → lorebase_store-0.3.0}/tests/conftest.py +0 -0
- {lorebase_store-0.2.3 → lorebase_store-0.3.0}/tests/read_support.py +0 -0
- {lorebase_store-0.2.3 → lorebase_store-0.3.0}/tests/support.py +0 -0
- {lorebase_store-0.2.3 → lorebase_store-0.3.0}/tests/test_background_branches.py +0 -0
- {lorebase_store-0.2.3 → lorebase_store-0.3.0}/tests/test_background_pace.py +0 -0
- {lorebase_store-0.2.3 → lorebase_store-0.3.0}/tests/test_bm25.py +0 -0
- {lorebase_store-0.2.3 → lorebase_store-0.3.0}/tests/test_bundle_limits.py +0 -0
- {lorebase_store-0.2.3 → lorebase_store-0.3.0}/tests/test_bundle_vault.py +0 -0
- {lorebase_store-0.2.3 → lorebase_store-0.3.0}/tests/test_bundle_vault_values.py +0 -0
- {lorebase_store-0.2.3 → lorebase_store-0.3.0}/tests/test_commit.py +0 -0
- {lorebase_store-0.2.3 → lorebase_store-0.3.0}/tests/test_commit_helpers.py +0 -0
- {lorebase_store-0.2.3 → lorebase_store-0.3.0}/tests/test_derived.py +0 -0
- {lorebase_store-0.2.3 → lorebase_store-0.3.0}/tests/test_endpoint_modes_integration.py +0 -0
- {lorebase_store-0.2.3 → lorebase_store-0.3.0}/tests/test_extraction_items.py +0 -0
- {lorebase_store-0.2.3 → lorebase_store-0.3.0}/tests/test_indexes.py +0 -0
- {lorebase_store-0.2.3 → lorebase_store-0.3.0}/tests/test_jobs_depth.py +0 -0
- {lorebase_store-0.2.3 → lorebase_store-0.3.0}/tests/test_lineage_backfill.py +0 -0
- {lorebase_store-0.2.3 → lorebase_store-0.3.0}/tests/test_materialize.py +0 -0
- {lorebase_store-0.2.3 → lorebase_store-0.3.0}/tests/test_merging.py +0 -0
- {lorebase_store-0.2.3 → lorebase_store-0.3.0}/tests/test_migration_revisions.py +0 -0
- {lorebase_store-0.2.3 → lorebase_store-0.3.0}/tests/test_nodestore.py +0 -0
- {lorebase_store-0.2.3 → lorebase_store-0.3.0}/tests/test_outbox_events.py +0 -0
- {lorebase_store-0.2.3 → lorebase_store-0.3.0}/tests/test_outbox_integration.py +0 -0
- {lorebase_store-0.2.3 → lorebase_store-0.3.0}/tests/test_overlay.py +0 -0
- {lorebase_store-0.2.3 → lorebase_store-0.3.0}/tests/test_profiling.py +0 -0
- {lorebase_store-0.2.3 → lorebase_store-0.3.0}/tests/test_profiling_record_files.py +0 -0
- {lorebase_store-0.2.3 → lorebase_store-0.3.0}/tests/test_profiling_records.py +0 -0
- {lorebase_store-0.2.3 → lorebase_store-0.3.0}/tests/test_ratelimit_buckets_integration.py +0 -0
- {lorebase_store-0.2.3 → lorebase_store-0.3.0}/tests/test_replicas.py +0 -0
- {lorebase_store-0.2.3 → lorebase_store-0.3.0}/tests/test_repositories.py +0 -0
- {lorebase_store-0.2.3 → lorebase_store-0.3.0}/tests/test_schema_integration.py +0 -0
- {lorebase_store-0.2.3 → lorebase_store-0.3.0}/tests/test_search_bm25.py +0 -0
- {lorebase_store-0.2.3 → lorebase_store-0.3.0}/tests/test_sensitivity_refresh.py +0 -0
- {lorebase_store-0.2.3 → lorebase_store-0.3.0}/tests/test_settings.py +0 -0
- {lorebase_store-0.2.3 → lorebase_store-0.3.0}/tests/test_suggestion_repository_integration.py +0 -0
- {lorebase_store-0.2.3 → lorebase_store-0.3.0}/tests/test_traverse.py +0 -0
- {lorebase_store-0.2.3 → lorebase_store-0.3.0}/tests/test_vault.py +0 -0
- {lorebase_store-0.2.3 → lorebase_store-0.3.0}/tests/test_webhook_keys.py +0 -0
- {lorebase_store-0.2.3 → lorebase_store-0.3.0}/tests/test_webhook_keys_integration.py +0 -0
- {lorebase_store-0.2.3 → lorebase_store-0.3.0}/tests/test_webhook_timeouts_integration.py +0 -0
- {lorebase_store-0.2.3 → lorebase_store-0.3.0}/tests/test_webhooks.py +0 -0
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: lorebase-store
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.3.0
|
|
4
4
|
Summary: Lorebase store: persistence, repositories, migrations and materializer
|
|
5
5
|
Requires-Python: >=3.12
|
|
6
6
|
Requires-Dist: alembic>=1.13
|
|
7
7
|
Requires-Dist: asyncpg>=0.29
|
|
8
8
|
Requires-Dist: cryptography>=42
|
|
9
|
-
Requires-Dist: lorebase-core<0.
|
|
10
|
-
Requires-Dist: lorebase-platform<0.
|
|
9
|
+
Requires-Dist: lorebase-core<0.4,>=0.3
|
|
10
|
+
Requires-Dist: lorebase-platform<0.4,>=0.3
|
|
11
11
|
Requires-Dist: opentelemetry-api>=1.25
|
|
12
12
|
Requires-Dist: pgvector>=0.3
|
|
13
13
|
Requires-Dist: sqlalchemy[asyncio]>=2.0.30
|
|
@@ -7,7 +7,8 @@ Besides the tables, this revision installs what model metadata cannot express:
|
|
|
7
7
|
- native enum types, created once and shared by several tables;
|
|
8
8
|
- `store.layout.hash_partitions` hash partitions on `domain_id`;
|
|
9
9
|
- lz4 TOAST compression of `tree_nodes.body`;
|
|
10
|
-
- the HNSW index of `embeddings` for `store.embeddings.dims
|
|
10
|
+
- the HNSW index of `embeddings` for `store.embeddings.dims`, with the build parameters of
|
|
11
|
+
`store.storage` (`hnsw_m`, `hnsw_ef_construction`);
|
|
11
12
|
- row-level security on `domain_id` keyed to the transaction setting `lorebase.domain_ids`
|
|
12
13
|
(comma-separated Domain ids), bypassed when `lorebase.bypass_rls` is `on`.
|
|
13
14
|
|
|
@@ -100,6 +101,21 @@ def _settings() -> tuple[int, int]:
|
|
|
100
101
|
return store.layout.hash_partitions, store.embeddings.dims
|
|
101
102
|
|
|
102
103
|
|
|
104
|
+
def _hnsw_options() -> str:
|
|
105
|
+
"""`WITH (...)` of the HNSW index from `store.storage.hnsw_m` and `hnsw_ef_construction`
|
|
106
|
+
(null: pgvector's default)."""
|
|
107
|
+
storage = op.get_context().config.attributes["store_settings"].storage
|
|
108
|
+
options = [
|
|
109
|
+
f"{name} = {value}"
|
|
110
|
+
for name, value in (
|
|
111
|
+
("m", storage.hnsw_m),
|
|
112
|
+
("ef_construction", storage.hnsw_ef_construction),
|
|
113
|
+
)
|
|
114
|
+
if value is not None
|
|
115
|
+
]
|
|
116
|
+
return f" WITH ({', '.join(options)})" if options else ""
|
|
117
|
+
|
|
118
|
+
|
|
103
119
|
def _create_partitions(partitions: int) -> None:
|
|
104
120
|
for table in HASH_PARTITIONED:
|
|
105
121
|
for remainder in range(partitions):
|
|
@@ -1024,7 +1040,8 @@ def upgrade() -> None:
|
|
|
1024
1040
|
_compress(partitions)
|
|
1025
1041
|
op.execute(
|
|
1026
1042
|
f"CREATE INDEX embeddings_hnsw_{dims} ON embeddings "
|
|
1027
|
-
f"USING hnsw ((embedding::vector({dims})) vector_cosine_ops)
|
|
1043
|
+
f"USING hnsw ((embedding::vector({dims})) vector_cosine_ops){_hnsw_options()} "
|
|
1044
|
+
f"WHERE dims = {dims}"
|
|
1028
1045
|
)
|
|
1029
1046
|
_enable_row_security(partitions)
|
|
1030
1047
|
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
"""Reconciliation candidates and validity periods on provenance (design 10.6).
|
|
2
|
+
|
|
3
|
+
`provenance.valid_from` and `valid_to` hold the validity period of the values of a `valid_time`
|
|
4
|
+
Attribute; existing entries read as "no period" (metadata-only change, no backfill).
|
|
5
|
+
`reconciliation_candidates` holds contradictions between Sources awaiting a decision, one per
|
|
6
|
+
`(domain_id, branch, fingerprint)`. Under the `domain_isolation` policy like every table with
|
|
7
|
+
a `domain_id`.
|
|
8
|
+
|
|
9
|
+
Revision ID: 0038
|
|
10
|
+
Revises: 0037
|
|
11
|
+
Create Date: 2026-10-29 09:00:00.000000
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
from __future__ import annotations
|
|
15
|
+
|
|
16
|
+
from collections.abc import Sequence
|
|
17
|
+
|
|
18
|
+
import sqlalchemy as sa
|
|
19
|
+
from alembic import op
|
|
20
|
+
from sqlalchemy.dialects import postgresql
|
|
21
|
+
|
|
22
|
+
revision: str = "0038"
|
|
23
|
+
down_revision: str | Sequence[str] | None = "0037"
|
|
24
|
+
branch_labels: str | Sequence[str] | None = None
|
|
25
|
+
depends_on: str | Sequence[str] | None = None
|
|
26
|
+
|
|
27
|
+
CANDIDATES = "reconciliation_candidates"
|
|
28
|
+
|
|
29
|
+
DOMAIN_VISIBLE = (
|
|
30
|
+
"current_setting('lorebase.bypass_rls', true) = 'on' "
|
|
31
|
+
"OR domain_id = ANY (string_to_array(current_setting('lorebase.domain_ids', true), ','))"
|
|
32
|
+
)
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def upgrade() -> None:
|
|
36
|
+
for column in ("valid_from", "valid_to"):
|
|
37
|
+
op.add_column("provenance", sa.Column(column, sa.DateTime(timezone=True), nullable=True))
|
|
38
|
+
op.create_table(
|
|
39
|
+
CANDIDATES,
|
|
40
|
+
sa.Column("domain_id", sa.Text(), nullable=False),
|
|
41
|
+
sa.Column("id", sa.Text(), nullable=False),
|
|
42
|
+
sa.Column("branch", sa.Text(), nullable=False),
|
|
43
|
+
sa.Column("type", sa.Text(), nullable=False),
|
|
44
|
+
sa.Column("status", sa.Text(), server_default=sa.text("'open'"), nullable=False),
|
|
45
|
+
sa.Column("fingerprint", sa.LargeBinary(), nullable=False),
|
|
46
|
+
sa.Column("kind_id", sa.Text(), nullable=False),
|
|
47
|
+
sa.Column("keys", postgresql.JSONB(astext_type=sa.Text()), nullable=False),
|
|
48
|
+
sa.Column("attribute_id", sa.Text(), nullable=True),
|
|
49
|
+
sa.Column(
|
|
50
|
+
"claims",
|
|
51
|
+
postgresql.JSONB(astext_type=sa.Text()),
|
|
52
|
+
server_default=sa.text("'[]'::jsonb"),
|
|
53
|
+
nullable=False,
|
|
54
|
+
),
|
|
55
|
+
sa.Column(
|
|
56
|
+
"signals",
|
|
57
|
+
postgresql.JSONB(astext_type=sa.Text()),
|
|
58
|
+
server_default=sa.text("'{}'::jsonb"),
|
|
59
|
+
nullable=False,
|
|
60
|
+
),
|
|
61
|
+
sa.Column("claim_count", sa.Integer(), server_default=sa.text("0"), nullable=False),
|
|
62
|
+
sa.Column("proposal_id", sa.Text(), nullable=True),
|
|
63
|
+
sa.Column("decision", postgresql.JSONB(astext_type=sa.Text()), nullable=True),
|
|
64
|
+
sa.Column("decided_by", sa.Text(), nullable=True),
|
|
65
|
+
sa.Column("decided_at", sa.DateTime(timezone=True), nullable=True),
|
|
66
|
+
sa.Column(
|
|
67
|
+
"created_at",
|
|
68
|
+
sa.DateTime(timezone=True),
|
|
69
|
+
server_default=sa.text("now()"),
|
|
70
|
+
nullable=False,
|
|
71
|
+
),
|
|
72
|
+
sa.Column(
|
|
73
|
+
"updated_at",
|
|
74
|
+
sa.DateTime(timezone=True),
|
|
75
|
+
server_default=sa.text("now()"),
|
|
76
|
+
nullable=False,
|
|
77
|
+
),
|
|
78
|
+
sa.CheckConstraint(
|
|
79
|
+
"octet_length(fingerprint) = 32",
|
|
80
|
+
name=op.f("ck_reconciliation_candidates_fingerprint_h32"),
|
|
81
|
+
),
|
|
82
|
+
sa.CheckConstraint(
|
|
83
|
+
"type IN ('duplicate', 'contradiction', 'superseded', 'stale')",
|
|
84
|
+
name=op.f("ck_reconciliation_candidates_type"),
|
|
85
|
+
),
|
|
86
|
+
sa.CheckConstraint(
|
|
87
|
+
"status IN ('open', 'resolved', 'dismissed', 'obsolete')",
|
|
88
|
+
name=op.f("ck_reconciliation_candidates_status"),
|
|
89
|
+
),
|
|
90
|
+
sa.ForeignKeyConstraint(
|
|
91
|
+
["domain_id"],
|
|
92
|
+
["domains.id"],
|
|
93
|
+
name=op.f("fk_reconciliation_candidates_domain_id_domains"),
|
|
94
|
+
ondelete="CASCADE",
|
|
95
|
+
),
|
|
96
|
+
sa.PrimaryKeyConstraint("domain_id", "id", name=op.f("pk_reconciliation_candidates")),
|
|
97
|
+
sa.UniqueConstraint(
|
|
98
|
+
"domain_id",
|
|
99
|
+
"branch",
|
|
100
|
+
"fingerprint",
|
|
101
|
+
name=op.f("uq_reconciliation_candidates_domain_id_branch_fingerprint"),
|
|
102
|
+
),
|
|
103
|
+
)
|
|
104
|
+
op.create_index(
|
|
105
|
+
op.f("ix_reconciliation_candidates_domain_id_branch_status_type"),
|
|
106
|
+
CANDIDATES,
|
|
107
|
+
["domain_id", "branch", "status", "type"],
|
|
108
|
+
)
|
|
109
|
+
op.create_index(
|
|
110
|
+
op.f("ix_reconciliation_candidates_domain_id_branch_kind_id_keys"),
|
|
111
|
+
CANDIDATES,
|
|
112
|
+
["domain_id", "branch", "kind_id", "keys"],
|
|
113
|
+
)
|
|
114
|
+
op.execute(f"ALTER TABLE {CANDIDATES} ENABLE ROW LEVEL SECURITY")
|
|
115
|
+
op.execute(f"ALTER TABLE {CANDIDATES} FORCE ROW LEVEL SECURITY")
|
|
116
|
+
op.execute(
|
|
117
|
+
f"CREATE POLICY domain_isolation ON {CANDIDATES} "
|
|
118
|
+
f"USING ({DOMAIN_VISIBLE}) WITH CHECK ({DOMAIN_VISIBLE})"
|
|
119
|
+
)
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
def downgrade() -> None:
|
|
123
|
+
op.drop_index(
|
|
124
|
+
op.f("ix_reconciliation_candidates_domain_id_branch_kind_id_keys"), table_name=CANDIDATES
|
|
125
|
+
)
|
|
126
|
+
op.drop_index(
|
|
127
|
+
op.f("ix_reconciliation_candidates_domain_id_branch_status_type"), table_name=CANDIDATES
|
|
128
|
+
)
|
|
129
|
+
op.drop_table(CANDIDATES)
|
|
130
|
+
for column in ("valid_to", "valid_from"):
|
|
131
|
+
op.drop_column("provenance", column)
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
"""Indexes of reconciliation candidate lookups (design 10.6).
|
|
2
|
+
|
|
3
|
+
`(domain_id, proposal_id)` serves the `reconciliation` Proposal check and the settlement of a
|
|
4
|
+
merged Proposal; a GIN index (`jsonb_path_ops`) on `keys` serves the containment test of the
|
|
5
|
+
Record page (`keys @> '["key"]'`), which the btree index on `(domain_id, branch, kind_id,
|
|
6
|
+
keys)` cannot answer.
|
|
7
|
+
|
|
8
|
+
Revision ID: 0039
|
|
9
|
+
Revises: 0038
|
|
10
|
+
Create Date: 2026-10-29 18:00:00.000000
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
from __future__ import annotations
|
|
14
|
+
|
|
15
|
+
from collections.abc import Sequence
|
|
16
|
+
|
|
17
|
+
from alembic import op
|
|
18
|
+
|
|
19
|
+
revision: str = "0039"
|
|
20
|
+
down_revision: str | Sequence[str] | None = "0038"
|
|
21
|
+
branch_labels: str | Sequence[str] | None = None
|
|
22
|
+
depends_on: str | Sequence[str] | None = None
|
|
23
|
+
|
|
24
|
+
CANDIDATES = "reconciliation_candidates"
|
|
25
|
+
BY_PROPOSAL = "ix_reconciliation_candidates_domain_id_proposal_id"
|
|
26
|
+
BY_KEY = "ix_reconciliation_candidates_keys"
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def upgrade() -> None:
|
|
30
|
+
op.create_index(BY_PROPOSAL, CANDIDATES, ["domain_id", "proposal_id"])
|
|
31
|
+
op.create_index(
|
|
32
|
+
BY_KEY,
|
|
33
|
+
CANDIDATES,
|
|
34
|
+
["keys"],
|
|
35
|
+
postgresql_using="gin",
|
|
36
|
+
postgresql_ops={"keys": "jsonb_path_ops"},
|
|
37
|
+
)
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def downgrade() -> None:
|
|
41
|
+
op.drop_index(BY_KEY, table_name=CANDIDATES)
|
|
42
|
+
op.drop_index(BY_PROPOSAL, table_name=CANDIDATES)
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
"""Last-written state of Sources (design 9.1 "Corrections that stick").
|
|
2
|
+
|
|
3
|
+
`source_written` holds, per Source, target branch, Kind or Relation and key, the hashes of the
|
|
4
|
+
values the Source last wrote and the Source branch Revision that holds them;
|
|
5
|
+
`source_written_pending` the state a run carries in its open Proposal, moved to
|
|
6
|
+
`source_written` when the Proposal merges. Both are empty at first: Sources seed their state
|
|
7
|
+
on their next run. Under the `domain_isolation` policy like every table with a `domain_id`.
|
|
8
|
+
|
|
9
|
+
Revision ID: 0040
|
|
10
|
+
Revises: 0039
|
|
11
|
+
Create Date: 2026-10-29 20:00:00.000000
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
from __future__ import annotations
|
|
15
|
+
|
|
16
|
+
from collections.abc import Sequence
|
|
17
|
+
|
|
18
|
+
import sqlalchemy as sa
|
|
19
|
+
from alembic import op
|
|
20
|
+
from sqlalchemy.dialects import postgresql
|
|
21
|
+
|
|
22
|
+
revision: str = "0040"
|
|
23
|
+
down_revision: str | Sequence[str] | None = "0039"
|
|
24
|
+
branch_labels: str | Sequence[str] | None = None
|
|
25
|
+
depends_on: str | Sequence[str] | None = None
|
|
26
|
+
|
|
27
|
+
WRITTEN = "source_written"
|
|
28
|
+
PENDING = "source_written_pending"
|
|
29
|
+
|
|
30
|
+
DOMAIN_VISIBLE = (
|
|
31
|
+
"current_setting('lorebase.bypass_rls', true) = 'on' "
|
|
32
|
+
"OR domain_id = ANY (string_to_array(current_setting('lorebase.domain_ids', true), ','))"
|
|
33
|
+
)
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def _state_columns() -> list[sa.Column[object]]:
|
|
37
|
+
return [
|
|
38
|
+
sa.Column(
|
|
39
|
+
"written",
|
|
40
|
+
postgresql.JSONB(astext_type=sa.Text()),
|
|
41
|
+
server_default=sa.text("'{}'::jsonb"),
|
|
42
|
+
nullable=False,
|
|
43
|
+
),
|
|
44
|
+
sa.Column("revision_id", sa.LargeBinary(), nullable=True),
|
|
45
|
+
sa.Column(
|
|
46
|
+
"value_revisions",
|
|
47
|
+
postgresql.JSONB(astext_type=sa.Text()),
|
|
48
|
+
server_default=sa.text("'{}'::jsonb"),
|
|
49
|
+
nullable=False,
|
|
50
|
+
),
|
|
51
|
+
sa.Column("deleted", sa.Boolean(), server_default=sa.text("false"), nullable=False),
|
|
52
|
+
]
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def _tail_columns() -> list[sa.Column[object]]:
|
|
56
|
+
return [
|
|
57
|
+
sa.Column("run_id", sa.Text(), nullable=False),
|
|
58
|
+
sa.Column(
|
|
59
|
+
"updated_at",
|
|
60
|
+
sa.DateTime(timezone=True),
|
|
61
|
+
server_default=sa.text("now()"),
|
|
62
|
+
nullable=False,
|
|
63
|
+
),
|
|
64
|
+
]
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def _isolate(table: str) -> None:
|
|
68
|
+
op.execute(f"ALTER TABLE {table} ENABLE ROW LEVEL SECURITY")
|
|
69
|
+
op.execute(f"ALTER TABLE {table} FORCE ROW LEVEL SECURITY")
|
|
70
|
+
op.execute(
|
|
71
|
+
f"CREATE POLICY domain_isolation ON {table} "
|
|
72
|
+
f"USING ({DOMAIN_VISIBLE}) WITH CHECK ({DOMAIN_VISIBLE})"
|
|
73
|
+
)
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
def upgrade() -> None:
|
|
77
|
+
op.create_table(
|
|
78
|
+
WRITTEN,
|
|
79
|
+
sa.Column("domain_id", sa.Text(), nullable=False),
|
|
80
|
+
sa.Column("source_id", sa.Text(), nullable=False),
|
|
81
|
+
sa.Column("target_branch", sa.Text(), nullable=False),
|
|
82
|
+
sa.Column("element", sa.Text(), nullable=False),
|
|
83
|
+
sa.Column("key", sa.Text(), nullable=False),
|
|
84
|
+
*_state_columns(),
|
|
85
|
+
*_tail_columns(),
|
|
86
|
+
sa.CheckConstraint(
|
|
87
|
+
"octet_length(revision_id) = 32", name=op.f("ck_source_written_revision_id_h32")
|
|
88
|
+
),
|
|
89
|
+
sa.ForeignKeyConstraint(
|
|
90
|
+
["domain_id"],
|
|
91
|
+
["domains.id"],
|
|
92
|
+
name=op.f("fk_source_written_domain_id_domains"),
|
|
93
|
+
ondelete="CASCADE",
|
|
94
|
+
),
|
|
95
|
+
sa.PrimaryKeyConstraint(
|
|
96
|
+
"domain_id",
|
|
97
|
+
"source_id",
|
|
98
|
+
"target_branch",
|
|
99
|
+
"element",
|
|
100
|
+
"key",
|
|
101
|
+
name=op.f("pk_source_written"),
|
|
102
|
+
),
|
|
103
|
+
)
|
|
104
|
+
op.create_table(
|
|
105
|
+
PENDING,
|
|
106
|
+
sa.Column("domain_id", sa.Text(), nullable=False),
|
|
107
|
+
sa.Column("proposal_id", sa.Text(), nullable=False),
|
|
108
|
+
sa.Column("element", sa.Text(), nullable=False),
|
|
109
|
+
sa.Column("key", sa.Text(), nullable=False),
|
|
110
|
+
sa.Column("source_id", sa.Text(), nullable=False),
|
|
111
|
+
sa.Column("target_branch", sa.Text(), nullable=False),
|
|
112
|
+
*_state_columns(),
|
|
113
|
+
sa.Column(
|
|
114
|
+
"preserve",
|
|
115
|
+
postgresql.JSONB(astext_type=sa.Text()),
|
|
116
|
+
server_default=sa.text("'[]'::jsonb"),
|
|
117
|
+
nullable=False,
|
|
118
|
+
),
|
|
119
|
+
*_tail_columns(),
|
|
120
|
+
sa.CheckConstraint(
|
|
121
|
+
"octet_length(revision_id) = 32",
|
|
122
|
+
name=op.f("ck_source_written_pending_revision_id_h32"),
|
|
123
|
+
),
|
|
124
|
+
sa.ForeignKeyConstraint(
|
|
125
|
+
["domain_id"],
|
|
126
|
+
["domains.id"],
|
|
127
|
+
name=op.f("fk_source_written_pending_domain_id_domains"),
|
|
128
|
+
ondelete="CASCADE",
|
|
129
|
+
),
|
|
130
|
+
sa.PrimaryKeyConstraint(
|
|
131
|
+
"domain_id", "proposal_id", "element", "key", name=op.f("pk_source_written_pending")
|
|
132
|
+
),
|
|
133
|
+
)
|
|
134
|
+
op.create_index(
|
|
135
|
+
op.f("ix_source_written_pending_domain_id_source_id_target_branch"),
|
|
136
|
+
PENDING,
|
|
137
|
+
["domain_id", "source_id", "target_branch"],
|
|
138
|
+
)
|
|
139
|
+
_isolate(WRITTEN)
|
|
140
|
+
_isolate(PENDING)
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
def downgrade() -> None:
|
|
144
|
+
op.drop_index(
|
|
145
|
+
op.f("ix_source_written_pending_domain_id_source_id_target_branch"), table_name=PENDING
|
|
146
|
+
)
|
|
147
|
+
op.drop_table(PENDING)
|
|
148
|
+
op.drop_table(WRITTEN)
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
"""Document versions and lifecycle (design 9.2 "Document versions and lifecycle", AC-35).
|
|
2
|
+
|
|
3
|
+
`documents` gains `source_key` (a stable identity: path, URL or external id), `version`,
|
|
4
|
+
`replaces` (the version a new one replaced), `status` (`active`, `superseded`, `retired`,
|
|
5
|
+
`purged`) and who retired or purged it when. Existing rows read as keyless, version 1,
|
|
6
|
+
`active`: the columns are added with defaults, no row is rewritten beyond that.
|
|
7
|
+
|
|
8
|
+
Indexes: the unique `(domain_id, sha256)` index becomes partial (keyless rows that are not
|
|
9
|
+
purged); keyed rows are unique by `(domain_id, source_key, version)`, at most one version of a
|
|
10
|
+
key is `active` (so the bytes of the current version are unique per key), and
|
|
11
|
+
`(domain_id, source_key, sha256)` finds a version by its bytes.
|
|
12
|
+
|
|
13
|
+
`extraction_cache` gains `chunk_hash`, the chunk an answer was computed from, so that purging a
|
|
14
|
+
document drops the cached answers of its chunks. Answers cached before keep it null.
|
|
15
|
+
|
|
16
|
+
Revision ID: 0041
|
|
17
|
+
Revises: 0040
|
|
18
|
+
Create Date: 2026-10-30 09:00:00.000000
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
from __future__ import annotations
|
|
22
|
+
|
|
23
|
+
from collections.abc import Sequence
|
|
24
|
+
|
|
25
|
+
import sqlalchemy as sa
|
|
26
|
+
from alembic import op
|
|
27
|
+
|
|
28
|
+
revision: str = "0041"
|
|
29
|
+
down_revision: str | Sequence[str] | None = "0040"
|
|
30
|
+
branch_labels: str | Sequence[str] | None = None
|
|
31
|
+
depends_on: str | Sequence[str] | None = None
|
|
32
|
+
|
|
33
|
+
TABLE = "documents"
|
|
34
|
+
SHA = "ix_documents_domain_id_sha256"
|
|
35
|
+
KEY_VERSION = "ix_documents_domain_id_source_key_version"
|
|
36
|
+
KEY_ACTIVE = "ix_documents_domain_id_source_key_active"
|
|
37
|
+
KEY_SHA = "ix_documents_domain_id_source_key_sha256"
|
|
38
|
+
CACHE = "extraction_cache"
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def upgrade() -> None:
|
|
42
|
+
op.add_column(TABLE, sa.Column("source_key", sa.Text(), nullable=True))
|
|
43
|
+
op.add_column(
|
|
44
|
+
TABLE, sa.Column("version", sa.Integer(), server_default=sa.text("1"), nullable=False)
|
|
45
|
+
)
|
|
46
|
+
op.add_column(TABLE, sa.Column("replaces", sa.Text(), nullable=True))
|
|
47
|
+
op.add_column(
|
|
48
|
+
TABLE, sa.Column("status", sa.Text(), server_default=sa.text("'active'"), nullable=False)
|
|
49
|
+
)
|
|
50
|
+
for name in ("retired_at", "purged_at"):
|
|
51
|
+
op.add_column(TABLE, sa.Column(name, sa.DateTime(timezone=True), nullable=True))
|
|
52
|
+
for name in ("retired_by", "purged_by"):
|
|
53
|
+
op.add_column(TABLE, sa.Column(name, sa.Text(), nullable=True))
|
|
54
|
+
op.create_check_constraint(
|
|
55
|
+
op.f("ck_documents_status"),
|
|
56
|
+
TABLE,
|
|
57
|
+
"status IN ('active', 'superseded', 'retired', 'purged')",
|
|
58
|
+
)
|
|
59
|
+
op.create_check_constraint(op.f("ck_documents_version"), TABLE, "version >= 1")
|
|
60
|
+
op.drop_index(op.f(SHA), table_name=TABLE)
|
|
61
|
+
op.create_index(
|
|
62
|
+
op.f(SHA),
|
|
63
|
+
TABLE,
|
|
64
|
+
["domain_id", "sha256"],
|
|
65
|
+
unique=True,
|
|
66
|
+
postgresql_where=sa.text("source_key IS NULL AND status <> 'purged'"),
|
|
67
|
+
)
|
|
68
|
+
op.create_index(
|
|
69
|
+
op.f(KEY_VERSION),
|
|
70
|
+
TABLE,
|
|
71
|
+
["domain_id", "source_key", "version"],
|
|
72
|
+
unique=True,
|
|
73
|
+
postgresql_where=sa.text("source_key IS NOT NULL"),
|
|
74
|
+
)
|
|
75
|
+
op.create_index(
|
|
76
|
+
op.f(KEY_ACTIVE),
|
|
77
|
+
TABLE,
|
|
78
|
+
["domain_id", "source_key"],
|
|
79
|
+
unique=True,
|
|
80
|
+
postgresql_where=sa.text("source_key IS NOT NULL AND status = 'active'"),
|
|
81
|
+
)
|
|
82
|
+
op.create_index(
|
|
83
|
+
op.f(KEY_SHA),
|
|
84
|
+
TABLE,
|
|
85
|
+
["domain_id", "source_key", "sha256"],
|
|
86
|
+
postgresql_where=sa.text("source_key IS NOT NULL"),
|
|
87
|
+
)
|
|
88
|
+
op.add_column(CACHE, sa.Column("chunk_hash", sa.LargeBinary(), nullable=True))
|
|
89
|
+
op.create_check_constraint(
|
|
90
|
+
op.f("ck_extraction_cache_chunk_hash_h32"), CACHE, "octet_length(chunk_hash) = 32"
|
|
91
|
+
)
|
|
92
|
+
op.create_index(
|
|
93
|
+
op.f("ix_extraction_cache_chunk_hash"),
|
|
94
|
+
CACHE,
|
|
95
|
+
["chunk_hash"],
|
|
96
|
+
postgresql_where=sa.text("chunk_hash IS NOT NULL"),
|
|
97
|
+
)
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
def downgrade() -> None:
|
|
101
|
+
op.drop_index(op.f("ix_extraction_cache_chunk_hash"), table_name=CACHE)
|
|
102
|
+
op.drop_constraint(op.f("ck_extraction_cache_chunk_hash_h32"), CACHE, type_="check")
|
|
103
|
+
op.drop_column(CACHE, "chunk_hash")
|
|
104
|
+
op.drop_index(op.f(KEY_SHA), table_name=TABLE)
|
|
105
|
+
op.drop_index(op.f(KEY_ACTIVE), table_name=TABLE)
|
|
106
|
+
op.drop_index(op.f(KEY_VERSION), table_name=TABLE)
|
|
107
|
+
op.drop_index(op.f(SHA), table_name=TABLE)
|
|
108
|
+
# Keyed versions and purged rows may share bytes with other rows; nothing is deleted here:
|
|
109
|
+
# the downgrade stops instead, and the operator decides which rows to keep.
|
|
110
|
+
op.execute(
|
|
111
|
+
"DO $$ BEGIN IF EXISTS (SELECT 1 FROM documents GROUP BY domain_id, sha256 "
|
|
112
|
+
"HAVING count(*) > 1) THEN RAISE EXCEPTION 'documents share bytes (versions or purged "
|
|
113
|
+
"rows): remove the extra rows before downgrading below 0041'; END IF; END $$"
|
|
114
|
+
)
|
|
115
|
+
op.create_index(op.f(SHA), TABLE, ["domain_id", "sha256"], unique=True)
|
|
116
|
+
op.drop_constraint(op.f("ck_documents_version"), TABLE, type_="check")
|
|
117
|
+
op.drop_constraint(op.f("ck_documents_status"), TABLE, type_="check")
|
|
118
|
+
for name in ("purged_by", "retired_by", "purged_at", "retired_at"):
|
|
119
|
+
op.drop_column(TABLE, name)
|
|
120
|
+
for name in ("status", "replaces", "version", "source_key"):
|
|
121
|
+
op.drop_column(TABLE, name)
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
"""The Kinds a Proposal touches (design 10.7, stewards).
|
|
2
|
+
|
|
3
|
+
`proposals.kinds` lists the ids of the Kinds whose Records or Links (either end) the
|
|
4
|
+
Proposal's changes touch, refreshed with each merge attempt, so the steward roles of a
|
|
5
|
+
Proposal are an index lookup. Existing rows read null ("not computed yet") until their next
|
|
6
|
+
merge preview; a GIN index serves the containment test.
|
|
7
|
+
|
|
8
|
+
Revision ID: 0042
|
|
9
|
+
Revises: 0041
|
|
10
|
+
Create Date: 2026-10-31 09:00:00.000000
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
from __future__ import annotations
|
|
14
|
+
|
|
15
|
+
from collections.abc import Sequence
|
|
16
|
+
|
|
17
|
+
import sqlalchemy as sa
|
|
18
|
+
from alembic import op
|
|
19
|
+
from sqlalchemy.dialects import postgresql
|
|
20
|
+
|
|
21
|
+
revision: str = "0042"
|
|
22
|
+
down_revision: str | Sequence[str] | None = "0041"
|
|
23
|
+
branch_labels: str | Sequence[str] | None = None
|
|
24
|
+
depends_on: str | Sequence[str] | None = None
|
|
25
|
+
|
|
26
|
+
TABLE = "proposals"
|
|
27
|
+
INDEX = "ix_proposals_kinds"
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def upgrade() -> None:
|
|
31
|
+
op.add_column(TABLE, sa.Column("kinds", postgresql.ARRAY(sa.Text()), nullable=True))
|
|
32
|
+
op.create_index(INDEX, TABLE, ["kinds"], postgresql_using="gin")
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def downgrade() -> None:
|
|
36
|
+
op.drop_index(INDEX, table_name=TABLE)
|
|
37
|
+
op.drop_column(TABLE, "kinds")
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
"""When a Source last saw each element (design 9.1 "Incremental Sources").
|
|
2
|
+
|
|
3
|
+
`source_written.last_seen_run` and `last_seen_at` name the run of an `incremental` Source that
|
|
4
|
+
last read the element upstream, changed or not, and when. The reconciliation sweep raises a
|
|
5
|
+
`stale` candidate (`source_unseen`) for Records no run has seen for longer than the Source's
|
|
6
|
+
`stale_after`. Both are null on existing rows; readers fall back to `updated_at` (the last
|
|
7
|
+
state advance) until the next run sees the element. Additive: no data is rewritten.
|
|
8
|
+
|
|
9
|
+
Revision ID: 0043
|
|
10
|
+
Revises: 0042
|
|
11
|
+
Create Date: 2026-10-31 12:00:00.000000
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
from __future__ import annotations
|
|
15
|
+
|
|
16
|
+
from collections.abc import Sequence
|
|
17
|
+
|
|
18
|
+
import sqlalchemy as sa
|
|
19
|
+
from alembic import op
|
|
20
|
+
|
|
21
|
+
revision: str = "0043"
|
|
22
|
+
down_revision: str | Sequence[str] | None = "0042"
|
|
23
|
+
branch_labels: str | Sequence[str] | None = None
|
|
24
|
+
depends_on: str | Sequence[str] | None = None
|
|
25
|
+
|
|
26
|
+
TABLE = "source_written"
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def upgrade() -> None:
|
|
30
|
+
op.add_column(TABLE, sa.Column("last_seen_run", sa.Text(), nullable=True))
|
|
31
|
+
op.add_column(TABLE, sa.Column("last_seen_at", sa.DateTime(timezone=True), nullable=True))
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def downgrade() -> None:
|
|
35
|
+
op.drop_column(TABLE, "last_seen_at")
|
|
36
|
+
op.drop_column(TABLE, "last_seen_run")
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
"""Index `mat_records` on `(domain_id, branch, object_hash)` for semantic search.
|
|
2
|
+
|
|
3
|
+
`embeddings` joins `mat_records` on `(domain_id, object_hash)` (design 8.8, filtered semantic
|
|
4
|
+
search). Without an index on that side of the join the planner cannot drive the HNSW scan lazily
|
|
5
|
+
under a nested loop: it hash-joins every vector of the model with the Records and sorts, a cost
|
|
6
|
+
linear in the table (82 ms at 100,000 vectors, 270 ms at 300,000). With it the HNSW index is read
|
|
7
|
+
and the Records are probed per candidate.
|
|
8
|
+
|
|
9
|
+
The index is built per partition with `CREATE INDEX CONCURRENTLY` (writes are not blocked),
|
|
10
|
+
attached to an index created `ON ONLY` the parent; an invalid leftover of an interrupted run is
|
|
11
|
+
rebuilt, as in revisions 0011 and 0026. Re-running changes nothing.
|
|
12
|
+
|
|
13
|
+
Revision ID: 0044
|
|
14
|
+
Revises: 0043
|
|
15
|
+
Create Date: 2026-10-31 15:00:00.000000
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
from __future__ import annotations
|
|
19
|
+
|
|
20
|
+
from collections.abc import Sequence
|
|
21
|
+
|
|
22
|
+
import sqlalchemy as sa
|
|
23
|
+
from alembic import op
|
|
24
|
+
|
|
25
|
+
revision: str = "0044"
|
|
26
|
+
down_revision: str | Sequence[str] | None = "0043"
|
|
27
|
+
branch_labels: str | Sequence[str] | None = None
|
|
28
|
+
depends_on: str | Sequence[str] | None = None
|
|
29
|
+
|
|
30
|
+
PARENT = "mat_records"
|
|
31
|
+
INDEX = "ix_mat_records_object_hash"
|
|
32
|
+
DEFINITION = "(domain_id, branch, object_hash)"
|
|
33
|
+
SUFFIX = "domain_id_branch_object_hash_idx"
|
|
34
|
+
"""Partition indexes are named `<partition>_<suffix>`."""
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def _partitions() -> list[str]:
|
|
38
|
+
found = op.get_bind().execute(
|
|
39
|
+
sa.text(
|
|
40
|
+
"SELECT c.relname FROM pg_inherits i JOIN pg_class c ON c.oid = i.inhrelid "
|
|
41
|
+
"WHERE i.inhparent = CAST(:parent AS regclass) ORDER BY c.relname"
|
|
42
|
+
),
|
|
43
|
+
{"parent": PARENT},
|
|
44
|
+
)
|
|
45
|
+
return list(found.scalars())
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def _state(name: str) -> tuple[bool, bool] | None:
|
|
49
|
+
"""`(valid, attached)` of an index, `None` when it does not exist."""
|
|
50
|
+
row = (
|
|
51
|
+
op.get_bind()
|
|
52
|
+
.execute(
|
|
53
|
+
sa.text(
|
|
54
|
+
"SELECT x.indisvalid, EXISTS (SELECT 1 FROM pg_inherits i "
|
|
55
|
+
"WHERE i.inhrelid = c.oid) FROM pg_class c JOIN pg_index x "
|
|
56
|
+
"ON x.indexrelid = c.oid WHERE c.relname = :name "
|
|
57
|
+
"AND c.relnamespace = (SELECT relnamespace FROM pg_class "
|
|
58
|
+
"WHERE oid = CAST(:parent AS regclass))"
|
|
59
|
+
),
|
|
60
|
+
{"name": name, "parent": PARENT},
|
|
61
|
+
)
|
|
62
|
+
.first()
|
|
63
|
+
)
|
|
64
|
+
return None if row is None else (bool(row[0]), bool(row[1]))
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def upgrade() -> None:
|
|
68
|
+
op.execute(f"CREATE INDEX IF NOT EXISTS {INDEX} ON ONLY {PARENT} {DEFINITION}")
|
|
69
|
+
with op.get_context().autocommit_block():
|
|
70
|
+
for partition in _partitions():
|
|
71
|
+
name = f"{partition}_{SUFFIX}"
|
|
72
|
+
state = _state(name)
|
|
73
|
+
if state is not None and not state[0]:
|
|
74
|
+
op.execute(f"DROP INDEX CONCURRENTLY IF EXISTS {name}")
|
|
75
|
+
state = None
|
|
76
|
+
if state is None:
|
|
77
|
+
op.execute(f"CREATE INDEX CONCURRENTLY {name} ON {partition} {DEFINITION}")
|
|
78
|
+
if state is None or not state[1]:
|
|
79
|
+
op.execute(f"ALTER INDEX {INDEX} ATTACH PARTITION {name}")
|
|
80
|
+
if _state(INDEX) != (True, False):
|
|
81
|
+
raise RuntimeError(f"index {INDEX} is not valid after attaching every partition")
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
def downgrade() -> None:
|
|
85
|
+
op.execute(f"DROP INDEX IF EXISTS {INDEX}")
|