Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 21 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,26 @@ All notable changes to Engraphis are documented here. Format loosely follows

## [Unreleased]

### Removed

- **"Signed compliance export" is no longer advertised on any surface.** It was granted to
Pro and Team by the control plane's plan→feature table and promoted in the account portal,
the dashboard upgrade panel, this changelog, and the README pricing table — but it was
never implemented on either side: no signing code in this client, and no export route, no
`export:*` token scope, and no export job kind in Engraphis Cloud. The `export` key is
removed from the plan→feature tables in both repos and from the dashboard's entitlement
vocabulary.
- **No entitlement behaviour changes.** Those tables are read only to decide which lock
badges and plan bullets to draw; every paid operation was, and still is, authorized by
the cloud's token scopes and paid-entitlement dependencies.
- **Local workspace export is unaffected and always was free.** `GET /export` returns the
full bi-temporal dump (memories, sessions, audit) on every plan and is deliberately not
entitlement-gated so data portability survives recovery mode. It is now listed in the
README as the free capability it is.
- `GET /export?signed=true` and `GET /analytics/export` still answer `501`, but now say
the capability is not implemented and name the working alternative, instead of implying
it is available in Engraphis Cloud.

### Changed

- Managed compute consent now travels with the cloud account: an installation connected to
Expand Down Expand Up @@ -797,7 +817,7 @@ and safe hosted deployment.
- Sleep-time consolidation with compaction accounting
- Personalized PageRank graph arm (HippoRAG-style)
- Offline signed license keys (no phone-home)
- Pro analytics dashboard and compliance export
- Pro analytics dashboard
- Code-symbol graph via tree-sitter or regex fallback
- Docker + docker-compose deployment
- 300+ tests, eval harness, ablation suite
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -419,11 +419,11 @@ recovery boundaries.
| Memory engine + 29 MCP tools | ✓ | ✓ | ✓ |
| Version-chain diffs, offline knowledge graph | ✓ | ✓ | ✓ |
| Manual local consolidation (dry-run by default) | ✓ | ✓ | ✓ |
| Local workspace export (JSON: memories, sessions, audit) | ✓ | ✓ | ✓ |
| Hosted Cloud Sync | | ✓ | ✓ |
| Hosted Analytics | | ✓ | ✓ |
| Hosted Auto Consolidation + retention policy | | ✓ | ✓ |
| Hosted Auto Dreaming + managed proposals | | ✓ | ✓ |
| Signed compliance export (checksummed bi-temporal bundle) | | ✓ | ✓ |
| Priority support | | ✓ | ✓ |
| Hosted multi-user dashboard: invitations, logins, roles, seat management | | | ✓ |
| Hosted Team audit log + CSV export | | | ✓ |
Expand Down
1 change: 0 additions & 1 deletion engraphis/hosted_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,6 @@
"automation": "pro",
"consolidation": "pro",
"dreaming": "pro",
"export": "pro",
"sync": "pro",
"team": "team",
}
Expand Down
50 changes: 35 additions & 15 deletions engraphis/routes/v2_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -1287,11 +1287,20 @@ def analytics(workspace: Optional[str] = None):

@router.get("/analytics/export")
def analytics_export(workspace: Optional[str] = None):
"""Self-contained HTML analytics report (inline CSS, zero CDN) — a shareable,
archivable artifact. Same Pro gate as the analytics view it renders."""
"""Not implemented here. A self-contained HTML analytics *report artifact* was planned
for this route and never built.

The previous 501 told customers to "download analytics reports from the Engraphis Cloud
dashboard", which overstates what is reachable: the control plane serves analytics as
JSON (``GET /analytics/latest``), and this client already surfaces exactly that through
``GET /analytics``. Point callers at the data that exists rather than at a file that may
not. Kept as an explicit 501 rather than removed so an older dashboard build calling this
path gets a truthful answer instead of a 404."""
raise HTTPException(status_code=501, detail={
"error": "Download analytics reports from the Engraphis Cloud dashboard.",
"managed_cloud": True,
"error": "This build does not generate analytics report files. Use GET /analytics "
"for the same data as JSON.",
"implemented": False,
"alternative": "/analytics",
})


