memstrata-conversational 0.6.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.
- memstrata_conversational-0.6.1/LICENSE-COMMERCIAL +26 -0
- memstrata_conversational-0.6.1/PKG-INFO +80 -0
- memstrata_conversational-0.6.1/billing/__init__.py +18 -0
- memstrata_conversational-0.6.1/billing/credit.py +109 -0
- memstrata_conversational-0.6.1/billing/email_notify.py +282 -0
- memstrata_conversational-0.6.1/billing/reconcile.py +120 -0
- memstrata_conversational-0.6.1/billing/webhook.py +345 -0
- memstrata_conversational-0.6.1/harness/__init__.py +8 -0
- memstrata_conversational-0.6.1/harness/__main__.py +7 -0
- memstrata_conversational-0.6.1/harness/cli.py +1161 -0
- memstrata_conversational-0.6.1/harness/config.py +199 -0
- memstrata_conversational-0.6.1/harness/counterfactual.py +340 -0
- memstrata_conversational-0.6.1/harness/degrade.py +63 -0
- memstrata_conversational-0.6.1/harness/emission.py +35 -0
- memstrata_conversational-0.6.1/harness/injection.py +277 -0
- memstrata_conversational-0.6.1/harness/memory_layer_client.py +237 -0
- memstrata_conversational-0.6.1/harness/providers/__init__.py +1 -0
- memstrata_conversational-0.6.1/harness/providers/anthropic.py +237 -0
- memstrata_conversational-0.6.1/harness/providers/base.py +65 -0
- memstrata_conversational-0.6.1/harness/providers/gemini.py +296 -0
- memstrata_conversational-0.6.1/harness/providers/ollama.py +230 -0
- memstrata_conversational-0.6.1/harness/providers/openai.py +159 -0
- memstrata_conversational-0.6.1/harness/server.py +2028 -0
- memstrata_conversational-0.6.1/harness/service/__init__.py +27 -0
- memstrata_conversational-0.6.1/harness/service/base.py +81 -0
- memstrata_conversational-0.6.1/harness/service/launchd.py +108 -0
- memstrata_conversational-0.6.1/harness/service/systemd.py +126 -0
- memstrata_conversational-0.6.1/harness/service/windows.py +188 -0
- memstrata_conversational-0.6.1/harness/streaming.py +137 -0
- memstrata_conversational-0.6.1/harness/telemetry.py +207 -0
- memstrata_conversational-0.6.1/harness/tokenizers/__init__.py +1 -0
- memstrata_conversational-0.6.1/harness/tokenizers/anthropic_counter.py +50 -0
- memstrata_conversational-0.6.1/harness/tokenizers/estimate_counter.py +54 -0
- memstrata_conversational-0.6.1/harness/tokenizers/tiktoken_counter.py +89 -0
- memstrata_conversational-0.6.1/memory_layer_pro/__init__.py +16 -0
- memstrata_conversational-0.6.1/memory_layer_pro/__main__.py +23 -0
- memstrata_conversational-0.6.1/memory_layer_pro/_paths.py +155 -0
- memstrata_conversational-0.6.1/memory_layer_pro/api_overlay.py +1235 -0
- memstrata_conversational-0.6.1/memory_layer_pro/baseline/__init__.py +0 -0
- memstrata_conversational-0.6.1/memory_layer_pro/baseline/cohort.py +211 -0
- memstrata_conversational-0.6.1/memory_layer_pro/closed_form_api.py +26 -0
- memstrata_conversational-0.6.1/memory_layer_pro/coder_product.py +96 -0
- memstrata_conversational-0.6.1/memory_layer_pro/dashboard_accuracy_tab.py +291 -0
- memstrata_conversational-0.6.1/memory_layer_pro/doctor.py +440 -0
- memstrata_conversational-0.6.1/memory_layer_pro/editor_config.py +179 -0
- memstrata_conversational-0.6.1/memory_layer_pro/env_setup.py +401 -0
- memstrata_conversational-0.6.1/memory_layer_pro/feature_gate.py +78 -0
- memstrata_conversational-0.6.1/memory_layer_pro/governance_outbox.py +653 -0
- memstrata_conversational-0.6.1/memory_layer_pro/hardware.py +328 -0
- memstrata_conversational-0.6.1/memory_layer_pro/identity.py +128 -0
- memstrata_conversational-0.6.1/memory_layer_pro/ingest/__init__.py +51 -0
- memstrata_conversational-0.6.1/memory_layer_pro/ingest/schema_v0.py +107 -0
- memstrata_conversational-0.6.1/memory_layer_pro/ingest/transport.py +561 -0
- memstrata_conversational-0.6.1/memory_layer_pro/integrations/__init__.py +3 -0
- memstrata_conversational-0.6.1/memory_layer_pro/integrations/langchain.py +173 -0
- memstrata_conversational-0.6.1/memory_layer_pro/kernel.py +663 -0
- memstrata_conversational-0.6.1/memory_layer_pro/licensing/__init__.py +37 -0
- memstrata_conversational-0.6.1/memory_layer_pro/licensing/exceptions.py +24 -0
- memstrata_conversational-0.6.1/memory_layer_pro/licensing/hwid.py +275 -0
- memstrata_conversational-0.6.1/memory_layer_pro/licensing/keychain.py +108 -0
- memstrata_conversational-0.6.1/memory_layer_pro/licensing/verifier.py +323 -0
- memstrata_conversational-0.6.1/memory_layer_pro/mcp_retrieval.py +163 -0
- memstrata_conversational-0.6.1/memory_layer_pro/memory/__init__.py +16 -0
- memstrata_conversational-0.6.1/memory_layer_pro/memory/absence_certificate.py +394 -0
- memstrata_conversational-0.6.1/memory_layer_pro/memory/absence_gold.py +339 -0
- memstrata_conversational-0.6.1/memory_layer_pro/memory/anchor_algebra_veto.py +618 -0
- memstrata_conversational-0.6.1/memory_layer_pro/memory/anchor_coverage.py +329 -0
- memstrata_conversational-0.6.1/memory_layer_pro/memory/anchor_index.py +317 -0
- memstrata_conversational-0.6.1/memory_layer_pro/memory/anchor_verify.py +815 -0
- memstrata_conversational-0.6.1/memory_layer_pro/memory/answer_citation.py +44 -0
- memstrata_conversational-0.6.1/memory_layer_pro/memory/answer_claim_repair.py +87 -0
- memstrata_conversational-0.6.1/memory_layer_pro/memory/answer_program.py +128 -0
- memstrata_conversational-0.6.1/memory_layer_pro/memory/answer_shape_router.py +95 -0
- memstrata_conversational-0.6.1/memory_layer_pro/memory/answer_tier.py +265 -0
- memstrata_conversational-0.6.1/memory_layer_pro/memory/answer_value_address.py +93 -0
- memstrata_conversational-0.6.1/memory_layer_pro/memory/binding_accept.py +107 -0
- memstrata_conversational-0.6.1/memory_layer_pro/memory/binding_certificate.py +155 -0
- memstrata_conversational-0.6.1/memory_layer_pro/memory/budget_router.py +161 -0
- memstrata_conversational-0.6.1/memory_layer_pro/memory/calibrate.py +432 -0
- memstrata_conversational-0.6.1/memory_layer_pro/memory/calibrated_score.py +51 -0
- memstrata_conversational-0.6.1/memory_layer_pro/memory/candidate_lattice.py +834 -0
- memstrata_conversational-0.6.1/memory_layer_pro/memory/canonicalize.py +114 -0
- memstrata_conversational-0.6.1/memory_layer_pro/memory/card_loci.py +1830 -0
- memstrata_conversational-0.6.1/memory_layer_pro/memory/certified_emission.py +49 -0
- memstrata_conversational-0.6.1/memory_layer_pro/memory/change_policy.py +56 -0
- memstrata_conversational-0.6.1/memory_layer_pro/memory/claim_disposition.py +43 -0
- memstrata_conversational-0.6.1/memory_layer_pro/memory/closed_form_resolver.py +168 -0
- memstrata_conversational-0.6.1/memory_layer_pro/memory/code_chunk_index.py +245 -0
- memstrata_conversational-0.6.1/memory_layer_pro/memory/code_index_cache.py +97 -0
- memstrata_conversational-0.6.1/memory_layer_pro/memory/code_symbol_oracle.py +90 -0
- memstrata_conversational-0.6.1/memory_layer_pro/memory/compiler_ir.py +102 -0
- memstrata_conversational-0.6.1/memory_layer_pro/memory/confidence_o10.py +212 -0
- memstrata_conversational-0.6.1/memory_layer_pro/memory/confidence_surface.py +270 -0
- memstrata_conversational-0.6.1/memory_layer_pro/memory/config.py +851 -0
- memstrata_conversational-0.6.1/memory_layer_pro/memory/consolidate.py +501 -0
- memstrata_conversational-0.6.1/memory_layer_pro/memory/context_mass.py +158 -0
- memstrata_conversational-0.6.1/memory_layer_pro/memory/context_provider.py +582 -0
- memstrata_conversational-0.6.1/memory_layer_pro/memory/contradiction.py +251 -0
- memstrata_conversational-0.6.1/memory_layer_pro/memory/contradiction_census.py +342 -0
- memstrata_conversational-0.6.1/memory_layer_pro/memory/dashboard_money_tab.py +508 -0
- memstrata_conversational-0.6.1/memory_layer_pro/memory/date_instruction.py +72 -0
- memstrata_conversational-0.6.1/memory_layer_pro/memory/dep_closure.py +29 -0
- memstrata_conversational-0.6.1/memory_layer_pro/memory/derivability.py +225 -0
- memstrata_conversational-0.6.1/memory_layer_pro/memory/diff_classifier.py +86 -0
- memstrata_conversational-0.6.1/memory_layer_pro/memory/discourse_order.py +201 -0
- memstrata_conversational-0.6.1/memory_layer_pro/memory/embed_backfill.py +115 -0
- memstrata_conversational-0.6.1/memory_layer_pro/memory/embeddings.py +55 -0
- memstrata_conversational-0.6.1/memory_layer_pro/memory/entity_alias_o8.py +720 -0
- memstrata_conversational-0.6.1/memory_layer_pro/memory/entity_registry.py +189 -0
- memstrata_conversational-0.6.1/memory_layer_pro/memory/entity_resolution.py +327 -0
- memstrata_conversational-0.6.1/memory_layer_pro/memory/episodic_store.py +62 -0
- memstrata_conversational-0.6.1/memory_layer_pro/memory/escalation_router.py +463 -0
- memstrata_conversational-0.6.1/memory_layer_pro/memory/event_date_bind.py +206 -0
- memstrata_conversational-0.6.1/memory_layer_pro/memory/event_graph.py +677 -0
- memstrata_conversational-0.6.1/memory_layer_pro/memory/evidence_pack.py +173 -0
- memstrata_conversational-0.6.1/memory_layer_pro/memory/explanations.py +33 -0
- memstrata_conversational-0.6.1/memory_layer_pro/memory/external_membership.py +64 -0
- memstrata_conversational-0.6.1/memory_layer_pro/memory/extraction_repair.py +575 -0
- memstrata_conversational-0.6.1/memory_layer_pro/memory/extractor.py +242 -0
- memstrata_conversational-0.6.1/memory_layer_pro/memory/fact_ingest.py +593 -0
- memstrata_conversational-0.6.1/memory_layer_pro/memory/fact_ledger.py +2137 -0
- memstrata_conversational-0.6.1/memory_layer_pro/memory/fact_oracle.py +913 -0
- memstrata_conversational-0.6.1/memory_layer_pro/memory/fact_span_extract.py +392 -0
- memstrata_conversational-0.6.1/memory_layer_pro/memory/flash_extract.py +185 -0
- memstrata_conversational-0.6.1/memory_layer_pro/memory/free_form_disagreement.py +194 -0
- memstrata_conversational-0.6.1/memory_layer_pro/memory/gate.py +594 -0
- memstrata_conversational-0.6.1/memory_layer_pro/memory/graph_traversal.py +60 -0
- memstrata_conversational-0.6.1/memory_layer_pro/memory/grounding.py +162 -0
- memstrata_conversational-0.6.1/memory_layer_pro/memory/information_density.py +60 -0
- memstrata_conversational-0.6.1/memory_layer_pro/memory/ledger.py +672 -0
- memstrata_conversational-0.6.1/memory_layer_pro/memory/line_ids.py +55 -0
- memstrata_conversational-0.6.1/memory_layer_pro/memory/llm.py +230 -0
- memstrata_conversational-0.6.1/memory_layer_pro/memory/logic_hash.py +71 -0
- memstrata_conversational-0.6.1/memory_layer_pro/memory/logic_verify.py +134 -0
- memstrata_conversational-0.6.1/memory_layer_pro/memory/mention_collapse.py +427 -0
- memstrata_conversational-0.6.1/memory_layer_pro/memory/micro_extract.py +758 -0
- memstrata_conversational-0.6.1/memory_layer_pro/memory/multihop_qa.py +80 -0
- memstrata_conversational-0.6.1/memory_layer_pro/memory/nli_semantic_provider.py +347 -0
- memstrata_conversational-0.6.1/memory_layer_pro/memory/numeric_normalization.py +178 -0
- memstrata_conversational-0.6.1/memory_layer_pro/memory/ontology_counting.py +152 -0
- memstrata_conversational-0.6.1/memory_layer_pro/memory/pack.py +220 -0
- memstrata_conversational-0.6.1/memory_layer_pro/memory/plan_compiler.py +405 -0
- memstrata_conversational-0.6.1/memory_layer_pro/memory/plan_lint.py +99 -0
- memstrata_conversational-0.6.1/memory_layer_pro/memory/proof_report.py +140 -0
- memstrata_conversational-0.6.1/memory_layer_pro/memory/propose.py +502 -0
- memstrata_conversational-0.6.1/memory_layer_pro/memory/quantity_bind.py +160 -0
- memstrata_conversational-0.6.1/memory_layer_pro/memory/quantity_gate.py +131 -0
- memstrata_conversational-0.6.1/memory_layer_pro/memory/query_class.py +85 -0
- memstrata_conversational-0.6.1/memory_layer_pro/memory/query_router.py +237 -0
- memstrata_conversational-0.6.1/memory_layer_pro/memory/question_aware_nli_provider.py +266 -0
- memstrata_conversational-0.6.1/memory_layer_pro/memory/recall_gate.py +26 -0
- memstrata_conversational-0.6.1/memory_layer_pro/memory/recall_router.py +74 -0
- memstrata_conversational-0.6.1/memory_layer_pro/memory/recommended_prompt.py +188 -0
- memstrata_conversational-0.6.1/memory_layer_pro/memory/refusal_ledger.py +449 -0
- memstrata_conversational-0.6.1/memory_layer_pro/memory/relative_dates.py +241 -0
- memstrata_conversational-0.6.1/memory_layer_pro/memory/resurrect.py +89 -0
- memstrata_conversational-0.6.1/memory_layer_pro/memory/routable.py +194 -0
- memstrata_conversational-0.6.1/memory_layer_pro/memory/rung2_category_router.py +59 -0
- memstrata_conversational-0.6.1/memory_layer_pro/memory/rung2_policy.py +192 -0
- memstrata_conversational-0.6.1/memory_layer_pro/memory/rung2_router.py +223 -0
- memstrata_conversational-0.6.1/memory_layer_pro/memory/runtime_test_index.py +717 -0
- memstrata_conversational-0.6.1/memory_layer_pro/memory/schema.py +333 -0
- memstrata_conversational-0.6.1/memory_layer_pro/memory/scope_envelope.py +158 -0
- memstrata_conversational-0.6.1/memory_layer_pro/memory/scoring.py +111 -0
- memstrata_conversational-0.6.1/memory_layer_pro/memory/session_intel.py +108 -0
- memstrata_conversational-0.6.1/memory_layer_pro/memory/session_recall.py +594 -0
- memstrata_conversational-0.6.1/memory_layer_pro/memory/set_coverage.py +139 -0
- memstrata_conversational-0.6.1/memory_layer_pro/memory/signals.py +72 -0
- memstrata_conversational-0.6.1/memory_layer_pro/memory/sketch.py +171 -0
- memstrata_conversational-0.6.1/memory_layer_pro/memory/sketch_calibrate.py +165 -0
- memstrata_conversational-0.6.1/memory_layer_pro/memory/speculative_plan.py +354 -0
- memstrata_conversational-0.6.1/memory_layer_pro/memory/sqlite_util.py +21 -0
- memstrata_conversational-0.6.1/memory_layer_pro/memory/state_delta.py +87 -0
- memstrata_conversational-0.6.1/memory_layer_pro/memory/state_index.py +1454 -0
- memstrata_conversational-0.6.1/memory_layer_pro/memory/state_rung3.py +1324 -0
- memstrata_conversational-0.6.1/memory_layer_pro/memory/symbols.py +70 -0
- memstrata_conversational-0.6.1/memory_layer_pro/memory/temporal_date_math.py +404 -0
- memstrata_conversational-0.6.1/memory_layer_pro/memory/temporal_order_o9.py +372 -0
- memstrata_conversational-0.6.1/memory_layer_pro/memory/temporal_reducer.py +221 -0
- memstrata_conversational-0.6.1/memory_layer_pro/memory/tenant_scope.py +52 -0
- memstrata_conversational-0.6.1/memory_layer_pro/memory/test_closure.py +204 -0
- memstrata_conversational-0.6.1/memory_layer_pro/memory/toon.py +43 -0
- memstrata_conversational-0.6.1/memory_layer_pro/memory/treesitter_chunk.py +141 -0
- memstrata_conversational-0.6.1/memory_layer_pro/memory/trust_tier.py +181 -0
- memstrata_conversational-0.6.1/memory_layer_pro/memory/trust_wiring.py +120 -0
- memstrata_conversational-0.6.1/memory_layer_pro/memory/turboquant.py +146 -0
- memstrata_conversational-0.6.1/memory_layer_pro/memory/unit_shape.py +210 -0
- memstrata_conversational-0.6.1/memory_layer_pro/memory/verify.py +252 -0
- memstrata_conversational-0.6.1/memory_layer_pro/memory/watcher.py +258 -0
- memstrata_conversational-0.6.1/memory_layer_pro/memory/wisdom_layer.py +91 -0
- memstrata_conversational-0.6.1/memory_layer_pro/memory/witness_edit_integration.py +246 -0
- memstrata_conversational-0.6.1/memory_layer_pro/memory/witness_sandbox.py +240 -0
- memstrata_conversational-0.6.1/memory_layer_pro/memory/witness_test_writer.py +139 -0
- memstrata_conversational-0.6.1/memory_layer_pro/memory/witnessability.py +144 -0
- memstrata_conversational-0.6.1/memory_layer_pro/memory/zone_certificate.py +211 -0
- memstrata_conversational-0.6.1/memory_layer_pro/pro_schema.py +61 -0
- memstrata_conversational-0.6.1/memory_layer_pro/product_profiles.py +96 -0
- memstrata_conversational-0.6.1/memory_layer_pro/tray/__init__.py +28 -0
- memstrata_conversational-0.6.1/memory_layer_pro/tray/__main__.py +13 -0
- memstrata_conversational-0.6.1/memory_layer_pro/tray/app.py +641 -0
- memstrata_conversational-0.6.1/memory_layer_pro/tray/assets/icon_gray.png +0 -0
- memstrata_conversational-0.6.1/memory_layer_pro/tray/assets/icon_gray_128.png +0 -0
- memstrata_conversational-0.6.1/memory_layer_pro/tray/assets/icon_gray_256.png +0 -0
- memstrata_conversational-0.6.1/memory_layer_pro/tray/assets/icon_gray_64.png +0 -0
- memstrata_conversational-0.6.1/memory_layer_pro/tray/assets/icon_green.png +0 -0
- memstrata_conversational-0.6.1/memory_layer_pro/tray/assets/icon_green_128.png +0 -0
- memstrata_conversational-0.6.1/memory_layer_pro/tray/assets/icon_green_256.png +0 -0
- memstrata_conversational-0.6.1/memory_layer_pro/tray/assets/icon_green_64.png +0 -0
- memstrata_conversational-0.6.1/memory_layer_pro/tray/assets/icon_red.png +0 -0
- memstrata_conversational-0.6.1/memory_layer_pro/tray/assets/icon_red_128.png +0 -0
- memstrata_conversational-0.6.1/memory_layer_pro/tray/assets/icon_red_256.png +0 -0
- memstrata_conversational-0.6.1/memory_layer_pro/tray/assets/icon_red_64.png +0 -0
- memstrata_conversational-0.6.1/memory_layer_pro/tray/assets/icon_template.png +0 -0
- memstrata_conversational-0.6.1/memory_layer_pro/tray/assets/icon_template_22.png +0 -0
- memstrata_conversational-0.6.1/memory_layer_pro/tray/assets/icon_template_44.png +0 -0
- memstrata_conversational-0.6.1/memory_layer_pro/tray/assets/icon_template_88.png +0 -0
- memstrata_conversational-0.6.1/memory_layer_pro/tray/assets/icon_yellow.png +0 -0
- memstrata_conversational-0.6.1/memory_layer_pro/tray/assets/icon_yellow_128.png +0 -0
- memstrata_conversational-0.6.1/memory_layer_pro/tray/assets/icon_yellow_256.png +0 -0
- memstrata_conversational-0.6.1/memory_layer_pro/tray/assets/icon_yellow_64.png +0 -0
- memstrata_conversational-0.6.1/memory_layer_pro/tray/daemon_client.py +207 -0
- memstrata_conversational-0.6.1/memory_layer_pro/tray/macos_launchagent.py +191 -0
- memstrata_conversational-0.6.1/memory_layer_pro/tray/state.py +205 -0
- memstrata_conversational-0.6.1/memory_layer_pro/wizard.py +1225 -0
- memstrata_conversational-0.6.1/memstrata_conversational.egg-info/PKG-INFO +80 -0
- memstrata_conversational-0.6.1/memstrata_conversational.egg-info/SOURCES.txt +234 -0
- memstrata_conversational-0.6.1/memstrata_conversational.egg-info/dependency_links.txt +1 -0
- memstrata_conversational-0.6.1/memstrata_conversational.egg-info/entry_points.txt +2 -0
- memstrata_conversational-0.6.1/memstrata_conversational.egg-info/requires.txt +39 -0
- memstrata_conversational-0.6.1/memstrata_conversational.egg-info/top_level.txt +3 -0
- memstrata_conversational-0.6.1/pyproject.toml +118 -0
- memstrata_conversational-0.6.1/setup.cfg +4 -0
- memstrata_conversational-0.6.1/tests/test_api_overlay.py +320 -0
- memstrata_conversational-0.6.1/tests/test_mcp_retrieval.py +144 -0
- memstrata_conversational-0.6.1/tests/test_product_profiles.py +90 -0
- memstrata_conversational-0.6.1/tests/test_windows_sku_contract.py +106 -0
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
MEMORY LAYER PRO — COMMERCIAL LICENSE
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Neeraj Yadav (yadu9989). All Rights Reserved.
|
|
4
|
+
|
|
5
|
+
This software and associated documentation files (the "Software") are the
|
|
6
|
+
proprietary and confidential property of the copyright holder. Unauthorized
|
|
7
|
+
copying, distribution, modification, or use of this software, in whole or in
|
|
8
|
+
part, is strictly prohibited without express written permission.
|
|
9
|
+
|
|
10
|
+
PERMITTED USE
|
|
11
|
+
A valid Memory Layer Pro license grants the licensee the right to:
|
|
12
|
+
1. Install and run the Software on machines owned or controlled by the licensee.
|
|
13
|
+
2. Use the Software for the licensee's own development workflows.
|
|
14
|
+
|
|
15
|
+
RESTRICTIONS
|
|
16
|
+
The licensee may not:
|
|
17
|
+
1. Sublicense, sell, rent, lease, or transfer rights to the Software.
|
|
18
|
+
2. Reverse-engineer, decompile, or disassemble the Software.
|
|
19
|
+
3. Remove or alter any proprietary notices or labels on the Software.
|
|
20
|
+
4. Use the Software to build a competing product.
|
|
21
|
+
|
|
22
|
+
NO WARRANTY
|
|
23
|
+
The Software is provided "AS IS" without warranty of any kind. The copyright
|
|
24
|
+
holder shall not be liable for any damages arising from use of the Software.
|
|
25
|
+
|
|
26
|
+
For licensing inquiries: yadavneeraj8@gmail.com
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: memstrata-conversational
|
|
3
|
+
Version: 0.6.1
|
|
4
|
+
Summary: MemStrata Pro — token-budgeted context harness, licensing, and packaging atop the memstrata open core
|
|
5
|
+
License: MEMORY LAYER PRO — COMMERCIAL LICENSE
|
|
6
|
+
|
|
7
|
+
Copyright (c) 2026 Neeraj Yadav (yadu9989). All Rights Reserved.
|
|
8
|
+
|
|
9
|
+
This software and associated documentation files (the "Software") are the
|
|
10
|
+
proprietary and confidential property of the copyright holder. Unauthorized
|
|
11
|
+
copying, distribution, modification, or use of this software, in whole or in
|
|
12
|
+
part, is strictly prohibited without express written permission.
|
|
13
|
+
|
|
14
|
+
PERMITTED USE
|
|
15
|
+
A valid Memory Layer Pro license grants the licensee the right to:
|
|
16
|
+
1. Install and run the Software on machines owned or controlled by the licensee.
|
|
17
|
+
2. Use the Software for the licensee's own development workflows.
|
|
18
|
+
|
|
19
|
+
RESTRICTIONS
|
|
20
|
+
The licensee may not:
|
|
21
|
+
1. Sublicense, sell, rent, lease, or transfer rights to the Software.
|
|
22
|
+
2. Reverse-engineer, decompile, or disassemble the Software.
|
|
23
|
+
3. Remove or alter any proprietary notices or labels on the Software.
|
|
24
|
+
4. Use the Software to build a competing product.
|
|
25
|
+
|
|
26
|
+
NO WARRANTY
|
|
27
|
+
The Software is provided "AS IS" without warranty of any kind. The copyright
|
|
28
|
+
holder shall not be liable for any damages arising from use of the Software.
|
|
29
|
+
|
|
30
|
+
For licensing inquiries: yadavneeraj8@gmail.com
|
|
31
|
+
|
|
32
|
+
Keywords: llm,context,proxy,harness,memory,telemetry,mcp,licensing
|
|
33
|
+
Classifier: Development Status :: 4 - Beta
|
|
34
|
+
Classifier: Intended Audience :: Developers
|
|
35
|
+
Classifier: License :: Other/Proprietary License
|
|
36
|
+
Classifier: Programming Language :: Python :: 3
|
|
37
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
38
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
39
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
40
|
+
Classifier: Topic :: Software Development
|
|
41
|
+
Requires-Python: >=3.10
|
|
42
|
+
Description-Content-Type: text/plain
|
|
43
|
+
License-File: LICENSE-COMMERCIAL
|
|
44
|
+
Requires-Dist: memstrata<0.7.0,>=0.6.1
|
|
45
|
+
Requires-Dist: fastapi>=0.115
|
|
46
|
+
Requires-Dist: uvicorn[standard]>=0.32
|
|
47
|
+
Requires-Dist: httpx>=0.27
|
|
48
|
+
Requires-Dist: pydantic>=2.10
|
|
49
|
+
Requires-Dist: python-dotenv>=1.0
|
|
50
|
+
Requires-Dist: tiktoken>=0.7
|
|
51
|
+
Requires-Dist: pystray>=0.19
|
|
52
|
+
Requires-Dist: Pillow>=10.0
|
|
53
|
+
Requires-Dist: keyring>=25.0
|
|
54
|
+
Requires-Dist: cryptography>=42
|
|
55
|
+
Requires-Dist: tomli>=2.0; python_version < "3.11"
|
|
56
|
+
Requires-Dist: numpy<3,>=1.26
|
|
57
|
+
Requires-Dist: tree-sitter-go<0.24,>=0.23
|
|
58
|
+
Requires-Dist: tree-sitter-rust<0.24,>=0.23
|
|
59
|
+
Requires-Dist: tree-sitter-java<0.24,>=0.23
|
|
60
|
+
Requires-Dist: tree-sitter-c<0.24,>=0.23
|
|
61
|
+
Requires-Dist: tree-sitter-cpp<0.24,>=0.23
|
|
62
|
+
Requires-Dist: tree-sitter-c-sharp<0.24,>=0.23
|
|
63
|
+
Requires-Dist: tree-sitter-ruby<0.24,>=0.23
|
|
64
|
+
Requires-Dist: tree-sitter-php<0.24,>=0.23
|
|
65
|
+
Requires-Dist: tree-sitter-bash<0.24,>=0.23
|
|
66
|
+
Provides-Extra: langchain
|
|
67
|
+
Requires-Dist: langchain-core<2,>=1.4; extra == "langchain"
|
|
68
|
+
Provides-Extra: nli
|
|
69
|
+
Requires-Dist: onnxruntime<2,>=1.18; extra == "nli"
|
|
70
|
+
Requires-Dist: tokenizers<1,>=0.19; extra == "nli"
|
|
71
|
+
Requires-Dist: truststore<1,>=0.10; extra == "nli"
|
|
72
|
+
Provides-Extra: dev
|
|
73
|
+
Requires-Dist: pytest>=8; extra == "dev"
|
|
74
|
+
Requires-Dist: pytest-asyncio>=0.23; extra == "dev"
|
|
75
|
+
Requires-Dist: respx>=0.21; extra == "dev"
|
|
76
|
+
Requires-Dist: ruff>=0.6; extra == "dev"
|
|
77
|
+
Requires-Dist: mypy>=1.11; extra == "dev"
|
|
78
|
+
Dynamic: license-file
|
|
79
|
+
|
|
80
|
+
MemStrata Conversational product package.
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
"""
|
|
2
|
+
memory-layer-pro billing subsystem — Phase 23 (Money-Back Guarantee).
|
|
3
|
+
|
|
4
|
+
Public API:
|
|
5
|
+
reconcile.compute_cycle_savings — aggregate measured savings for a billing cycle
|
|
6
|
+
credit.compute_credit — derive credit amount from savings vs subscription
|
|
7
|
+
webhook.handle_invoice_upcoming — Stripe webhook handler
|
|
8
|
+
email_notify.render_reconciliation_email / send_reconciliation_email
|
|
9
|
+
"""
|
|
10
|
+
from .credit import Credit, compute_credit
|
|
11
|
+
from .reconcile import CycleSavings, compute_cycle_savings
|
|
12
|
+
|
|
13
|
+
__all__ = [
|
|
14
|
+
"CycleSavings",
|
|
15
|
+
"compute_cycle_savings",
|
|
16
|
+
"Credit",
|
|
17
|
+
"compute_credit",
|
|
18
|
+
]
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Phase 23.1 — credit calculation rules.
|
|
3
|
+
|
|
4
|
+
Single source of truth for the money-back guarantee math. The credit cap rule
|
|
5
|
+
(credit ≤ subscription_usd) must be enforced here and nowhere else; callers
|
|
6
|
+
must not re-implement it.
|
|
7
|
+
|
|
8
|
+
Money math uses integer cents (Kimi C3 / K3-H7) so half-cent boundaries do not
|
|
9
|
+
depend on IEEE-754 banker's rounding. Float dollars only appear at the API edge.
|
|
10
|
+
"""
|
|
11
|
+
from __future__ import annotations
|
|
12
|
+
|
|
13
|
+
from dataclasses import dataclass
|
|
14
|
+
from decimal import ROUND_HALF_UP, Decimal
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
@dataclass(frozen=True)
|
|
18
|
+
class Credit:
|
|
19
|
+
credit_usd: float
|
|
20
|
+
shortfall_usd: float
|
|
21
|
+
subscription_usd: float
|
|
22
|
+
savings_usd: float
|
|
23
|
+
rationale: str
|
|
24
|
+
|
|
25
|
+
@property
|
|
26
|
+
def is_due(self) -> bool:
|
|
27
|
+
return self.credit_usd > 0.0
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def usd_to_cents(amount_usd: float | Decimal | int | str | None) -> int:
|
|
31
|
+
"""Convert a dollar amount to integer cents with half-up rounding.
|
|
32
|
+
|
|
33
|
+
Public so dashboard / reconcile / webhooks share one money substrate (K3-M5).
|
|
34
|
+
``None`` and non-numeric → 0.
|
|
35
|
+
"""
|
|
36
|
+
if amount_usd is None:
|
|
37
|
+
return 0
|
|
38
|
+
try:
|
|
39
|
+
d = Decimal(str(amount_usd))
|
|
40
|
+
except Exception:
|
|
41
|
+
return 0
|
|
42
|
+
if d.is_nan() or d.is_infinite():
|
|
43
|
+
return 0
|
|
44
|
+
return int((d * 100).quantize(Decimal("1"), rounding=ROUND_HALF_UP))
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def cents_to_usd(cents: int) -> float:
|
|
48
|
+
"""Integer cents → float dollars (display / API edge only)."""
|
|
49
|
+
return float(Decimal(int(cents)) / Decimal(100))
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
# Back-compat private aliases
|
|
53
|
+
_to_cents = usd_to_cents
|
|
54
|
+
_cents_to_usd = cents_to_usd
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def sum_usd_as_cents(amounts: list[float | Decimal | int | str | None]) -> int:
|
|
58
|
+
"""Sum dollar amounts in integer cents (no binary float accumulation)."""
|
|
59
|
+
return sum(usd_to_cents(a) for a in amounts)
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def compute_credit(savings_usd: float, subscription_usd: float) -> Credit:
|
|
63
|
+
"""
|
|
64
|
+
Derive the credit due to a user from measured savings vs subscription cost.
|
|
65
|
+
|
|
66
|
+
Rules (AV5.3 §4.1):
|
|
67
|
+
shortfall = max(0, subscription_usd - savings_usd)
|
|
68
|
+
credit = min(shortfall, subscription_usd) ← cap: never owe beyond subscription
|
|
69
|
+
|
|
70
|
+
The cap prevents a degenerate case where a subscription amount of $0 or a
|
|
71
|
+
negative savings figure could produce a nonsensical result.
|
|
72
|
+
|
|
73
|
+
Raises ValueError for negative inputs — savings and subscription must both
|
|
74
|
+
be non-negative dollar amounts.
|
|
75
|
+
"""
|
|
76
|
+
if savings_usd < 0:
|
|
77
|
+
raise ValueError(f"savings_usd must be ≥ 0, got {savings_usd}")
|
|
78
|
+
if subscription_usd < 0:
|
|
79
|
+
raise ValueError(f"subscription_usd must be ≥ 0, got {subscription_usd}")
|
|
80
|
+
|
|
81
|
+
savings_cents = usd_to_cents(savings_usd)
|
|
82
|
+
sub_cents = usd_to_cents(subscription_usd)
|
|
83
|
+
shortfall_cents = max(0, sub_cents - savings_cents)
|
|
84
|
+
credit_cents = min(shortfall_cents, sub_cents)
|
|
85
|
+
|
|
86
|
+
credit = cents_to_usd(credit_cents)
|
|
87
|
+
shortfall = cents_to_usd(shortfall_cents)
|
|
88
|
+
sub_usd = cents_to_usd(sub_cents)
|
|
89
|
+
sav_usd = cents_to_usd(savings_cents)
|
|
90
|
+
|
|
91
|
+
if credit_cents == 0:
|
|
92
|
+
rationale = (
|
|
93
|
+
f"Savings ${sav_usd:.2f} ≥ subscription ${sub_usd:.2f}; "
|
|
94
|
+
f"no credit due."
|
|
95
|
+
)
|
|
96
|
+
else:
|
|
97
|
+
rationale = (
|
|
98
|
+
f"Savings ${sav_usd:.2f} < subscription ${sub_usd:.2f}; "
|
|
99
|
+
f"crediting ${credit:.2f} (shortfall capped at subscription ceiling "
|
|
100
|
+
f"${sub_usd:.2f})."
|
|
101
|
+
)
|
|
102
|
+
|
|
103
|
+
return Credit(
|
|
104
|
+
credit_usd=credit,
|
|
105
|
+
shortfall_usd=shortfall,
|
|
106
|
+
subscription_usd=sub_usd,
|
|
107
|
+
savings_usd=sav_usd,
|
|
108
|
+
rationale=rationale,
|
|
109
|
+
)
|
|
@@ -0,0 +1,282 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Phase 23.3 — end-of-cycle reconciliation email.
|
|
3
|
+
|
|
4
|
+
Renders and (optionally) sends the transactional email showing the user's
|
|
5
|
+
cost-vs-savings footprint for the billing cycle.
|
|
6
|
+
|
|
7
|
+
Template content per AV5.3 §4.1. The sender backend is configurable via
|
|
8
|
+
MEMORY_LAYER_EMAIL_BACKEND env var: 'sendgrid' | 'resend' | 'smtp' | 'noop'.
|
|
9
|
+
"""
|
|
10
|
+
from __future__ import annotations
|
|
11
|
+
|
|
12
|
+
import logging
|
|
13
|
+
import os
|
|
14
|
+
import smtplib
|
|
15
|
+
import textwrap
|
|
16
|
+
from dataclasses import dataclass
|
|
17
|
+
from email.mime.multipart import MIMEMultipart
|
|
18
|
+
from email.mime.text import MIMEText
|
|
19
|
+
|
|
20
|
+
from .credit import Credit
|
|
21
|
+
from .reconcile import CycleSavings
|
|
22
|
+
|
|
23
|
+
log = logging.getLogger(__name__)
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
# ---------------------------------------------------------------------------
|
|
27
|
+
# Rendered email
|
|
28
|
+
# ---------------------------------------------------------------------------
|
|
29
|
+
|
|
30
|
+
@dataclass
|
|
31
|
+
class ReconciliationEmail:
|
|
32
|
+
to_address: str
|
|
33
|
+
subject: str
|
|
34
|
+
body_text: str
|
|
35
|
+
body_html: str
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def render_reconciliation_email(
|
|
39
|
+
user_email: str,
|
|
40
|
+
month_label: str,
|
|
41
|
+
savings: CycleSavings,
|
|
42
|
+
credit: Credit,
|
|
43
|
+
) -> ReconciliationEmail:
|
|
44
|
+
"""
|
|
45
|
+
Render the end-of-cycle reconciliation email per AV5.3 §4.1.
|
|
46
|
+
|
|
47
|
+
Parameters
|
|
48
|
+
----------
|
|
49
|
+
user_email: Recipient address.
|
|
50
|
+
month_label: Human-readable billing period, e.g. "May 2026".
|
|
51
|
+
savings: CycleSavings from reconcile.compute_cycle_savings.
|
|
52
|
+
credit: Credit from credit.compute_credit.
|
|
53
|
+
"""
|
|
54
|
+
subject = f"Your Memory Layer reconciliation for {month_label}"
|
|
55
|
+
|
|
56
|
+
if credit.is_due:
|
|
57
|
+
credit_line = f"Credit on next invoice: ${credit.credit_usd:.2f}"
|
|
58
|
+
footer = (
|
|
59
|
+
"Your dashboard remains active. Memory Layer learns and improves with use;\n"
|
|
60
|
+
"next month's savings should grow as your project state densifies."
|
|
61
|
+
)
|
|
62
|
+
else:
|
|
63
|
+
credit_line = (
|
|
64
|
+
f"No credit due — your savings (${savings.total_usd:.2f}) exceeded "
|
|
65
|
+
f"your subscription (${credit.subscription_usd:.2f})."
|
|
66
|
+
)
|
|
67
|
+
footer = (
|
|
68
|
+
"You're net positive this month! Your savings exceeded your subscription "
|
|
69
|
+
"cost.\nKeep using Memory Layer — your project knowledge is compounding."
|
|
70
|
+
)
|
|
71
|
+
|
|
72
|
+
body_text = textwrap.dedent(f"""\
|
|
73
|
+
Subject: {subject}
|
|
74
|
+
|
|
75
|
+
This cycle:
|
|
76
|
+
Input savings: ${savings.input_savings_usd:.2f} (measured)
|
|
77
|
+
Cache savings: ${savings.cache_savings_usd:.2f} (measured)
|
|
78
|
+
Output savings: ${savings.output_savings_usd:.2f} (cohort-measured)
|
|
79
|
+
Total savings: ${savings.total_usd:.2f}
|
|
80
|
+
Subscription: ${credit.subscription_usd:.2f}
|
|
81
|
+
|
|
82
|
+
{credit_line}
|
|
83
|
+
|
|
84
|
+
{footer}
|
|
85
|
+
|
|
86
|
+
— The Memory Layer Team
|
|
87
|
+
""")
|
|
88
|
+
|
|
89
|
+
body_html = _render_html(
|
|
90
|
+
month_label=month_label,
|
|
91
|
+
savings=savings,
|
|
92
|
+
credit=credit,
|
|
93
|
+
credit_line=credit_line,
|
|
94
|
+
footer=footer,
|
|
95
|
+
)
|
|
96
|
+
|
|
97
|
+
return ReconciliationEmail(
|
|
98
|
+
to_address=user_email,
|
|
99
|
+
subject=subject,
|
|
100
|
+
body_text=body_text,
|
|
101
|
+
body_html=body_html,
|
|
102
|
+
)
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
def _render_html(
|
|
106
|
+
*,
|
|
107
|
+
month_label: str,
|
|
108
|
+
savings: CycleSavings,
|
|
109
|
+
credit: Credit,
|
|
110
|
+
credit_line: str,
|
|
111
|
+
footer: str,
|
|
112
|
+
) -> str:
|
|
113
|
+
highlight = "#2ecc71" if not credit.is_due else "#e67e22"
|
|
114
|
+
return f"""\
|
|
115
|
+
<!DOCTYPE html>
|
|
116
|
+
<html lang="en">
|
|
117
|
+
<head>
|
|
118
|
+
<meta charset="utf-8">
|
|
119
|
+
<title>Memory Layer Reconciliation</title>
|
|
120
|
+
<style>
|
|
121
|
+
body {{ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
|
|
122
|
+
background: #f9f9f9; margin: 0; padding: 20px; }}
|
|
123
|
+
.card {{ background: #fff; border-radius: 8px; max-width: 540px; margin: 0 auto;
|
|
124
|
+
padding: 32px; box-shadow: 0 2px 8px rgba(0,0,0,.08); }}
|
|
125
|
+
h1 {{ font-size: 1.2rem; color: #111; margin-bottom: 24px; }}
|
|
126
|
+
table {{ width: 100%; border-collapse: collapse; margin-bottom: 16px; }}
|
|
127
|
+
td {{ padding: 6px 0; color: #333; font-size: .95rem; }}
|
|
128
|
+
td.label {{ color: #666; }}
|
|
129
|
+
td.amount {{ text-align: right; font-variant-numeric: tabular-nums; }}
|
|
130
|
+
.divider {{ border-top: 1px solid #eee; margin: 12px 0; }}
|
|
131
|
+
.credit-line {{ font-size: 1rem; font-weight: 600; color: {highlight};
|
|
132
|
+
margin: 16px 0; }}
|
|
133
|
+
.footer {{ margin-top: 24px; font-size: .85rem; color: #888; }}
|
|
134
|
+
</style>
|
|
135
|
+
</head>
|
|
136
|
+
<body>
|
|
137
|
+
<div class="card">
|
|
138
|
+
<h1>Memory Layer — {month_label} reconciliation</h1>
|
|
139
|
+
<table>
|
|
140
|
+
<tr><td class="label">Input savings</td>
|
|
141
|
+
<td class="amount">${savings.input_savings_usd:.2f} <small>measured</small></td></tr>
|
|
142
|
+
<tr><td class="label">Cache savings</td>
|
|
143
|
+
<td class="amount">${savings.cache_savings_usd:.2f} <small>measured</small></td></tr>
|
|
144
|
+
<tr><td class="label">Output savings</td>
|
|
145
|
+
<td class="amount">${savings.output_savings_usd:.2f} <small>cohort-measured</small></td></tr>
|
|
146
|
+
</table>
|
|
147
|
+
<div class="divider"></div>
|
|
148
|
+
<table>
|
|
149
|
+
<tr><td class="label">Total savings</td>
|
|
150
|
+
<td class="amount"><strong>${savings.total_usd:.2f}</strong></td></tr>
|
|
151
|
+
<tr><td class="label">Subscription</td>
|
|
152
|
+
<td class="amount">${credit.subscription_usd:.2f}</td></tr>
|
|
153
|
+
</table>
|
|
154
|
+
<div class="credit-line">{credit_line}</div>
|
|
155
|
+
<div class="footer">{footer.replace(chr(10), '<br>')}</div>
|
|
156
|
+
<div class="footer" style="margin-top:12px">— The Memory Layer Team</div>
|
|
157
|
+
</div>
|
|
158
|
+
</body>
|
|
159
|
+
</html>"""
|
|
160
|
+
|
|
161
|
+
|
|
162
|
+
# ---------------------------------------------------------------------------
|
|
163
|
+
# Send backends
|
|
164
|
+
# ---------------------------------------------------------------------------
|
|
165
|
+
|
|
166
|
+
def send_reconciliation_email(
|
|
167
|
+
email: ReconciliationEmail,
|
|
168
|
+
*,
|
|
169
|
+
backend: str | None = None,
|
|
170
|
+
sendgrid_api_key: str | None = None,
|
|
171
|
+
resend_api_key: str | None = None,
|
|
172
|
+
smtp_host: str = "localhost",
|
|
173
|
+
smtp_port: int = 587,
|
|
174
|
+
smtp_username: str | None = None,
|
|
175
|
+
smtp_password: str | None = None,
|
|
176
|
+
from_address: str = "noreply@memorylayer.dev",
|
|
177
|
+
) -> bool:
|
|
178
|
+
"""
|
|
179
|
+
Send `email` via the configured backend. Returns True on success.
|
|
180
|
+
|
|
181
|
+
Backend priority:
|
|
182
|
+
1. `backend` argument if supplied.
|
|
183
|
+
2. MEMORY_LAYER_EMAIL_BACKEND env var.
|
|
184
|
+
3. Falls back to 'noop' (logs only, no send).
|
|
185
|
+
"""
|
|
186
|
+
resolved = backend or os.environ.get("MEMORY_LAYER_EMAIL_BACKEND", "noop")
|
|
187
|
+
|
|
188
|
+
if resolved == "sendgrid":
|
|
189
|
+
return _send_sendgrid(
|
|
190
|
+
email, api_key=sendgrid_api_key or os.environ["SENDGRID_API_KEY"],
|
|
191
|
+
from_address=from_address,
|
|
192
|
+
)
|
|
193
|
+
if resolved == "resend":
|
|
194
|
+
return _send_resend(
|
|
195
|
+
email, api_key=resend_api_key or os.environ["RESEND_API_KEY"],
|
|
196
|
+
from_address=from_address,
|
|
197
|
+
)
|
|
198
|
+
if resolved == "smtp":
|
|
199
|
+
return _send_smtp(
|
|
200
|
+
email, host=smtp_host, port=smtp_port,
|
|
201
|
+
username=smtp_username, password=smtp_password,
|
|
202
|
+
from_address=from_address,
|
|
203
|
+
)
|
|
204
|
+
|
|
205
|
+
# noop — log and return True so callers don't treat this as an error
|
|
206
|
+
log.info(
|
|
207
|
+
"email_notify [noop] would send '%s' to %s",
|
|
208
|
+
email.subject, email.to_address,
|
|
209
|
+
)
|
|
210
|
+
return True
|
|
211
|
+
|
|
212
|
+
|
|
213
|
+
def _send_sendgrid(email: ReconciliationEmail, *, api_key: str, from_address: str) -> bool:
|
|
214
|
+
try:
|
|
215
|
+
import sendgrid
|
|
216
|
+
from sendgrid.helpers.mail import Mail, To, From, Subject, HtmlContent, Content
|
|
217
|
+
|
|
218
|
+
sg = sendgrid.SendGridAPIClient(api_key=api_key)
|
|
219
|
+
message = Mail(
|
|
220
|
+
from_email=from_address,
|
|
221
|
+
to_emails=email.to_address,
|
|
222
|
+
subject=email.subject,
|
|
223
|
+
html_content=email.body_html,
|
|
224
|
+
)
|
|
225
|
+
message.add_content(Content("text/plain", email.body_text))
|
|
226
|
+
response = sg.send(message)
|
|
227
|
+
success = 200 <= response.status_code < 300
|
|
228
|
+
if not success:
|
|
229
|
+
log.error("SendGrid returned %s", response.status_code)
|
|
230
|
+
return success
|
|
231
|
+
except Exception as exc:
|
|
232
|
+
log.error("SendGrid send failed: %s", exc)
|
|
233
|
+
return False
|
|
234
|
+
|
|
235
|
+
|
|
236
|
+
def _send_resend(email: ReconciliationEmail, *, api_key: str, from_address: str) -> bool:
|
|
237
|
+
try:
|
|
238
|
+
import resend
|
|
239
|
+
|
|
240
|
+
resend.api_key = api_key
|
|
241
|
+
params = {
|
|
242
|
+
"from": from_address,
|
|
243
|
+
"to": [email.to_address],
|
|
244
|
+
"subject": email.subject,
|
|
245
|
+
"html": email.body_html,
|
|
246
|
+
"text": email.body_text,
|
|
247
|
+
}
|
|
248
|
+
resend.Emails.send(params)
|
|
249
|
+
return True
|
|
250
|
+
except Exception as exc:
|
|
251
|
+
log.error("Resend send failed: %s", exc)
|
|
252
|
+
return False
|
|
253
|
+
|
|
254
|
+
|
|
255
|
+
def _send_smtp(
|
|
256
|
+
email: ReconciliationEmail,
|
|
257
|
+
*,
|
|
258
|
+
host: str,
|
|
259
|
+
port: int,
|
|
260
|
+
username: str | None,
|
|
261
|
+
password: str | None,
|
|
262
|
+
from_address: str,
|
|
263
|
+
) -> bool:
|
|
264
|
+
try:
|
|
265
|
+
msg = MIMEMultipart("alternative")
|
|
266
|
+
msg["Subject"] = email.subject
|
|
267
|
+
msg["From"] = from_address
|
|
268
|
+
msg["To"] = email.to_address
|
|
269
|
+
msg.attach(MIMEText(email.body_text, "plain"))
|
|
270
|
+
msg.attach(MIMEText(email.body_html, "html"))
|
|
271
|
+
|
|
272
|
+
with smtplib.SMTP(host, port) as server:
|
|
273
|
+
server.ehlo()
|
|
274
|
+
if port != 25:
|
|
275
|
+
server.starttls()
|
|
276
|
+
if username and password:
|
|
277
|
+
server.login(username, password)
|
|
278
|
+
server.sendmail(from_address, [email.to_address], msg.as_string())
|
|
279
|
+
return True
|
|
280
|
+
except Exception as exc:
|
|
281
|
+
log.error("SMTP send failed: %s", exc)
|
|
282
|
+
return False
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Phase 23.1 — billing reconciliation.
|
|
3
|
+
|
|
4
|
+
Aggregates measured savings from telemetry_session_timeline for a user's billing
|
|
5
|
+
cycle. Only the three honest measurement bases are included; output_estimated is
|
|
6
|
+
excluded from all credit-eligible totals (Hard Rule 60).
|
|
7
|
+
|
|
8
|
+
Money accumulation is integer cents end-to-end (Kimi K3-M5 / H7): SQLite float
|
|
9
|
+
SUM is never trusted for credit math.
|
|
10
|
+
"""
|
|
11
|
+
from __future__ import annotations
|
|
12
|
+
|
|
13
|
+
import sqlite3
|
|
14
|
+
from dataclasses import dataclass
|
|
15
|
+
from datetime import datetime
|
|
16
|
+
|
|
17
|
+
from billing.credit import cents_to_usd, sum_usd_as_cents, usd_to_cents
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
_CREDIT_ELIGIBLE_BASES = ("input_measured", "cache_measured", "output_cohort_measured")
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
@dataclass
|
|
24
|
+
class CycleSavings:
|
|
25
|
+
user_id: str
|
|
26
|
+
cycle_start: datetime
|
|
27
|
+
cycle_end: datetime
|
|
28
|
+
input_savings_usd: float
|
|
29
|
+
cache_savings_usd: float
|
|
30
|
+
output_savings_usd: float
|
|
31
|
+
# Integer cents retained for exact credit handoff (optional consumers).
|
|
32
|
+
input_savings_cents: int = 0
|
|
33
|
+
cache_savings_cents: int = 0
|
|
34
|
+
output_savings_cents: int = 0
|
|
35
|
+
|
|
36
|
+
@property
|
|
37
|
+
def total_cents(self) -> int:
|
|
38
|
+
return (
|
|
39
|
+
self.input_savings_cents
|
|
40
|
+
+ self.cache_savings_cents
|
|
41
|
+
+ self.output_savings_cents
|
|
42
|
+
)
|
|
43
|
+
|
|
44
|
+
@property
|
|
45
|
+
def total_usd(self) -> float:
|
|
46
|
+
return cents_to_usd(self.total_cents)
|
|
47
|
+
|
|
48
|
+
def to_dict(self) -> dict:
|
|
49
|
+
return {
|
|
50
|
+
"user_id": self.user_id,
|
|
51
|
+
"cycle_start": self.cycle_start.isoformat(),
|
|
52
|
+
"cycle_end": self.cycle_end.isoformat(),
|
|
53
|
+
"input_savings_usd": round(self.input_savings_usd, 2),
|
|
54
|
+
"cache_savings_usd": round(self.cache_savings_usd, 2),
|
|
55
|
+
"output_savings_usd": round(self.output_savings_usd, 2),
|
|
56
|
+
"total_usd": round(self.total_usd, 2),
|
|
57
|
+
"input_savings_cents": self.input_savings_cents,
|
|
58
|
+
"cache_savings_cents": self.cache_savings_cents,
|
|
59
|
+
"output_savings_cents": self.output_savings_cents,
|
|
60
|
+
"total_cents": self.total_cents,
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def compute_cycle_savings(
|
|
65
|
+
user_id: str,
|
|
66
|
+
cycle_start: datetime,
|
|
67
|
+
cycle_end: datetime,
|
|
68
|
+
*,
|
|
69
|
+
conn: sqlite3.Connection,
|
|
70
|
+
) -> CycleSavings:
|
|
71
|
+
"""
|
|
72
|
+
Sum credit-eligible savings for `user_id` between `cycle_start` and `cycle_end`.
|
|
73
|
+
|
|
74
|
+
Filters to measurement_basis IN ('input_measured', 'cache_measured',
|
|
75
|
+
'output_cohort_measured'). Rows tagged 'output_estimated' are intentionally
|
|
76
|
+
excluded — they are not auditable and never credited (Hard Rule 60).
|
|
77
|
+
|
|
78
|
+
Each row is converted to integer cents with half-up rounding, then summed —
|
|
79
|
+
never ``SUM(float)`` in SQLite / Python float accumulate.
|
|
80
|
+
"""
|
|
81
|
+
placeholders = ",".join("?" for _ in _CREDIT_ELIGIBLE_BASES)
|
|
82
|
+
rows = conn.execute(
|
|
83
|
+
f"""
|
|
84
|
+
SELECT
|
|
85
|
+
saved_input_cost_usd,
|
|
86
|
+
saved_cache_cost_usd,
|
|
87
|
+
saved_output_cost_usd
|
|
88
|
+
FROM telemetry_session_timeline
|
|
89
|
+
WHERE user_id = ?
|
|
90
|
+
AND recorded_at BETWEEN ? AND ?
|
|
91
|
+
AND measurement_basis IN ({placeholders})
|
|
92
|
+
""",
|
|
93
|
+
(
|
|
94
|
+
user_id,
|
|
95
|
+
cycle_start.isoformat(),
|
|
96
|
+
cycle_end.isoformat(),
|
|
97
|
+
*_CREDIT_ELIGIBLE_BASES,
|
|
98
|
+
),
|
|
99
|
+
).fetchall()
|
|
100
|
+
|
|
101
|
+
in_c = sum_usd_as_cents([r[0] for r in rows])
|
|
102
|
+
ca_c = sum_usd_as_cents([r[1] for r in rows])
|
|
103
|
+
ou_c = sum_usd_as_cents([r[2] for r in rows])
|
|
104
|
+
|
|
105
|
+
return CycleSavings(
|
|
106
|
+
user_id=user_id,
|
|
107
|
+
cycle_start=cycle_start,
|
|
108
|
+
cycle_end=cycle_end,
|
|
109
|
+
input_savings_usd=cents_to_usd(in_c),
|
|
110
|
+
cache_savings_usd=cents_to_usd(ca_c),
|
|
111
|
+
output_savings_usd=cents_to_usd(ou_c),
|
|
112
|
+
input_savings_cents=in_c,
|
|
113
|
+
cache_savings_cents=ca_c,
|
|
114
|
+
output_savings_cents=ou_c,
|
|
115
|
+
)
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
def savings_total_usd(*parts: float) -> float:
|
|
119
|
+
"""Sum display-side USD parts via cents (dashboard / API helpers)."""
|
|
120
|
+
return cents_to_usd(sum(usd_to_cents(p) for p in parts))
|