diff --git a/BENCHMARKS.md b/BENCHMARKS.md
index 6cb1a2c7..15629a99 100644
--- a/BENCHMARKS.md
+++ b/BENCHMARKS.md
@@ -94,14 +94,14 @@ interpretation and do not count as additional benchmark-quality gains.
### Public numeric evidence registry
Every exact public aggregate retained below comes from the checked-in, public-safe
-[`offline-fixtures-release-1.7.12-20261102.json`](docs/benchmark-evidence/offline-fixtures-release-1.7.12-20261102.json) artifact. Its
+[`offline-fixtures-paid-plans-20261104.json`](docs/benchmark-evidence/offline-fixtures-paid-plans-20261104.json) artifact. Its
SHA-256 is
-`025183601c5a28a37daf9326a061010244bc9a3f9f7db1e3e6d4f866eaafc660`, also recorded in the
+`cc66984fd3dd78c17addeb979aea0e0bf40ff0f533ebcd683da4b7f532da0055`, also recorded in the
adjacent `.sha256` file. The artifact contains no raw questions, answers, prompts, customer data,
or per-record content fingerprints.
The fixture-suite digest is
-`54422b65475bb0d3e5c4ad0613bbda87dd5e01cb43e15513469b7306446831c9`. The artifact defines
+`93d671089dd360954fce2c97ce1fdd105f87166ba9224e644d61ce15f9857e79`. The artifact defines
the digest algorithm and records the SHA-256 of every suite and dataset file. Each evidence ID
also binds its exact command through `sha256(UTF-8 exact command)`:
@@ -134,7 +134,7 @@ retrieval-quality improvement. It does not qualify browser behavior, hosted serv
external models, or provider billing. The registered artifact was produced with:
```bash
-python -m scripts.export_offline_evidence --output docs/benchmark-evidence/offline-fixtures-release-1.7.12-20261102.json
+python -m scripts.export_offline_evidence --output docs/benchmark-evidence/offline-fixtures-paid-plans-20261104.json
```
The earlier release-review predecessors,
@@ -203,13 +203,13 @@ grounded checks in separate panels; provider billing and MCP transport are not m
the SVG and matching PNG with:
```bash
-python scripts/render_benchmark_report.py --report docs/benchmark-evidence/offline-fixtures-release-1.7.12-20261102.json --output docs/images/context-efficiency.svg --png-output docs/images/context-efficiency.png
+python scripts/render_benchmark_report.py --report docs/benchmark-evidence/offline-fixtures-paid-plans-20261104.json --output docs/images/context-efficiency.svg --png-output docs/images/context-efficiency.png
```
The companion examples are also generated from that artifact with:
```bash
-python -m scripts.render_benchmark_examples --report docs/benchmark-evidence/offline-fixtures-release-1.7.12-20261102.json --output docs/images/evidence-backed-agent-examples.svg --png-output docs/images/evidence-backed-agent-examples.png
+python -m scripts.render_benchmark_examples --report docs/benchmark-evidence/offline-fixtures-paid-plans-20261104.json --output docs/images/evidence-backed-agent-examples.svg --png-output docs/images/evidence-backed-agent-examples.png
```
The historical-to-executable mapping is in
[`docs/BENCHMARK_CHANGE_COVERAGE.md`](docs/BENCHMARK_CHANGE_COVERAGE.md).
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 8acfdad2..e381b8eb 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -5,6 +5,10 @@ All notable changes to Engraphis are documented here. Format loosely follows
## [Unreleased]
+- Add one checked-in entitlement contract as the read-only plan→feature mirror, and fail the
+ release gate when it drifts from the private hosted contract. Local workspace export stays
+ free; a server grant this build cannot render is reported instead of silently dropped.
+
## [1.7.12] - 2026-11-02
Candidate package version: **1.7.12**. Publication and hosted-service qualification remain separate.
diff --git a/Dockerfile b/Dockerfile
index 0b5e611e..6f779689 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -1,5 +1,5 @@
# Engraphis — self-hosted AI memory engine. Local-first; you bring the LLM.
-FROM python:3.11-slim@sha256:b7a9e5f6a93d6d4a31f129bbd4bdee69efccb6ded0fb230d78aafaf37af98377 AS base
+FROM python:3.11.18-slim@sha256:db20d8506e2f15b6db39b40b6015ca46bae855dec6dd326d462cfe79ae972893 AS base
# ENGRAPHIS_HOST is deliberately NOT set here: docker-entrypoint.sh chooses IPv6 for a
# Railway deployment (which injects RAILWAY_SERVICE_NAME) and 0.0.0.0 for ordinary Docker.
diff --git a/MANIFEST.in b/MANIFEST.in
index 7b4a9491..9655b752 100644
--- a/MANIFEST.in
+++ b/MANIFEST.in
@@ -4,6 +4,7 @@ global-exclude *.pyo
recursive-include engraphis/dashboard_assets *.html *.css *.js *.png *.ico
recursive-include engraphis/dashboard_assets/vendor *
include engraphis/commercial_manifest.json
+include engraphis/entitlement_contract.json
recursive-include integrations/commandcode *.py
recursive-include scripts/templates *.svg
include LICENSE NOTICE README.md CHANGELOG.md BENCHMARKS.md
diff --git a/README.md b/README.md
index b38847f3..08b38a58 100644
--- a/README.md
+++ b/README.md
@@ -52,7 +52,7 @@ Connect a coding agent over MCP with the [agent setup guide](https://github.com/
The current registered artifact contains three deterministic offline fixture runs. It separates context size, retrieval quality, and grounded decision checks; these small fixtures do not establish general task performance.
-
+ Three offline fixtures separate context reduction, candidate and packed retrieval quality, and grounded behavior. The chart shows the artifact checksum prefix; see the benchmark guide for the full checksum and reproduction steps.
diff --git a/docs/LICENSING.md b/docs/LICENSING.md
index 0a41ed0e..f370ef26 100644
--- a/docs/LICENSING.md
+++ b/docs/LICENSING.md
@@ -52,6 +52,17 @@ a private repository. Public clients communicate with those services through aut
versioned protocols. This boundary protects new commercial implementation work; it does not
change the Apache license or prevent a fork from modifying code already released here.
+## Entitlement presentation mirror
+
+`engraphis/entitlement_contract.json` is a checked-in, read-only mirror of the private
+control plane's plan→feature contract. It tells the dashboard which plan advertises a
+capability and which copy to show when one is unavailable. It is not an entitlement:
+`engraphis/entitlements.py` cannot authorize an operation, mint a credential, or unlock a
+route. Every paid operation is still authorized by the hosted service, and a server grant
+this build does not recognize is reported as unrecognized rather than inferred or silently
+dropped. The release gate compares the mirror with the private contract, so a capability that
+exists only server-side fails CI instead of disappearing from a paying customer's dashboard.
+
## Trial, grace, and recovery
The server-issued trial lasts **7 active days for Pro or 14 active days for Team**. Grace is a
diff --git a/docs/SYNC.md b/docs/SYNC.md
index b3432b50..c7247572 100644
--- a/docs/SYNC.md
+++ b/docs/SYNC.md
@@ -86,27 +86,35 @@ owner-private `~/.engraphis/config.env`, then restart the local dashboard. The S
only whether the key is valid and the local encryption dependency is installed, never the key
itself. A read-only pull still needs the same key to decrypt received bundles.
+The dashboard can also generate the key in place: **Settings → Cloud Sync → Create sync key**.
+It is written once to `~/.engraphis/sync.e2ee_key` (owner-only, mode `0600`), shown exactly once
+for you to store, and never returned by the status endpoint again. An operator-provisioned
+`ENGRAPHIS_SYNC_E2EE_KEY` always takes precedence and cannot be replaced from the dashboard.
+Rotation is explicit and warns that data synced under the previous key is unreadable until every
+device uses the new one. This path is for a trusted local device; a headless deployment should
+still provision the key through its secrets manager.
+
```bash
-python -c "import base64, secrets; print(base64.urlsafe_b64encode(secrets.token_bytes(32)).decode().rstrip('='))"
-```
-
-Treat the command output as a secret: use a trusted terminal without transcript or log capture,
-store it in your secret manager, and never paste it into the Cloud portal or a support request.
-Before the first upload, keep a separate secure backup and confirm you can retrieve it. If the
-key is lost, previously synced encrypted bundles cannot be decrypted; Engraphis Cloud cannot
-restore it.
-
-For the first device, add a single `ENGRAPHIS_SYNC_E2EE_KEY=` assignment to its
-owner-private `~/.engraphis/config.env` with a trusted editor, retaining any existing settings.
-Use the path named by `ENGRAPHIS_ENV_FILE` instead when that override is configured. A secrets
-manager may inject the same process variable instead of using the file. Restart the dashboard,
-then check **Settings → Cloud Sync**: Connection must show **Connected**, Encryption key
-**Valid**, and Sync readiness **Ready** before **Sync now** is enabled. If encryption support is
-missing, install `engraphis[cloud-sync]` in the dashboard's Python environment and restart.
-On each additional device, provision the *same* backed-up key through your secure channel;
-generating a different key will make earlier encrypted bundles unreadable there.
-
-The dashboard's **Sync now** action invokes the same customer protocol. The public package does
+python -c "import base64, secrets; print(base64.urlsafe_b64encode(secrets.token_bytes(32)).decode().rstrip('='))"
+```
+
+Treat the command output as a secret: use a trusted terminal without transcript or log capture,
+store it in your secret manager, and never paste it into the Cloud portal or a support request.
+Before the first upload, keep a separate secure backup and confirm you can retrieve it. If the
+key is lost, previously synced encrypted bundles cannot be decrypted; Engraphis Cloud cannot
+restore it.
+
+For the first device, add a single `ENGRAPHIS_SYNC_E2EE_KEY=` assignment to its
+owner-private `~/.engraphis/config.env` with a trusted editor, retaining any existing settings.
+Use the path named by `ENGRAPHIS_ENV_FILE` instead when that override is configured. A secrets
+manager may inject the same process variable instead of using the file. Restart the dashboard,
+then check **Settings → Cloud Sync**: Connection must show **Connected**, Encryption key
+**Valid**, and Sync readiness **Ready** before **Sync now** is enabled. If encryption support is
+missing, install `engraphis[cloud-sync]` in the dashboard's Python environment and restart.
+On each additional device, provision the *same* backed-up key through your secure channel;
+generating a different key will make earlier encrypted bundles unreadable there.
+
+The dashboard's **Sync now** action invokes the same customer protocol. The public package does
not run a local auto-sync loop or ship a cron/Task Scheduler wrapper. Hosted automation belongs
to the private service. A round with any incomplete workspace is a failure, even when other peers
were applied successfully: the bounded report retains those good-peer totals, labels the result
diff --git a/docs/benchmark-evidence/offline-fixtures-paid-plans-20261104.json b/docs/benchmark-evidence/offline-fixtures-paid-plans-20261104.json
new file mode 100644
index 00000000..9afc2048
--- /dev/null
+++ b/docs/benchmark-evidence/offline-fixtures-paid-plans-20261104.json
@@ -0,0 +1,702 @@
+{
+ "environment": {
+ "embedding": "deterministic",
+ "numpy": "2.4.6",
+ "platform": "win32",
+ "python": "3.11.17",
+ "vector_backend": "numpy"
+ },
+ "generated_on": "2026-11-04",
+ "privacy": {
+ "contains_answers": false,
+ "contains_customer_data": false,
+ "contains_per_record_fingerprints": false,
+ "contains_prompts": false,
+ "contains_raw_questions": false
+ },
+ "runs": [
+ {
+ "boundary": "Deterministic offline retrieval fixture; normalized-character token estimator; not external QA or provider billing.",
+ "command": "python -m eval.chunking_eval --dataset eval/datasets/longdoc.jsonl --k 5",
+ "config_digest": "c1c8196aa7e1568ef3844a9fb2d76b87f342c39108e32d6ad144b885a76143b8",
+ "config_digest_method": "sha256(UTF-8 exact command)",
+ "id": "offline-chunking",
+ "result": {
+ "chunked": {
+ "max_stored_tokens": 59,
+ "mean_context_tokens": 214.3,
+ "mean_evidence_tokens": 42.4,
+ "memories": 24,
+ "recall_at_k": 1.0
+ },
+ "context_reduction_pct": 71.1,
+ "documents": 6,
+ "k": 5,
+ "questions": 18,
+ "token_counter": "engraphis.chars4.v1",
+ "whole": {
+ "max_stored_tokens": 213,
+ "mean_context_tokens": 740.3,
+ "mean_evidence_tokens": 162.2,
+ "memories": 6,
+ "recall_at_k": 1.0
+ }
+ }
+ },
+ {
+ "boundary": "Deterministic offline CodeMem fixture; serialized JSON-shape payload proxies, not MCP transport responses, provider billing, or latency claims.",
+ "command": "python -m eval.performance --dataset eval/datasets/codemem.jsonl --k 5 --iterations 10 --json",
+ "config_digest": "bbe4aca81e58d4830e50a8fc7729a1d15b71d97a6299bccd79432b7f119677d7",
+ "config_digest_method": "sha256(UTF-8 exact command)",
+ "id": "offline-performance",
+ "result": {
+ "answer_token_recall": 1.0,
+ "compact_serialized_payload_tokens": 11138,
+ "dataset_cases": 14,
+ "full_serialized_payload_tokens": 24590,
+ "hit_at_k": 1.0,
+ "k": 5,
+ "max_context_tokens": 108,
+ "mean_context_tokens": 85.38,
+ "memories": 44,
+ "packed_quality": {
+ "answer_token_recall": 1.0,
+ "hit_at_k": 1.0,
+ "recall_at_k": 1.0,
+ "sample_count": 26
+ },
+ "payload_boundary": {
+ "kind": "serialized_json_shape_proxy",
+ "mcp_envelope_serialized": false,
+ "token_counter": "engraphis.regex.v1",
+ "transport_measured": false
+ },
+ "quality_scope": {
+ "packed": "packed_quality fields score only chunks admitted to reader context",
+ "retrieved": "legacy quality fields score all candidate chunks returned before context packing"
+ },
+ "questions": 26,
+ "recall_at_k": 1.0,
+ "saved_serialized_payload_tokens": 13452,
+ "serialized_payload_savings_ratio": 0.5471,
+ "timed_recalls": 260,
+ "token_budget": 1500,
+ "token_counter": "engraphis.regex.v1"
+ }
+ },
+ {
+ "boundary": "Deterministic offline support/abstention fixture; not a frontier-model answer-quality score.",
+ "command": "python -m eval.grounded",
+ "config_digest": "590442e51e3642c10489165759919dc86ffac62c182937330c153e7f8d5fc26f",
+ "config_digest_method": "sha256(UTF-8 exact command)",
+ "id": "offline-grounded",
+ "result": {
+ "abstained": 6,
+ "answerable": 5,
+ "decision_accuracy": 1.0,
+ "grounded": 5,
+ "off_topic": 6,
+ "quarantine_hits": 1,
+ "quarantined": 1
+ }
+ }
+ ],
+ "schema": "engraphis-public-offline-fixtures/v1",
+ "suite": {
+ "digest": "93d671089dd360954fce2c97ce1fdd105f87166ba9224e644d61ce15f9857e79",
+ "digest_method": "sha256(canonical compact JSON mapping each sorted path to its file SHA-256)",
+ "files": {
+ "engraphis/__init__.py": "1cde9f19e05ddc4f5f28517e00b0a7bd5ebf09d6be8ec2681cebf5b698195d70",
+ "engraphis/ai_context.py": "4dfd5d39eb95d05c591d1981e9e855eced73232f53efd9fe9966e0e08957d030",
+ "engraphis/app.py": "44d68ad8c0ff46baed01978c9b04e40b8be5031609d5a69e205b7f05da3777fb",
+ "engraphis/backends/__init__.py": "a9f22b9278362904166614081f1df78469d453601b298ce4e8afdba8a3722b25",
+ "engraphis/backends/codegraph.py": "83e723a91068d23694092fbe00157fcb2597061d453eb36f955bf55bc5b4d35e",
+ "engraphis/backends/embedder_api.py": "56a6bceea4f757325dcea987b0339e41d3875634b1ae5103f0537a358cf5878b",
+ "engraphis/backends/embedder_deterministic.py": "ee2476c33ba83a9bba5d7caaa4dbb40861ee22cbdb1cf3c1092ca6c018a6c38f",
+ "engraphis/backends/embedder_st.py": "c497b47038227516a0bc43d4bc0a91fc46d267bbffb0382c0b893e44f016d6b4",
+ "engraphis/backends/encrypted_db.py": "25f6c1480d296a88f317213700a8b3c81e2732399bfac464b0d893465b25e846",
+ "engraphis/backends/extractor.py": "f2e3455ab7f14caee1d5b5c4ef071e498e90118f1b0ddcb8510c969583b0fc57",
+ "engraphis/backends/graph_extractor.py": "88561efa0d3fabc447a0a005b10e36261379d46218cf62d905e6928cd2fda676",
+ "engraphis/backends/jev_decision.py": "31d4a789a013226f5be3eb47c54d3ca34af09b1c77b7c9ab1f57622035951d08",
+ "engraphis/backends/jev_query_planner.py": "c64818474c15acf5d42f673f8084f0fbf782e78b23b1f8cb9a32d6d044d5af63",
+ "engraphis/backends/jev_transport.py": "e65368169d53998d3e02cc78a42ed7c92fd27749cc1c9b4f95aa856e618a0ee9",
+ "engraphis/backends/model_source.py": "8c3c7681f95214a2bbabd8de222e5ee11f42fe13402d27365654ae75fb363d4e",
+ "engraphis/backends/postgres_schema.py": "8468578c3add701d30d5eaa36d768ded2375d116e55f1836e09f6107a07a267e",
+ "engraphis/backends/query_planner.py": "bbdd77afc9b5523421b85b2ae63c8da7f5a7b777265450e0d21708a83e7bb23c",
+ "engraphis/backends/reranker.py": "747761d6cbfa421388974bcfd98d844f92391d80f4bf6a4feca00b0c7a6908ca",
+ "engraphis/backends/resources.py": "47cc867c3aecc8bd95fa284bc5bb04715f3339c19a0a11512973ef6171c95944",
+ "engraphis/backends/retention.py": "381d9371e3951d762f8b55eb54711de5697642acb39de99a714f246c059ecbd0",
+ "engraphis/backends/sync_folder.py": "e4f70a92a17f6a365910670df041e6e3ca421d44ada2827917cd66b4dc067bfa",
+ "engraphis/backends/sync_relay.py": "88a24af68641cfca42a4838ded05337dfe01e3364dcfd39ad81ed4a658433f2e",
+ "engraphis/backends/vector_numpy.py": "c598831bea547824cfe08844816fa79857d3617cb0631238f95dad955a424f72",
+ "engraphis/backends/vector_sqlitevec.py": "6148e14ceaacc19239b64c642a3fba0e98797c78cec356210157afadf475a08b",
+ "engraphis/build_info.py": "624c22471e56d4c4047160808c4245488292af611564d1a63ca437605bbb414f",
+ "engraphis/cloud_authz.py": "6d86e17c4bdd4d3897ddc4b8bae44059d47c8763860745e37ee94fa10cdae7be",
+ "engraphis/cloud_features.py": "ffc7dc0fa690b94248c24807497784407a262365f110514e6a193724c7f4f55f",
+ "engraphis/cloud_session.py": "cc6ab27c6af69b5c676e721ab06c520730e205f5bbffe27818650edfb71d4b63",
+ "engraphis/commercial.py": "184f312066a9e682e51a0abeff042f1c0e8eed2d47470157b23930b5a17633aa",
+ "engraphis/config.py": "7a1aa04246bda341f26866c5145854412e8c157d9880212fe1d7579ac986b764",
+ "engraphis/core/__init__.py": "dd5143729c3939237f04636f437032b1f2d3a5f7d82c91bbc2a5a283c3f0ebaa",
+ "engraphis/core/adaptive_context.py": "cc5ce48109bb0d5230a5b2b8424b829c853feec5b5d5b82596413f2279b0c9f9",
+ "engraphis/core/browsing.py": "cfae752d52ef51b17c4ffbe44dde62d5b0e1ed0ca34ec0e1788f3991d94bf05f",
+ "engraphis/core/codegraph_export.py": "4641074258d7b23498f92dd45053a0fbb111863eaad2001c08e5e3c2dc2fd54f",
+ "engraphis/core/conflicts.py": "28530be25a4af0bffd8f609b965b33ca7f93199a70887789b2148fda8a61a486",
+ "engraphis/core/consolidate.py": "f66eedfe08319a64b761261ccf1c99b163eba564d6d4060534bf8c575aecf232",
+ "engraphis/core/context.py": "8cc15746e4de88bce7d34db8c11dfaa545f17305788e2167a286179171717fb9",
+ "engraphis/core/diagnostics.py": "5ba449bdf5087d8ccb5808379596e695c8f405da497372e99f2bac509f96e8e4",
+ "engraphis/core/documents.py": "9b4a5dce557bbd25217c61b51d5838a8b7c693c77f7a700d010ed607cb39b150",
+ "engraphis/core/engine.py": "c908dea96113a97dcd56679f62c9e587b28130204888b16e4c8e64d68f47d3fe",
+ "engraphis/core/evidence.py": "97912b52a3d22de909218f83c09c92572ed695d7b3c2117e5330271c6d84a7bd",
+ "engraphis/core/fsutil.py": "6db770fa8bd3e1a57dfa70eb8e8bc46d48c2dc53ead1b0b43eeecf085ef58cfc",
+ "engraphis/core/graph_layers.py": "64d74ab01c77119f6343ba6f1d6a84f9653f1a5d34d47ce7966f3ac31b29d2ea",
+ "engraphis/core/graph_policy.py": "ec5b373d01adb2de87df31d9f543130018e9a73faaaed239a184f14a32646615",
+ "engraphis/core/graph_scene.py": "b79c53e156b11102d64196ac5082c6549c13fe0cc57b9760d66872459a0f2110",
+ "engraphis/core/graphrank.py": "1279a58396104d3f906bfd5ec75b32efedfefe52201467bf19d80be3517017a5",
+ "engraphis/core/grounded.py": "0d6b60ef16f3cd65b7032bdb60ba853dac364d0df58a74c932ecf12aec98e4b7",
+ "engraphis/core/ids.py": "e47eeadfaf560bc7638e1879fc491fa82d981cd42b9e4b05ef87033b2d7dfa60",
+ "engraphis/core/interfaces.py": "bd5f7f4751a035348cf99cae3e8be64f205cb2cb8fb6eb3bd7e56f5099d81f0f",
+ "engraphis/core/mutations.py": "3d32b63f530204ae0394f3ebafbdf667cd86143a863ca808527d1e754d3a246e",
+ "engraphis/core/obsidian.py": "39fa18707972563581b06fa129e03841200b8bc05a114876cbbb7f352a885bee",
+ "engraphis/core/poisoning.py": "e91b6b23d2717680b3f25a7a540ba04e19e815e4ce4744bdb70da9bada21e031",
+ "engraphis/core/query_planner.py": "249062d67392ab7c203cc71e9040e99bee91bf570604e90949149a93cb652120",
+ "engraphis/core/read_snapshots.py": "be08e63a88bd38ed91d61b28657a65201c38994856db2798b209a73151dd202a",
+ "engraphis/core/recall.py": "b56dc6736414cab451bb195df7948a6088c7ae5e14f5961d57daffb4a5bafb28",
+ "engraphis/core/relocation.py": "86bbd292b374539b480dddc1a9ac19292fbc17ff9fa8e1fe6ff69c91ee7c8bb9",
+ "engraphis/core/resolve.py": "f01a6f55e44320ab04b97e516342f20155668863b2fc4765305e066d87586524",
+ "engraphis/core/retention_policy.py": "864c03bdb6e743cd0002c706de471e920f1fa1f1a9918ab343ef4c2042b47429",
+ "engraphis/core/retrieval_policy.py": "d169eb442115bd06c1e1da776fa6c1849e0795e658edd732a8d6821326b46b03",
+ "engraphis/core/savings.py": "e34a8f4a26c313a1e439dada16b7fd4a0c3fada2038e2f4aed03978b53310bbc",
+ "engraphis/core/schema.py": "8384672f2d5406abc5969a3e134e14684d71dbeb44685f8198023b8e3f03be77",
+ "engraphis/core/scoring.py": "bf0cf02521ab3dae6de4a0e7c9892bf3feb02f73de22478c7fb0c64ce55ab0d3",
+ "engraphis/core/secrets.py": "78ab40870d6c5918c189307028b107df239d08cd918abe08c85fa15b58fa1663",
+ "engraphis/core/source_paths.py": "2e71de7f19f260b335137602f07a28a80efa5d65e3d2497215356ba5f172b47e",
+ "engraphis/core/spec_crawl.py": "23359f9e28a2a68a3abe59f1e705b8be1f6c72880d708123bc85391029e769e5",
+ "engraphis/core/store.py": "b04fc2cfc162539e54d80691f84dda4b4f454f2be1d31027776d5e49655fe8e8",
+ "engraphis/core/sync.py": "0d28dd697eb8e8acdb7fed3e346fcba8fce23cbf4606eb11de68cb1fb001fcf2",
+ "engraphis/core/textutil.py": "acd65031729fa5d91d09527b8eb52518b83cfce77a55e6b7ae2d94686e35c1a6",
+ "engraphis/core/user_model.py": "3147ec8ee7cfd855783f639874b63f331cdc822bd8bd9298cfb326dd18666026",
+ "engraphis/core/vector_repair.py": "a8c1812de4e3ed288eda3e54a505e136d6ebd3ec296788bcbb8804b11e13cfc8",
+ "engraphis/core/vector_search.py": "75800052e573e9af01c6fd098eaf8647c3c45cd7eb2601dae05d42359e19b234",
+ "engraphis/dashboard_app.py": "77546609aa29031dbf935228ee00162b3635f4c3438d2a5922bfe94eb211a084",
+ "engraphis/dashboard_assets/__init__.py": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
+ "engraphis/device_connect.py": "c8cd0a22e9fd2d92a65bd74047cc3a8b7159bfc9a297cb639c1c7f1699f8802e",
+ "engraphis/document_import.py": "5378d17355cbf97db304ea8ac2d1978206b244b553c7e0645f8a060d272664b5",
+ "engraphis/engines/__init__.py": "111232af583889195c5f5a60298e32484348608e81d2bdd822fd3ecd2a33c1e4",
+ "engraphis/engines/embedder.py": "998b65dd566966bb6581fd9f09cdf46c58a3923ef5df3073ccdaa541153a3581",
+ "engraphis/engines/ingest.py": "1a5d4b52c13e533864329f9fff11c0f6ebc299ff7093a9d9275e5c9626a39ce2",
+ "engraphis/engines/intelligence.py": "b561589b98deb98271f104dfec6276aeba13c4815e627259be5f8769f2e7ad82",
+ "engraphis/engines/recall.py": "f979580d065599c07acbc3add59f71e52d9a186a2104b0e257daa7959ea88d26",
+ "engraphis/engines/reweight.py": "91ec5815f5d356a7068c36133d24405334450f361f902f99275bed3ccebffd49",
+ "engraphis/engines/thoughts.py": "4adb9c8a9bcfe736cb42fff9b5ce24631da6ec473d178e83f1c176fde3b8b814",
+ "engraphis/entitlements.py": "05a4826de62a3b0ad061be4325f502a5928c6db78ea118e32ae5ac2c8f480717",
+ "engraphis/factory.py": "be26899074d80b2f96fff9a5a70969894e818dc71e2cfdf4f7ee965f1f4cc550",
+ "engraphis/file_guard.py": "44eccaeca3afef7149bfdfea2321c325472253ee39a9e0bc24c720b3da3d685b",
+ "engraphis/graphdata.py": "f5dc93395f52203c66deaeec8bc58d5c1f990a35a535151e0b9a84cecf7d8430",
+ "engraphis/hosted_client.py": "6a81afd9b40fb783cc9809ebb2dfd23539bc8ae614e03ad9a5b98ed5014fefcd",
+ "engraphis/http_deadline.py": "bb29ac2d1e2fb49829e8484bb17f8abe98c23895ef17c68abe135d13f5df0099",
+ "engraphis/http_security.py": "5cd96551778d7a964ce1d87c6bce032a5e70c7a1779fb2f6731432b96f5f069b",
+ "engraphis/inspector/__init__.py": "720cac28b8a6019d0a0c53809d5905b7d6eb9d4ecbac767505904c6ec3c39071",
+ "engraphis/inspector/app.py": "8aefe8a935397dde3c6466e691a7e6a7203ebe42e07aa00c332b733465e59e04",
+ "engraphis/licensing.py": "7e73c28b0e1c3536e2080a129af614f838d2cae3ec3a48a3d8ed7e5153c9e935",
+ "engraphis/llm/__init__.py": "f3096d2ddd652b99e6fc0b4c5a8786d9bbaba41b259df0b44ce93b57bde3a8b1",
+ "engraphis/llm/client.py": "0c84000033d3f85b0e0161f100c271c35b591e1480701455ec65c7d3f111ec42",
+ "engraphis/local_auth.py": "8fd6d42594454d68c99bf25054adcfbf2379d4e08f17affd36af994fc7ad78bc",
+ "engraphis/logging_setup.py": "f7d2edc756458a852e0401453e9c71b785aba08fa7859aacbdc23fb30dc7982a",
+ "engraphis/managed_processing.py": "6d33cdfd10800d9552fcfae3c2b071d2b39fe10fb69d1a8e5d9ed6026882197e",
+ "engraphis/mcp_classic_cli.py": "778122b121a1c654b8f85810fad05d9c8d7799cf13903d7dc274938b51008da0",
+ "engraphis/mcp_cli.py": "f94e63805aa05cb913e48200819473b86b9623b2b958578f41f025adb0f1a21e",
+ "engraphis/mcp_http_cli.py": "c0605c53ff53ffde8d98d02ca2812e3c2c792a34e260b0a197433fb0ed2455af",
+ "engraphis/mcp_server.py": "b1a35b28e81ac44c0812d2569e662b1626704b00c7e6dce67cd70428cef25bea",
+ "engraphis/mcp_stdio_bridge.py": "a2e64883f4737415c633e9a69c1f3bdafe1860e1ee97d579dfc16814dd36c480",
+ "engraphis/models.py": "6e76e97db0aca3805c6f582ea78cc6e1c0ccd2665e16fd8ab81eb91b94141b51",
+ "engraphis/netutil.py": "15cd7c9d0792b5a95d0a722d94e433cf6d07182777f26bdf19f047270673b08b",
+ "engraphis/observability.py": "a3a6945bf33a0d8e216da56ca7efec0031b82dc64a66cff36f0b2161f5db4981",
+ "engraphis/obsidian_import.py": "cff5d117a592ceb8aa822e60a6142878da2f3aec1fcba44a5d64853c7bb0cee7",
+ "engraphis/private_state.py": "7485570efcaee8a1dc00b64ebfdc23178517235ae17609aa78db8bdda7e45fe3",
+ "engraphis/read_only_api.py": "dab1721cf39b7d374462002f5b770d53510a642d955eb4c9a972969e39841bf8",
+ "engraphis/redirector.py": "5ba964b81f09008c9369180cb49d247519000763274aabc5e046215b12fa641d",
+ "engraphis/routes/__init__.py": "f0d59080212cfa0d9b50877bca28e5832d1ae917899500d52c68b1c8821231af",
+ "engraphis/routes/memory.py": "9ca066e1762eeeaafd9790ed57d730efcaceb57742dbe0c830ad8764500ff9e5",
+ "engraphis/routes/v2_api.py": "647b9ec769127849da0a148c0f520766407e01e2ccfc73952ec332e25fdfa4e2",
+ "engraphis/routes/vault.py": "503304c177cf4d81bec5a9836731c0c32b62159175dea6d0fb8ce30c97a2bc6b",
+ "engraphis/service.py": "dec62fa2912c19b13c904e1d7585babc5c55edfd995846f0bab33d62a20dff85",
+ "engraphis/service_context.py": "0cd84538ca18b07d6440fa4cb0147510eacef2af5cf04b7a0519f2ac15a7ff87",
+ "engraphis/stores/__init__.py": "48ee4326c8f28eecf46f558b7aea21adb779226a7038299017ab90196d5d84be",
+ "engraphis/stores/graph.py": "ebf603b54cf8450e7c9a7319bd05db2f39fcda8491f5969d6af3e8da61571bc2",
+ "engraphis/stores/ledger.py": "df5cbb30d977decc0a9c3a2365c9c115cfbb5fe951bae48446436661c80e5d30",
+ "engraphis/stores/vaults.py": "2c986129b9d1e7aab33e18a3b9a278eb5ad2236895587f69b5816a5d96796bc3",
+ "engraphis/stores/vectors.py": "45a1baca381fc647548cc89424eb36d5853f7562c275b191b35b99339a190b7d",
+ "engraphis/update_check.py": "ffbf5ef682fb15177915073ebc0b5eab4dee9092bbf0f58d36a8a54c6605ccf4",
+ "eval/__init__.py": "639f0c6d9d6aac8ff6dc605a34a0a301058905cc53bff4eaed5912247f0e7c56",
+ "eval/ablation.py": "16f159dee75d2f96cc42f230c2403fa19ea0bda7091c4823660da553463a194a",
+ "eval/adversarial_memory_security.py": "35dd8d981bcbad50e9815465be420b05b62a9dea28a78eb6cc1e09ec51c320c4",
+ "eval/agent_benchmarks.py": "8e88943e45a1b2083b366cbb0329326ef918b119a2366ed395c404c2b7ef98b0",
+ "eval/benchmark.py": "b71832affdf87d23bc1db7b522a669a7888571b3989b03a235c70f5da9952bd6",
+ "eval/benchmark_analysis.py": "1200791425d029695fee0b7da1f188a8962f337aa31eb4217d50e4503641d2b3",
+ "eval/benchmark_campaign.py": "033dbebfb47df5fcd3a6588b29ea4ab5fd44d4387ae45334379278ab30f3e57f",
+ "eval/campaign_adapters.py": "2bdb51de0dcdfceb1be2a649d84541780ac4d1fc0f9b7585004d87490073213e",
+ "eval/campaign_api.py": "323be4e1d9520e8047ab54c9cbb158013785402b06ee4e93e55227971c54bf95",
+ "eval/campaign_candidate.py": "88541840d16c3b7368586ed0ee3566054214b1f594826b746b28d0e407cb812a",
+ "eval/campaign_continuation.py": "7f77a20ac8f85cd96729bba0cf45872c98a3421af005bd735434b7b416dd7e1f",
+ "eval/campaign_ledger.py": "ba3079ba541cb1f70929a76f147d2bc5e8b264103a1cd7faad97685e0ecc7790",
+ "eval/campaign_oracle.py": "9ea5479786efd9caa2b59e612e891a93dd34b500a2b36aa20bcf749fb6d4d48a",
+ "eval/campaign_storage.py": "ebcd1f4aeceaa5ff9304494c64dd31fe31637ceb107d8cc39fa292e3b14055d4",
+ "eval/capacity_matrix.py": "8c25bd97754c1d7a8468a687042a3c87cc9c0299cf01c19b80dacfec2be4e552",
+ "eval/chunking_eval.py": "db5c39d98ad7272d25e2f33f39779da74a0d558798441561196bedbd38882e49",
+ "eval/closed_history.py": "beadb9dbfa97af2ca41efc737f41e8758f4c4635f468e6c7a2ac5c0c95f73f3d",
+ "eval/code_agent_ab.py": "d98bba6b77700ff6bf86ff0d2cf518a67e5a2676ef51bbddabbb2ff26e1f3aaa",
+ "eval/code_arm.py": "d211166fce1b8a4173848e1617873b7e84aadeff43746effe7483c54bbdb6f1d",
+ "eval/codex_oauth.py": "f235fca482de4201d1850bbfb583765ac5f1a057b4ad7e2d504d036bfc03f391",
+ "eval/coding_acceptance.py": "4b39cbcb60d7fba503cca597399cf9d04ad9567a43cdb950015ccf552e6a2773",
+ "eval/coding_corpus.py": "7b5205e8544578fe99d9d9cf6cfc34e40e5d1d049238d502ff7bc6f3c14948f5",
+ "eval/consolidation_ranking.py": "917b578d4e0bcb929bf1a1a37611acf7716a520c076abf4ade0d8d12a1c455de",
+ "eval/context_economy.py": "709ac7cc866855f96d7717ab2bea12e8b0d3a140d15fb978a2a929ad085931f2",
+ "eval/context_efficiency_guardrails.py": "22afd1a6fe17219e74701dc587ec35f569a5bf22bea270944525f51746723f14",
+ "eval/datasets/codemem.jsonl": "341313023c22850a2e14f02742b571ad1deca824f886a1654a59541304c01f3c",
+ "eval/datasets/coding_memory_v1/oracles/atlas-green--code_relationships.py": "969aa71235e0ae3cb764b9ee12b789184cedb5fea6fcc0d86e5dcb2c0498fa56",
+ "eval/datasets/coding_memory_v1/oracles/atlas-green--condition_values.py": "648702646fb62990168e98fba0dc6a27be128876548fa7035a59d71aee9da02d",
+ "eval/datasets/coding_memory_v1/oracles/atlas-green--corrections.py": "c7132c7fe39b7afbdf11065037369c0f3c38a22e69d8742e8afddfd5785d2e61",
+ "eval/datasets/coding_memory_v1/oracles/atlas-green--long_documents.py": "65dffb25944cb612872655f12db16af73f7a095e7abcf6a7cc5d31dc91f083b1",
+ "eval/datasets/coding_memory_v1/oracles/atlas-green--multilingual.py": "ff49874a691bfc30c082695ee6d3c89b9f2e1e5eecf086026110770d8e1b0452",
+ "eval/datasets/coding_memory_v1/oracles/atlas-green--paraphrases.py": "dbdcf5d7abf136aa5816df8465bfde842284aef5ef002e955cee05ae69d574ca",
+ "eval/datasets/coding_memory_v1/oracles/atlas-green--poisoning.py": "58b8ca4a37f55c4d112640414dc82620e32e643ab0aa539260884a5ecc50740f",
+ "eval/datasets/coding_memory_v1/oracles/atlas-green--scope_boundaries.py": "c1e92b10b40004c95abd3dfbada1a38fe0ce56300ecddca8360b51a1e6671458",
+ "eval/datasets/coding_memory_v1/oracles/atlas-green--temporal_history.py": "a64e4e5a29ce1040cf53e94571491325347f443d72a93f05bcf87c2878665952",
+ "eval/datasets/coding_memory_v1/oracles/atlas-green--unsupported_questions.py": "c8b415052ea7320e1df3c5475a676d4dd24602c45dbf1868fba6880846e029ee",
+ "eval/datasets/coding_memory_v1/oracles/atlas-north--code_relationships.py": "bb4305d15a81b4b4ef80b374acb59fee6fc32b213b101dd4b173ac4144d8570d",
+ "eval/datasets/coding_memory_v1/oracles/atlas-north--condition_values.py": "4bc5b1aff48ef6ad5ece1bd915f6949f72d365bb4815c4d82515b438bc644112",
+ "eval/datasets/coding_memory_v1/oracles/atlas-north--corrections.py": "7851502a9bc842f840bee1001e42140ee1df08be2730b563b1b197285d14742c",
+ "eval/datasets/coding_memory_v1/oracles/atlas-north--long_documents.py": "6204123eded20fc0c7aa59b312f8b5bd29aa88a720f7318e32b3c6a17651b57b",
+ "eval/datasets/coding_memory_v1/oracles/atlas-north--multilingual.py": "5c989b34fdad1e076b61ffb1f7a1371dc635f44a537abc743b765d5f6009004e",
+ "eval/datasets/coding_memory_v1/oracles/atlas-north--paraphrases.py": "fd189285d0cde9991dea461af9cd18b4203656a69da1298494fccf972ed8a58b",
+ "eval/datasets/coding_memory_v1/oracles/atlas-north--poisoning.py": "66305d69419736bdbb11a896543933604d898cd5394d94eedda8b92e632a09ac",
+ "eval/datasets/coding_memory_v1/oracles/atlas-north--scope_boundaries.py": "d9fbbe3924b5178f32f1937485af3b8db242a0e3a08c7bf41a3d2a57f34a8879",
+ "eval/datasets/coding_memory_v1/oracles/atlas-north--temporal_history.py": "7e7be0d6e558a71d3baad14a8812a6c998ad25e3f52c997a301333e0711f820e",
+ "eval/datasets/coding_memory_v1/oracles/atlas-north--unsupported_questions.py": "91f574dd678bf244a1fa6f9708e945259bb8ff8e2eb213426be5c3b187be3c3b",
+ "eval/datasets/coding_memory_v1/oracles/atlas-violet--code_relationships.py": "d0998e13db38d1fae9be6d254431b4fe592f89eb03ee25705f4c70985cd48c89",
+ "eval/datasets/coding_memory_v1/oracles/atlas-violet--condition_values.py": "f97610aee190dca03b01aa48ffe4aa3f75e099a59fdd867db98f743f4a376729",
+ "eval/datasets/coding_memory_v1/oracles/atlas-violet--corrections.py": "b7c31f7a9a1f978321ca5445997e0d284deea770c85f6d81a80204e548ad53ea",
+ "eval/datasets/coding_memory_v1/oracles/atlas-violet--long_documents.py": "20ec2b41cd3b6691164bce096dff16f7bf5be674f922cb637120c0347e60d0b6",
+ "eval/datasets/coding_memory_v1/oracles/atlas-violet--multilingual.py": "131e7dc978d3f2f7df395b6f1b4a9150051c0ca93c6dc5c386e58d46c8fdc573",
+ "eval/datasets/coding_memory_v1/oracles/atlas-violet--paraphrases.py": "1e7d9da36e6634ca2d2b11dc97468bd671df133345dcb76859e1dc3cb77dc664",
+ "eval/datasets/coding_memory_v1/oracles/atlas-violet--poisoning.py": "48460691ac0086c47102eec85387ac03bee9190ac83e3dafc4babc79b71d9bd7",
+ "eval/datasets/coding_memory_v1/oracles/atlas-violet--scope_boundaries.py": "b96207e9b1fd09f49218102a5b88fde5b9656604df4cfd314e86426e3b6d6075",
+ "eval/datasets/coding_memory_v1/oracles/atlas-violet--temporal_history.py": "7a196ff9a1db05b0c8f3a83086ae44cdc580dc58e4acc217d1db2575d9711b88",
+ "eval/datasets/coding_memory_v1/oracles/atlas-violet--unsupported_questions.py": "724564b5b75b9425b6889faae6d5e650bd5c940016c2b96054e5359d9d8b0887",
+ "eval/datasets/coding_memory_v1/oracles/atlas-west--code_relationships.py": "14295f735bcac4d4ae738eedfbd8fad4593afc914bc835233ec35e2263d43613",
+ "eval/datasets/coding_memory_v1/oracles/atlas-west--condition_values.py": "8ac70d9ca0c9122567bb9986d2cc5798104966df20e262b8087920e8fb5d82a2",
+ "eval/datasets/coding_memory_v1/oracles/atlas-west--corrections.py": "89d6a1a7781465c311830b7e769d2a76df41ab5c703ff662b3df1cc4c9a01668",
+ "eval/datasets/coding_memory_v1/oracles/atlas-west--long_documents.py": "38e67a26f69707089f852657a810ef1fca0395beb5511fff67c30f21ed63df57",
+ "eval/datasets/coding_memory_v1/oracles/atlas-west--multilingual.py": "bccf6579681d45940542ccb1f936ea1b225f4cf551a3ef31f3f8b1a026856e51",
+ "eval/datasets/coding_memory_v1/oracles/atlas-west--paraphrases.py": "89c8ce16c682553b27652e176b67a06f3228ae6209fbc8a7264260680f5651a9",
+ "eval/datasets/coding_memory_v1/oracles/atlas-west--poisoning.py": "fe9beb97d27eacdd87056fa1c9b04c62e2facae9354e765b912de60b35021621",
+ "eval/datasets/coding_memory_v1/oracles/atlas-west--scope_boundaries.py": "2eb3b1130c54644fcbade52da8f50cad1376a07b51856a962522913cbcbb7649",
+ "eval/datasets/coding_memory_v1/oracles/atlas-west--temporal_history.py": "116ee20af475da329cb535b0a02bd19ea1e7518b5b780ec12ba396ec896cc116",
+ "eval/datasets/coding_memory_v1/oracles/atlas-west--unsupported_questions.py": "080969e026215b9fe4ede6d0b4d02e9143e1293bea5d7383681e478426ddf121",
+ "eval/datasets/coding_memory_v1/oracles/borealis-green--code_relationships.py": "3965671e5e2432c8236c250f7982d4f478d8f1fe1268ffe57c555cf5fbbcd414",
+ "eval/datasets/coding_memory_v1/oracles/borealis-green--condition_values.py": "92994990327445258984ee6bf1d95f47359ff4b194e1f36e193980a523e6c5e0",
+ "eval/datasets/coding_memory_v1/oracles/borealis-green--corrections.py": "c5b473f962deacd6d59ea9967626e029888abbdb48f320704bcb6de876ce3f46",
+ "eval/datasets/coding_memory_v1/oracles/borealis-green--long_documents.py": "54aee2c1031c17b5678db2b0955eda8fafe5990aa9e52f02936584a9b75b9ab1",
+ "eval/datasets/coding_memory_v1/oracles/borealis-green--multilingual.py": "c458ac5c63e440e3d9486d096278759920d636f73e1901258679251afd86eb60",
+ "eval/datasets/coding_memory_v1/oracles/borealis-green--paraphrases.py": "250d027660f268cef1caa745cf3155de6e2f4adfaed02ba675eaa83224ffeee8",
+ "eval/datasets/coding_memory_v1/oracles/borealis-green--poisoning.py": "978f16eb5033c602ec09ed57ba0667edec9ac3e26accc1ce1456bc893ac9e6a7",
+ "eval/datasets/coding_memory_v1/oracles/borealis-green--scope_boundaries.py": "4b73a2e5dc5ac815c14e08a34acefd2ad426282cfae379ca2fd3060a3784c9b7",
+ "eval/datasets/coding_memory_v1/oracles/borealis-green--temporal_history.py": "6636d511bbcca51ca85c4eb0ec5ae956b3baaa5f9af89458ceb86329115743bd",
+ "eval/datasets/coding_memory_v1/oracles/borealis-green--unsupported_questions.py": "5cd4f70627bc60572c720a797bff122ef9bf219f32d265f694affa33b1f9f4fe",
+ "eval/datasets/coding_memory_v1/oracles/borealis-north--code_relationships.py": "769903aeac3ce360d845ddf1a6e89192737dbb460e1f2e251230b74d40763dd6",
+ "eval/datasets/coding_memory_v1/oracles/borealis-north--condition_values.py": "f6e69f525fd240a09152989b9d8a529e9abe076e7fc2c98d10bd84f9c6da2b56",
+ "eval/datasets/coding_memory_v1/oracles/borealis-north--corrections.py": "987ece1c7507a69a973c1e051b27c1d3b104ecda79df48959e49a62da31aa137",
+ "eval/datasets/coding_memory_v1/oracles/borealis-north--long_documents.py": "011134140cc486a3c089b407adbfbf5b958e1f8c1bffd572c29b8cd5d7f07f7a",
+ "eval/datasets/coding_memory_v1/oracles/borealis-north--multilingual.py": "1d29536a4a5836f64f56adcd1a965db85bc3371a12ad5bbb5644520c9d9606c8",
+ "eval/datasets/coding_memory_v1/oracles/borealis-north--paraphrases.py": "09a5fad90c24f75f2f448854c8778aab896c67854c97f884c2d689ead808583e",
+ "eval/datasets/coding_memory_v1/oracles/borealis-north--poisoning.py": "c1bec2ed9b31103e39db9dbab2be8db401cb0874bb3ce8573a0841a054e28a46",
+ "eval/datasets/coding_memory_v1/oracles/borealis-north--scope_boundaries.py": "faf51b368d7ba9406442edde29e290c931e67bff2799783dfd27f2226020e15b",
+ "eval/datasets/coding_memory_v1/oracles/borealis-north--temporal_history.py": "da52f43530149a4a293065674623fa49f83b37cd84b9871044b3bf32762fd8c1",
+ "eval/datasets/coding_memory_v1/oracles/borealis-north--unsupported_questions.py": "be0abc698692c9fe82f40f76a4694c472882a53843894dddd631ab1ab77aae45",
+ "eval/datasets/coding_memory_v1/oracles/borealis-violet--code_relationships.py": "9497b7d2373bb9f858e994f7919cff72e7e3c4c4eaa1efda287524b1a182d1f4",
+ "eval/datasets/coding_memory_v1/oracles/borealis-violet--condition_values.py": "aea13e4b7721019e7bc1f54878bb25b9f27a8a5044fe444e86ba5460deb13e09",
+ "eval/datasets/coding_memory_v1/oracles/borealis-violet--corrections.py": "bfd446d431afb6c05ad9d8775b90887947b85981037756e05262f3b101d41e7b",
+ "eval/datasets/coding_memory_v1/oracles/borealis-violet--long_documents.py": "eae829a374c0cd6237fac4ec52fec0d98d7340ec0b307b9b0b0c90c116d4b472",
+ "eval/datasets/coding_memory_v1/oracles/borealis-violet--multilingual.py": "e821f91999b5f12cff53fdaa956af4e0bb2f929497f95b4e2ceb57a6621e58f7",
+ "eval/datasets/coding_memory_v1/oracles/borealis-violet--paraphrases.py": "1a99c15898831a1c9aacab951868101d57e59e91ed90a2777015a26dbcc3043c",
+ "eval/datasets/coding_memory_v1/oracles/borealis-violet--poisoning.py": "411877fc07b053201a63aa4e45cb499344abccb0a562c236d53a672078b1eb63",
+ "eval/datasets/coding_memory_v1/oracles/borealis-violet--scope_boundaries.py": "b198dfb636910953ba372edf1cd6db382d4e5725bf7f820171be8c63f9fedf7d",
+ "eval/datasets/coding_memory_v1/oracles/borealis-violet--temporal_history.py": "3de5efd6ef661572191c6aa426fb10caf9f4bed4aa495b6298815aab3acdce22",
+ "eval/datasets/coding_memory_v1/oracles/borealis-violet--unsupported_questions.py": "baf5dd85e86a97dedb0b395b5c574ea5ffcdad7a46bbbe9787cfdc33d6c7a06c",
+ "eval/datasets/coding_memory_v1/oracles/borealis-west--code_relationships.py": "48e53b3a561f91fdf4eedb2d63aad65eece8ae80188bb3d5bdcaf184fb0b6945",
+ "eval/datasets/coding_memory_v1/oracles/borealis-west--condition_values.py": "2dd7a524bd2a2aa2fbf212246880d4e4ecf03f503b70f25179f3ab6bab28a07a",
+ "eval/datasets/coding_memory_v1/oracles/borealis-west--corrections.py": "7d3080e05544e10243040fe0a969a8c524e4262f8f5c38abdc3d8e06c67fdabd",
+ "eval/datasets/coding_memory_v1/oracles/borealis-west--long_documents.py": "fc42e935879e7a81f6eef9fe72f8683244501ae10654008a7863d397ee73058c",
+ "eval/datasets/coding_memory_v1/oracles/borealis-west--multilingual.py": "19181c442ce5f6941485190ecf050e455914dae988f86efa42665cdef0d835a2",
+ "eval/datasets/coding_memory_v1/oracles/borealis-west--paraphrases.py": "f7a5cb842aae3d2bc8d62ea40325b332a8bd4f013b76bac8bf154fedb3dbcbe1",
+ "eval/datasets/coding_memory_v1/oracles/borealis-west--poisoning.py": "285a2526a6be4e556a564899e9a376ae1286fef6f0ad22eb9289fba1905a2b68",
+ "eval/datasets/coding_memory_v1/oracles/borealis-west--scope_boundaries.py": "fb994823743ff5b4853b142c17779778bdb11296958e033e5c6be598833a4cf5",
+ "eval/datasets/coding_memory_v1/oracles/borealis-west--temporal_history.py": "f30a74c85ad654a7450e20d07baceb3b4fb2a56b454d7b43b854bef3cdb2cee6",
+ "eval/datasets/coding_memory_v1/oracles/borealis-west--unsupported_questions.py": "01cbbd0b21a745720514aea6a21a98a3cccdbf0c0518bd5508019433b964776b",
+ "eval/datasets/coding_memory_v1/oracles/cinder-green--code_relationships.py": "8b08e7c65289515a9eee2ae5f4cd60ce94350e9a561225bef0e8f8815ea28f79",
+ "eval/datasets/coding_memory_v1/oracles/cinder-green--condition_values.py": "92e7d75d38f5dbcc685075e947005a9ae5a410207e7f17aa770b034b8690cbe5",
+ "eval/datasets/coding_memory_v1/oracles/cinder-green--corrections.py": "424cbf0924a8cbc1a59a6d7bc82b5527e41f52c8ee9f558ea3e398ab4d913919",
+ "eval/datasets/coding_memory_v1/oracles/cinder-green--long_documents.py": "2a52df9cef3ee66357847322794a382c927b828c6ab1a2819a799ff55d53657b",
+ "eval/datasets/coding_memory_v1/oracles/cinder-green--multilingual.py": "e47e791dd15b045a6a958ca114b1359ca8537e160274b015214746a2564363c6",
+ "eval/datasets/coding_memory_v1/oracles/cinder-green--paraphrases.py": "9e92a79ec00231cb381ce58e389dfdb2520a17ecf1e9c2e39e79501449e9ddd0",
+ "eval/datasets/coding_memory_v1/oracles/cinder-green--poisoning.py": "33213e6f194332969da88f4748c8f26ef93219b6c4bc710a142082f8147ccfcc",
+ "eval/datasets/coding_memory_v1/oracles/cinder-green--scope_boundaries.py": "8d2489da1fba137981e45e42c843f9cbd17e851c82ddcdc5904b4a86af0d9692",
+ "eval/datasets/coding_memory_v1/oracles/cinder-green--temporal_history.py": "29403fd08f0de7da3dfcc6c181e1f448a5c5264e95ff3924f3066508d85468b1",
+ "eval/datasets/coding_memory_v1/oracles/cinder-green--unsupported_questions.py": "ba21014188f9ae849e0bb79babe163194703c4cf01f0600f4c9f71058e7fed56",
+ "eval/datasets/coding_memory_v1/oracles/cinder-north--code_relationships.py": "46280659557bd08afff185352438bb63dfd308dabf5c7c1862c2f442bd171d48",
+ "eval/datasets/coding_memory_v1/oracles/cinder-north--condition_values.py": "6c62d673b1b6cb4b5c89ff2af4ecf57c2c9d73074a01ab92b8fde57815cab783",
+ "eval/datasets/coding_memory_v1/oracles/cinder-north--corrections.py": "8245b196072389517fad6d76ed7709d7e12eb52e08732e4ddcfc40fc1d986879",
+ "eval/datasets/coding_memory_v1/oracles/cinder-north--long_documents.py": "6790c7b89d80ab909d9e1a31b59bfa5cd199cddabed2e159daf7c5c049ff9a43",
+ "eval/datasets/coding_memory_v1/oracles/cinder-north--multilingual.py": "9532bbd14506cac0edcc09f474e7c96a402903de947e0e76f5c8aab4bff37646",
+ "eval/datasets/coding_memory_v1/oracles/cinder-north--paraphrases.py": "cc904350ae7d40e26d91d482ac15d87ec8022d4c82905021dd38003c5fa2d067",
+ "eval/datasets/coding_memory_v1/oracles/cinder-north--poisoning.py": "e8e451c49c98744b4a484141f86d337db4ec5326264b1500893a20c3451c5ae4",
+ "eval/datasets/coding_memory_v1/oracles/cinder-north--scope_boundaries.py": "50d42fc48aa70d55b6a3b39c9bf17437726f37e1e5ae9870d415fccad92c7bae",
+ "eval/datasets/coding_memory_v1/oracles/cinder-north--temporal_history.py": "14b1336fbaed11cad455f588bbe078effc31f5f357f670e1f189ec3d78b172f5",
+ "eval/datasets/coding_memory_v1/oracles/cinder-north--unsupported_questions.py": "0a2f740700a990d22b4c3051aa3e4260e3df4ce65931d419052f74e05c750a67",
+ "eval/datasets/coding_memory_v1/oracles/cinder-violet--code_relationships.py": "0e29053f11fe618c01fa90a274688f18ba8a7f3cfea56757a96d5a5bc95b61a8",
+ "eval/datasets/coding_memory_v1/oracles/cinder-violet--condition_values.py": "ca57ac4e22e9b4739217020d7792ac642326935b8db9305ead2ec5bea2a6e456",
+ "eval/datasets/coding_memory_v1/oracles/cinder-violet--corrections.py": "9edbfaa58a99776e835c325e50a77df5b4b302b3ddd0932dc2a349a77b4100b5",
+ "eval/datasets/coding_memory_v1/oracles/cinder-violet--long_documents.py": "4b3fbe7380201285ac4b4a1ec4d8238c5663b97af01cbdc8ffff8fdf2a9467c5",
+ "eval/datasets/coding_memory_v1/oracles/cinder-violet--multilingual.py": "b0bba5ecf669f3368ef29373b755dc1296502665584574c771ebf4664debb5ce",
+ "eval/datasets/coding_memory_v1/oracles/cinder-violet--paraphrases.py": "e146f7aa1ffabf3b313251b59c59e1836c0cd17901d07fc6ef5334eaccf8bf36",
+ "eval/datasets/coding_memory_v1/oracles/cinder-violet--poisoning.py": "692ee07e750fb08652a1ca6bc6dcbbef77219701585f1336c6037dae5a7c5767",
+ "eval/datasets/coding_memory_v1/oracles/cinder-violet--scope_boundaries.py": "8ca0b911d38a72e4ecce6871ba3d0c7dbab433b0541cb64b116f7a88d521554c",
+ "eval/datasets/coding_memory_v1/oracles/cinder-violet--temporal_history.py": "6ba5400c5435af64c80a7366fdf49fea132f1011f9d84f9d0cc5f2f2f30a67ac",
+ "eval/datasets/coding_memory_v1/oracles/cinder-violet--unsupported_questions.py": "1d3324db0a085be30ce3dff1a2ec3866b8592a5ef6b43f39211a2e3a59fc1cd6",
+ "eval/datasets/coding_memory_v1/oracles/cinder-west--code_relationships.py": "0149333a3fb0f8d60939bf927921305eae447f7511c74f042ff3c880eadff825",
+ "eval/datasets/coding_memory_v1/oracles/cinder-west--condition_values.py": "3158243f9f7f0b92aaee4164379159b46143acd11fc3d41a708630c1d33ea22c",
+ "eval/datasets/coding_memory_v1/oracles/cinder-west--corrections.py": "a9fbb89aa629a23e7a1fddf4fca09da40365bbc7a9a6192d805e498fe17f93ff",
+ "eval/datasets/coding_memory_v1/oracles/cinder-west--long_documents.py": "3c702c653a8ddff84eb5c24a77567aa63d2069c48ed174bcf29065c0ddb30ec6",
+ "eval/datasets/coding_memory_v1/oracles/cinder-west--multilingual.py": "b8f5f7e00e6edac3f8e0b7e8bc09ea6c7d661370fa2226eb1795306ce3c634a7",
+ "eval/datasets/coding_memory_v1/oracles/cinder-west--paraphrases.py": "10361be87e9d9cb98d2ff1bbc18252178d475a5c5f2efb77bc81dea05109df05",
+ "eval/datasets/coding_memory_v1/oracles/cinder-west--poisoning.py": "889a5a43604f7d1677ee0f28683abfc48a8a597b052f558c1b4d19c5f33444ec",
+ "eval/datasets/coding_memory_v1/oracles/cinder-west--scope_boundaries.py": "b84e78812baaa92422226c06d78cfe7c1be260e2bfcf9c5bb1ed73f2f3cc136b",
+ "eval/datasets/coding_memory_v1/oracles/cinder-west--temporal_history.py": "9d99a981fca0c801a0a52e2af24cd4262dad72f4f34cd84894784285d241bd05",
+ "eval/datasets/coding_memory_v1/oracles/cinder-west--unsupported_questions.py": "43b8f854961d9cca5cb83b53b9e07d22388d0c7db406dc8e4f8cfdaa5f89fffd",
+ "eval/datasets/coding_memory_v1/oracles/delta-green--code_relationships.py": "97a08f2bf9b1a11f95a062f7df27d69db3e9dbc421837347acfe62111af02885",
+ "eval/datasets/coding_memory_v1/oracles/delta-green--condition_values.py": "5e53695288b84a526c61b748b170f6d6219f94a942edc0887ea6157535ad8d52",
+ "eval/datasets/coding_memory_v1/oracles/delta-green--corrections.py": "c6d16232ebe6bdb0b5c94db1ba4f108257b47ee8f8b96cda5f8e17212022b542",
+ "eval/datasets/coding_memory_v1/oracles/delta-green--long_documents.py": "bedb6f495437beada344ce6cdd92954108aa4c639622fd1cc294d2a1139b26d9",
+ "eval/datasets/coding_memory_v1/oracles/delta-green--multilingual.py": "dbf84f4e8c016c5e60fdac5fd3ac2faf79fb0534f5c2ac534491acc31d5f3530",
+ "eval/datasets/coding_memory_v1/oracles/delta-green--paraphrases.py": "ea8f00c86e8fbc2ab10a448f48019146181d0e55438def6f47f41f0ef3181be4",
+ "eval/datasets/coding_memory_v1/oracles/delta-green--poisoning.py": "4eb4e289e944e7762c39ce85a8fc2582e310de6a86cbc1cc36b44112653140ea",
+ "eval/datasets/coding_memory_v1/oracles/delta-green--scope_boundaries.py": "2fe9b96deecfb488430abd9657adc6b3ea092b18f32c41c1669d29175ec6e860",
+ "eval/datasets/coding_memory_v1/oracles/delta-green--temporal_history.py": "531cbb953400584356cb6525bc1a2e559f7ec3867d845c736d8026a046b91e7d",
+ "eval/datasets/coding_memory_v1/oracles/delta-green--unsupported_questions.py": "edebf25c2168411fdefae1307a71d829224dba3dbc7ea000054416e52123d5a3",
+ "eval/datasets/coding_memory_v1/oracles/delta-north--code_relationships.py": "9357ad0d5f6a1cc787f980d0d506b07808538179f71ae3e8ce1c3eb655d24e35",
+ "eval/datasets/coding_memory_v1/oracles/delta-north--condition_values.py": "4199f7409696a260b1531f4ec7cfd15555b4d58d06b0b8a88b1c7621fe7d328b",
+ "eval/datasets/coding_memory_v1/oracles/delta-north--corrections.py": "b8bd58db7c5ae93d33e6223cc34f0e09d58831361ac013ec08277fe2c102ac17",
+ "eval/datasets/coding_memory_v1/oracles/delta-north--long_documents.py": "0f08b744446b1c52e8fe86626ab05c171377f17c46e604cb2ea647ed5d0daddd",
+ "eval/datasets/coding_memory_v1/oracles/delta-north--multilingual.py": "ada9eb404190d05637cea59e3f8ed541aea2c9e0f9be0d166885d6789351f7a3",
+ "eval/datasets/coding_memory_v1/oracles/delta-north--paraphrases.py": "04ec2e8308f47f46e38c89f4a2fe44deef4a0b20a1c8517395ad00983bf7361c",
+ "eval/datasets/coding_memory_v1/oracles/delta-north--poisoning.py": "d72ea7b98db47e4de03884e2786206dc3da5cc08e5203c86b1d38a2d48c2fd47",
+ "eval/datasets/coding_memory_v1/oracles/delta-north--scope_boundaries.py": "cedb3234b22a40a7b889f50cfb5751fbba31e46f541b19feb2c4759c8007bac3",
+ "eval/datasets/coding_memory_v1/oracles/delta-north--temporal_history.py": "2f7c1bd4ebc051eee411d11251aff2ee5fee602a0800410bc0dbd2e86e99628b",
+ "eval/datasets/coding_memory_v1/oracles/delta-north--unsupported_questions.py": "f1f0b9858c806956122144c6e769f3baf9f87f07ff92eec98eb58d8019e6fba2",
+ "eval/datasets/coding_memory_v1/oracles/delta-violet--code_relationships.py": "79579639a60d96365781e853d1319a2bf85d9a2b4d8639410da7e3c1efc364b2",
+ "eval/datasets/coding_memory_v1/oracles/delta-violet--condition_values.py": "27defbfe7c237f971ae29e8f000f098eae32a11f3afad06f2bf3b27af432d902",
+ "eval/datasets/coding_memory_v1/oracles/delta-violet--corrections.py": "b3b685a5c33d1086e8ab4258d26c5ec9aa7396b8250a250351f0a6057fcf2075",
+ "eval/datasets/coding_memory_v1/oracles/delta-violet--long_documents.py": "4a5ce75372c8762bcd7986f8ceef454800c3636124ffd9de3a8315d210ae85f1",
+ "eval/datasets/coding_memory_v1/oracles/delta-violet--multilingual.py": "7ef19f25e97a24522b79a9d7e7f3d2d9e395a1fc2c60564e2f66081533bdacb5",
+ "eval/datasets/coding_memory_v1/oracles/delta-violet--paraphrases.py": "b6e00019bead0a5bc0facdac886b48fccfd3cccaa254267fda780e933d57904f",
+ "eval/datasets/coding_memory_v1/oracles/delta-violet--poisoning.py": "3e3e1e1fe04ae3e0c615ca21e794fead5d06f887d6fe7347fb9122684c8b8017",
+ "eval/datasets/coding_memory_v1/oracles/delta-violet--scope_boundaries.py": "336ae66910d533344712a2d87126f53df48d43eae9e7d17797938c3f9c560204",
+ "eval/datasets/coding_memory_v1/oracles/delta-violet--temporal_history.py": "a81cb5d2a5eb521dfd161ffbd21bd366777b1239f2d3412acc77b0a9c7c7d82b",
+ "eval/datasets/coding_memory_v1/oracles/delta-violet--unsupported_questions.py": "3341895608073374b98e67a88489abb9020fed5bb692231a0a61e15c8de0ddf1",
+ "eval/datasets/coding_memory_v1/oracles/delta-west--code_relationships.py": "e8f17f3838cb74e1ade0cfd6edf540dc086507b51ca67c2020ed490d75dbffe5",
+ "eval/datasets/coding_memory_v1/oracles/delta-west--condition_values.py": "a935a6e7a4081737eef76a93d54b34dc885b4699e02d797f1be78286dd642a2b",
+ "eval/datasets/coding_memory_v1/oracles/delta-west--corrections.py": "ee407e5aa8d57254bb7029d537bb2b59a297958cdff9bf271de3787b6448241e",
+ "eval/datasets/coding_memory_v1/oracles/delta-west--long_documents.py": "55fa08b75982d0547f0c2f13cdc6522d0fbd33efb4af9ae8c6ada11b2d46f321",
+ "eval/datasets/coding_memory_v1/oracles/delta-west--multilingual.py": "ce8a3e45ad8417ca1798579a22e12eeb7d170a11ebfb865746388eb0e7dca848",
+ "eval/datasets/coding_memory_v1/oracles/delta-west--paraphrases.py": "e6458588e20e76596e24be6cc838bd4b0d5a47b05b7bf0bb6017b0cecd0d6a68",
+ "eval/datasets/coding_memory_v1/oracles/delta-west--poisoning.py": "4c70955a2650d13eba24d6e933f765e0bd40da0a1cd8b920416989a76bdaf2d7",
+ "eval/datasets/coding_memory_v1/oracles/delta-west--scope_boundaries.py": "cfe9120c49224b6f9ed86ea7d85e496000bef0648c92c7fd474c540e892863cd",
+ "eval/datasets/coding_memory_v1/oracles/delta-west--temporal_history.py": "3527edc8a85c71f889ff0b2ac8bf9ab005ad5bb6c95bcd917dedae44aa3efc72",
+ "eval/datasets/coding_memory_v1/oracles/delta-west--unsupported_questions.py": "8c40923b8d4bd6bfc78ff6548ef9a840319b7ee4ea967554f26a8d24a4349935",
+ "eval/datasets/coding_memory_v1/oracles/ember-green--code_relationships.py": "cc7f82ad85363ee35ccd75744159bf4c4f07965e396bff07989cca7a55071679",
+ "eval/datasets/coding_memory_v1/oracles/ember-green--condition_values.py": "3fa888c125812f1488de9e021358cd531a9d50bb7d4b6173be3656268474b635",
+ "eval/datasets/coding_memory_v1/oracles/ember-green--corrections.py": "7cc639d4bd7b287ed0e3f2188491d6e4866e021b21874b34b365c1eb2b2d6d68",
+ "eval/datasets/coding_memory_v1/oracles/ember-green--long_documents.py": "972e7879a30ab26f86f09bce6dc9853c7f96124d0bbe5062111607496bcc9a95",
+ "eval/datasets/coding_memory_v1/oracles/ember-green--multilingual.py": "0f0585bd3264de3d5fed5928b54ff4fb87f71b2edb1d35c83a534251121124b4",
+ "eval/datasets/coding_memory_v1/oracles/ember-green--paraphrases.py": "a3c15541bb87cc862d1c9764cfdc1ffebbc831876b55167470ecb3b66519740a",
+ "eval/datasets/coding_memory_v1/oracles/ember-green--poisoning.py": "ace5b7b12c4ca068739af4bc5e215af1af7fe153399ff17ffb7a67b8b9aa1f94",
+ "eval/datasets/coding_memory_v1/oracles/ember-green--scope_boundaries.py": "daf132e130c5743d67129c1564f329fbb2670d1242648920b646a83bc2236e4b",
+ "eval/datasets/coding_memory_v1/oracles/ember-green--temporal_history.py": "1438f09f0ae41b1f6b42930518203a36c7de31a870b684dbb7f16e71a37c784b",
+ "eval/datasets/coding_memory_v1/oracles/ember-green--unsupported_questions.py": "da27396afbc1529c61972f739cefb26dcaf97bea529c084518a95080eac4fe78",
+ "eval/datasets/coding_memory_v1/oracles/ember-north--code_relationships.py": "debaf885151c3f47660053ff50e015338b8aa9bca089d7e901ee8bc34f7085e6",
+ "eval/datasets/coding_memory_v1/oracles/ember-north--condition_values.py": "109a7ee90c04da83f57a94a9a724dcf98a3e96405c7a456686c198ff5d85a69c",
+ "eval/datasets/coding_memory_v1/oracles/ember-north--corrections.py": "c246ba011c68d3ad0a4ee1d14231304a7f17f28027f1764e60da93062cf1cd3c",
+ "eval/datasets/coding_memory_v1/oracles/ember-north--long_documents.py": "176200cf2a011c7231ed4cadacbe86b1eeb695fbbee282bf4c4a96430d98fe84",
+ "eval/datasets/coding_memory_v1/oracles/ember-north--multilingual.py": "e850a910515e71a27edff26f7bedf39d8cb75c726e48357a2c41d4da051be6e9",
+ "eval/datasets/coding_memory_v1/oracles/ember-north--paraphrases.py": "0a6f03310e44abcf4ca61d69e22edbd21ce47a17629adf4f5079f7565534b469",
+ "eval/datasets/coding_memory_v1/oracles/ember-north--poisoning.py": "a81ee758fc57bcc58bf0bf2a38dae8b6eb4aeb14949063206e32cdff6f301dfb",
+ "eval/datasets/coding_memory_v1/oracles/ember-north--scope_boundaries.py": "fe42df24612c66b175f1c98794ed9f13a1a7819418e4aeb5ff98531515221de1",
+ "eval/datasets/coding_memory_v1/oracles/ember-north--temporal_history.py": "410f9770097228724a5af1357003ea23caefe1226275d030d46efa535c42d306",
+ "eval/datasets/coding_memory_v1/oracles/ember-north--unsupported_questions.py": "eebe43443938693a74081661d2ff3733b39c6dac5c7ecd88c4dbab5ad18689ee",
+ "eval/datasets/coding_memory_v1/oracles/ember-violet--code_relationships.py": "c00a30669fbd1c740b44e17c7c7369582c28a16da3aeec9d27420b8aef7c553a",
+ "eval/datasets/coding_memory_v1/oracles/ember-violet--condition_values.py": "1645e7c56c870f4367c263ccc9041181b1d187a698252203ca651235db59e684",
+ "eval/datasets/coding_memory_v1/oracles/ember-violet--corrections.py": "735f1d125d2ade6f3eabbf1082a408fe6afd4328cd954833e23df275df498e37",
+ "eval/datasets/coding_memory_v1/oracles/ember-violet--long_documents.py": "e1ded8e9641639443a6b14da18b2e0cbcddc6fc6045bf0b57404b59b59327dce",
+ "eval/datasets/coding_memory_v1/oracles/ember-violet--multilingual.py": "fbf04cda8e1231c7ac4c64d4c1856f7bfafcb6f1d016de0b1bdbbaaafcfe03b6",
+ "eval/datasets/coding_memory_v1/oracles/ember-violet--paraphrases.py": "2fd495def306778073df30b20dbc7a1500c06c90638acf6d9b9280db78dec53a",
+ "eval/datasets/coding_memory_v1/oracles/ember-violet--poisoning.py": "335674216a418254a01a7f5505344af20ad499a5fe41cb9c8654a0705574a18e",
+ "eval/datasets/coding_memory_v1/oracles/ember-violet--scope_boundaries.py": "078d60287888903e90343d144d8f54e08a3751ffcba82fd827ca0c9f8ee75fe7",
+ "eval/datasets/coding_memory_v1/oracles/ember-violet--temporal_history.py": "f7ad47f3253fe922977cbd0b63679abf00559f1b2dc895246e09840652b2c41b",
+ "eval/datasets/coding_memory_v1/oracles/ember-violet--unsupported_questions.py": "b91545082a31952351bc4c08d6cc09132432126dfd772952c818396bd7a68af1",
+ "eval/datasets/coding_memory_v1/oracles/ember-west--code_relationships.py": "74fb3e0d99a5bd4f54b8b57f6040ce7978a81bffa1ae70f9069fb2322e6ea581",
+ "eval/datasets/coding_memory_v1/oracles/ember-west--condition_values.py": "26d605b42d18cf029dfdc29124eea898e0dfa5642c43c1c775007fb443ba6cda",
+ "eval/datasets/coding_memory_v1/oracles/ember-west--corrections.py": "65c2ef8544b42ecb6b3ea9ce8bc95ec5f0ea8bb07c2b74d52e7880d99962f563",
+ "eval/datasets/coding_memory_v1/oracles/ember-west--long_documents.py": "4c8e54c7ec930d6cc782fa1a0519a0f1e0e916e3929902e13199775cefdaf338",
+ "eval/datasets/coding_memory_v1/oracles/ember-west--multilingual.py": "356e407f47d39e7f53337ffc4f27c68935a2c831301dc3dd006b2939cc61decd",
+ "eval/datasets/coding_memory_v1/oracles/ember-west--paraphrases.py": "c91cf574cf8b5aadaa6c9331c3f8f4a8fec7ec33c227f20894f463d81d110a7d",
+ "eval/datasets/coding_memory_v1/oracles/ember-west--poisoning.py": "5f6b8740798a7a093994748ef4b11e0e1de0be562c7f09790e1f0ebb9ed21f7d",
+ "eval/datasets/coding_memory_v1/oracles/ember-west--scope_boundaries.py": "5e20c5154818cc29b7e71444054d67836eddc882f2f17be8aed78afe31a9037f",
+ "eval/datasets/coding_memory_v1/oracles/ember-west--temporal_history.py": "fc51d1a39693d1f7fb2db4bf1ffe865d4c44848c159188cdcfda54618c0d8fe2",
+ "eval/datasets/coding_memory_v1/oracles/ember-west--unsupported_questions.py": "079c421cae974bcccc2107dac74421dd5bc2d1b40f7d91cb79a8014af526d260",
+ "eval/datasets/coding_memory_v1/oracles/fjord-green--code_relationships.py": "b5e4a284bad1d7ff81d5be9cf1faaaaf5e718cb51624189482624721cb459f1b",
+ "eval/datasets/coding_memory_v1/oracles/fjord-green--condition_values.py": "63c152b8da5655ae5c701cbb51099340d5f5103fcc47840239098a7366d24151",
+ "eval/datasets/coding_memory_v1/oracles/fjord-green--corrections.py": "b7b33b8de153c205ff4fdaadd5538eae8a22fd4401e70db9f645dae205c62cba",
+ "eval/datasets/coding_memory_v1/oracles/fjord-green--long_documents.py": "624cf014bc795a24e2f1f4fa2f9dbf11d2ad1c38f81cbc58ab3948f96c48c8c4",
+ "eval/datasets/coding_memory_v1/oracles/fjord-green--multilingual.py": "f8d2483fd9266946c980764feba0eda27c95ea2ff11222a4dd4247fb537f1f00",
+ "eval/datasets/coding_memory_v1/oracles/fjord-green--paraphrases.py": "151be253b730d5c5b4bbcabcce3bd218477241ebf0f1bd5fe9dc8d70798feb6f",
+ "eval/datasets/coding_memory_v1/oracles/fjord-green--poisoning.py": "e7b0efca3813bbb1d4ea9cefa752b4318942b118bb61ee9dcbbf640daa66644d",
+ "eval/datasets/coding_memory_v1/oracles/fjord-green--scope_boundaries.py": "56177cfb45f32c548be83cc78197fcf5b53826f01cca69e471535476184ae8f9",
+ "eval/datasets/coding_memory_v1/oracles/fjord-green--temporal_history.py": "2c79d26467eb8443d743bb8823cc1f2a5d1e5da7c887754505baa130e37f1b2e",
+ "eval/datasets/coding_memory_v1/oracles/fjord-green--unsupported_questions.py": "fb5f222f4e0715379591f559036c0549f67a7b6e3491e880c6cf8d5b91fa9000",
+ "eval/datasets/coding_memory_v1/oracles/fjord-north--code_relationships.py": "07402078200db9ab15fc61a72e31d9df0938edfb1e9d5b7e532b422dccab15aa",
+ "eval/datasets/coding_memory_v1/oracles/fjord-north--condition_values.py": "8c1549b5a444c9cbf1304fcca13b79263bf8d9f0b4f41c16da539b3bf5da3bf4",
+ "eval/datasets/coding_memory_v1/oracles/fjord-north--corrections.py": "a2634af73134c025de1b6239756ee2051e505e0a535221947babef5ba161fa13",
+ "eval/datasets/coding_memory_v1/oracles/fjord-north--long_documents.py": "4f80f3673afd1320d16a6bd45fefc85522d6f4bb889ff96f6e99410580feec09",
+ "eval/datasets/coding_memory_v1/oracles/fjord-north--multilingual.py": "3a9c953fbe76eb7374992a6a81d35b8dc0a635c02b90f328af140bbda2acc180",
+ "eval/datasets/coding_memory_v1/oracles/fjord-north--paraphrases.py": "ec2d9d7bbd678be73946cba8177184839505cd891df410ae8e676fc254df2ca5",
+ "eval/datasets/coding_memory_v1/oracles/fjord-north--poisoning.py": "6d9b91b4d53925d58d3c0cea694f591572a53184f23816101af0aa644aa1b86d",
+ "eval/datasets/coding_memory_v1/oracles/fjord-north--scope_boundaries.py": "2e98afa064e8a295a78bea654014a92734696a235fb0538732bb8d85799c7287",
+ "eval/datasets/coding_memory_v1/oracles/fjord-north--temporal_history.py": "4059ab8e427c2b6fd68155992404fbf493c7ebdf0a4357aa5c5f4ff0c67808f9",
+ "eval/datasets/coding_memory_v1/oracles/fjord-north--unsupported_questions.py": "79f14e18e282c1b6bbe21ef2affc9dd8a259a171c2e4975d42363f7c91689c1e",
+ "eval/datasets/coding_memory_v1/oracles/fjord-violet--code_relationships.py": "9ea8a9ade1001bcd43929782c3410f35fc5056e438342ca848fe0b1b0ff56170",
+ "eval/datasets/coding_memory_v1/oracles/fjord-violet--condition_values.py": "09e223a1c0bff6c95a5863fdc28dc00ed96b90887cd635d014712058079ccbfe",
+ "eval/datasets/coding_memory_v1/oracles/fjord-violet--corrections.py": "f822c373fc4a93189fdce09999a9dcbe8797b0c0dc780eddd4e342edf21dc14e",
+ "eval/datasets/coding_memory_v1/oracles/fjord-violet--long_documents.py": "983aeaae1e14937a740218228778be3e1938cdf1ff9228eba44064336188f297",
+ "eval/datasets/coding_memory_v1/oracles/fjord-violet--multilingual.py": "6e929f8846cfc756c71bcc21bc0f27ef8fc23270d21292fc35d70e6bbd31c066",
+ "eval/datasets/coding_memory_v1/oracles/fjord-violet--paraphrases.py": "c644d9f01edbd213447618bc269f56804450901faf4f4660c1f666d4e9b4ada4",
+ "eval/datasets/coding_memory_v1/oracles/fjord-violet--poisoning.py": "cf3849312f96b0072efdfd4e858abb24d7e6a808bf883bcc9dde5dd5a4594364",
+ "eval/datasets/coding_memory_v1/oracles/fjord-violet--scope_boundaries.py": "373d10e20713bdc26849706525f765f2be4ab57e87e576e39a1a1dd4e7feed88",
+ "eval/datasets/coding_memory_v1/oracles/fjord-violet--temporal_history.py": "4d2c36480cc04d43aa8f535bf998968b474f2f596d5349dcd42684c5a667c6a4",
+ "eval/datasets/coding_memory_v1/oracles/fjord-violet--unsupported_questions.py": "b381d2016dea525eebd8de8d026dc48ad2080b93823a95d6e2bb06d29cc86775",
+ "eval/datasets/coding_memory_v1/oracles/fjord-west--code_relationships.py": "e1e10ed9ee8a4d5c2e798c2bf04cba5c39eaa3873901bd93724af166ba2479ae",
+ "eval/datasets/coding_memory_v1/oracles/fjord-west--condition_values.py": "427764a62d2e11f6c9fe4b2cdcd32fa92f61749403dff648601a47187e38787a",
+ "eval/datasets/coding_memory_v1/oracles/fjord-west--corrections.py": "4aa041aa87f4c8628d113bab855a151c2e38bd6919cd02224827fd07d060fb34",
+ "eval/datasets/coding_memory_v1/oracles/fjord-west--long_documents.py": "89875548e6952ce810d8983b0eaf2724bf8f329bf44e4d9193cfa3405e1ce80e",
+ "eval/datasets/coding_memory_v1/oracles/fjord-west--multilingual.py": "8b5c14cdb26941d1ae25701bc99efc5fe4ae6e50fed3c65b193952444f1593d1",
+ "eval/datasets/coding_memory_v1/oracles/fjord-west--paraphrases.py": "bcdc9a5f1fe45ef63fd5e6b728c83c7a8d05c7b80957e3276c074700b409e924",
+ "eval/datasets/coding_memory_v1/oracles/fjord-west--poisoning.py": "5504e1bab0865fb3041c88f151938789d79422eff91a3ce72f4a41fc14e2f897",
+ "eval/datasets/coding_memory_v1/oracles/fjord-west--scope_boundaries.py": "63f226631a663dca0ef62a4f32bb98fbe1e30e3b1f030ee42c3f7f2bf6a09af0",
+ "eval/datasets/coding_memory_v1/oracles/fjord-west--temporal_history.py": "75c9582a16db2c89f12506bd7a4115fcd871c6da63e80429a8e70e31c1f5f862",
+ "eval/datasets/coding_memory_v1/oracles/fjord-west--unsupported_questions.py": "05e4ba65be7cb4e6500af5e14721c70f93fd7390abe58a60f6f8e4f8e9b0ae5c",
+ "eval/datasets/coding_memory_v1/oracles/grove-green--code_relationships.py": "bd5619518e839f84ca03297c4f7842a2944d8b445ec1863a8dfc68e4c2114a03",
+ "eval/datasets/coding_memory_v1/oracles/grove-green--condition_values.py": "67377847b6ff1ab865f5c25351fcfa1ee13e6ec6a089444cb3eb0709b660148b",
+ "eval/datasets/coding_memory_v1/oracles/grove-green--corrections.py": "c07e8a03c6c47818f9c574d4f21b27668989711fb5e0af3b5cf1a87b293abcbd",
+ "eval/datasets/coding_memory_v1/oracles/grove-green--long_documents.py": "3f75fc2158c66a19dbbd19209fc80026339abe93a2282ff55c7c01429ac549f3",
+ "eval/datasets/coding_memory_v1/oracles/grove-green--multilingual.py": "19b1f800b38a11cf0f50df6da3a011806098561e32a279b78e1806de9b8c11fb",
+ "eval/datasets/coding_memory_v1/oracles/grove-green--paraphrases.py": "a5996376b9f589283dbe301dd1e0644af92a8b6a0222b51b99ed6fbb83570782",
+ "eval/datasets/coding_memory_v1/oracles/grove-green--poisoning.py": "c4dee0969e39b52dc9b697e45587c8cd84926a97c9a48f7211c0a00571bfb657",
+ "eval/datasets/coding_memory_v1/oracles/grove-green--scope_boundaries.py": "8d45422f0a1a2115deb90f560409fdad1105b7cae1f03052f8ebac404ba9517c",
+ "eval/datasets/coding_memory_v1/oracles/grove-green--temporal_history.py": "abc1b386173c8252ac10f11a36fbcdd6d91c731686108a6d32c62d17955d21b9",
+ "eval/datasets/coding_memory_v1/oracles/grove-green--unsupported_questions.py": "1ee09741a1c94a1fa83c7edf5b6c07d8da2e45826f748d899ec71917e8369d54",
+ "eval/datasets/coding_memory_v1/oracles/grove-north--code_relationships.py": "fd75d734a5f45a5ec54b125c1d379df67c5531659f212a2f5c1c5e88169167e0",
+ "eval/datasets/coding_memory_v1/oracles/grove-north--condition_values.py": "c600c0e5f50e352406b99527b050f4df6dc8ab8920b5b946de190ad7629b5e6d",
+ "eval/datasets/coding_memory_v1/oracles/grove-north--corrections.py": "4d6fdbe4f5eb9c8e4bfc2a974e6c3cbf1d124d0b20d3f050c84e766f507b4be7",
+ "eval/datasets/coding_memory_v1/oracles/grove-north--long_documents.py": "5b0bbb9f0a90eb377cbfe45ff6f37077a9ce32c7c6730720f0bfd49b844fdf4b",
+ "eval/datasets/coding_memory_v1/oracles/grove-north--multilingual.py": "525ea2fdad66d66d7b90892e9424ebae281c1dabfb49f2668784a687b7ec894e",
+ "eval/datasets/coding_memory_v1/oracles/grove-north--paraphrases.py": "1105e52a8c8d3ff79c64b59ec5f23d66d742b8dcecf91e8e62060895c7371f4a",
+ "eval/datasets/coding_memory_v1/oracles/grove-north--poisoning.py": "7d32e533f6dac57e5d651868386686d2b78e7db1cc29b1860fede23fc9c49813",
+ "eval/datasets/coding_memory_v1/oracles/grove-north--scope_boundaries.py": "7bea4129589ea34ba10667a7035b126a32b423ec7f3b1ff2b10f6ffc9aa1c2ee",
+ "eval/datasets/coding_memory_v1/oracles/grove-north--temporal_history.py": "859b6504250f5746ce197e4a0a7c1006f1c2257b939c53a493f9d36f6d2b7d6a",
+ "eval/datasets/coding_memory_v1/oracles/grove-north--unsupported_questions.py": "6b893c4c124f071e9ad5d9946448b070c2b2f5f10da6f058e60840c18510fd0f",
+ "eval/datasets/coding_memory_v1/oracles/grove-violet--code_relationships.py": "09bb7df6b115b83cb98a4274b2d883e81b809643ed4f912b383a9e09a7f7a1f9",
+ "eval/datasets/coding_memory_v1/oracles/grove-violet--condition_values.py": "a68c126f0d7b2fc0950baa5df095d7d513cdb8359883404bad3c1c84c8de666d",
+ "eval/datasets/coding_memory_v1/oracles/grove-violet--corrections.py": "46024b5fb187aaf3574d380af1a3bb2aa44028f89377f810ca5e98ee03c3b9aa",
+ "eval/datasets/coding_memory_v1/oracles/grove-violet--long_documents.py": "ec8d401f6437fbaa3b11ddb96973b31944ff374da677f7aae26c0e79e6a1315b",
+ "eval/datasets/coding_memory_v1/oracles/grove-violet--multilingual.py": "d14e8bf66593ceab7b691119b8a82f211933e2b3f6ebf8a25a75e61ffedbc1c0",
+ "eval/datasets/coding_memory_v1/oracles/grove-violet--paraphrases.py": "963b7321a32931f238d12e7cdc3849f64682c7cb704b77f4b849543cc40b6df0",
+ "eval/datasets/coding_memory_v1/oracles/grove-violet--poisoning.py": "39e5edc76a122d37d90b1af28c2293e2f8b0c38e1bdb6460340c0bc2a71ad87d",
+ "eval/datasets/coding_memory_v1/oracles/grove-violet--scope_boundaries.py": "472298607415aedfb9a36bad7783e7b7e3bccc339ee44cd83746d782ca99e411",
+ "eval/datasets/coding_memory_v1/oracles/grove-violet--temporal_history.py": "dcf793210a0a547c25587bba70e01d4d56576604856ed084eb5fa466b8be7b83",
+ "eval/datasets/coding_memory_v1/oracles/grove-violet--unsupported_questions.py": "bbe1769dc0afbe220e8069ef21b85164c31ecff6c3c7f5bf6d18d1e48f5f3c72",
+ "eval/datasets/coding_memory_v1/oracles/grove-west--code_relationships.py": "5374fb66cd91be79b5eabd2696ada5fee89605d8a1fc84ce36bd6a8ca82cbe5e",
+ "eval/datasets/coding_memory_v1/oracles/grove-west--condition_values.py": "2e807846b760b65afe0f5e48947463a66c15b8d89899a2724cddd865311760b0",
+ "eval/datasets/coding_memory_v1/oracles/grove-west--corrections.py": "bca219aae5f85e3f5504d5b39646efe89ff7dc6c007a4323abd5c5154cb4d5a9",
+ "eval/datasets/coding_memory_v1/oracles/grove-west--long_documents.py": "19c3ca44f78ef79ca1b907c80214e11a8195e994893f1de683b5d9796eb29d6b",
+ "eval/datasets/coding_memory_v1/oracles/grove-west--multilingual.py": "7842efb41f88fcd49e7a2350e9b44a3a1946bf73e5cb103e4a91a11e810301a2",
+ "eval/datasets/coding_memory_v1/oracles/grove-west--paraphrases.py": "fd70e8a20da665073ad172219bd57b24e53575dccc344adc1fa0190770ffc6cd",
+ "eval/datasets/coding_memory_v1/oracles/grove-west--poisoning.py": "473acfa06852554dee20445dd1a86df4e626e0363a7b72de57d68819314bf938",
+ "eval/datasets/coding_memory_v1/oracles/grove-west--scope_boundaries.py": "3ba0e93abf07498c570cd81b0c4f753bb75b6d4f4bf6c786461bad1a3b301286",
+ "eval/datasets/coding_memory_v1/oracles/grove-west--temporal_history.py": "334bc7b057f570efd20896d282e54c986f5b6c642930057c8520f84b721ed5e9",
+ "eval/datasets/coding_memory_v1/oracles/grove-west--unsupported_questions.py": "5102eb2dadcce0dcc0ba157495d4fa10c4983e4bf6627aff25bbd547a08115ca",
+ "eval/datasets/coding_memory_v1/oracles/helios-green--code_relationships.py": "d376cda89c44b3282a86afe7cd01583f0b53f2c80cd92b47bc2fa4a19271e9ee",
+ "eval/datasets/coding_memory_v1/oracles/helios-green--condition_values.py": "7b6eaf2f46a5b2f1ae6e8e6da0bcfe6044fea5639df158e137fb8ba32b929909",
+ "eval/datasets/coding_memory_v1/oracles/helios-green--corrections.py": "2e849f6c926aa0ce1b2e69ce8cdc5ba9c50feb89b33b257fc71bf3aa3b8461b8",
+ "eval/datasets/coding_memory_v1/oracles/helios-green--long_documents.py": "884c17b7d20d986708c0eff2821cc7b76c5f7b16afd26c43ca80fb507e2f3ea7",
+ "eval/datasets/coding_memory_v1/oracles/helios-green--multilingual.py": "1182048aef0dcfbc416a004bc43f49f1d2a5de4132e7243ab7d147f54aab1acc",
+ "eval/datasets/coding_memory_v1/oracles/helios-green--paraphrases.py": "a73950ae7fceacfaa6c7c7a26c8379f9164a29ebea883cede9222e72c506db5e",
+ "eval/datasets/coding_memory_v1/oracles/helios-green--poisoning.py": "60c5112afb7b84ee5513f2657f42d0eacb0607ba345c28618af3b3b3eaab753e",
+ "eval/datasets/coding_memory_v1/oracles/helios-green--scope_boundaries.py": "dab035b811e7e9a60bf447d1ea26f6db915c320b5dc2dd04cda4b6f1023296be",
+ "eval/datasets/coding_memory_v1/oracles/helios-green--temporal_history.py": "c22dd57bc7cd659863231b164192caf6c4929c5baaf8edd8be8cac00268e5847",
+ "eval/datasets/coding_memory_v1/oracles/helios-green--unsupported_questions.py": "f78986b8093eab4ecc8c1319d359498d64177db55a4e38cf8d0b32d1e5af4b2a",
+ "eval/datasets/coding_memory_v1/oracles/helios-north--code_relationships.py": "81721c60bd4e3466600442214147ae6641ebc05ae414965f4a09b89cc74cb8d6",
+ "eval/datasets/coding_memory_v1/oracles/helios-north--condition_values.py": "8fbcf116c1f0d480d61b2974e7bef73acef053d45bf0ac3b12e6ea48b017e43f",
+ "eval/datasets/coding_memory_v1/oracles/helios-north--corrections.py": "beae8d6c5828c1f225e5e953236136a217ee48e67cebc82049caf8f1c60cfdcc",
+ "eval/datasets/coding_memory_v1/oracles/helios-north--long_documents.py": "019e7ff3b9efcb461c1c68acc657b2438cc2d63d4b4165c0caa4523c440f12bc",
+ "eval/datasets/coding_memory_v1/oracles/helios-north--multilingual.py": "a48324390ab60686c2b1417f0c049f47601737c55fdf000b696ccc5de260ccab",
+ "eval/datasets/coding_memory_v1/oracles/helios-north--paraphrases.py": "9e2552399b988a6d722ccfaec7cfbea10853164336fadc9e9146dbae788a2d8a",
+ "eval/datasets/coding_memory_v1/oracles/helios-north--poisoning.py": "75eecaa42c302898581d3bef8aee00fa1dec6c3bc968c0014a522196e18c7681",
+ "eval/datasets/coding_memory_v1/oracles/helios-north--scope_boundaries.py": "8bd32a30cd1c2e346375bf2b37ef4908c3feeb67a80ec52ed3108bfe9cbd548e",
+ "eval/datasets/coding_memory_v1/oracles/helios-north--temporal_history.py": "1d1118cb5d438f91bb027acaa3b64f867e93cca7eb0f480bb83073584fafd97a",
+ "eval/datasets/coding_memory_v1/oracles/helios-north--unsupported_questions.py": "02a854bf89085637e366f801d2be6ab26b9e18296f13531485d08c2a64621c7c",
+ "eval/datasets/coding_memory_v1/oracles/helios-violet--code_relationships.py": "e32101847ccad5cd18fbc2c667ba4d1b63e3e7f8067dc89de5f485546dfef525",
+ "eval/datasets/coding_memory_v1/oracles/helios-violet--condition_values.py": "c8ef59dc9dcfe3aecc990d82fe44f1f93b7adc8c65f1fc38e79b934d351635c6",
+ "eval/datasets/coding_memory_v1/oracles/helios-violet--corrections.py": "a3a36bb554a6f4a83ea612bbbe9c0ee4ee1a08071e490a9a909c46d3049bb379",
+ "eval/datasets/coding_memory_v1/oracles/helios-violet--long_documents.py": "f9fa707bb28b4d7ea96e3bacfa22f24502b571a76a79defd147fe9d5205963a2",
+ "eval/datasets/coding_memory_v1/oracles/helios-violet--multilingual.py": "cfbfe0535e905dd58434bc25d84e7746dbdf1fc50d72d0fa96cc78677eaf18b9",
+ "eval/datasets/coding_memory_v1/oracles/helios-violet--paraphrases.py": "1743c8cbf0564d91dfb4db88b694072fdc9bb5a0b9f26f578649af7f539ee393",
+ "eval/datasets/coding_memory_v1/oracles/helios-violet--poisoning.py": "02cf390520757473e9d677f5c1f0934ecf50645ad5edcdc64caec23721a5d99e",
+ "eval/datasets/coding_memory_v1/oracles/helios-violet--scope_boundaries.py": "5ab003dfc23cb00d0a70195541a780a4b80b1d496728b2b88f7f154310e67a6d",
+ "eval/datasets/coding_memory_v1/oracles/helios-violet--temporal_history.py": "209e95f62c39d3f2223ed00a378788e396d04de392324455cd9d19dedb3a87ad",
+ "eval/datasets/coding_memory_v1/oracles/helios-violet--unsupported_questions.py": "a3ff0703297e2f743917e74abeffc0c922a6241318f37d63a49242c6bcc18059",
+ "eval/datasets/coding_memory_v1/oracles/helios-west--code_relationships.py": "4d8543e4c49d59f844830ccee3fcea0643ee17ce43f51863b94a1b4fbc9825ea",
+ "eval/datasets/coding_memory_v1/oracles/helios-west--condition_values.py": "f2efa7fd803df0e10c440d5ec2d98dedf5e02e23a4cedddcde5ea722f4039e40",
+ "eval/datasets/coding_memory_v1/oracles/helios-west--corrections.py": "181c42640cee47db2b5c779bdf8fe2e84103a92459d7f6cb0d9587458e421406",
+ "eval/datasets/coding_memory_v1/oracles/helios-west--long_documents.py": "49337830deb24f9d6b11ba4e01f397c0cf1a40a15524f1ce33354a2c61615072",
+ "eval/datasets/coding_memory_v1/oracles/helios-west--multilingual.py": "f1e4ef8be6bba13457a2da1c3a2082f81bba765f2cf1f07b93eb20edf5e2debe",
+ "eval/datasets/coding_memory_v1/oracles/helios-west--paraphrases.py": "29abfff3b323496fb62e739cdc0be2aada1b7e1467a141cc8d19a1d54c1ab130",
+ "eval/datasets/coding_memory_v1/oracles/helios-west--poisoning.py": "14b7f874c10681ff3829b2659cbe7500250369652d11b26c842e62718093ca0f",
+ "eval/datasets/coding_memory_v1/oracles/helios-west--scope_boundaries.py": "decbdb3cd7ab0c40c0aadb1ecdeef9e2ee2586dd8802b9bb16235f211ce05ee1",
+ "eval/datasets/coding_memory_v1/oracles/helios-west--temporal_history.py": "6e7c2369157234374d3f9633b7e772b4a3bff405d418c6e1336e56c87ebf28de",
+ "eval/datasets/coding_memory_v1/oracles/helios-west--unsupported_questions.py": "379716803a300bbecd3fc8f1661bdac2fcd8a98a2bb18514d8de168bd57fa144",
+ "eval/datasets/coding_memory_v1/oracles/island-green--code_relationships.py": "833962801d594247d5c5644df0e6aee877d452127ea5c0dbc22c81512132ab21",
+ "eval/datasets/coding_memory_v1/oracles/island-green--condition_values.py": "cfbb4ea0a4dd12d69686178ba8246bd08b93743dc5395cd0479984a53655d909",
+ "eval/datasets/coding_memory_v1/oracles/island-green--corrections.py": "4c7ee2538df9988864e24d9b86805d26b64d1a37638b57abdd1761e24c4ab340",
+ "eval/datasets/coding_memory_v1/oracles/island-green--long_documents.py": "f73cd2b884539680c500af9a5c8cc00d1dc9f1d2ef7bda61f6c8253b2bf799ed",
+ "eval/datasets/coding_memory_v1/oracles/island-green--multilingual.py": "e954fa56aa819ef9088c8d6da4abd647b689956c4ff4a7b0b8e31078522f2702",
+ "eval/datasets/coding_memory_v1/oracles/island-green--paraphrases.py": "4cde223eac40d4a2962aa2ace533b8761c5b82784767b0307c884ac5e6ad0576",
+ "eval/datasets/coding_memory_v1/oracles/island-green--poisoning.py": "08725e295848b2c58c9e6d6a0554a2dd4a6506af75039205c444251d11a62acb",
+ "eval/datasets/coding_memory_v1/oracles/island-green--scope_boundaries.py": "257cd579af07491748f5de88d04f8af85056b0448d348e59c7b0cbb28754fb1b",
+ "eval/datasets/coding_memory_v1/oracles/island-green--temporal_history.py": "e93105f0639a83b300a6de3bbae56166eedac2e58122f7e84ce5a40a074db6b6",
+ "eval/datasets/coding_memory_v1/oracles/island-green--unsupported_questions.py": "a707cee90fb3f0cabd0e692399a361f59da0fc4e9e7bbf0cde702e05579f0b3f",
+ "eval/datasets/coding_memory_v1/oracles/island-north--code_relationships.py": "f00e022ce900e4ad0fcec93f2dbf016337a4d83afacbcf739f61f71316723173",
+ "eval/datasets/coding_memory_v1/oracles/island-north--condition_values.py": "c704a03a70253585a67823826dc0a6ebaf6323a8880c00af80c148c150176daf",
+ "eval/datasets/coding_memory_v1/oracles/island-north--corrections.py": "e2cadf6e4f365f94cf0f440758b173da4d14ba12c03a84351e0608f0c60bccc5",
+ "eval/datasets/coding_memory_v1/oracles/island-north--long_documents.py": "300246fd3d85d73b63066d133361bc784ae26b46f4197c5c41ac960eb37f9508",
+ "eval/datasets/coding_memory_v1/oracles/island-north--multilingual.py": "f8aba8e14ed3c420e90e701880ea38e924e1219e3cc3643ba8bde17cb31ddef6",
+ "eval/datasets/coding_memory_v1/oracles/island-north--paraphrases.py": "503ce2c132c666bb67920e90d4a898f5a73f1d66779cd83c1885812f223e30e5",
+ "eval/datasets/coding_memory_v1/oracles/island-north--poisoning.py": "3369d956e5af12bc6be80828c4c6ab03ccf80c79e38dea3162b4e2c09771ff30",
+ "eval/datasets/coding_memory_v1/oracles/island-north--scope_boundaries.py": "9b4f6186f71713175f1eb5aacb0b6258c746b2297ca7c692f54bf5feabd5d261",
+ "eval/datasets/coding_memory_v1/oracles/island-north--temporal_history.py": "7c22b0edbf8edb7a511a581ffe6f1ce14454ef3feb527eca90d1386ce00b2c6b",
+ "eval/datasets/coding_memory_v1/oracles/island-north--unsupported_questions.py": "c1e5d526b0c6b88394f19a8b8edca7dae1db718b24be2d57374eb3769bbd1f24",
+ "eval/datasets/coding_memory_v1/oracles/island-violet--code_relationships.py": "9672d608ea0c8623ff1fe760f943173b2fdac5dc4ea4b7ae0dba33436d860254",
+ "eval/datasets/coding_memory_v1/oracles/island-violet--condition_values.py": "cfea91adcc9b3f9eb28cc24cf310e10b436f81cff51297f161b9a3b59a21f62c",
+ "eval/datasets/coding_memory_v1/oracles/island-violet--corrections.py": "075d48a2dfd91af8328982fe475ec915c00b75db38819aff3b4c2343147e2bfc",
+ "eval/datasets/coding_memory_v1/oracles/island-violet--long_documents.py": "85cc789277e59925b36ea3bd82e23f989e79fb0be2e55083e2656e518641dd3c",
+ "eval/datasets/coding_memory_v1/oracles/island-violet--multilingual.py": "84615b70eb74eb2180dd8a89d8d34c11e904030bfaa26c57e2e6c968daf64d29",
+ "eval/datasets/coding_memory_v1/oracles/island-violet--paraphrases.py": "5244d32ffc4127d519e2f5a875ffaecf7bc51eaa1426d2aacbc42490cf291d22",
+ "eval/datasets/coding_memory_v1/oracles/island-violet--poisoning.py": "9bde478876ea0be60555d5bd26d3790e4f305327ece10bf6c258bc564354fa1d",
+ "eval/datasets/coding_memory_v1/oracles/island-violet--scope_boundaries.py": "8e8b0a64342963b4dbc000578a72c0e0801fc1ca4af140f09b55d4cdb03b1274",
+ "eval/datasets/coding_memory_v1/oracles/island-violet--temporal_history.py": "3e187ce259c7f57f6dba4a28c3ecdf31fbe6458f7d908b22cb8b5ce8325437ae",
+ "eval/datasets/coding_memory_v1/oracles/island-violet--unsupported_questions.py": "0e60f29a47953c5d434c159eece26bbb03cc837842c493111b34310f9cff4693",
+ "eval/datasets/coding_memory_v1/oracles/island-west--code_relationships.py": "c19c42978fa5f9c6e1f41886eb1cc0fdb7e12304ba58b160b4cbe1975640acfe",
+ "eval/datasets/coding_memory_v1/oracles/island-west--condition_values.py": "07c4c591e08f5248f3016dc16cecfea7dd75bebff1a61599f258938d7031043d",
+ "eval/datasets/coding_memory_v1/oracles/island-west--corrections.py": "a7d93dfb579ec4f58cf2f6d29a6d0ab7dbf1a17f6ae49b98902a5ea22525e649",
+ "eval/datasets/coding_memory_v1/oracles/island-west--long_documents.py": "20a50dd63e04ce751115fca671b9da7ebbf240e26f39d494583cb405a03b750c",
+ "eval/datasets/coding_memory_v1/oracles/island-west--multilingual.py": "65d82c4d7968e0e597509c692015bdac0b92841c726d87560d9caa32429176f4",
+ "eval/datasets/coding_memory_v1/oracles/island-west--paraphrases.py": "feb7eb2e8592162d7d1f433610cb02ba04cd9797191d65a9f0e5bc4f4711c4ed",
+ "eval/datasets/coding_memory_v1/oracles/island-west--poisoning.py": "3d7c2c6a7f247f3e8158d5ba007bbbdf027a74e9a2c52f52953b3ccf2c21f17f",
+ "eval/datasets/coding_memory_v1/oracles/island-west--scope_boundaries.py": "ff0f36139f6da5428b9538d108aded522e62ca803035ebef78e11255cf55e08b",
+ "eval/datasets/coding_memory_v1/oracles/island-west--temporal_history.py": "42cb1c552ce01308b47d526c6e6993fb3270239b392cfa9f5e42b8e0fcfb3f82",
+ "eval/datasets/coding_memory_v1/oracles/island-west--unsupported_questions.py": "911b4257380640d35d6e2fc5292e235eedc113f5e71d8e71e8ccab7c72d4f290",
+ "eval/datasets/coding_memory_v1/oracles/juniper-green--code_relationships.py": "373395d3b70f64f8102609d27ce10c4aeb937ac73988c2ce2be1e283df94ef82",
+ "eval/datasets/coding_memory_v1/oracles/juniper-green--condition_values.py": "f04794a96b06d537550227ec308b1f02405cdab42d7d3ae4db4afd2c25bee8cd",
+ "eval/datasets/coding_memory_v1/oracles/juniper-green--corrections.py": "5a354a8c8efdbc498d25ffbf6095fee7b3d7d638134b452c7c9ecb186b4e1bc7",
+ "eval/datasets/coding_memory_v1/oracles/juniper-green--long_documents.py": "8610157668d6a90c794a956f6f0b432f03e3559238599ad1cb412f6f1a2f7e46",
+ "eval/datasets/coding_memory_v1/oracles/juniper-green--multilingual.py": "dcb99335fa18b008197532809ff7127be2cda600848e10c8bfe4677bd896f90e",
+ "eval/datasets/coding_memory_v1/oracles/juniper-green--paraphrases.py": "7a4cb1bbe3de2e6fe6a33b1f78829f4aeb21d21c2e8d4d3399d3d98958ba0909",
+ "eval/datasets/coding_memory_v1/oracles/juniper-green--poisoning.py": "ba73c2c6f04590538e16c50be527da60f5949781bc8833a3c18a7c597146eb10",
+ "eval/datasets/coding_memory_v1/oracles/juniper-green--scope_boundaries.py": "9d09c0851a3906b6281cb1c5811714224161e27a1aa46edd81c5659e7f93cd3d",
+ "eval/datasets/coding_memory_v1/oracles/juniper-green--temporal_history.py": "fd05595a452952dca2d0089f5701a60145a3b5705b78a5ff2f8f9f13fbf1c259",
+ "eval/datasets/coding_memory_v1/oracles/juniper-green--unsupported_questions.py": "f58bfcb89db799dc1578fc8791b49e97e7f22b30e720aeaf661f0ad2e6a9660c",
+ "eval/datasets/coding_memory_v1/oracles/juniper-north--code_relationships.py": "a84f02bd3c2b9c367248dae8aa3580d83de6a1beb6fe464553eb8a2bfb172b75",
+ "eval/datasets/coding_memory_v1/oracles/juniper-north--condition_values.py": "d70311ae339556f18240f824d0f0bd0024060c2698fcca8fe7cb540e8a8bc727",
+ "eval/datasets/coding_memory_v1/oracles/juniper-north--corrections.py": "fa290629339007fdd4230a76d49fa294a218c5f0cf6b90c3074678b0f16c539a",
+ "eval/datasets/coding_memory_v1/oracles/juniper-north--long_documents.py": "e30025d68ae3b48b566bf3fb2aea5e3603f5f7ab658da8fcb2256d3b1964c85a",
+ "eval/datasets/coding_memory_v1/oracles/juniper-north--multilingual.py": "b8bb0199c4acfe71df8d4e7df75bdd37e038cc72aa82bc6df10715819aeb3686",
+ "eval/datasets/coding_memory_v1/oracles/juniper-north--paraphrases.py": "aabf70509d3b0602d43c9590f04fa3ad1370a40c266d20f64a5ece15d4c79a51",
+ "eval/datasets/coding_memory_v1/oracles/juniper-north--poisoning.py": "ebfb9d7bf3c50eed8f44b73e391496ef377942de389b6feb64c7a1c0f9646444",
+ "eval/datasets/coding_memory_v1/oracles/juniper-north--scope_boundaries.py": "06c2c9d57ade79139c9b0d20568703477bb9e3a934f2f8900ad717ef157e5844",
+ "eval/datasets/coding_memory_v1/oracles/juniper-north--temporal_history.py": "0d97ba6d7a5d7741bf248b599ed18ae81b1c0053ad53cbc64934f31e9c3461aa",
+ "eval/datasets/coding_memory_v1/oracles/juniper-north--unsupported_questions.py": "526648ba7a4edc21bc2eb589c1e5c98a20fb07aa692687de4c3845e559be8096",
+ "eval/datasets/coding_memory_v1/oracles/juniper-violet--code_relationships.py": "c73fd24d0adbeda6c7afeb15ab07296c0438402893e8d442af26865106d6e8ea",
+ "eval/datasets/coding_memory_v1/oracles/juniper-violet--condition_values.py": "4aadae570ef84f976d49ba1fc92574ef55630b1c3017e90dbec1f85e6fb74c4a",
+ "eval/datasets/coding_memory_v1/oracles/juniper-violet--corrections.py": "07236b2a88df73c8d0f6afbe7d972fdc18f8c835a852f5c20748a369c77d82e2",
+ "eval/datasets/coding_memory_v1/oracles/juniper-violet--long_documents.py": "54d3201b621b168ce03963c423942f142cb403f9d90234f063ff33b7add4d2dc",
+ "eval/datasets/coding_memory_v1/oracles/juniper-violet--multilingual.py": "e0cc26d743cbea8e98261895474879d078fd53941c075606bfc6e52ee098db93",
+ "eval/datasets/coding_memory_v1/oracles/juniper-violet--paraphrases.py": "a1f836316408052cab1253b56b5caa6b1432fe6eeaabbb5872da97ec9bde633a",
+ "eval/datasets/coding_memory_v1/oracles/juniper-violet--poisoning.py": "bc09271eadf77f1b6c03fff20ca9c0f0a42a99d1c5600e6b7843f0a0d549ed5d",
+ "eval/datasets/coding_memory_v1/oracles/juniper-violet--scope_boundaries.py": "17a7258bed9efe1c86a05756c5b5d17926fd72f01de4b2addd5d2fb4550f4da5",
+ "eval/datasets/coding_memory_v1/oracles/juniper-violet--temporal_history.py": "8bda8599d861857204c48b55729607c8cb0cb3ae2701eb18fb600d398468cafe",
+ "eval/datasets/coding_memory_v1/oracles/juniper-violet--unsupported_questions.py": "10cdbb013f78f0e54c31a6e76f03d8d15a6063fd7971a2d803a390890c036ad2",
+ "eval/datasets/coding_memory_v1/oracles/juniper-west--code_relationships.py": "f0a682d413b3d8418c664db2775de4eef35240c508cc71ca6bbc055cd0c8dbe6",
+ "eval/datasets/coding_memory_v1/oracles/juniper-west--condition_values.py": "200a4b78f201e992ac10112a194c97b88841dc5930e3e7de9bfc076bf4c69fa7",
+ "eval/datasets/coding_memory_v1/oracles/juniper-west--corrections.py": "41ea9b5a5f40d8703343f4570593c3f27905e5ff70fae57bdbdb5fcccc585d22",
+ "eval/datasets/coding_memory_v1/oracles/juniper-west--long_documents.py": "7d8cdd01c5d90ad62e9da93a99febead683e3f01c5ca37315382a2fb0f512d76",
+ "eval/datasets/coding_memory_v1/oracles/juniper-west--multilingual.py": "1815adc5946bf9e7dba16786d8c29f9466537b7a43b1c392f927b61c56da54ee",
+ "eval/datasets/coding_memory_v1/oracles/juniper-west--paraphrases.py": "5d0110bc2098d2143303476fb5c5714086d0278b848110c63aa626e0b88311cc",
+ "eval/datasets/coding_memory_v1/oracles/juniper-west--poisoning.py": "4e16d9de9931cffc6915805b4fc375dad3ffb7fad17eb893205ce6d07102bcb3",
+ "eval/datasets/coding_memory_v1/oracles/juniper-west--scope_boundaries.py": "6c5d875184c890a922ee18ac423598fdcece109a91e9e62d5a7ccc82d2eaaa70",
+ "eval/datasets/coding_memory_v1/oracles/juniper-west--temporal_history.py": "3404fdbb5cc4693333d48d27da928531279d148ffe1cdadbdd741dfc1a8d38d3",
+ "eval/datasets/coding_memory_v1/oracles/juniper-west--unsupported_questions.py": "b85a4cf14f9596cd14b480acd323772fc91084ad1413fa27435bdb7ffe5baa6b",
+ "eval/datasets/longdoc.jsonl": "7f5ade95e1f283d0db8cf78e53ed8995d3534f847e616d2c0005fd8da37ac790",
+ "eval/desktop_memory.py": "65a3b5fcd248948c209baa115b275a6db40b386be5f0828bf9ca611787fa02a6",
+ "eval/engine_capacity.py": "1940da1c3a435d81c7b7a1a689e966c23b92e4f94af115b45813dd30e2c04eec",
+ "eval/evidence_contracts.py": "03969fdf01e78135c66e698d3f523e5312ccca3737dd3036b37a2c6676ba4ea2",
+ "eval/external.py": "98500a97c18152b1b86a270062033c48176224d7468ac0f703ddc7de862a30e2",
+ "eval/external_checkpoints.py": "484e298039bfa04eef86bd7145428c312aaeaf059f5bdf8da7ae29d1150008d7",
+ "eval/extractor_quality.py": "50820fe7f821d111e17e4e77a3d1159e7d6979d110b052c4f254a5b309c2652a",
+ "eval/fts_insert_scaling.py": "6088997e0f85d430fafb74509e24a960fbd59c06c8b2839285942b97d2116724",
+ "eval/graph_every_bench.py": "79da573c5edf315f71bfab412d3ea283b8da45d1fdabc78ddd19302ead09e4f0",
+ "eval/graph_traversal.py": "b094f75c3a1d75ba3cf19e372187d692d3c595a1d9bde19bbe95ae0c79a5175f",
+ "eval/grounded.py": "053d5193b716a2c3e507fcd44057d392de910a4442b46bd7cc1f30ac0ba68541",
+ "eval/handoff_quality.py": "7daf635510764e236f48ca7e2537a85d8ebc1ad995513144329c1f0236405937",
+ "eval/harness.py": "8c96c26a121dfc2d9ea051a05861af8951c93a732dba9cb13de0de178414b016",
+ "eval/hosted_evidence.py": "7946cd8c1e3aa291268271b2aa11210d5f09c9b05ba635aa0b64bef07bdcee45",
+ "eval/hosted_ledger.py": "a53036d12ff671371c148910a816fb20c7e7f3346250a303722352f47b06c476",
+ "eval/hosted_luna.py": "4dbf02a65eec38bcde92d82952a0b372abbac1f68378d11a04528f4a31a835dc",
+ "eval/jev_recall_quality.py": "3c31080767c2bc0301f1085c837eaa60ac6e1f5ae8eeb6ff908e573a630510cd",
+ "eval/local_benchmark_queue.py": "d87ace3cf9ed59d65e57917e5387ff226c1b8a4abef8b98719c5de3aa1b8dddc",
+ "eval/local_capacity_campaign.py": "ab4935263f9d24c4e38bd4f16164b4c4c07eff91c695809b6fc75299d4f03d62",
+ "eval/longmemeval_v2.py": "defb4d47f453aa4615a8f101b82df3fadf64f0f9eae0ce1021d86d3750dad437",
+ "eval/longmemeval_v2_evidence.py": "8486bd4dcfdd8b1f7a14c32fe4f427c980ccdcc3aa2eb018d7dfc40305412eff",
+ "eval/longmemeval_v2_matrix.py": "ca085cd59481813cce5dbdfbc94f40f67173ac3a1bc3dce6f6d3e09eb7b08153",
+ "eval/metrics.py": "16857e2cf6ed339cb57a26c9bfa1879444b4d279bd972e5a9fa644ed1308afe0",
+ "eval/native_coverage_scaling.py": "1df274ae8f0a8266282fd5b530167aa749ab67024349bf97c6c0a5a4987bfa77",
+ "eval/performance.py": "dccc55c26dc396f9fee98defd152900cf8aabeb5d6198bc711e0eb8f472ecc57",
+ "eval/performance_engine.py": "3d37cf0a5989c8fa6e8b6ab7ae0b2e0d5410a8b922d2f3130c1a7794d93dcbb1",
+ "eval/planned_recall.py": "f9a87291ecb181045b98ca65fe55db820bf7cee4ae4f4d087ca3458afdd7837e",
+ "eval/proactive_ranking.py": "8610541f1d547f9c0eb46d078dbcaa670c0a97157acc37f96ec08b482cb7a6ab",
+ "eval/productivity.py": "6d4644ebdc44472aeb3879963774fab276bfa269b781139c46ded48774a22717",
+ "eval/public_readiness.py": "5ce8a18d0bfe09e75e88548a589b8fc6d2cbf04c0f8cd1ce51cd9519136a2751",
+ "eval/redteam_poisoning.py": "fce120cc3adf2ee966b59cd3ea7f20d242a0af49b52492143543130fe14c0cc8",
+ "eval/reinforcement.py": "72ed766775a2658eaa728afec51c0ac22d97a90e813111954df66a6ec50f2bef",
+ "eval/repair_discovery.py": "db05496fbbcb0df86c5cdc2f0c85fb6b6605b0cace6cb44ae2add10b72784b5f",
+ "eval/resolver_reworded_corrections.py": "a9054778a37b2175b46f04b674b4779e358931eee5d2ae52bbc5f953d234fb9e",
+ "eval/resource_hierarchy.py": "5ab6c989bb143c4386749c45a33c190447e829c1b5657e8c0aca30f34bd69461",
+ "eval/rework_statistics.py": "e12c14288797c5cf2dd93d51f606244287bc2f4ff8bd401f1d1b072efd1f9529",
+ "eval/run_longmemeval_v2.py": "866863d9f8f7ce8f7c3c36741ab324faa5fae417827f907c655eace38de0f5ac",
+ "eval/spec_crawl.py": "da3b079ab5674ce1004b0dafcfd471da9897c951a03456ba704b720b719f3295",
+ "eval/task_pairs.py": "fddc54804e8837ec0731813297fb55825458317f898e29176e16f3f5a2f527fd",
+ "eval/user_journeys.py": "a1c8436d4a6871baff21f9aa4f6ac1545def60d946cd3b1454c3d9cd8661a049",
+ "eval/vector_scale.py": "3f9c327d9eca1a857512ddc208e972933be1a7d4fa7fa0017aca0cbf8fe7bb6d",
+ "eval/vector_scale_storage.py": "24040fd1b96b9f9cd43ea37b0b37ea118a02bd096aff77fc67ff8b0df769b1dc",
+ "eval/vector_scan_plan.py": "34fba3d029bfc78134e8c9c450b019ff56c3bdcbeb921400888077cb44e9b848",
+ "scripts/export_offline_evidence.py": "4e10c2b3d5f6a2024a7f95f7b87ce47ec2cb4ef31c55611b2e5cf35502ebc0ba"
+ }
+ }
+}
diff --git a/docs/benchmark-evidence/offline-fixtures-paid-plans-20261104.json.sha256 b/docs/benchmark-evidence/offline-fixtures-paid-plans-20261104.json.sha256
new file mode 100644
index 00000000..5312b0c0
--- /dev/null
+++ b/docs/benchmark-evidence/offline-fixtures-paid-plans-20261104.json.sha256
@@ -0,0 +1 @@
+cc66984fd3dd78c17addeb979aea0e0bf40ff0f533ebcd683da4b7f532da0055 offline-fixtures-paid-plans-20261104.json
diff --git a/docs/images/context-efficiency.png b/docs/images/context-efficiency.png
index a041b686..0a9d62a8 100644
Binary files a/docs/images/context-efficiency.png and b/docs/images/context-efficiency.png differ
diff --git a/docs/images/context-efficiency.svg b/docs/images/context-efficiency.svg
index 0ebae922..3cce3816 100644
--- a/docs/images/context-efficiency.svg
+++ b/docs/images/context-efficiency.svg
@@ -1,6 +1,6 @@
diff --git a/docs/images/evidence-backed-agent-examples.png b/docs/images/evidence-backed-agent-examples.png
index 47911885..9683d93c 100644
Binary files a/docs/images/evidence-backed-agent-examples.png and b/docs/images/evidence-backed-agent-examples.png differ
diff --git a/docs/images/evidence-backed-agent-examples.svg b/docs/images/evidence-backed-agent-examples.svg
index 5631c9c1..3b8ddac7 100644
--- a/docs/images/evidence-backed-agent-examples.svg
+++ b/docs/images/evidence-backed-agent-examples.svg
@@ -1,6 +1,6 @@
diff --git a/engraphis/backends/sync_relay.py b/engraphis/backends/sync_relay.py
index 85be16f1..b0e3b1fb 100644
--- a/engraphis/backends/sync_relay.py
+++ b/engraphis/backends/sync_relay.py
@@ -112,10 +112,72 @@ def decode_sync_e2ee_key(value: object) -> bytes:
return key
+def sync_e2ee_key_source() -> str:
+ """Return where the Cloud Sync key comes from, without disclosing it.
+
+ The environment stays authoritative so an operator-managed deployment cannot be
+ silently replaced by a key generated in the dashboard. Otherwise a key generated on
+ this device lives in the same owner-only state directory as the sync credential.
+ """
+
+ env_value = os.environ.get(SYNC_E2EE_KEY_ENV)
+ if env_value is not None and str(env_value).strip():
+ return "environment"
+ return "state_file" if _sync_e2ee_key_path().exists() else "none"
+
+
+def load_sync_e2ee_key() -> str:
+ """Read the saved owner-only key, failing closed when it is absent or unsafe."""
+
+ try:
+ value = read_private_text(
+ _sync_e2ee_key_path(), max_bytes=128, allow_missing=True
+ )
+ except (OSError, UnsafeStateFile):
+ raise RelayError(
+ "saved Cloud Sync encryption key is unreadable; generate or configure a new one",
+ status=409,
+ ) from None
+ if value is None or not value.strip():
+ raise RelayError(
+ "Cloud Sync needs a 32-byte end-to-end encryption key in "
+ + SYNC_E2EE_KEY_ENV,
+ status=409,
+ )
+ return value.strip()
+
+
+def has_saved_sync_e2ee_key() -> bool:
+ """Return whether a saved key exists. The key itself is never returned here."""
+
+ try:
+ return bool(load_sync_e2ee_key())
+ except RelayError:
+ return False
+
+
+def save_sync_e2ee_key(value: object) -> str:
+ """Validate and persist one key, returning its normalized unpadded form.
+
+ The key is written through ``atomic_private_text`` (0600, owner-only directory) and is
+ never logged. Callers must show it to the operator exactly once; a later call cannot
+ read it back through the API.
+ """
+
+ raw = decode_sync_e2ee_key(value)
+ normalized = base64.urlsafe_b64encode(raw).decode("ascii").rstrip("=")
+ _atomic_private_text(_sync_e2ee_key_path(), normalized)
+ return normalized
+
+
def configured_sync_e2ee_key(value: object = None) -> bytes:
- """Return an explicit key or fail closed before a Cloud upload can begin."""
- configured = os.environ.get(SYNC_E2EE_KEY_ENV) if value is None else value
- return decode_sync_e2ee_key(configured)
+ """Return an explicit, environment, or saved key; fail closed before any upload."""
+ if value is not None:
+ return decode_sync_e2ee_key(value)
+ env_value = os.environ.get(SYNC_E2EE_KEY_ENV)
+ if env_value is not None and str(env_value).strip():
+ return decode_sync_e2ee_key(env_value)
+ return decode_sync_e2ee_key(load_sync_e2ee_key())
def _new_e2ee_cipher(key: bytes):
@@ -237,6 +299,10 @@ def _sync_read_only_path() -> Path:
return _sync_token_path().with_name("sync.read_only")
+def _sync_e2ee_key_path() -> Path:
+ return _sync_token_path().with_name("sync.e2ee_key")
+
+
def _atomic_private_text(path: Path, value: str) -> None:
"""Atomically write one owner-only state value next to the sync credential."""
atomic_private_text(path, value + "\n", harden_parent=True)
diff --git a/engraphis/cloud_authz.py b/engraphis/cloud_authz.py
index a834518a..da3ee2bc 100644
--- a/engraphis/cloud_authz.py
+++ b/engraphis/cloud_authz.py
@@ -15,6 +15,8 @@
import logging
from typing import Any, Optional
+from engraphis.hosted_client import account_url
+
logger = logging.getLogger("engraphis.cloud_authz")
# Machine-readable reason codes the control plane may return.
@@ -37,7 +39,7 @@
"Your subscription has expired. Renew your plan to continue using Team features."
),
REASON_SEAT_LIMIT_EXCEEDED: (
- "All named seats are in use. Upgrade your plan to add more team members."
+ "All named seats are in use. Add a seat in the Engraphis Cloud account portal."
),
REASON_CROSS_ORG_DENIED: (
"Access denied: this credential belongs to a different organization."
@@ -62,6 +64,10 @@
REASON_ENTITLEMENT_EXPIRED,
REASON_SEAT_LIMIT_EXCEEDED,
})
+#: The only reason values this client will translate or log. An unrecognized body is a
+#: generic denial, and a long one is discarded before it can reach a log line.
+KNOWN_REASONS = frozenset(_DENIAL_MESSAGES)
+_MAX_REASON_CHARS = 64
def interpret_denial(
@@ -89,6 +95,10 @@ def interpret_denial(
reason = str(detail.get("reason") or "")
elif isinstance(detail, str):
reason = detail
+ # A denial body is untrusted input. Keep only a short, fixed-vocabulary code; never
+ # log or render an arbitrary provider string.
+ if len(reason) > _MAX_REASON_CHARS:
+ reason = ""
message = _DENIAL_MESSAGES.get(reason)
if message is None:
@@ -104,12 +114,14 @@ def interpret_denial(
"upgrade_url": None,
"retryable": reason == REASON_TOKEN_STALE,
}
- # Prefer the server-provided upgrade_url; fall back to the known billing path.
+ # Prefer the server-provided upgrade_url; fall back to the real account portal. The
+ # old relative "/billing/upgrade" path does not exist on the dashboard host and
+ # produced a 404 exactly when a paying customer was trying to fix billing.
upgrade_url = body.get("upgrade_url")
if upgrade_url and isinstance(upgrade_url, str):
result["upgrade_url"] = upgrade_url
elif reason in _UPGRADE_REASONS:
- result["upgrade_url"] = "/billing/upgrade"
+ result["upgrade_url"] = account_url()
return result
diff --git a/engraphis/cloud_features.py b/engraphis/cloud_features.py
index 321ab2e5..b6051b0c 100644
--- a/engraphis/cloud_features.py
+++ b/engraphis/cloud_features.py
@@ -108,11 +108,15 @@ class CloudFeatureError(RuntimeError):
"""A bounded, redacted managed-cloud failure suitable for an HTTP/UI boundary."""
def __init__(self, message: str, *, status: Optional[int] = None,
- transient: bool = False, code: Optional[str] = None) -> None:
+ transient: bool = False, code: Optional[str] = None,
+ reason: Optional[str] = None) -> None:
super().__init__(message)
self.status = status
self.transient = transient
self.code = code
+ # A fixed-vocabulary server reason (never provider text). The dashboard uses it to
+ # choose the right recovery action: billing, seats, reconnect, or administrator.
+ self.reason = reason
class _NoRedirect(urllib.request.HTTPRedirectHandler):
@@ -126,6 +130,35 @@ def redirect_request(self, req, fp, code, msg, headers, newurl):
# escaped from this error path as a raw traceback.
_DRAIN_FAILURES = (OSError, ValueError, http.client.HTTPException)
+#: Maximum bytes read from an error body to recover one allow-listed reason code. The
+#: body itself never crosses the boundary; this is deliberately much smaller than a normal
+#: response and is discarded after the reason is extracted.
+MAX_ERROR_BODY_BYTES = 8 * 1024
+
+
+def _denial_reason(status: int, raw: bytes) -> Optional[str]:
+ """Return a known denial reason from a bounded error body, never the body itself.
+
+ The private service may include implementation details in an error payload. Only a
+ fixed reason code is allowed through; an oversized, malformed, or unknown body becomes
+ ``None`` and the caller falls back to its status-based recovery copy.
+ """
+
+ if not raw or len(raw) > MAX_ERROR_BODY_BYTES:
+ return None
+ try:
+ body = json.loads(raw.decode("utf-8"))
+ except (UnicodeDecodeError, ValueError, RecursionError):
+ return None
+ if not isinstance(body, dict):
+ return None
+ try:
+ from engraphis.cloud_authz import KNOWN_REASONS, interpret_denial
+ reason = interpret_denial(status, body).get("reason")
+ except Exception: # noqa: BLE001 - presentation metadata must never mask the failure
+ return None
+ return reason if isinstance(reason, str) and reason in KNOWN_REASONS else None
+
def managed_compute_consent(service: Any = None, workspace: Optional[str] = None) -> bool:
"""Return explicit approval for this workspace; account connection never grants it.
@@ -505,13 +538,15 @@ def _request(self, method: str, path: str, payload: Optional[dict] = None) -> di
raw = response.read(MAX_RESPONSE_BYTES + 1)
except urllib.error.HTTPError as exc:
message, transient = _public_http_error(exc.code)
- # Do not inspect or reflect provider diagnostics: they can contain internal
- # details. Drain only to release the connection, and guard the drain and close
- # independently because both can fail for a malformed/truncated response.
+ # Read the error body once, bounded. Only a fixed-vocabulary reason code is
+ # extracted; provider diagnostics never reach the dashboard. The same bytes
+ # drain the connection, and both the read and close are guarded because a
+ # malformed or truncated response can fail either independently.
+ raw_error = b""
try:
- exc.read(MAX_RESPONSE_BYTES + 1)
+ raw_error = exc.read(MAX_ERROR_BODY_BYTES + 1)
except _DRAIN_FAILURES:
- pass
+ raw_error = b""
finally:
try:
exc.close()
@@ -521,6 +556,7 @@ def _request(self, method: str, path: str, payload: Optional[dict] = None) -> di
message,
status=exc.code,
transient=transient,
+ reason=_denial_reason(exc.code, raw_error),
) from None
except (urllib.error.URLError, TimeoutError, OSError, http.client.HTTPException) as exc:
raise CloudFeatureError(
diff --git a/engraphis/dashboard_assets/index.html b/engraphis/dashboard_assets/index.html
index 95997f22..e176b228 100644
--- a/engraphis/dashboard_assets/index.html
+++ b/engraphis/dashboard_assets/index.html
@@ -940,6 +940,7 @@
This installation
Cloud sync
—
Included
Included
Managed automation
—
Included
Included
Portfolio analytics
—
Included
Included
+
Managed Jev decisions
Local heuristics
Included · up to 500 calls/day
Included · up to 500 calls/day
Shared workspaces
—
—
Included
Members and roles
—
—
Included
Remote agent access
—
—
Included
diff --git a/engraphis/dashboard_assets/ledger.css b/engraphis/dashboard_assets/ledger.css
index 707b7e7b..13afed38 100644
--- a/engraphis/dashboard_assets/ledger.css
+++ b/engraphis/dashboard_assets/ledger.css
@@ -1112,6 +1112,7 @@ body[data-theme="sepia"] .graph-header {
.automation-policy-form { padding: 0; }
.automation-policy-note { margin: 0; color: var(--c-mid); font-size: 12px; line-height: 1.55; }
.automation-policy-actions { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
+.sync-key-value { display: block; padding: 10px 12px; border: 1px solid var(--c-line); border-radius: 5px; background: var(--c-raised); color: var(--c-fg); font: 12px/1.5 var(--mono); overflow-wrap: anywhere; user-select: all; }
.billing-toggle { display: flex; align-items: center; gap: 8px; color: var(--c-mid); }
.plan-cards { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 10px; margin-bottom: 16px; }
.plan-card { display: grid; gap: 10px; padding: 20px; border: 1px solid var(--c-line); border-radius: 5px; background: var(--c-surface); }
diff --git a/engraphis/dashboard_assets/ledger.js b/engraphis/dashboard_assets/ledger.js
index 5a1a1571..89031df5 100644
--- a/engraphis/dashboard_assets/ledger.js
+++ b/engraphis/dashboard_assets/ledger.js
@@ -372,8 +372,18 @@
}
if (!response.ok) {
const error = new Error(errorMessage(payload, response.status));
+ const detail = payload && payload.detail && typeof payload.detail === 'object'
+ ? payload.detail : null;
error.status = response.status;
- error.code = payload && ((payload.detail && payload.detail.code) || payload.code);
+ error.code = (detail && detail.code) || (payload && payload.code);
+ error.detail = detail;
+ error.reason = (detail && detail.reason) || (payload && payload.reason) || '';
+ error.tierRequired = (detail && detail.tier_required)
+ || (payload && payload.tier_required) || '';
+ error.upgradeUrl = (detail && detail.upgrade_url)
+ || (payload && payload.upgrade_url) || '';
+ error.accountUrl = (detail && detail.account_url)
+ || (payload && payload.account_url) || '';
error.localBrowser = payload && payload.local_browser;
throw error;
}
@@ -5969,7 +5979,16 @@
};
}
if (status === 402 || status === 501) {
- return { title: `${feature} needs Engraphis Pro`, detail: lockReason(), action: 'plan' };
+ const seat = error.reason === 'seat_limit_exceeded';
+ return {
+ title: seat ? 'All Team seats are in use' : `${feature} needs Engraphis Pro`,
+ detail: seat
+ ? 'Add a named seat in the Engraphis Cloud account portal, or remove a member who no longer needs access.'
+ : lockReason(),
+ action: seat ? 'account' : 'plan',
+ actionLabel: seat ? 'Manage seats' : '',
+ href: seat ? (safeUrl(error.accountUrl) || hostedAccountUrl('seat_limit')) : '',
+ };
}
if (status === 401) {
if (error.code === 'cloud_unconfigured' && licenseTrialAvailable()) {
@@ -5979,6 +5998,24 @@
title: 'Reconnect Engraphis Cloud',
detail: 'This installation’s Engraphis Cloud session expired or was revoked. Connect again to resume hosted features; your plan is unchanged.',
action: 'reconnect',
+ actionLabel: 'Open Engraphis Cloud',
+ href: safeUrl(error.accountUrl) || hostedAccountUrl('reconnect'),
+ };
+ }
+ if (status === 403) {
+ const disabled = error.reason === 'member_disabled';
+ const role = error.reason === 'insufficient_role';
+ return {
+ title: disabled ? 'Your account is disabled'
+ : role ? 'Administrator permission required' : 'Access denied',
+ detail: disabled
+ ? 'An organization administrator can restore this member account. Your local memories are unaffected.'
+ : role
+ ? 'Ask an organization owner or administrator to grant the role this action needs.'
+ : 'This credential belongs to a different organization or lacks the required role. Open the account portal to check access.',
+ action: 'account',
+ actionLabel: 'Open account portal',
+ href: safeUrl(error.accountUrl) || hostedAccountUrl('account'),
};
}
return null;
@@ -5989,10 +6026,11 @@
const actions = node('div', 'automation-policy-actions');
if (failure.action === 'consent') {
actions.append(button('Review workspace processing', 'primary-button', () => switchManageTab('settings')));
- } else if (failure.action === 'reconnect') {
- const href = hostedAccountUrl('reconnect');
+ } else if (failure.action === 'reconnect' || failure.action === 'account') {
+ const href = safeUrl(failure.href) || hostedAccountUrl(failure.action);
if (href) {
- const link = node('a', 'secondary-button', 'Open Engraphis Cloud');
+ const link = node('a', 'secondary-button',
+ failure.actionLabel || 'Open Engraphis Cloud');
link.href = href;
link.target = '_blank';
link.rel = 'noopener';
@@ -6558,12 +6596,22 @@
}
} else if (!ready) {
const missingKey = !hasKey;
+ const invalid = state.syncStatus.key_state === 'invalid';
+ const canGenerate = state.syncStatus.can_generate_key === true;
+ const canRotate = state.syncStatus.can_rotate_key === true;
const detail = missingKey
- ? state.syncStatus.key_state === 'invalid'
- ? 'The configured Cloud Sync encryption key is invalid. Replace it with a 32-byte URL-safe base64 key, then restart this dashboard.'
- : 'Cloud Sync needs a user-held encryption key. Configure ENGRAPHIS_SYNC_E2EE_KEY on this device, then restart this dashboard.'
+ ? invalid
+ ? 'The saved Cloud Sync encryption key is invalid. Generate a replacement; every device that syncs this workspace must use the same key.'
+ : canGenerate
+ ? 'Cloud Sync needs a user-held encryption key. Generate one here; it is stored on this device and shown only once.'
+ : 'Cloud Sync needs a user-held encryption key. Configure ENGRAPHIS_SYNC_E2EE_KEY on this device, then restart this dashboard.'
: 'Cloud Sync encryption support is unavailable. Install engraphis[cloud-sync] and restart this dashboard.';
target.append(node('p', 'automation-policy-note', detail));
+ if (missingKey && canGenerate) {
+ actions.append(button('Create sync key', 'primary-button', () => createSyncKey(false)));
+ } else if (missingKey && canRotate) {
+ actions.append(button('Replace sync key', 'secondary-button', () => createSyncKey(true)));
+ }
const guide = node('a', 'secondary-button', 'Cloud Sync setup guide');
guide.href = 'https://github.com/Coding-Dev-Tools/engraphis/blob/main/docs/SYNC.md#configure-a-customer-installation';
guide.target = '_blank';
@@ -6573,6 +6621,54 @@
target.append(actions);
}
+ function renderCreatedSyncKey(target, key, status) {
+ target.replaceChildren(
+ node('h3', '', 'Cloud Sync key created'),
+ node('p', 'automation-policy-note',
+ 'Store this key somewhere safe. It is shown only once, and every device that syncs this workspace needs it.'),
+ node('code', 'sync-key-value', key),
+ );
+ const actions = node('div', 'automation-policy-actions');
+ actions.append(button('Copy key', 'primary-button', async () => {
+ try {
+ if (!navigator.clipboard) throw new Error('clipboard unavailable');
+ await navigator.clipboard.writeText(key);
+ showNotice('Cloud Sync key copied.');
+ } catch (_) {
+ showNotice('Copy failed. Select the key and copy it manually.');
+ }
+ }));
+ actions.append(button('I stored the key', 'secondary-button', () => renderSyncStatus(status)));
+ target.append(actions);
+ }
+
+ async function createSyncKey(rotate) {
+ const target = byId('sync-result');
+ if (!target) return;
+ if (rotate && !window.confirm(
+ 'Replace the saved Cloud Sync key? Data already synced under the old key cannot be read until every device uses the new one.'
+ )) return;
+ const request = beginScopedRequest('sync-key');
+ try {
+ const result = await api('/sync/key', {
+ method: 'POST',
+ body: { rotate: Boolean(rotate) },
+ signal: request.signal,
+ });
+ if (!isCurrentScopedRequest(request)) return;
+ if (!result || !result.key) throw new Error('Engraphis did not return a key.');
+ const status = await api('/sync/status', { signal: request.signal });
+ if (!isCurrentScopedRequest(request)) return;
+ state.syncStatus = status;
+ renderCreatedSyncKey(target, result.key, status);
+ } catch (error) {
+ if (!isCurrentScopedRequest(request)) return;
+ const failure = hostedFailure('sync', error);
+ if (failure) renderHostedFailure(target, failure);
+ else target.replaceChildren(empty(`Could not create a Cloud Sync key: ${error.message}`, 'form-error'));
+ }
+ }
+
async function loadSync() {
const request = beginScopedRequest('sync-status');
const target = byId('sync-result');
@@ -6640,6 +6736,7 @@
'Growth, retention, decay and entity Analytics',
'Auto Consolidation with hosted retention policies',
'Auto Dreaming with reviewable managed proposals',
+ 'Managed Jev decisions: up to 500 calls per person per day',
'Private account and billing support',
],
team: [
@@ -6718,6 +6815,13 @@
local: 'Not connected',
};
+ function licenseTrialDaysRemaining(license = state.license, now = Date.now()) {
+ const ends = Number(license && license.trial && license.trial.ends_at);
+ if (!(ends > 0)) return null;
+ const remaining = Math.ceil((ends * 1000 - now) / 86_400_000);
+ return Number.isFinite(remaining) ? Math.max(0, remaining) : null;
+ }
+
function licenseTrialEnds(license = state.license) {
const ends = Number(license && license.trial && license.trial.ends_at);
const date = ends > 0 ? validDate(ends) : null;
@@ -6762,7 +6866,13 @@
target.append(box);
};
if (license.error) callout('Hosted authorization unavailable', text(license.error));
- if (access === 'trial_expired') {
+ if (access === 'trial') {
+ const daysLeft = licenseTrialDaysRemaining(license);
+ const when = daysLeft === null ? 'Your free trial is active'
+ : daysLeft === 0 ? 'Your free trial ends today'
+ : `Your free trial ends in ${daysLeft} day${daysLeft === 1 ? '' : 's'}`;
+ callout(when, `${ends ? `It is active until ${ends}. ` : ''}Subscribe to keep Cloud Sync, Analytics, Automation, and Team access.`);
+ } else if (access === 'trial_expired') {
callout(`Your free trial has ended${ends ? ` on ${ends}` : ''}`, 'Hosted features are locked. Everything you wrote is still in your local database and fully usable. The trial runs once per account, so restoring hosted features means subscribing.');
} else if (access === 'lapsed') {
const reason = LICENSE_STATUS_NOTES[license.entitlement_status];
@@ -6783,6 +6893,42 @@
});
target.append(list);
}
+ // Benefits included with a paid plan that are not hosted feature keys. This is what
+ // makes the paid plan legible: Jev is the largest included benefit and has no lock row.
+ const services = license.included_services && typeof license.included_services === 'object'
+ ? Object.values(license.included_services).filter(item => item && typeof item === 'object')
+ : [];
+ if (services.length) {
+ const list = node('ul', 'license-features');
+ list.setAttribute('aria-label', 'Included with paid plans');
+ services.forEach(service => {
+ const calls = Number(service.calls_per_person_per_day);
+ const label = text(service.label || 'Included service');
+ const detail = Number.isSafeInteger(calls) && calls > 0
+ ? `${label} · up to ${calls.toLocaleString()} calls per person per day`
+ : label;
+ const item = node('li', '', `★ ${detail}`);
+ item.setAttribute('aria-label', `${label}: included with paid plans`);
+ list.append(item);
+ });
+ target.append(
+ node('p', 'automation-policy-note', 'Included with Pro and Team'),
+ list,
+ );
+ }
+ // A server grant this build cannot render must never look like a lock on something the
+ // customer bought. Name it and ask for an update instead of silently dropping it.
+ const unrecognized = Array.isArray(license.unrecognized_features)
+ ? license.unrecognized_features.filter(item => typeof item === 'string' && item)
+ : [];
+ if (access !== 'inactive' && unrecognized.length) {
+ const box = node('p', 'license-callout');
+ box.append(
+ node('strong', '', 'Your plan includes a newer capability'),
+ document.createTextNode(` ${unrecognized.map(item => text(item)).join(', ')}. Update Engraphis to use it.`),
+ );
+ target.append(box);
+ }
if (access === 'active') target.append(node('p', 'plan-support', 'Thank you for supporting Engraphis. Your subscription funds hosted infrastructure and ongoing development.'));
}
diff --git a/engraphis/entitlement_contract.json b/engraphis/entitlement_contract.json
new file mode 100644
index 00000000..4ca0e8bb
--- /dev/null
+++ b/engraphis/entitlement_contract.json
@@ -0,0 +1,93 @@
+{
+ "schema": "engraphis-entitlement-contract/v1",
+ "source": "Read-only mirror of the private control plane's hosted entitlement contract. The server authorizes every paid operation; this file only decides which plan advertises a capability.",
+ "plans": {
+ "free": [],
+ "pro": [
+ "analytics",
+ "automation",
+ "consolidation",
+ "dreaming",
+ "sync"
+ ],
+ "team": [
+ "analytics",
+ "automation",
+ "consolidation",
+ "dreaming",
+ "sync",
+ "team"
+ ]
+ },
+ "features": {
+ "analytics": {
+ "plan": "pro",
+ "label": "Analytics",
+ "status": "available"
+ },
+ "automation": {
+ "plan": "pro",
+ "label": "Automation",
+ "status": "available"
+ },
+ "consolidation": {
+ "plan": "pro",
+ "label": "Auto Consolidation",
+ "status": "available"
+ },
+ "dreaming": {
+ "plan": "pro",
+ "label": "Auto Dreaming",
+ "status": "available"
+ },
+ "sync": {
+ "plan": "pro",
+ "label": "Cloud Sync",
+ "status": "available"
+ },
+ "team": {
+ "plan": "team",
+ "label": "Team administration",
+ "status": "available"
+ },
+ "export": {
+ "plan": "free",
+ "label": "Local workspace export",
+ "status": "free"
+ },
+ "compliance_export": {
+ "plan": "team",
+ "label": "Compliance export",
+ "status": "declared"
+ },
+ "hosted_multi_user_roles": {
+ "plan": "team",
+ "label": "Team roster and roles",
+ "status": "declared"
+ },
+ "hosted_team_audit_export": {
+ "plan": "team",
+ "label": "Team audit export",
+ "status": "declared"
+ },
+ "hosted_scoped_agent_tokens": {
+ "plan": "team",
+ "label": "Scoped agent tokens",
+ "status": "declared"
+ }
+ },
+ "included_services": {
+ "managed_jev": {
+ "label": "Managed Jev decisions",
+ "plan": "pro",
+ "calls_per_person_per_day": 500
+ }
+ },
+ "limits": {
+ "trial_days_by_plan": {
+ "pro": 7,
+ "team": 14
+ },
+ "grace_hours": 24
+ }
+}
diff --git a/engraphis/entitlements.py b/engraphis/entitlements.py
new file mode 100644
index 00000000..0d3f67b2
--- /dev/null
+++ b/engraphis/entitlements.py
@@ -0,0 +1,301 @@
+"""Read-only mirror of the private hosted entitlement contract.
+
+This module answers one question: which plan *advertises* a hosted capability. It never
+authorizes anything. Every paid operation is still gated on the control plane's response,
+and the dashboard only renders what the contract declares.
+
+Keeping one machine-readable mirror here -- instead of three hand-maintained copies in the
+client and its tests -- is the point. ``scripts/check_commercial_manifest.py`` compares this
+file with the private contract in CI, so a capability that exists only server-side fails the
+release gate instead of silently disappearing from a paying customer's dashboard.
+"""
+from __future__ import annotations
+
+import json
+from functools import lru_cache
+from pathlib import Path
+from typing import Optional
+
+
+SCHEMA = "engraphis-entitlement-contract/v1"
+PLANS = ("free", "pro", "team")
+STATUSES = ("available", "free", "declared")
+CONTRACT_PATH = Path(__file__).with_name("entitlement_contract.json")
+
+_MAX_KEY_CHARS = 64
+_MAX_UNRECOGNIZED = 32
+
+
+@lru_cache(maxsize=1)
+def contract() -> dict:
+ """Return the validated contract, cached for the life of the process."""
+
+ data = json.loads(CONTRACT_PATH.read_text(encoding="utf-8"))
+ _validate(data)
+ return data
+
+
+def reload() -> None:
+ """Drop the cached contract. Tests and release tooling only."""
+
+ contract.cache_clear()
+
+
+def _require(condition: object, message: str) -> None:
+ if not condition:
+ raise ValueError("entitlement contract: " + message)
+
+
+def _validate(data: object) -> None:
+ """Reject a malformed contract before any caller can misread it.
+
+ The checks are deliberately structural rather than a JSON Schema dependency: the core
+ must keep running on the numpy-only floor, and a clear ``ValueError`` is more useful to
+ a release engineer than a schema-library stack trace.
+ """
+
+ _require(isinstance(data, dict), "root must be a JSON object")
+ _require(data.get("schema") == SCHEMA, "schema must be " + SCHEMA)
+
+ plans = data.get("plans")
+ _require(isinstance(plans, dict), "plans must be an object")
+ _require(set(plans) == set(PLANS), "plans must name free, pro and team")
+
+ features = data.get("features")
+ _require(isinstance(features, dict) and features, "features must be a non-empty object")
+
+ for plan in PLANS:
+ names = plans[plan]
+ _require(isinstance(names, list), "plans.%s must be a list" % plan)
+ _require(
+ all(isinstance(name, str) and name.strip() for name in names),
+ "plans.%s must contain non-empty feature names" % plan,
+ )
+ _require(
+ len(names) == len(set(names)),
+ "plans.%s must not repeat a feature" % plan,
+ )
+ for name in names:
+ spec = features.get(name)
+ _require(
+ isinstance(spec, dict),
+ "plans.%s names an unknown feature: %s" % (plan, name),
+ )
+ # ``plan`` is the *minimum* plan that grants a feature; Team inherits Pro.
+ declared_plan = spec.get("plan")
+ if plan == "team":
+ _require(
+ declared_plan in ("pro", "team"),
+ "plans.team contains %s, which declares plan %r"
+ % (name, declared_plan),
+ )
+ else:
+ _require(
+ declared_plan == plan,
+ "plans.%s contains %s, which declares plan %r"
+ % (plan, name, declared_plan),
+ )
+ _require(
+ spec.get("status") == "available",
+ "plans.%s contains %s, which is not available" % (plan, name),
+ )
+
+ _require(
+ set(plans["pro"]).issubset(set(plans["team"])),
+ "the team plan must include every pro feature",
+ )
+
+ for name, spec in features.items():
+ _require(
+ isinstance(name, str) and name.strip() and len(name) <= _MAX_KEY_CHARS,
+ "feature names must be non-empty and at most %d characters" % _MAX_KEY_CHARS,
+ )
+ _require(isinstance(spec, dict), "feature %s must be an object" % name)
+ _require(spec.get("plan") in PLANS, "feature %s names an unknown plan" % name)
+ _require(
+ isinstance(spec.get("label"), str) and bool(spec["label"].strip()),
+ "feature %s needs a non-empty label" % name,
+ )
+ _require(
+ spec.get("status") in STATUSES,
+ "feature %s has an unknown status" % name,
+ )
+ if spec["status"] == "free":
+ _require(
+ spec["plan"] == "free",
+ "free feature %s must resolve to the free plan" % name,
+ )
+ _require(
+ name not in plans["pro"] and name not in plans["team"],
+ "free feature %s must not be advertised as paid" % name,
+ )
+ if spec["status"] == "available":
+ _require(
+ name in plans[spec["plan"]],
+ "available feature %s is missing from plans.%s" % (name, spec["plan"]),
+ )
+ if spec["plan"] == "pro":
+ _require(
+ name in plans["team"],
+ "available pro feature %s is missing from plans.team" % name,
+ )
+ if spec["status"] == "declared":
+ _require(
+ not any(name in names for names in plans.values()),
+ "declared feature %s must not be granted until it is available" % name,
+ )
+
+ included = data.get("included_services", {})
+ _require(isinstance(included, dict), "included_services must be an object")
+ for name, spec in included.items():
+ _require(
+ isinstance(name, str) and name.strip() and len(name) <= _MAX_KEY_CHARS,
+ "included service names must be non-empty and bounded",
+ )
+ _require(isinstance(spec, dict), "included service %s must be an object" % name)
+ _require(spec.get("plan") in PLANS, "included service %s names an unknown plan" % name)
+ _require(
+ isinstance(spec.get("label"), str) and bool(spec["label"].strip()),
+ "included service %s needs a non-empty label" % name,
+ )
+ calls = spec.get("calls_per_person_per_day")
+ if calls is not None:
+ _require(
+ isinstance(calls, int) and not isinstance(calls, bool) and calls > 0,
+ "included service %s calls_per_person_per_day must be a positive integer"
+ % name,
+ )
+
+ limits = data.get("limits")
+ _require(isinstance(limits, dict), "limits must be an object")
+ days = limits.get("trial_days_by_plan")
+ _require(
+ isinstance(days, dict) and set(days) == {"pro", "team"},
+ "limits.trial_days_by_plan must name pro and team",
+ )
+ for plan, value in days.items():
+ _require(
+ isinstance(value, int) and not isinstance(value, bool) and value > 0,
+ "limits.trial_days_by_plan.%s must be a positive integer" % plan,
+ )
+ grace = limits.get("grace_hours")
+ _require(
+ isinstance(grace, int) and not isinstance(grace, bool) and grace > 0,
+ "limits.grace_hours must be a positive integer",
+ )
+
+
+def features_for(plan: object) -> list:
+ """Return the hosted features a plan advertises, sorted and never guessed.
+
+ ``local`` is the client's own alias for an unconnected installation and resolves to the
+ free plan, exactly as the server treats an unknown or unpaid plan.
+ """
+
+ key = str(plan or "").strip().lower()
+ if key == "local":
+ key = "free"
+ return sorted(contract()["plans"].get(key, ()))
+
+
+def feature_spec(feature: object) -> Optional[dict]:
+ """Return one feature's contract entry, or ``None`` when this build does not know it."""
+
+ key = str(feature or "").strip().lower()
+ if not key or len(key) > _MAX_KEY_CHARS:
+ return None
+ spec = contract()["features"].get(key)
+ return spec if isinstance(spec, dict) else None
+
+
+def required_plans() -> dict:
+ """Return every known capability mapped to the minimum plan that grants it."""
+
+ plans = {name: str(spec["plan"]) for name, spec in contract()["features"].items()}
+ plans.update({
+ name: str(spec["plan"])
+ for name, spec in contract().get("included_services", {}).items()
+ })
+ return plans
+
+
+def included_services() -> dict:
+ """Return benefits included with a paid plan but not gated as hosted features.
+
+ These are informational: the server still authorizes every call. Keeping them out of
+ the feature-row list prevents a tick-box that implies a live grant when the account
+ portal may have a different allowance state.
+ """
+
+ return {
+ name: dict(spec)
+ for name, spec in contract().get("included_services", {}).items()
+ }
+
+
+def required_plan(feature: object) -> str:
+ """Return the advertised minimum plan for a feature.
+
+ Unknown names keep the historical ``pro`` fallback so a new server key never tells a
+ Team customer to buy Pro. Callers that need to distinguish "unknown" from "Pro" use
+ :func:`feature_spec`.
+ """
+
+ spec = feature_spec(feature)
+ return str(spec["plan"]) if spec else "pro"
+
+
+def labels() -> dict:
+ """Return the labels for features this build can actually render."""
+
+ return {
+ name: str(spec["label"])
+ for name, spec in contract()["features"].items()
+ if spec.get("status") == "available"
+ }
+
+
+def is_known(feature: object) -> bool:
+ """Return whether this build has any contract entry for *feature*."""
+
+ return feature_spec(feature) is not None
+
+
+def unrecognized(granted: object, plan: object) -> list:
+ """Return granted keys this build does not know, bounded and sorted.
+
+ A future server release may grant a capability this client predates. The grant must
+ never become a feature row (it cannot be rendered or verified here), but staying silent
+ is how a paying customer ends up with a lock over something they bought. The caller
+ surfaces this list as "update Engraphis to use it" instead.
+ """
+
+ if not isinstance(granted, (list, tuple)):
+ return []
+ values = set()
+ for item in granted:
+ if not isinstance(item, str):
+ continue
+ key = item.strip().lower()
+ if key and len(key) <= _MAX_KEY_CHARS:
+ values.add(key)
+ if "automation" in values:
+ values.update(("consolidation", "dreaming"))
+ allowed = set(features_for(plan))
+ known = contract()["features"]
+ return sorted(name for name in values if name not in allowed and name not in known)[
+ :_MAX_UNRECOGNIZED
+ ]
+
+
+def trial_days_by_plan() -> dict:
+ """Return the disclosed trial duration for each paid plan."""
+
+ days = contract()["limits"]["trial_days_by_plan"]
+ return {plan: int(days[plan]) for plan in ("pro", "team")}
+
+
+def grace_hours() -> int:
+ """Return the disclosed hosted-account write-grace window."""
+
+ return int(contract()["limits"]["grace_hours"])
diff --git a/engraphis/hosted_client.py b/engraphis/hosted_client.py
index a1931ac9..e97f9769 100644
--- a/engraphis/hosted_client.py
+++ b/engraphis/hosted_client.py
@@ -16,6 +16,9 @@
from typing import Optional
from urllib.parse import urlsplit, urlunsplit
+from engraphis.entitlements import required_plan as _contract_required_plan
+from engraphis.entitlements import required_plans as _contract_required_plans
+
TRIAL_DAYS = 7
TRIAL_SECONDS = 7 * 24 * 60 * 60
@@ -52,22 +55,11 @@
),
}
-_REQUIRED_PLAN = {
- "analytics": "pro",
- "automation": "pro",
- "consolidation": "pro",
- "dreaming": "pro",
- "export": "pro",
- "compliance_export": "team",
- "sync": "pro",
- "team": "team",
- # Team-only capabilities named in commercial_manifest.json. Without explicit entries
- # these fell through to the "pro" default, so a Team capability would tell a customer
- # who already holds Pro to buy Pro again.
- "hosted_team_audit_export": "team",
- "hosted_scoped_agent_tokens": "team",
- "hosted_multi_user_roles": "team",
-}
+# One read-only mirror of the private hosted entitlement contract. This table decides which
+# plan a *message* advertises; it never authorizes anything. The contract lives in
+# ``engraphis/entitlement_contract.json`` so the client, its tests, and the release gate all
+# read the same file instead of maintaining three copies that drift independently.
+_REQUIRED_PLAN = _contract_required_plans()
class CloudUrlUnresolved(ValueError):
@@ -95,7 +87,7 @@ def __init__(self, message: str, *, feature: Optional[str] = None):
def required_plan(feature: str) -> str:
"""Return the advertised minimum hosted plan for a feature."""
- return _REQUIRED_PLAN.get(str(feature or "").strip().lower(), "pro")
+ return _contract_required_plan(feature)
def _safe_target(value: str) -> str:
diff --git a/engraphis/routes/v2_api.py b/engraphis/routes/v2_api.py
index 3ec59f65..fc14c6cd 100644
--- a/engraphis/routes/v2_api.py
+++ b/engraphis/routes/v2_api.py
@@ -15,6 +15,7 @@
import logging
import math
import os
+import secrets
import threading
import time
import urllib.error
@@ -29,6 +30,7 @@
from pydantic import BaseModel, Field, StrictBool, StrictInt
from starlette.concurrency import run_in_threadpool
+from engraphis import entitlements as _entitlements
from engraphis import licensing
from engraphis.commercial import trial_days_by_plan
from engraphis.config import DEFAULT_RELAY_URL, canonicalize_relay_url, settings
@@ -300,8 +302,12 @@ def _managed_error_message(exc) -> str:
return message
-def _managed_call(fn, *args, **kwargs):
- """Map the public cloud-client protocol onto structured dashboard errors."""
+def _managed_call(fn, *args, feature: Optional[str] = None, **kwargs):
+ """Map the public cloud-client protocol onto structured dashboard errors.
+
+ ``feature`` names the capability being attempted so a denial can carry the plan that
+ actually sells it. It is consumed here and never forwarded to the cloud client.
+ """
from engraphis.cloud_features import CloudFeatureError
try:
@@ -324,10 +330,40 @@ def _managed_call(fn, *args, **kwargs):
else:
logger.debug("managed cloud operation failed (%s, status=%s, transient=%s)",
type(exc).__name__, exc.status, exc.transient)
+ reason = getattr(exc, "reason", None)
detail = {"error": _managed_error_message(exc), "managed_cloud": True,
"transient": exc.transient}
if exc.code in {"consent_required", "cloud_unconfigured"}:
detail["code"] = exc.code
+ if reason:
+ detail["reason"] = reason
+ if exc.status in {401, 402, 403} and exc.code not in {
+ "consent_required", "cloud_unconfigured",
+ }:
+ # A denied paid action must tell the customer what fixes it. The status alone
+ # cannot distinguish a lapsed plan, a full Team seat list, a revoked session,
+ # and a role problem; those need four different next steps.
+ fallback = (
+ "entitlement_expired" if exc.status == 402
+ else "token_stale" if exc.status == 401
+ else "insufficient_role"
+ )
+ detail["reason"] = reason or fallback
+ detail["account_url"] = licensing.account_url()
+ tier = licensing.required_plan(feature) if feature else ""
+ if reason == "seat_limit_exceeded":
+ tier = "team"
+ if tier in ("pro", "team"):
+ detail["tier_required"] = tier
+ detail["upgrade_url"] = (
+ licensing.upgrade_url(tier)
+ if exc.status == 402
+ else licensing.account_url()
+ )
+ else:
+ # No feature name means no safe plan to sell. The account portal is
+ # always correct; guessing "pro" would upsell the wrong product.
+ detail["upgrade_url"] = licensing.account_url()
raise HTTPException(
status_code=exc.status or 503,
detail=detail,
@@ -1783,7 +1819,7 @@ def ensure_current_local_intent():
"already submitted work may continue until Cloud responds."}
def rejected(error=exc):
raise error
- return _managed_call(rejected)
+ return _managed_call(rejected, feature="automation")
# Do not hold a database write lock over the network call. Compare the local
# revision atomically when applying its acknowledgement instead, so a late
# enable cannot undo an opt-out from another tab or process.
@@ -2343,7 +2379,8 @@ def analytics(workspace: Optional[str] = None):
from engraphis.cloud_features import run_managed_job
ws = workspace or _require_ws()
- envelope = _managed_call(run_managed_job, service(), ws, "analytics")
+ envelope = _managed_call(run_managed_job, service(), ws, "analytics",
+ feature="analytics")
result = envelope.get("result", envelope)
if isinstance(result, dict) and isinstance(envelope, dict):
if "state" in envelope and "state" not in result:
@@ -2357,7 +2394,8 @@ def analytics_job_result(job_id: str, workspace: Optional[str] = None):
from engraphis.cloud_features import get_analytics_job_result
ws = workspace or _require_ws()
- return _managed_call(get_analytics_job_result, service(), ws, job_id)
+ return _managed_call(get_analytics_job_result, service(), ws, job_id,
+ feature="analytics")
@router.get("/analytics/export")
@@ -2457,13 +2495,15 @@ def automation_get(workspace: Optional[str] = None):
"error": "The selected workspace does not exist.",
"managed_cloud": True,
})
- cloud = _managed_call(CloudFeatureClient.from_environment, workspace_id)
- policy = _managed_call(cloud.get_policy, workspace_id)
+ cloud = _managed_call(CloudFeatureClient.from_environment, workspace_id,
+ feature="automation")
+ policy = _managed_call(cloud.get_policy, workspace_id, feature="automation")
try:
unconfigured = int(policy.get("version", -1)) == 0
except (AttributeError, TypeError, ValueError, OverflowError):
unconfigured = False
- recent = _managed_call(cloud.list_jobs, workspace_id, limit=10)
+ recent = _managed_call(cloud.list_jobs, workspace_id, limit=10,
+ feature="automation")
recent_jobs = recent.get("jobs") if isinstance(recent, dict) else []
if not isinstance(recent_jobs, list):
recent_jobs = []
@@ -2502,7 +2542,8 @@ def automation_bootstrap(workspace: Optional[str] = None):
"error": "The selected workspace does not exist.",
"managed_cloud": True,
})
- cloud = _managed_call(CloudFeatureClient.from_environment, bootstrap_workspace_id)
+ cloud = _managed_call(CloudFeatureClient.from_environment, bootstrap_workspace_id,
+ feature="automation")
default_policy = {
"enabled": True,
"cadence_minutes": 1440,
@@ -2512,7 +2553,8 @@ def automation_bootstrap(workspace: Optional[str] = None):
"infer": False,
}
with _automation_bootstrap_lock(cloud.organization_id, bootstrap_workspace_id):
- policy = _managed_call(cloud.get_policy, bootstrap_workspace_id)
+ policy = _managed_call(cloud.get_policy, bootstrap_workspace_id,
+ feature="automation")
try:
unconfigured = int(policy.get("version", -1)) == 0
except (AttributeError, TypeError, ValueError, OverflowError):
@@ -2524,10 +2566,11 @@ def automation_bootstrap(workspace: Optional[str] = None):
if phase != "policy_saved":
if phase != "snapshot_uploaded":
workspace_id, snapshot = _managed_call(
- build_managed_snapshot, service(), ws
+ build_managed_snapshot, service(), ws, feature="automation"
)
receipt = _managed_call(
- cloud.upload_snapshot, workspace_id, snapshot
+ cloud.upload_snapshot, workspace_id, snapshot,
+ feature="automation",
)
generation = (
receipt.get("generation", snapshot["generation"])
@@ -2541,7 +2584,8 @@ def automation_bootstrap(workspace: Optional[str] = None):
"snapshot_uploaded",
generation=int(generation),
)
- _managed_call(cloud.save_policy, bootstrap_workspace_id, default_policy)
+ _managed_call(cloud.save_policy, bootstrap_workspace_id, default_policy,
+ feature="automation")
save_automation_bootstrap_phase(
service(),
cloud.organization_id,
@@ -2568,8 +2612,9 @@ def automation_set(req: _AutomationReq, workspace: Optional[str] = None):
"error": "The selected workspace does not exist.",
"managed_cloud": True,
})
- cloud = _managed_call(CloudFeatureClient.from_environment, workspace_id)
- current = _managed_call(cloud.get_policy, workspace_id)
+ cloud = _managed_call(CloudFeatureClient.from_environment, workspace_id,
+ feature="automation")
+ current = _managed_call(cloud.get_policy, workspace_id, feature="automation")
cadence_minutes = (
req.cadence_hours * 60 if req.cadence_hours is not None
else int(_automation_policy_value(current, "cadence_minutes", 1440))
@@ -2596,9 +2641,12 @@ def automation_set(req: _AutomationReq, workspace: Optional[str] = None):
if enabled:
# Upload only when managed processing is actually enabled. Merely viewing or
# disabling a cloud policy must never transfer memory content.
- workspace_id, snapshot = _managed_call(build_managed_snapshot, service(), ws)
- _managed_call(cloud.upload_snapshot, workspace_id, snapshot)
- saved = _managed_call(cloud.save_policy, workspace_id, policy)
+ workspace_id, snapshot = _managed_call(build_managed_snapshot, service(), ws,
+ feature="automation")
+ _managed_call(cloud.upload_snapshot, workspace_id, snapshot,
+ feature="automation")
+ saved = _managed_call(cloud.save_policy, workspace_id, policy,
+ feature="automation")
return {
**policy,
**saved,
@@ -2617,7 +2665,8 @@ def maintenance_run(req: _MaintenanceReq, workspace: Optional[str] = None):
from engraphis.cloud_features import run_managed_job
ws = _require_ws(workspace)
- envelope = _managed_call(run_managed_job, service(), ws, "consolidate")
+ envelope = _managed_call(run_managed_job, service(), ws, "consolidate",
+ feature="automation")
result = envelope.get("result", envelope)
return {
"dry_run": True,
@@ -3140,35 +3189,27 @@ def code_export(workspace: str, repo: str,
# ── license ───────────────────────────────────────────────────────────────────
-# Mirror of the private control plane's plan→feature table (read-only reference:
-# the hosted entitlement contract the hosted plan-feature contract). Plans are lowercase
-# and only ``pro``/``team`` are paid; any other value — unknown, empty, or mis-cased —
-# resolves to no features, exactly as the server treats it.
+# One read-only mirror of the private control plane's plan→feature contract, loaded from
+# ``engraphis/entitlement_contract.json``. Plans are lowercase and only ``pro``/``team`` are
+# paid; any other value — unknown, empty, or mis-cased — resolves to no features, exactly as
+# the server treats it.
#
-# The server's own keys are {analytics, automation, export, sync, team}. This client's
-# commercial manifest additionally names Auto Consolidation and Auto Dreaming, which the
-# server grants under ``automation``. They are expanded here so the dashboard can never
-# draw a lock on a capability the customer's plan already includes.
+# The server grants Auto Consolidation and Auto Dreaming under ``automation``; this client
+# lists them separately so a paying customer never sees a lock on a capability the plan
+# already includes. ``_normalized_features`` expands that alias on the way in.
_AUTOMATION_FEATURES = ("automation", "consolidation", "dreaming")
-_PRO_FEATURES = ("analytics", "sync") + _AUTOMATION_FEATURES
+_PRO_FEATURES = tuple(_entitlements.features_for("pro"))
_HOSTED_ENTITLEMENTS = {
- "free": (),
- "local": (),
+ "free": tuple(_entitlements.features_for("free")),
+ "local": tuple(_entitlements.features_for("local")),
"pro": _PRO_FEATURES,
- "team": _PRO_FEATURES + ("team",),
+ "team": tuple(_entitlements.features_for("team")),
}
# Labels for the dashboard's entitlement list; it renders one row per key and ticks the
-# ones ``features`` contains. Naming follows the customer-facing vocabulary in
-# .env.example and the commercial manifest.
-_FEATURE_LABELS = {
- "analytics": "Analytics",
- "automation": "Automation",
- "consolidation": "Auto Consolidation",
- "dreaming": "Auto Dreaming",
- "sync": "Cloud Sync",
- "team": "Team administration",
-}
+# ones ``features`` contains. Only capabilities this build can render are labelled, so the
+# set of known labels stays equal to the Team plan's grant.
+_FEATURE_LABELS = dict(_entitlements.labels())
def entitled_features(plan: str) -> list:
@@ -3179,7 +3220,7 @@ def entitled_features(plan: str) -> list:
Cloud and gated on its response status.
"""
- return sorted(_HOSTED_ENTITLEMENTS.get(str(plan or "").strip().lower(), ()))
+ return _entitlements.features_for(plan)
# ── authoritative plan resolution ─────────────────────────────────────────────────────
@@ -3434,6 +3475,12 @@ def _normalized_features(values: object, plan: str) -> list:
return sorted(granted & allowed & set(_FEATURE_LABELS))
+def _unrecognized_features(values: object, plan: str) -> list:
+ """Return server grants this build cannot render, so the dashboard can say so."""
+
+ return _entitlements.unrecognized(values, plan)
+
+
def _session_entitlement_snapshot() -> tuple[dict, Optional[str]]:
"""Read the session once; return its entitlement and the digest of those bytes.
@@ -3465,6 +3512,9 @@ def _session_entitlement_snapshot() -> tuple[dict, Optional[str]]:
# own plan table fills it in.
"features": _normalized_features(declared.get("cloud_features"), plan)
if active else [],
+ "unrecognized_features": _unrecognized_features(
+ declared.get("cloud_features"), plan
+ ) if active else [],
"cloud_access_active": active,
"organization_id": str(declared.get("organization_id") or ""),
"fetched_at": float(declared.get("entitlement_checked_at") or 0.0),
@@ -3552,6 +3602,8 @@ def _read_entitlement_cache_snapshot() -> tuple[dict, Optional[str]]:
resolved = {
"plan": plan,
"features": _normalized_features(value.get("features"), plan) if active else [],
+ "unrecognized_features": _unrecognized_features(value.get("features"), plan)
+ if active else [],
"cloud_access_active": active,
"organization_id": organization_id,
"fetched_at": fetched_at,
@@ -4061,6 +4113,7 @@ def _resolved_entitlement(known: dict, *, source: str) -> dict:
resolved = {
"plan": known["plan"],
"features": list(known["features"]),
+ "unrecognized_features": list(known.get("unrecognized_features") or []),
"source": source,
"cloud_access_active": known["cloud_access_active"],
"checked_at": known["fetched_at"],
@@ -4116,9 +4169,18 @@ def hosted_plan_summary() -> dict:
# every hosted call is about to reject. One rule for both: the features are the ones
# that are live right now.
features = list(entitlement["features"]) if state in ("active", "trial") else []
+ # A server grant this build cannot render is not a grant either, but silence is how a
+ # paying customer ends up staring at a lock over something they bought. Keep it out of
+ # ``features`` (the lock-badge loop would tick a row it cannot verify) and surface it
+ # separately so the dashboard can ask for an update.
+ unrecognized = (
+ list(entitlement.get("unrecognized_features") or [])
+ if state in ("active", "trial") else []
+ )
return {
"plan": entitlement["plan"],
"features": features,
+ "unrecognized_features": unrecognized,
"plan_source": entitlement["source"],
"cloud_access_active": entitlement["cloud_access_active"],
"plan_checked_at": entitlement["checked_at"],
@@ -4192,6 +4254,12 @@ def get_license():
"plan": plan,
"features": summary["features"],
"known_features": dict(_FEATURE_LABELS),
+ # Benefits included with a paid plan that are not gated as hosted feature keys.
+ # Informational only: the server still authorizes every call.
+ "included_services": _entitlements.included_services(),
+ # Keys the control plane granted that this build does not know. They are never
+ # rendered as feature rows; the dashboard offers an update instead of a lock.
+ "unrecognized_features": summary["unrecognized_features"],
# Diagnostics for support: which resolution rule produced this plan — one of
# ``environment`` (the operator override), ``session`` (the entitlement the cloud
# returned on registration/refresh), ``cloud`` (the cached entitlements read),
@@ -4312,11 +4380,11 @@ def _relay_url() -> str:
def sync_status():
"""Report local sync readiness without disclosing the encryption key."""
from engraphis.backends.sync_relay import (
- SYNC_E2EE_KEY_ENV,
RelayError,
_new_e2ee_cipher,
configured_sync_e2ee_key,
has_sync_token,
+ sync_e2ee_key_source,
sync_read_only,
)
from engraphis.cloud_session import CloudSessionError, configured
@@ -4330,13 +4398,14 @@ def sync_status():
# A connected account cannot sync without the separate, user-held E2EE key.
# Validate the same key shape and optional crypto backend as the relay transport,
# but return only bounded status labels; never serialize key bytes or input.
- key_state = "missing" if not os.environ.get(SYNC_E2EE_KEY_ENV, "").strip() else "invalid"
+ key_source = sync_e2ee_key_source()
+ key_state = "missing"
has_key = False
encryption_available = False
try:
key = configured_sync_e2ee_key()
except RelayError:
- pass
+ key_state = "invalid" if key_source != "none" else "missing"
else:
has_key = True
key_state = "ready"
@@ -4352,6 +4421,11 @@ def sync_status():
"ready": available and local_prerequisites_ready,
"has_key": has_key,
"key_state": key_state,
+ # Where the key came from, without ever including it. The dashboard only offers
+ # generation when no key exists; environment-managed deployments keep precedence.
+ "key_source": key_source,
+ "can_generate_key": key_source == "none",
+ "can_rotate_key": key_source == "state_file",
"encryption_available": encryption_available,
"local_prerequisites_ready": local_prerequisites_ready,
"has_cloud_session": has_cloud_session,
@@ -4369,6 +4443,60 @@ def sync_status():
}
+class _SyncKeyReq(BaseModel):
+ rotate: StrictBool = False
+
+
+@router.post("/sync/key")
+def create_sync_key(req: Optional[_SyncKeyReq] = None):
+ """Create a user-held Cloud Sync key once, on this device.
+
+ The key is returned exactly once and written only to the owner-only state directory.
+ It is never logged, never re-readable through the API, and never overrides an
+ operator-provisioned ``ENGRAPHIS_SYNC_E2EE_KEY``. Rotation is explicit because data
+ already synced under the old key cannot be read until every device uses the new one.
+ """
+ from fastapi.responses import JSONResponse
+
+ from engraphis.backends.sync_relay import (
+ SYNC_E2EE_KEY_ENV,
+ RelayError,
+ has_saved_sync_e2ee_key,
+ save_sync_e2ee_key,
+ )
+
+ if os.environ.get(SYNC_E2EE_KEY_ENV, "").strip():
+ raise HTTPException(status_code=409, detail={
+ "error": "Cloud Sync key is managed by %s." % SYNC_E2EE_KEY_ENV,
+ "code": "sync_key_managed_by_environment",
+ })
+ rotate = bool(req.rotate) if req is not None else False
+ if has_saved_sync_e2ee_key() and not rotate:
+ raise HTTPException(status_code=409, detail={
+ "error": "A Cloud Sync key already exists on this device. Rotating it makes "
+ "data synced under the old key unreadable until every device uses "
+ "the new key.",
+ "code": "sync_key_exists",
+ })
+ try:
+ key = save_sync_e2ee_key(secrets.token_urlsafe(32))
+ except (OSError, RelayError, ValueError):
+ raise HTTPException(status_code=503, detail={
+ "error": "The Cloud Sync key could not be saved on this device.",
+ "code": "sync_key_unwritable",
+ }) from None
+ return JSONResponse(
+ {
+ "configured": True,
+ "key": key,
+ "key_source": "state_file",
+ "env_var": SYNC_E2EE_KEY_ENV,
+ "restart_required": False,
+ },
+ headers={"Cache-Control": "no-store"},
+ )
+
+
def _sync_all(svc) -> dict:
"""Push every workspace's memories to the relay and pull every peer's — the shared
core behind the dashboard's explicit 'Sync now' action.
diff --git a/pyproject.toml b/pyproject.toml
index e23dd269..b382beb6 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -239,7 +239,7 @@ include = ["engraphis*", "scripts*", "eval*", "integrations.commandcode"]
# runtime artifacts such as dashboard_assets/__pycache__/*.pyc after a local compile check.
# vendor/**/* covers nested vendor assets too (vendor/* alone doesn't cross "/").
"engraphis.dashboard_assets" = ["*.html", "*.css", "*.js", "*.png", "*.ico", "vendor/*", "vendor/**/*"]
-"engraphis" = ["commercial_manifest.json"]
+"engraphis" = ["commercial_manifest.json", "entitlement_contract.json"]
"eval" = [
"BASELINES.md",
"EVIDENCE.md",
diff --git a/scripts/check_commercial_manifest.py b/scripts/check_commercial_manifest.py
index 923cd8ab..cecad121 100644
--- a/scripts/check_commercial_manifest.py
+++ b/scripts/check_commercial_manifest.py
@@ -11,6 +11,14 @@
ROOT = Path(__file__).resolve().parents[1]
MANIFEST_PATH = ROOT / "engraphis" / "commercial_manifest.json"
+CONTRACT_PATH = ROOT / "engraphis" / "entitlement_contract.json"
+CONTRACT_SCHEMA = "engraphis-entitlement-contract/v1"
+TEAM_ONLY_CAPABILITIES = frozenset({
+ "hosted_multi_user_roles",
+ "hosted_team_audit_export",
+ "hosted_scoped_agent_tokens",
+})
+AUTOMATION_ALIASES = frozenset({"consolidation", "dreaming"})
sys.path.insert(0, str(ROOT))
@@ -71,6 +79,193 @@ def _checkout_mapping(manifest: dict, errors: list[str]) -> dict:
return mapping
+def _read_contract(errors: list[str]) -> dict:
+ """Load the checked-in entitlement mirror, reporting rather than raising."""
+
+ try:
+ contract = json.loads(CONTRACT_PATH.read_text(encoding="utf-8"))
+ except (OSError, ValueError) as exc:
+ _fail(errors, "entitlement contract is missing or unreadable: %s" % exc)
+ return {}
+ if not isinstance(contract, dict) or contract.get("schema") != CONTRACT_SCHEMA:
+ _fail(errors, "entitlement contract schema must be " + CONTRACT_SCHEMA)
+ return contract if isinstance(contract, dict) else {}
+ return contract
+
+
+def _check_entitlement_contract(manifest: dict, errors: list[str]) -> dict:
+ """Validate the single plan→feature mirror against the public commercial manifest.
+
+ The client reads this file at runtime, so the release gate has to prove it is complete
+ before a paid capability can silently disappear from a customer's dashboard.
+ """
+
+ contract = _read_contract(errors)
+ if not contract:
+ return contract
+ plans = contract.get("plans")
+ features = contract.get("features")
+ if not isinstance(plans, dict) or set(plans) != {"free", "pro", "team"}:
+ _fail(errors, "entitlement contract plans must name free, pro and team")
+ return contract
+ if not isinstance(features, dict) or not features:
+ _fail(errors, "entitlement contract features must be a non-empty object")
+ return contract
+
+ for plan in ("free", "pro", "team"):
+ names = plans.get(plan)
+ if not isinstance(names, list) or len(names) != len(set(names)):
+ _fail(errors, "entitlement contract plans.%s must be a unique list" % plan)
+ continue
+ for name in names:
+ spec = features.get(name)
+ if not isinstance(spec, dict) or spec.get("status") != "available":
+ _fail(
+ errors,
+ "entitlement contract plans.%s names a feature that is not "
+ "available: %s" % (plan, name),
+ )
+ continue
+ # ``plan`` is the minimum grant; Team inherits every Pro feature.
+ declared_plan = spec.get("plan")
+ allowed = declared_plan in ("pro", "team") if plan == "team" else declared_plan == plan
+ if not allowed:
+ _fail(
+ errors,
+ "entitlement contract plans.%s names %s, which declares plan %r"
+ % (plan, name, declared_plan),
+ )
+ if not set(plans["pro"]).issubset(set(plans["team"])):
+ _fail(errors, "entitlement contract team plan must include every pro feature")
+
+ for name, spec in features.items():
+ if not isinstance(spec, dict):
+ _fail(errors, "entitlement contract feature %s must be an object" % name)
+ continue
+ if spec.get("plan") not in {"free", "pro", "team"}:
+ _fail(errors, "entitlement contract feature %s names an unknown plan" % name)
+ if not isinstance(spec.get("label"), str) or not spec["label"].strip():
+ _fail(errors, "entitlement contract feature %s needs a non-empty label" % name)
+ if spec.get("status") not in {"available", "free", "declared"}:
+ _fail(errors, "entitlement contract feature %s has an unknown status" % name)
+ if spec.get("status") == "declared" and any(
+ name in names for names in plans.values()
+ ):
+ _fail(errors, "entitlement contract declares but still grants %s" % name)
+ if spec.get("status") == "available":
+ if name not in plans.get(spec.get("plan"), ()):
+ _fail(
+ errors,
+ "entitlement contract omits available feature %s from its plan" % name,
+ )
+ if spec.get("plan") == "pro" and name not in plans.get("team", ()):
+ _fail(
+ errors,
+ "entitlement contract omits pro feature %s from the team plan" % name,
+ )
+
+ included = contract.get("included_services", {})
+ if not isinstance(included, dict):
+ _fail(errors, "entitlement contract included_services must be an object")
+ else:
+ for name, spec in included.items():
+ if not isinstance(spec, dict):
+ _fail(errors, "included service %s must be an object" % name)
+ continue
+ if spec.get("plan") not in {"free", "pro", "team"}:
+ _fail(errors, "included service %s names an unknown plan" % name)
+ if not isinstance(spec.get("label"), str) or not spec["label"].strip():
+ _fail(errors, "included service %s needs a non-empty label" % name)
+ calls = spec.get("calls_per_person_per_day")
+ if calls is not None and (
+ not isinstance(calls, int) or isinstance(calls, bool) or calls <= 0
+ ):
+ _fail(
+ errors,
+ "included service %s calls_per_person_per_day must be positive"
+ % name,
+ )
+
+ # Local workspace export is free and documented free. It must never re-enter the paid
+ # vocabulary, or a Pro/Team row starts advertising something the free tier owns.
+ export = features.get("export")
+ if not isinstance(export, dict) or export.get("plan") != "free" or export.get("status") != "free":
+ _fail(errors, "entitlement contract must keep local export on the free plan")
+ if "export" in plans.get("pro", ()) or "export" in plans.get("team", ()):
+ _fail(errors, "entitlement contract must not advertise local export as paid")
+
+ # Every hosted capability the manifest turns on must resolve deliberately.
+ for name, enabled in (manifest.get("features") or {}).items():
+ if not enabled or not name.startswith("hosted_") or name == "hosted_authorization":
+ continue
+ key = name.replace("hosted_opt_in_cloud_", "")
+ spec = features.get(key)
+ if not isinstance(spec, dict):
+ _fail(errors, "manifest sells %s but the entitlement contract omits it" % name)
+ continue
+ expected = "team" if name in TEAM_ONLY_CAPABILITIES else "pro"
+ if spec.get("plan") != expected:
+ _fail(errors, "manifest capability %s must resolve to %s" % (name, expected))
+
+ limits = contract.get("limits") or {}
+ trial = manifest.get("trial") or {}
+ if limits.get("trial_days_by_plan") != trial.get("days_by_plan"):
+ _fail(errors, "entitlement contract trial days differ from the manifest")
+ lifecycle = manifest.get("entitlement_lifecycle") or {}
+ if limits.get("grace_hours") != lifecycle.get("max_grace_hours"):
+ _fail(errors, "entitlement contract grace hours differ from the manifest")
+ return contract
+
+
+def _check_cloud_contract(cloud: dict, manifest: dict, errors: list[str]) -> None:
+ """Compare the checked-in mirror with the private control plane's contract.
+
+ The private service publishes only a secret-free contract (no price ids, no keys). When
+ it names ``hosted_entitlements``, the plan→feature sets must match the client mirror
+ exactly; a new server grant that never reached this repository is a release blocker,
+ not something a paying customer discovers as a lock.
+ """
+
+ if not isinstance(cloud, dict):
+ _fail(errors, "cloud contract must be an object")
+ return
+ # The private service has published both shapes: the original schema_version 1
+ # contract and the later engraphis-public-offer/v1 document. Accept either, then
+ # apply the same trial and entitlement-table comparisons to both.
+ if not (
+ cloud.get("schema_version") == 1
+ or cloud.get("schema") == "engraphis-public-offer/v1"
+ ):
+ _fail(
+ errors,
+ "cloud contract schema must be schema_version 1 or "
+ "engraphis-public-offer/v1",
+ )
+ return
+ if (cloud.get("trial") or {}).get("days_by_plan") != (manifest.get("trial") or {}).get(
+ "days_by_plan"
+ ):
+ _fail(errors, "cloud trial authority differs from the public per-plan contract")
+ hosted = cloud.get("hosted_entitlements")
+ if hosted is None:
+ return
+ if not isinstance(hosted, dict):
+ _fail(errors, "cloud contract hosted_entitlements must be an object")
+ return
+ contract = _read_contract(errors)
+ if not contract:
+ return
+ for plan in ("free", "pro", "team"):
+ local = set((contract.get("plans") or {}).get(plan) or ()) - AUTOMATION_ALIASES
+ remote = set(hosted.get(plan) or ())
+ if local != remote:
+ _fail(
+ errors,
+ "entitlement contract plans.%s differs from the private cloud contract: "
+ "client=%s cloud=%s" % (plan, sorted(local), sorted(remote)),
+ )
+
+
def _check_repository(manifest: dict, errors: list[str]) -> None:
if manifest.get("schema") != "engraphis-commercial/v2":
_fail(errors, "commercial manifest schema must be engraphis-commercial/v2")
@@ -124,6 +319,8 @@ def _check_repository(manifest: dict, errors: list[str]) -> None:
if manifest.get("billing") != expected_billing:
_fail(errors, "Stripe must be the sole launch billing authority")
+ _check_entitlement_contract(manifest, errors)
+
pyproject = (ROOT / "pyproject.toml").read_text(encoding="utf-8")
match = re.search(r'^version\s*=\s*"([^"]+)"', pyproject, re.MULTILINE)
if not match or match.group(1) != manifest["version"]:
@@ -235,12 +432,7 @@ def main() -> int:
_check_repository(manifest, errors)
if args.cloud_contract:
cloud = json.loads(args.cloud_contract.read_text(encoding="utf-8-sig"))
- schema_valid = (
- cloud.get("schema_version") == 1
- or cloud.get("schema") == "engraphis-public-offer/v1"
- )
- if not schema_valid or cloud.get("trial", {}).get("days_by_plan") != manifest["trial"]["days_by_plan"]:
- _fail(errors, "cloud trial authority differs from the public per-plan contract")
+ _check_cloud_contract(cloud, manifest, errors)
if args.website_root:
_check_website(manifest, args.website_root.resolve(), errors)
if errors:
diff --git a/tests/e2e/ledger.spec.js b/tests/e2e/ledger.spec.js
index b453d2eb..c76e9375 100644
--- a/tests/e2e/ledger.spec.js
+++ b/tests/e2e/ledger.spec.js
@@ -46,6 +46,7 @@ async function mockApi(page, options = {}) {
requests.automationPolicies = [];
requests.automationBootstraps = [];
requests.syncRuns = [];
+ requests.syncKeyRequests = [];
requests.details = [];
requests.documentImports = [];
requests.contextSavingsQueries = [];
@@ -281,7 +282,22 @@ async function mockApi(page, options = {}) {
llmStatus.extractor = llmStatus.extractor_enabled ? 'llm_structured' : 'none';
return ok({ ok: true, extractor_enabled: llmStatus.extractor_enabled, persisted: true });
}
- if (path === '/sync/status') return ok(options.syncStatus || { available: false, last: null });
+ if (path === '/sync/status') {
+ const body = requests.syncKeyRequests.length && options.syncStatusAfterKey
+ ? options.syncStatusAfterKey
+ : options.syncStatus || { available: false, last: null };
+ return ok(body);
+ }
+ if (path === '/sync/key') {
+ requests.syncKeyRequests.push(JSON.parse(request.postData() || '{}'));
+ return ok({
+ configured: true,
+ key: options.syncKey || 'A'.repeat(43),
+ key_source: 'state_file',
+ env_var: 'ENGRAPHIS_SYNC_E2EE_KEY',
+ restart_required: false,
+ });
+ }
if (path === '/sync/run') {
requests.syncRuns.push(JSON.parse(request.postData() || '{}'));
const summary = options.syncRun || {
@@ -1621,6 +1637,34 @@ test('Ledger guides a connected account with a missing Cloud Sync key', async ({
expect(requests.syncRuns).toEqual([]);
});
+test('Ledger creates a Cloud Sync key once and confirms it was stored', async ({ page }) => {
+ const requests = await mockApi(page, {
+ syncStatus: {
+ available: true, ready: false, has_key: false, key_state: 'missing',
+ can_generate_key: true, can_rotate_key: false,
+ encryption_available: true, local_prerequisites_ready: false, last: null,
+ },
+ syncStatusAfterKey: {
+ available: true, ready: true, has_key: true, key_state: 'ready',
+ can_generate_key: false, can_rotate_key: true,
+ encryption_available: true, local_prerequisites_ready: true, last: null,
+ },
+ syncKey: 'A'.repeat(43),
+ });
+ await page.goto('/?view=manage&tab=sync');
+
+ const result = page.locator('#sync-result');
+ await result.getByRole('button', { name: 'Create sync key' }).click();
+ await expect(result).toContainText('Cloud Sync key created');
+ await expect(result.locator('.sync-key-value')).toHaveText('A'.repeat(43));
+ expect(requests.syncKeyRequests).toEqual([{ rotate: false }]);
+
+ await result.getByRole('button', { name: 'I stored the key' }).click();
+ await expect(result).toContainText('Encryption keyValid');
+ await expect(result).toContainText('Sync readinessReady');
+ expect(requests.syncRuns).toEqual([]);
+});
+
test('Ledger explains an invalid Cloud Sync key without exposing it', async ({ page }) => {
const requests = await mockApi(page, {
syncStatus: {
@@ -1634,7 +1678,9 @@ test('Ledger explains an invalid Cloud Sync key without exposing it', async ({ p
const result = page.locator('#sync-result');
await expect(result).toContainText('Encryption keyInvalid');
- await expect(result).toContainText('Replace it with a 32-byte URL-safe base64 key');
+ await expect(result).toContainText(
+ 'The saved Cloud Sync encryption key is invalid. Generate a replacement'
+ );
await expect(result.getByRole('button', { name: 'Sync now' })).toBeDisabled();
expect(requests.syncRuns).toEqual([]);
});
@@ -2825,6 +2871,30 @@ test('Ledger gives active Pro members direct Cloud access and saves hosted polic
expect(errors).toEqual([]);
});
+test('Ledger names the days remaining in a live trial', async ({ page }) => {
+ const live = license();
+ live.plan = 'pro';
+ live.access_state = 'trial';
+ live.is_trial = true;
+ live.cloud_access_active = true;
+ live.plan_source = 'session';
+ live.trial = {
+ ...live.trial,
+ active: true,
+ available: false,
+ used: false,
+ ends_at: Date.now() / 1000 + 2.5 * 86_400,
+ };
+ await mockApi(page, { license: live });
+ await page.goto('/');
+ await page.getByRole('button', { name: 'Settings' }).click();
+ await page.getByRole('tab', { name: 'Plans & billing' }).click();
+
+ await expect(page.locator('#license-status-body')).toContainText(
+ 'Your free trial ends in 3 days',
+ );
+});
+
test('Ledger omits unknown Team trial duration from an older license response', async ({ page }) => {
const legacy = license();
delete legacy.trial.days_by_plan;
@@ -2866,6 +2936,10 @@ test('billing cadence selects the exact Pro and Team checkout target', async ({
await expect(page.locator('.plan-card.featured .plan-benefits')).toContainText(
'Cloud Sync, Analytics, Auto Consolidation, and Auto Dreaming',
);
+ await expect(page.locator('.plan-card.featured .plan-benefit-list')).toContainText(
+ 'Managed Jev decisions: up to 500 calls per person per day',
+ );
+ await expect(page.locator('.comparison-table')).toContainText('Managed Jev decisions');
const pro = page.locator('#plan-cards [data-pro-cta="pro"]');
const team = page.locator('#plan-cards [data-pro-cta="team"]');
diff --git a/tests/test_benchmark_evidence.py b/tests/test_benchmark_evidence.py
index bede7085..c024dd41 100644
--- a/tests/test_benchmark_evidence.py
+++ b/tests/test_benchmark_evidence.py
@@ -34,8 +34,8 @@
ROOT = Path(__file__).resolve().parents[1]
-PUBLIC_OFFLINE_ARTIFACT = "offline-fixtures-release-1.7.12-20261102.json"
-PUBLIC_OFFLINE_SHA = "025183601c5a28a37daf9326a061010244bc9a3f9f7db1e3e6d4f866eaafc660"
+PUBLIC_OFFLINE_ARTIFACT = "offline-fixtures-paid-plans-20261104.json"
+PUBLIC_OFFLINE_SHA = "cc66984fd3dd78c17addeb979aea0e0bf40ff0f533ebcd683da4b7f532da0055"
@pytest.fixture(scope="module")
diff --git a/tests/test_client_launch.py b/tests/test_client_launch.py
index 5a267a13..236d0327 100644
--- a/tests/test_client_launch.py
+++ b/tests/test_client_launch.py
@@ -34,19 +34,18 @@
pytest.importorskip("fastapi", reason="full-stack extra not installed")
from engraphis import hosted_client, update_check # noqa: E402
+from engraphis.entitlements import features_for # noqa: E402
from engraphis.routes import v2_api # noqa: E402
REPO_ROOT = Path(__file__).resolve().parents[1]
UPDATER = REPO_ROOT / "scripts" / "update.py"
DASHBOARD_JS = REPO_ROOT / "engraphis" / "dashboard_assets" / "ledger.js"
-# The private control plane's plan→feature table, mirrored from (read-only)
-# the hosted entitlement contract plan-feature mapping. A drift here means a
-# purchased capability silently renders as locked.
+# The private control plane's plan→feature table has exactly one checked-in mirror:
+# engraphis/entitlement_contract.json. Import it instead of maintaining another copy here,
+# so a capability cannot be added to one surface and silently dropped from another.
SERVER_HOSTED_ENTITLEMENTS = {
- "free": set(),
- "pro": {"analytics", "automation", "sync"},
- "team": {"analytics", "automation", "sync", "team"},
+ plan: set(features_for(plan)) for plan in ("free", "pro", "team")
}
# Named separately by this client's commercial manifest; the server grants both under
# ``automation``, so any plan granting ``automation`` must grant these too.
diff --git a/tests/test_entitlement_contract.py b/tests/test_entitlement_contract.py
new file mode 100644
index 00000000..2a5bbf46
--- /dev/null
+++ b/tests/test_entitlement_contract.py
@@ -0,0 +1,184 @@
+"""One plan→feature mirror, and a loud failure when it drifts.
+
+The client used to keep three hand-maintained copies of the private control plane's hosted
+entitlement table (the runtime, plus two test mirrors), and each copy was only ever
+compared to itself. These tests pin the replacement: one checked-in contract, one loader,
+and a runtime surface that reports a server grant it cannot render instead of silently
+hiding it behind a lock.
+"""
+from __future__ import annotations
+
+import json
+from pathlib import Path
+
+import pytest
+
+from engraphis import commercial, entitlements
+from engraphis.hosted_client import required_plan
+
+
+ROOT = Path(__file__).resolve().parents[1]
+CONTRACT = ROOT / "engraphis" / "entitlement_contract.json"
+
+
+def test_the_contract_is_valid_json_and_declares_every_plan():
+ data = json.loads(CONTRACT.read_text(encoding="utf-8"))
+
+ assert data["schema"] == entitlements.SCHEMA
+ assert set(data["plans"]) == {"free", "pro", "team"}
+ assert set(data["features"]) >= set(data["plans"]["pro"])
+
+
+def test_a_malformed_contract_is_rejected_instead_of_misread():
+ with pytest.raises(ValueError):
+ entitlements._validate({"schema": "not-the-contract"})
+ with pytest.raises(ValueError):
+ entitlements._validate(
+ {
+ "schema": entitlements.SCHEMA,
+ "plans": {"free": [], "pro": ["sync"], "team": []},
+ "features": {
+ "sync": {"plan": "pro", "label": "Cloud Sync", "status": "available"}
+ },
+ "limits": {"trial_days_by_plan": {"pro": 7, "team": 14}, "grace_hours": 24},
+ }
+ )
+
+
+def test_features_for_is_case_and_whitespace_insensitive_and_treats_local_as_free():
+ assert entitlements.features_for(" PRO ") == entitlements.features_for("pro")
+ assert entitlements.features_for("local") == []
+ assert entitlements.features_for("free") == []
+ assert entitlements.features_for("enterprise") == []
+ assert entitlements.features_for(None) == []
+
+
+def test_team_grants_every_pro_feature_and_one_more():
+ pro = set(entitlements.features_for("pro"))
+ team = set(entitlements.features_for("team"))
+
+ assert pro and pro < team
+ assert team - pro == {"team"}
+
+
+def test_the_contract_covers_every_hosted_capability_the_manifest_sells():
+ manifest = commercial.manifest()
+
+ for name, enabled in manifest["features"].items():
+ if not enabled or not name.startswith("hosted_") or name == "hosted_authorization":
+ continue
+ key = name.replace("hosted_opt_in_cloud_", "")
+ spec = entitlements.feature_spec(key)
+ assert isinstance(spec, dict), key
+ expected = "team" if name in {
+ "hosted_multi_user_roles",
+ "hosted_team_audit_export",
+ "hosted_scoped_agent_tokens",
+ } else "pro"
+ assert spec["plan"] == expected, name
+ assert required_plan(key) == expected, name
+
+
+def test_local_export_is_free_and_never_advertised_as_a_paid_grant():
+ assert entitlements.feature_spec("export")["plan"] == "free"
+ assert required_plan("export") == "free"
+ assert "export" not in entitlements.features_for("pro")
+ assert "export" not in entitlements.features_for("team")
+
+
+def test_unknown_features_keep_the_conservative_pro_fallback():
+ assert entitlements.feature_spec("not-a-real-feature") is None
+ assert required_plan("not-a-real-feature") == "pro"
+ assert entitlements.unrecognized(["not-a-real-feature"], "pro") == ["not-a-real-feature"]
+
+
+def test_unknown_grants_are_reported_but_never_become_feature_rows():
+ granted = ["analytics", "future_capability", "future_capability"]
+
+ assert entitlements.unrecognized(granted, "pro") == ["future_capability"]
+ assert entitlements.unrecognized(["analytics", "automation"], "pro") == []
+
+
+def test_managed_jev_is_an_included_paid_benefit_with_a_bounded_allowance():
+ service = entitlements.included_services()["managed_jev"]
+
+ assert service["plan"] == "pro"
+ assert service["calls_per_person_per_day"] == 500
+ assert entitlements.required_plan("managed_jev") == "pro"
+ # It is informational, not a hosted feature row: no lock badge, no tick box.
+ assert "managed_jev" not in entitlements.labels()
+
+
+def test_license_exposes_the_included_paid_benefit(monkeypatch):
+ pytest.importorskip("fastapi", reason="full-stack extra not installed")
+ from engraphis.routes import v2_api
+
+ monkeypatch.setenv("ENGRAPHIS_CLOUD_PLAN", "pro")
+ payload = v2_api.get_license()
+
+ assert payload["included_services"]["managed_jev"]["calls_per_person_per_day"] == 500
+ assert "managed_jev" not in payload["features"]
+
+
+def test_trial_and_grace_limits_match_the_commercial_manifest():
+ manifest = commercial.manifest()
+
+ assert entitlements.trial_days_by_plan() == manifest["trial"]["days_by_plan"]
+ assert entitlements.grace_hours() == manifest["entitlement_lifecycle"]["max_grace_hours"]
+
+
+def test_the_release_gate_accepts_the_checked_in_contract():
+ from scripts.check_commercial_manifest import _check_repository
+
+ errors: list[str] = []
+ _check_repository(commercial.manifest(), errors)
+
+ assert errors == []
+
+
+def test_the_distribution_ships_the_contract():
+ pyproject = (ROOT / "pyproject.toml").read_text(encoding="utf-8")
+ manifest = (ROOT / "MANIFEST.in").read_text(encoding="utf-8")
+
+ assert "entitlement_contract.json" in pyproject
+ assert "include engraphis/entitlement_contract.json" in manifest
+
+
+def test_license_reports_an_unknown_server_grant_without_unlocking_a_row(monkeypatch):
+ pytest.importorskip("fastapi", reason="full-stack extra not installed")
+ from engraphis.routes import v2_api
+
+ monkeypatch.setattr(
+ v2_api,
+ "_plan_entitlement",
+ lambda: {
+ "plan": "pro",
+ "features": v2_api.entitled_features("pro"),
+ "unrecognized_features": ["future_capability"],
+ "source": "session",
+ "cloud_access_active": True,
+ "checked_at": 0.0,
+ "status": "",
+ "is_trial": False,
+ "trial_consumed": True,
+ "trial_ends_at": 0.0,
+ },
+ )
+
+ payload = v2_api.get_license()
+
+ assert payload["unrecognized_features"] == ["future_capability"]
+ assert "future_capability" not in payload["features"]
+ assert set(payload["features"]) <= set(payload["known_features"])
+
+
+def test_normalization_drops_an_unknown_grant_from_the_feature_list(monkeypatch):
+ pytest.importorskip("fastapi", reason="full-stack extra not installed")
+ from engraphis.routes import v2_api
+
+ assert v2_api._normalized_features(["analytics", "future_capability"], "pro") == [
+ "analytics"
+ ]
+ assert v2_api._unrecognized_features(["analytics", "future_capability"], "pro") == [
+ "future_capability"
+ ]
diff --git a/tests/test_hosted_plan_resolution.py b/tests/test_hosted_plan_resolution.py
index ca312e1e..6c9616dd 100644
--- a/tests/test_hosted_plan_resolution.py
+++ b/tests/test_hosted_plan_resolution.py
@@ -43,6 +43,7 @@
pytest.importorskip("fastapi", reason="full-stack extra not installed")
from engraphis import cloud_session # noqa: E402
+from engraphis.entitlements import features_for # noqa: E402
from engraphis.routes import v2_api # noqa: E402
REPO_ROOT = Path(__file__).resolve().parents[1]
@@ -52,11 +53,9 @@
CONTROL_URL = "https://control.example.test"
# The authoritative payload shape, mirrored from (read-only)
-# the hosted entitlement contract the hosted entitlement response / the hosted plan-feature contract.
+# engraphis/entitlement_contract.json. One import target, three consumers.
SERVER_HOSTED_ENTITLEMENTS = {
- "free": [],
- "pro": ["analytics", "automation", "sync"],
- "team": ["analytics", "automation", "sync", "team"],
+ plan: list(features_for(plan)) for plan in ("free", "pro", "team")
}
# The trial disclosure this client now depends on, mirrored from (read-only)
# the hosted entitlement response and the hosted registration response. Both surfaces
diff --git a/tests/test_licensing_launch.py b/tests/test_licensing_launch.py
index f97846cc..cd9b506f 100644
--- a/tests/test_licensing_launch.py
+++ b/tests/test_licensing_launch.py
@@ -23,14 +23,15 @@
from engraphis.cloud_session import CloudSessionError
# ── the private control plane's vocabulary, mirrored from (read-only) ──────────
-# the hosted entitlement contract:20-28 and security.py:37-38.
-# A mismatch here means a purchased feature silently never activates on the client.
+# engraphis/entitlement_contract.json is the single checked-in mirror. Import it rather
+# than maintaining a second copy: a mismatch here means a purchased feature silently never
+# activates on the client.
+from engraphis.entitlements import features_for
+
SERVER_PLANS = {"free", "pro", "team"}
SERVER_PAID_PLANS = {"pro", "team"}
SERVER_HOSTED_ENTITLEMENTS = {
- "free": set(),
- "pro": {"analytics", "automation", "export", "sync"},
- "team": {"analytics", "automation", "export", "sync", "team"},
+ plan: set(features_for(plan)) for plan in ("free", "pro", "team")
}
# The compatibility scalar is the Pro duration; Team uses the per-plan offer.
SERVER_TRIAL_DURATION_SECONDS = 604_800
diff --git a/tests/test_managed_denial_metadata.py b/tests/test_managed_denial_metadata.py
new file mode 100644
index 00000000..f79e0abf
--- /dev/null
+++ b/tests/test_managed_denial_metadata.py
@@ -0,0 +1,141 @@
+"""Denial metadata that turns a paid-feature rejection into a recovery action.
+
+The dashboard can only offer the right button if the client tells it *why* the call was
+denied. These tests pin the fixed-vocabulary reason, the plan that actually sells the
+feature, the account destination, and the promise that provider text never crosses.
+"""
+from __future__ import annotations
+
+import io
+import urllib.error
+
+import pytest
+
+pytest.importorskip("fastapi", reason="full-stack extra not installed")
+
+from fastapi import HTTPException # noqa: E402
+
+from engraphis import cloud_authz, licensing # noqa: E402
+from engraphis import cloud_features # noqa: E402
+from engraphis.cloud_features import CloudFeatureClient, CloudFeatureError # noqa: E402
+from engraphis.routes import v2_api # noqa: E402
+
+
+class _Opener:
+ def __init__(self, status: int, body: bytes):
+ self.status = status
+ self.body = body
+
+ def open(self, request, timeout=None):
+ raise urllib.error.HTTPError(
+ "https://compute.example.test/private", self.status, "failure", {},
+ io.BytesIO(self.body),
+ )
+
+
+def _client_response(monkeypatch, status: int, body: bytes) -> CloudFeatureError:
+ monkeypatch.setattr(
+ cloud_features, "build_pinned_https_opener",
+ lambda *handlers: _Opener(status, body),
+ )
+ client = CloudFeatureClient("https://compute.example.test", "org_1", "token")
+ with pytest.raises(CloudFeatureError) as caught:
+ client._request("GET", "/private")
+ return caught.value
+
+
+def test_a_known_reason_is_extracted_without_reflecting_the_body(monkeypatch):
+ error = _client_response(
+ monkeypatch,
+ 402,
+ b'{"reason":"entitlement_expired","detail":"internal https://backend.invalid"}',
+ )
+
+ assert error.status == 402
+ assert error.reason == "entitlement_expired"
+ assert "internal" not in str(error)
+ assert "backend.invalid" not in str(error)
+
+
+@pytest.mark.parametrize(
+ "body",
+ (
+ b"not json",
+ b'{"reason":"not_a_real_reason"}',
+ b'{"detail":"missing reason"}',
+ b'{"reason":"' + b"x" * 200 + b'"}',
+ b'{"reason":"entitlement_expired"}' + b"x" * (cloud_features.MAX_ERROR_BODY_BYTES + 1),
+ ),
+)
+def test_unknown_or_oversized_reason_bodies_degrade_to_none(monkeypatch, body):
+ error = _client_response(monkeypatch, 402, body)
+
+ assert error.reason is None
+
+
+def test_a_402_names_the_plan_that_sells_the_feature(monkeypatch):
+ def fail():
+ raise CloudFeatureError("denied", status=402, reason="entitlement_expired")
+
+ with pytest.raises(HTTPException) as caught:
+ v2_api._managed_call(fail, feature="sync")
+
+ detail = caught.value.detail
+ assert detail["reason"] == "entitlement_expired"
+ assert detail["tier_required"] == "pro"
+ assert detail["upgrade_url"] == licensing.upgrade_url("pro")
+ assert detail["account_url"] == licensing.account_url()
+ assert detail["transient"] is False
+
+
+def test_a_full_seat_list_routes_to_the_account_portal_not_the_pro_plan(monkeypatch):
+ def fail():
+ raise CloudFeatureError("denied", status=402, reason="seat_limit_exceeded")
+
+ with pytest.raises(HTTPException) as caught:
+ v2_api._managed_call(fail, feature="automation")
+
+ detail = caught.value.detail
+ assert detail["reason"] == "seat_limit_exceeded"
+ assert detail["tier_required"] == "team"
+ assert detail["upgrade_url"] == licensing.upgrade_url("team")
+
+
+def test_a_session_denial_reconnects_at_the_account_portal(monkeypatch):
+ def fail():
+ raise CloudFeatureError("denied", status=401)
+
+ with pytest.raises(HTTPException) as caught:
+ v2_api._managed_call(fail, feature="analytics")
+
+ detail = caught.value.detail
+ assert detail["reason"] == "token_stale"
+ assert detail["upgrade_url"] == licensing.account_url()
+ assert detail["account_url"] == licensing.account_url()
+
+
+def test_a_role_denial_does_not_advertise_a_plan(monkeypatch):
+ def fail():
+ raise CloudFeatureError("denied", status=403, reason="insufficient_role")
+
+ with pytest.raises(HTTPException) as caught:
+ v2_api._managed_call(fail, feature="team")
+
+ detail = caught.value.detail
+ assert detail["reason"] == "insufficient_role"
+ assert detail["tier_required"] == "team"
+ assert detail["upgrade_url"] == licensing.account_url()
+
+
+def test_cloud_authz_fallback_is_the_real_account_portal():
+ result = cloud_authz.interpret_denial(402, {"reason": "entitlement_expired"})
+
+ assert result["upgrade_url"] == licensing.account_url()
+ assert "/billing/upgrade" not in result["upgrade_url"]
+
+
+def test_the_seat_message_tells_a_team_owner_to_add_a_seat():
+ result = cloud_authz.interpret_denial(402, {"reason": "seat_limit_exceeded"})
+
+ assert "Add a seat" in result["message"]
+ assert "Upgrade your plan" not in result["message"]
diff --git a/tests/test_release_infrastructure.py b/tests/test_release_infrastructure.py
index eaa815c9..dda9f52f 100644
--- a/tests/test_release_infrastructure.py
+++ b/tests/test_release_infrastructure.py
@@ -215,8 +215,8 @@ def test_ci_and_release_audit_production_image_dependencies():
assert "tesseract-ocr" in _text("Dockerfile")
assert (
_text("Dockerfile").splitlines()[1]
- == "FROM python:3.11-slim@sha256:"
- "b7a9e5f6a93d6d4a31f129bbd4bdee69efccb6ded0fb230d78aafaf37af98377 AS base"
+ == "FROM python:3.11.18-slim@sha256:"
+ "db20d8506e2f15b6db39b40b6015ca46bae855dec6dd326d462cfe79ae972893 AS base"
)
assert "Verify production image OCR runtime" in ci
assert "Verify production image OCR runtime" in release
diff --git a/tests/test_sync_key_management.py b/tests/test_sync_key_management.py
new file mode 100644
index 00000000..17acba4e
--- /dev/null
+++ b/tests/test_sync_key_management.py
@@ -0,0 +1,125 @@
+"""In-product Cloud Sync key generation.
+
+A paying customer must be able to reach a working sync without editing an environment
+variable. These tests pin the whole promise: one-time reveal, owner-only storage, explicit
+rotation, environment precedence, and a status payload that never contains the key.
+"""
+from __future__ import annotations
+
+import base64
+import json
+import os
+import stat
+
+import pytest
+
+from engraphis.backends import sync_relay
+
+pytest.importorskip("fastapi", reason="full-stack extra not installed")
+
+from fastapi import HTTPException # noqa: E402
+
+from engraphis.routes import v2_api # noqa: E402
+
+
+KEY_A = base64.urlsafe_b64encode(bytes(range(32))).decode("ascii").rstrip("=")
+KEY_B = base64.urlsafe_b64encode(bytes(range(1, 33))).decode("ascii").rstrip("=")
+
+
+@pytest.fixture()
+def state_dir(monkeypatch, tmp_path):
+ root = tmp_path / ".engraphis"
+ monkeypatch.setenv("ENGRAPHIS_STATE_DIR", str(root))
+ monkeypatch.delenv(sync_relay.SYNC_E2EE_KEY_ENV, raising=False)
+ return root
+
+
+def _payload(response):
+ return json.loads(response.body)
+
+
+def test_generated_key_round_trips_and_status_never_discloses_it(state_dir):
+ response = v2_api.create_sync_key()
+ payload = _payload(response)
+
+ assert response.status_code == 200
+ assert response.headers["cache-control"] == "no-store"
+ assert payload["configured"] is True
+ assert payload["key_source"] == "state_file"
+ assert payload["restart_required"] is False
+ assert len(payload["key"]) == 43
+ assert (state_dir / "sync.e2ee_key").is_file()
+
+ key = payload["key"]
+ assert sync_relay.configured_sync_e2ee_key() == sync_relay.decode_sync_e2ee_key(key)
+
+ status = v2_api.sync_status()
+ assert status["key_source"] == "state_file"
+ assert status["has_key"] is True
+ assert status["key_state"] == "ready"
+ assert status["can_generate_key"] is False
+ assert status["can_rotate_key"] is True
+ assert key not in json.dumps(status)
+
+
+def test_a_second_generation_refuses_to_overwrite(state_dir):
+ first = _payload(v2_api.create_sync_key())["key"]
+
+ with pytest.raises(HTTPException) as caught:
+ v2_api.create_sync_key()
+
+ assert caught.value.status_code == 409
+ assert caught.value.detail["code"] == "sync_key_exists"
+ assert sync_relay.configured_sync_e2ee_key() == sync_relay.decode_sync_e2ee_key(first)
+
+
+def test_rotation_is_explicit_and_replaces_the_saved_key(state_dir):
+ first = _payload(v2_api.create_sync_key())["key"]
+ second = _payload(
+ v2_api.create_sync_key(v2_api._SyncKeyReq(rotate=True))
+ )["key"]
+
+ assert second != first
+ assert sync_relay.configured_sync_e2ee_key() == sync_relay.decode_sync_e2ee_key(second)
+
+
+def test_an_environment_key_keeps_precedence_and_cannot_be_replaced(state_dir, monkeypatch):
+ monkeypatch.setenv(sync_relay.SYNC_E2EE_KEY_ENV, KEY_A)
+ sync_relay.save_sync_e2ee_key(KEY_B)
+
+ assert sync_relay.sync_e2ee_key_source() == "environment"
+ assert sync_relay.configured_sync_e2ee_key() == sync_relay.decode_sync_e2ee_key(KEY_A)
+
+ with pytest.raises(HTTPException) as caught:
+ v2_api.create_sync_key()
+
+ assert caught.value.detail["code"] == "sync_key_managed_by_environment"
+
+
+def test_saved_key_is_owner_only(state_dir):
+ if os.name == "nt":
+ pytest.skip("POSIX permission semantics")
+ v2_api.create_sync_key()
+
+ mode = stat.S_IMODE((state_dir / "sync.e2ee_key").stat().st_mode)
+ assert mode == 0o600
+
+
+def test_status_reports_an_invalid_saved_key_without_disclosing_it(state_dir):
+ state_dir.mkdir(parents=True, exist_ok=True)
+ (state_dir / "sync.e2ee_key").write_text("not-a-valid-key", encoding="utf-8")
+
+ status = v2_api.sync_status()
+
+ assert status["key_source"] == "state_file"
+ assert status["has_key"] is False
+ assert status["key_state"] == "invalid"
+ assert status["can_generate_key"] is False
+ assert status["can_rotate_key"] is True
+ assert "not-a-valid-key" not in json.dumps(status)
+
+
+def test_configured_key_fails_closed_when_absent(state_dir):
+ with pytest.raises(sync_relay.RelayError):
+ sync_relay.configured_sync_e2ee_key()
+ assert sync_relay.sync_e2ee_key_source() == "none"
diff --git a/tests/test_v2_service_binding.py b/tests/test_v2_service_binding.py
index ffe6fc42..e23b61e0 100644
--- a/tests/test_v2_service_binding.py
+++ b/tests/test_v2_service_binding.py
@@ -161,7 +161,11 @@ def from_environment(workspace_id):
monkeypatch.setattr(v2_api, "_service", _AutomationService())
monkeypatch.setattr(cloud_features, "CloudFeatureClient", _CloudFactory)
- monkeypatch.setattr(v2_api, "_managed_call", lambda fn, *args, **kwargs: fn(*args, **kwargs))
+ monkeypatch.setattr(
+ v2_api,
+ "_managed_call",
+ lambda fn, *args, feature=None, **kwargs: fn(*args, **kwargs),
+ )
result = v2_api.automation_get("acme")
@@ -224,7 +228,11 @@ def save_phase(service, organization_id, workspace_id, value, **kwargs):
monkeypatch.setattr(v2_api, "_service", _AutomationService())
monkeypatch.setattr(v2_api, "_AUTOMATION_BOOTSTRAP_LOCKS", {})
- monkeypatch.setattr(v2_api, "_managed_call", lambda fn, *args, **kwargs: fn(*args, **kwargs))
+ monkeypatch.setattr(
+ v2_api,
+ "_managed_call",
+ lambda fn, *args, feature=None, **kwargs: fn(*args, **kwargs),
+ )
monkeypatch.setattr(cloud_features, "CloudFeatureClient", _CloudFactory)
monkeypatch.setattr(cloud_features, "build_managed_snapshot", build_snapshot)
monkeypatch.setattr(