Expand All @@ -1314,19 +1323,26 @@ def ready():
status_code=200 if is_ready else 503)


# ── compliance export (Pro) ───────────────────────────────────────────────────
# ── workspace export (local, free) ────────────────────────────────────────────
@router.get("/export")
def export(workspace: Optional[str] = None, signed: bool = False):
"""Full bi-temporal workspace dump (memories + sessions + audit). Pro-gated.
"""Full bi-temporal workspace dump (memories + sessions + audit).

Local and free: this is the data-portability path that must keep working even in
recovery mode, so it is deliberately not entitlement-gated.

``signed=true`` wraps the dump in a SHA-256 compliance manifest (see
:func:`_sign_export`) — a tamper-evident, self-verifying audit bundle."""
``signed=true`` was specified as a SHA-256 compliance manifest wrapping the same dump —
a tamper-evident, self-verifying audit bundle. It was never implemented; no signing
code exists in this client and Engraphis Cloud has no export route, no ``export:*``
token scope, and no export job kind. It answers 501 rather than silently returning an
*unsigned* bundle, because a caller asking for tamper-evidence must not be handed
something that merely looks like it."""
if signed:
raise HTTPException(status_code=501, detail={
"error": "Signed compliance exports are available in Engraphis Cloud.",
"cloud_only": True,
"feature": "export",
"upgrade_url": licensing.upgrade_url(),
"error": "Signed compliance exports are not implemented. Omit signed=true for "
"the unsigned workspace export, which contains the same data.",
"implemented": False,
"alternative": "/export",
})
ws = workspace or _default_ws()
return _run(service().export_workspace, workspace=ws, recovery=True)
Expand Down Expand Up @@ -1730,12 +1746,17 @@ def code_export(workspace: str, repo: str):
# 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
# The server's own keys are {analytics, automation, 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.
#
# ``export`` was removed from both tables: it was disclosed by the server and rendered here
# for the whole pre-launch period while no signed-export capability existed on either side.
# Plain local workspace export is unaffected — it is a free, local-only route (``GET
# /export``) and was never a hosted entitlement.
_AUTOMATION_FEATURES = ("automation", "consolidation", "dreaming")
_PRO_FEATURES = ("analytics", "export", "sync") + _AUTOMATION_FEATURES
_PRO_FEATURES = ("analytics", "sync") + _AUTOMATION_FEATURES
_PLAN_FEATURES = {
"free": (),
"local": (),
Expand All @@ -1751,7 +1772,6 @@ def code_export(workspace: str, repo: str):
"automation": "Automation",
"consolidation": "Auto Consolidation",
"dreaming": "Auto Dreaming",
"export": "Compliance export",
"sync": "Cloud Sync",
"team": "Team administration",
}
Expand Down
4 changes: 2 additions & 2 deletions engraphis/static/dashboard.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 1 addition & 2 deletions tests/e2e/commercial.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,11 @@ const knownFeatures = {
automation: 'Automation',
consolidation: 'Auto Consolidation',
dreaming: 'Auto Dreaming',
export: 'Compliance export',
sync: 'Cloud Sync',
team: 'Team administration',
};

const proFeatures = ['analytics', 'automation', 'consolidation', 'dreaming', 'export', 'sync'];
const proFeatures = ['analytics', 'automation', 'consolidation', 'dreaming', 'sync'];
const teamFeatures = [...proFeatures, 'team'];

function licenseFor(plan, features) {
Expand Down
4 changes: 2 additions & 2 deletions tests/test_client_launch.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,8 +45,8 @@
# purchased capability silently renders as locked.
SERVER_PLAN_FEATURES = {
"free": set(),
"pro": {"analytics", "automation", "export", "sync"},
"team": {"analytics", "automation", "export", "sync", "team"},
"pro": {"analytics", "automation", "sync"},
"team": {"analytics", "automation", "sync", "team"},
}
# Named separately by this client's commercial manifest; the server grants both under
# ``automation``, so any plan granting ``automation`` must grant these too.
Expand Down
2 changes: 1 addition & 1 deletion tests/test_cloud_session.py
Original file line number Diff line number Diff line change
Expand Up @@ -311,7 +311,7 @@ def _downgrade_state(monkeypatch, *, body: dict) -> dict:
"token_subject": "member",
"plan": "team",
"cloud_access_active": True,
"cloud_features": ["analytics", "automation", "export", "sync", "team"],
"cloud_features": ["analytics", "automation", "sync", "team"],
}

