tandem-server 0.5.5

HTTP server for Tandem engine APIs

#[tokio::test]
async fn coder_memory_events_include_normalized_artifact_fields() {
    let state = test_state().await;
    state
        .capability_resolver
        .refresh_builtin_bindings()
        .await
        .expect("refresh builtin bindings");
    let mut rx = state.event_bus.subscribe();
    let app = app_router(state.clone());

    let create_req = Request::builder()
        .method("POST")
        .uri("/coder/runs")
        .header("content-type", "application/json")
        .body(Body::from(
            json!({
                "coder_run_id": "coder-run-memory-events",
                "workflow_mode": "issue_triage",
                "repo_binding": {
                    "project_id": "proj-engine",
                    "workspace_id": "ws-tandem",
                    "workspace_root": "/tmp/tandem-repo",
                    "repo_slug": "user123/tandem"
                },
                "github_ref": {
                    "kind": "issue",
                    "number": 335
                },
                "source_client": "desktop_developer_mode"
            })
            .to_string(),
        ))
        .expect("create request");
    let create_resp = app
        .clone()
        .oneshot(create_req)
        .await
        .expect("create response");
    assert_eq!(create_resp.status(), StatusCode::OK);

    let summary_req = Request::builder()
        .method("POST")
        .uri("/coder/runs/coder-run-memory-events/triage-summary")
        .header("content-type", "application/json")
        .body(Body::from(
            json!({
                "summary": "Capability readiness drift already explained this failure",
                "confidence": "high"
            })
            .to_string(),
        ))
        .expect("summary request");
    let summary_resp = app
        .clone()
        .oneshot(summary_req)
        .await
        .expect("summary response");
    assert_eq!(summary_resp.status(), StatusCode::OK);
    let summary_payload: Value = serde_json::from_slice(
        &to_bytes(summary_resp.into_body(), usize::MAX)
            .await
            .expect("summary body"),
    )
    .expect("summary json");
    let triage_candidate_id = summary_payload
        .get("generated_candidates")
        .and_then(Value::as_array)
        .and_then(|rows| {
            rows.iter().find_map(|row| {
                (row.get("kind").and_then(Value::as_str) == Some("triage_memory")).then(|| {
                    row.get("candidate_id")
                        .and_then(Value::as_str)
                        .map(ToString::to_string)
                })?
            })
        })
        .expect("triage candidate id");

    let candidate_event = next_event_of_type(&mut rx, "coder.memory.candidate_added").await;
    assert_eq!(
        candidate_event
            .properties
            .get("kind")
            .and_then(Value::as_str),
        Some("memory_candidate")
    );
    assert_eq!(
        candidate_event
            .properties
            .get("artifact_type")
            .and_then(Value::as_str),
        Some("coder_memory_candidate")
    );
    assert!(candidate_event
        .properties
        .get("artifact_id")
        .and_then(Value::as_str)
        .is_some());
    assert!(candidate_event
        .properties
        .get("artifact_path")
        .and_then(Value::as_str)
        .is_some());

    let promote_req = Request::builder()
        .method("POST")
        .uri(format!(
            "/coder/runs/coder-run-memory-events/memory-candidates/{triage_candidate_id}/promote"
        ))
        .header("content-type", "application/json")
        .body(Body::from(
            json!({
                "to_tier": "project",
                "reviewer_id": "reviewer-1",
                "approval_id": "approval-1",
                "reason": "approved reusable triage memory"
            })
            .to_string(),
        ))
        .expect("promote request");
    let promote_resp = app
        .clone()
        .oneshot(promote_req)
        .await
        .expect("promote response");
    assert_eq!(promote_resp.status(), StatusCode::OK);

    let promoted_event = next_event_of_type(&mut rx, "coder.memory.promoted").await;
    assert_eq!(
        promoted_event
            .properties
            .get("kind")
            .and_then(Value::as_str),
        Some("memory_promotion")
    );
    assert_eq!(
        promoted_event
            .properties
            .get("artifact_type")
            .and_then(Value::as_str),
        Some("coder_memory_promotion")
    );
    assert!(promoted_event
        .properties
        .get("artifact_id")
        .and_then(Value::as_str)
        .is_some());
    assert!(promoted_event
        .properties
        .get("artifact_path")
        .and_then(Value::as_str)
        .is_some());
}