Files
yvgude--lean-ctx/rust/tests/cloud_pro_features_e2e.rs
wehub-resource-sync 26382a7ac6
CodeQL / Analyze (javascript-typescript) (push) Waiting to run
JetBrains Plugin / Actionlint (push) Waiting to run
CodeQL / Analyze (actions) (push) Waiting to run
CodeQL / Analyze (rust) (push) Waiting to run
JetBrains Plugin / Validation (push) Waiting to run
JetBrains Plugin / Build (push) Waiting to run
JetBrains Plugin / Test (push) Blocked by required conditions
Security Check / Security Scan (push) Waiting to run
CI / Clippy (push) Failing after 15m13s
CI / Test (ubuntu-latest) (push) Failing after 16m1s
CI / Test (macos-latest) (push) Has been cancelled
CI / Test (windows-latest) (push) Has been cancelled
CI / Build (no embeddings / no ORT) (push) Has been cancelled
CI / Format (push) Has been cancelled
CI / Cookbook (Node) (push) Has been cancelled
CI / Pi Extension (Node) (push) Has been cancelled
CI / Rust SDK (lean-ctx-client) (push) Has been cancelled
CI / Embed SDK (lean-ctx-sdk) (push) Has been cancelled
CI / Python SDK (leanctx) (push) Has been cancelled
CI / Hermes Plugin (Python) (push) Has been cancelled
CI / SDK Conformance Matrix (push) Has been cancelled
CI / Coverage (push) Has been cancelled
CI / cargo-deny (push) Has been cancelled
CI / Adversarial Safety (push) Has been cancelled
CI / Benchmarks (push) Has been cancelled
CI / Output-Quality Gate (eval A/B) (push) Has been cancelled
CI / Documentation (push) Has been cancelled
CI / CI Green (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 12:35:30 +08:00

255 lines
10 KiB
Rust

//! Live, `#[ignore]`d end-to-end proof for the **whole advertised Pro surface**,
//! including the literal "my context follows me to another machine" experience.
//!
//! It drives the real engine client (`cloud_client`) against a real open backend
//! wired to a billing stub that resolves the caller's plan. The one-shot harness
//! `scripts/cloud_pro_features_e2e.sh` provisions an ephemeral Postgres + backend
//! + stub and runs this test once per phase via `LEANCTX_E2E_PHASE`:
//!
//! - `device-a` — a Pro machine *pushes* every bucket (knowledge, gotchas, the
//! five telemetry streams, and the hosted index bundle).
//! - `device-b` — a *different* Pro machine (separate data dir, same account,
//! same repo identity) *restores*: `pull_knowledge` returns the entry and
//! `pull_index_bundle` reconstructs the index artifacts. This is the real
//! cross-device hand-off.
//! - `free` — a Free account is refused (`402`) on all eight gated buckets.
//!
//! Env (set by the harness):
//! - `LEAN_CTX_API_URL` — backend base URL
//! - `LEAN_CTX_DATA_DIR` — isolated data dir with `cloud/credentials.json`
//! - `LEANCTX_E2E_PHASE` — `device-a` | `device-b` | `free`
//! - `LEANCTX_E2E_PROJECT` — scratch project root (device-a / device-b phases)
//!
//! Ciphertext-at-rest is asserted out-of-band by the harness, which greps
//! `knowledge_blobs.blob`, `gotcha_blobs.blob` and `index_bundles.bytes` for the
//! needle below — it must appear in none of them.
use lean_ctx::cloud_client;
use serde_json::{Value, json};
use std::path::PathBuf;
/// Secret embedded in all three *encrypted* buckets; the harness greps every
/// at-rest table for it and it must never appear in plaintext.
const NEEDLE: &str = "PRO-E2E-NEEDLE-9b1e4d77";
#[test]
#[ignore = "live E2E: needs a running backend + billing stub (run scripts/cloud_pro_features_e2e.sh)"]
fn pro_features_cross_device() {
match std::env::var("LEANCTX_E2E_PHASE").as_deref() {
Ok("device-a") => device_a_push(),
Ok("device-b") => device_b_restore(),
Ok("free") => free_is_gated(),
other => panic!("set LEANCTX_E2E_PHASE=device-a|device-b|free (got {other:?})"),
}
}
// ── Schema-valid payloads (mirror the server structs so the `Json<T>` extractor
// accepts them and execution reaches the entitlement gate) ──────────────────
fn knowledge_entry() -> Value {
json!({
"category": "decision", "key": "cross-device", "value": NEEDLE,
"updated_by": "pro@example.com", "updated_at": "2026-01-01T00:00:00Z",
})
}
fn gotcha_entry() -> Value {
json!({
"pattern": NEEDLE, "fix": "seal client-side", "severity": "high",
"category": "e2e", "occurrences": 1, "prevented_count": 0, "confidence": 0.9,
})
}
fn command_entry() -> Value {
json!({ "command": "cargo test", "source": "e2e", "count": 1, "tokens_saved": 10 })
}
fn cep_entry() -> Value {
json!({ "recorded_at": "2026-01-01T00:00:00Z", "score": 0.87, "tokens_saved": 1234 })
}
fn gain_entry() -> Value {
json!({
"recorded_at": "2026-01-01T00:00:00Z", "total": 0.8, "compression": 0.7,
"cost_efficiency": 0.6, "quality": 0.9, "consistency": 0.85,
})
}
fn buddy_state() -> Value {
json!({ "name": "e2e", "species": "otter", "level": 2, "xp": 10, "streak_days": 3 })
}
fn feedback_entry() -> Value {
json!({ "language": "rust", "entropy": 0.5, "jaccard": 0.6, "sample_count": 10, "avg_efficiency": 0.8 })
}
fn project_root() -> PathBuf {
PathBuf::from(std::env::var("LEANCTX_E2E_PROJECT").expect("LEANCTX_E2E_PROJECT"))
}
/// The hosted-index artifact dir for `root`, discovered from the engine's own
/// `NothingToBundle` error so the test needs no crate-internal path helper.
fn discover_vectors_dir(root: &std::path::Path) -> PathBuf {
match cloud_client::push_index_bundle(root) {
Ok((hash, _)) => panic!("expected NothingToBundle on an empty project, got hash {hash}"),
Err(e) => {
let raw = e
.split("found in ")
.nth(1)
.and_then(|s| s.split(" \u{2014}").next())
.unwrap_or_else(|| panic!("unexpected index error: {e}"))
.trim();
PathBuf::from(raw)
}
}
}
/// Device A: a Pro machine pushes every advertised bucket.
fn device_a_push() {
let knowledge = knowledge_entry();
assert!(
cloud_client::push_knowledge(std::slice::from_ref(&knowledge))
.expect("device-a: push_knowledge")
.contains("synced")
);
let gotcha = gotcha_entry();
cloud_client::push_gotchas(std::slice::from_ref(&gotcha)).expect("device-a: push_gotchas");
// Five plaintext-telemetry buckets (server-readable by design; excluded from
// the E2E claim) — each must pass the Pro gate and store.
let command = command_entry();
assert!(
cloud_client::push_commands(std::slice::from_ref(&command))
.expect("device-a: push_commands")
.contains("synced")
);
let cep = cep_entry();
assert!(
cloud_client::push_cep(std::slice::from_ref(&cep))
.expect("device-a: push_cep")
.contains("synced")
);
let gain = gain_entry();
assert!(
cloud_client::push_gain(std::slice::from_ref(&gain))
.expect("device-a: push_gain")
.contains("synced")
);
cloud_client::push_buddy(&buddy_state()).expect("device-a: push_buddy");
let feedback = feedback_entry();
cloud_client::push_feedback(std::slice::from_ref(&feedback)).expect("device-a: push_feedback");
// Hosted Personal Index: build the two artifacts, then pack→encrypt→upload.
let root = project_root();
let dir = discover_vectors_dir(&root);
std::fs::create_dir_all(&dir).expect("create vectors dir");
std::fs::write(dir.join("bm25_index.bin.zst"), b"e2e-bm25-artifact-bytes").unwrap();
let embeddings = json!({ "vectors": [{ "id": "e2e", "value": NEEDLE }] });
std::fs::write(
dir.join("embeddings.json"),
serde_json::to_vec(&embeddings).unwrap(),
)
.unwrap();
let (_hash, size) =
cloud_client::push_index_bundle(&root).expect("device-a: push_index_bundle");
assert!(size > 0, "encrypted bundle should be non-empty");
// Personal Cloud dashboard (`lean-ctx cloud status` / leanctx.com/account/cloud)
// must reflect the active Pro entitlement.
let dash = cloud_client::fetch_account_cloud().expect("device-a: fetch_account_cloud");
assert_eq!(
dash.get("cloud_sync").and_then(Value::as_bool),
Some(true),
"dashboard must report cloud_sync for Pro: {dash}"
);
assert_eq!(
dash.get("plan").and_then(Value::as_str),
Some("pro"),
"dashboard must report the Pro plan: {dash}"
);
println!(
"DEVICE-A-OK: knowledge+gotchas+commands+cep+gain+buddy+feedback+index pushed; dashboard=pro"
);
}
/// Device B: a *different* machine on the same account restores Device A's data.
fn device_b_restore() {
// Knowledge: the vault key derives from the account API key alone, so a fresh
// machine with only the credentials reconstructs the store.
let pulled = cloud_client::pull_knowledge().expect("device-b: pull_knowledge");
assert!(
pulled
.iter()
.any(|e| e.get("value").and_then(|v| v.as_str()) == Some(NEEDLE)),
"device-b did not restore Device A's knowledge entry: {pulled:#?}"
);
// Hosted index: same repo identity → same bucket. Pull reconstructs the
// artifacts into this machine's (previously empty) vectors dir.
let root = project_root();
let dir = discover_vectors_dir(&root); // empty here → reveals device B's dir
cloud_client::pull_index_bundle(&root).expect("device-b: pull_index_bundle");
let restored = std::fs::read_to_string(dir.join("embeddings.json"))
.expect("device-b: pull must restore embeddings.json");
assert!(
restored.contains(NEEDLE),
"device-b restored a bundle without the needle"
);
println!("DEVICE-B-OK: restored knowledge + hosted index from another device");
}
/// Free plan: every gated bucket must be refused with `402`. Against the same
/// healthy backend the Pro device just used, the only reason these fail is the
/// entitlement gate — a clean differential proof of the paywall.
fn free_is_gated() {
let knowledge = knowledge_entry();
let gotcha = gotcha_entry();
let command = command_entry();
let cep = cep_entry();
let gain = gain_entry();
let feedback = feedback_entry();
gate_blocks(
"push_knowledge",
cloud_client::push_knowledge(std::slice::from_ref(&knowledge)),
);
gate_blocks(
"push_commands",
cloud_client::push_commands(std::slice::from_ref(&command)),
);
gate_blocks(
"push_cep",
cloud_client::push_cep(std::slice::from_ref(&cep)),
);
gate_blocks(
"push_gotchas",
cloud_client::push_gotchas(std::slice::from_ref(&gotcha)),
);
gate_blocks("push_buddy", cloud_client::push_buddy(&buddy_state()));
gate_blocks(
"push_feedback",
cloud_client::push_feedback(std::slice::from_ref(&feedback)),
);
gate_blocks(
"push_gain",
cloud_client::push_gain(std::slice::from_ref(&gain)),
);
gate_blocks("index_status", cloud_client::index_bundle_status());
println!("FREE-ALL-GATED-OK: all 8 cloud-sync buckets returned 402");
}
/// Assert a Free-plan call was refused by the payment gate (any `Ok` is a
/// paywall leak). Accepts the `402` status or the gate's prose.
fn gate_blocks<T: std::fmt::Debug>(name: &str, result: Result<T, String>) {
match result {
Ok(v) => panic!("PAYWALL LEAK: {name} succeeded for a Free account: {v:?}"),
Err(e) => {
println!(" {name}: blocked -> {e}");
assert!(
e.contains("402")
|| e.to_lowercase().contains("payment")
|| e.to_lowercase().contains("pro"),
"{name}: expected a 402/payment gate error, got: {e}"
);
}
}
}