def _replace(value: dict) -> None:
Expand Down
5 changes: 4 additions & 1 deletion tests/test_dashboard_auth_placement.py
Original file line number Diff line number Diff line change
Expand Up @@ -330,11 +330,14 @@ def test_pro_upgrade_panel_lists_every_pro_benefit_and_purchase_cta():
"Growth, retention, decay, and entity Analytics",
"Auto Consolidation with hosted retention policies",
"Auto Dreaming with reviewable managed proposals",
"Signed compliance exports with bi-temporal checksums",
"Priority support",
):
assert benefit in script
assert ".upgrade-panel" in styles
# Regression: the panel sold "Signed compliance exports with bi-temporal checksums"
# while no signing code existed in this client and Engraphis Cloud had no export
# route, scope, or job kind. Do not re-add it without a capability behind it.
assert "compliance export" not in script.lower()


def test_team_invitations_and_password_setup_are_not_in_local_client():
Expand Down
16 changes: 14 additions & 2 deletions tests/test_dashboard_v2.py
Original file line number Diff line number Diff line change
Expand Up @@ -282,16 +282,28 @@ def test_portfolio_and_report_analytics_are_hosted_only(monkeypatch, tmp_path):
assert client.get("/api/analytics/export?workspace=demo").status_code == 501


def test_raw_owner_export_is_free_but_signed_report_is_cloud_only(
def test_raw_owner_export_is_free_and_signed_export_is_honestly_unimplemented(
monkeypatch, tmp_path
):
"""The signed variant must not claim to exist somewhere else.

It previously answered ``cloud_only: True`` — but Engraphis Cloud has no export route,
no ``export:*`` token scope, and no export job kind, so that pointed a customer at a
product that does not exist. The 501 now says the capability is unimplemented and names
the working unsigned export instead.
"""

with _client(monkeypatch, tmp_path) as client:
raw = client.get("/api/export?workspace=demo")
assert raw.status_code == 200
assert raw.json()["counts"]["memories"] >= 1
signed = client.get("/api/export?workspace=demo&signed=true")
assert signed.status_code == 501
assert signed.json()["detail"]["cloud_only"] is True
detail = signed.json()["detail"]
assert detail["implemented"] is False
assert detail["alternative"] == "/export"
assert "cloud_only" not in detail
assert "Engraphis Cloud" not in detail["error"]


def test_health_and_readiness_remain_public(monkeypatch, tmp_path):
Expand Down
4 changes: 2 additions & 2 deletions tests/test_hosted_plan_resolution.py
Original file line number Diff line number Diff line change
Expand Up @@ -53,8 +53,8 @@
# engraphis-cloud/engraphis_cloud/entitlements.py ``EntitlementDTO`` / ``PLAN_FEATURES``.
SERVER_PLAN_FEATURES = {
"free": [],
"pro": ["analytics", "automation", "export", "sync"],
"team": ["analytics", "automation", "export", "sync", "team"],
"pro": ["analytics", "automation", "sync"],
"team": ["analytics", "automation", "sync", "team"],
}


Expand Down
4 changes: 2 additions & 2 deletions tests/test_licensing_launch.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,8 @@
SERVER_PAID_PLANS = {"pro", "team"}
SERVER_PLAN_FEATURES = {
"free": set(),
"pro": {"analytics", "automation", "export", "sync"},
"team": {"analytics", "automation", "export", "sync", "team"},
"pro": {"analytics", "automation", "sync"},
"team": {"analytics", "automation", "sync", "team"},
}
SERVER_TRIAL_DURATION_SECONDS = 259_200
SERVER_WORKSPACE_WRITE_GRACE_MAX_SECONDS = 86_400
Expand Down
Loading