relay-knowledge 1.1.17

Graph-database-based knowledge graph project.
Documentation
use std::sync::Arc;

use crate::{
    api::{ApiError, ApiMetadata, CodeRepositoryIndexStartResponse, RequestContext},
    domain::{
        CodeIndexMode, CodeIndexRequest, CodeIndexResourceBudget, CodeRepositoryStatus,
        clean_git_commit_from_snapshot_identity,
    },
    storage::{CodeIndexTaskSeed, KnowledgeStore},
};

pub(super) async fn index_start_response_from_task(
    store: &Arc<dyn KnowledgeStore>,
    fallback_status: CodeRepositoryStatus,
    task: crate::domain::CodeIndexTaskRecord,
    requested_ref: String,
    context: &RequestContext,
) -> Result<CodeRepositoryIndexStartResponse, ApiError> {
    let checkpoint = store
        .code_index_checkpoint(task.source_scope.clone())
        .await
        .map_err(storage_api_error)?;
    let graph_version = store
        .current_graph_version()
        .await
        .map_err(storage_api_error)?;
    let status = store
        .code_repository_status(task.repository_id.clone())
        .await
        .map_err(storage_api_error)?
        .unwrap_or(fallback_status);

    Ok(CodeRepositoryIndexStartResponse {
        metadata: ApiMetadata::graph_only(context, graph_version),
        scope: crate::api::CodeRepositoryScopeMetadata::from_index_task(&task, requested_ref),
        summary: None,
        status,
        task: Some(task),
        checkpoint,
    })
}

use super::state::{fresh_full_index_probe, previous_index_state_for_index};
use crate::application::code_repository::{
    clock::now_millis, errors::storage_api_error, scope::merged_filters,
};

pub(super) async fn queue_worktree_overlay_index_task(
    store: &Arc<dyn KnowledgeStore>,
    status: &CodeRepositoryStatus,
    request: &CodeIndexRequest,
) -> Result<crate::domain::CodeIndexTaskRecord, ApiError> {
    let previous = previous_index_state_for_index(store, status, request).await?;
    let base_commit = previous.base_resolved_commit_sha.ok_or_else(|| {
        ApiError::invalid_argument(format!(
            "worktree overlay for code repository '{}' requires a resolved HEAD base scope",
            status.alias
        ))
    })?;
    let path_filters = merged_filters(&status.path_filters, &request.repository.path_filters);
    let language_filters = merged_filters(
        &status.language_filters,
        &request.repository.language_filters,
    );
    let workspace_detection_json = serde_json::to_string(&request.workspace_detection)
        .map_err(|error| ApiError::invalid_argument(error.to_string()))?;
    let payload_json = pinned_worktree_overlay_payload(request, &base_commit)
        .map_err(|error| ApiError::invalid_argument(error.to_string()))?;
    let tree_hash = format!("worktree:pending:{base_commit}");
    let source_scope = crate::domain::code_snapshot_scope_id_with_workspace_detection(
        &status.repository_id,
        &tree_hash,
        &path_filters,
        &language_filters,
        &request.workspace_detection,
    );
    let queued_at_ms = now_millis();
    let input_fingerprint = worktree_overlay_input_fingerprint(
        status,
        request,
        &base_commit,
        &path_filters,
        &language_filters,
        &workspace_detection_json,
        queued_at_ms,
    );
    store
        .queue_code_index_task(CodeIndexTaskSeed {
            repository_id: status.repository_id.clone(),
            alias: status.alias.clone(),
            ref_selector: base_commit.clone(),
            resolved_commit_sha: tree_hash.clone(),
            tree_hash,
            source_scope,
            path_filters,
            language_filters,
            mode: request.mode.clone(),
            input_fingerprint,
            resource_budget: CodeIndexResourceBudget::default(),
            payload_json,
            now_ms: queued_at_ms,
        })
        .await
        .map_err(storage_api_error)
}

pub(super) async fn queue_incremental_index_task(
    store: &Arc<dyn KnowledgeStore>,
    status: &CodeRepositoryStatus,
    request: &CodeIndexRequest,
) -> Result<crate::domain::CodeIndexTaskRecord, ApiError> {
    let CodeIndexMode::Incremental { head_ref, .. } = &request.mode else {
        return Err(ApiError::invalid_argument(
            "incremental task queue requires incremental index mode",
        ));
    };
    let previous = previous_index_state_for_index(store, status, request).await?;
    let base_commit = previous.base_resolved_commit_sha.ok_or_else(|| {
        ApiError::invalid_argument(format!(
            "incremental update for code repository '{}' requires a resolved base commit",
            status.alias
        ))
    })?;
    let mut head_selector = request.repository.clone();
    head_selector.ref_selector = head_ref.clone();
    let head = fresh_full_index_probe(status, &head_selector).await?;
    if status
        .last_indexed_commit
        .as_deref()
        .is_some_and(|identity| identity.starts_with("worktree:"))
        && clean_git_commit_from_snapshot_identity(
            status.last_indexed_commit.as_deref().unwrap_or_default(),
        ) == Some(head.resolved_commit_sha.as_str())
    {
        return Err(ApiError::invalid_argument(format!(
            "code repository '{}' still points at a worktree overlay whose clean base is already {}; create a commit first or query the worktree scope explicitly",
            status.alias, head.resolved_commit_sha
        )));
    }
    let pinned_mode =
        CodeIndexMode::incremental(base_commit.clone(), head.resolved_commit_sha.clone())
            .map_err(|error| ApiError::invalid_argument(error.to_string()))?;
    let mut pinned_request = request.clone();
    pinned_request.mode = pinned_mode.clone();
    let payload_json = serde_json::to_string(&pinned_request)
        .map_err(|error| ApiError::invalid_argument(error.to_string()))?;
    let workspace_detection_json = serde_json::to_string(&request.workspace_detection)
        .map_err(|error| ApiError::invalid_argument(error.to_string()))?;
    let source_scope = crate::domain::code_snapshot_scope_id_with_workspace_detection(
        &status.repository_id,
        &head.tree_hash,
        &head.path_filters,
        &head.language_filters,
        &request.workspace_detection,
    );
    let input_fingerprint = format!(
        "incremental:{}:{}:{}:{}:{}",
        status.repository_id,
        base_commit,
        head.resolved_commit_sha,
        source_scope,
        workspace_detection_json
    );

    store
        .queue_code_index_task(CodeIndexTaskSeed {
            repository_id: status.repository_id.clone(),
            alias: status.alias.clone(),
            ref_selector: request.repository.ref_selector.clone(),
            resolved_commit_sha: head.resolved_commit_sha,
            tree_hash: head.tree_hash,
            source_scope,
            path_filters: head.path_filters,
            language_filters: head.language_filters,
            mode: pinned_mode,
            input_fingerprint,
            resource_budget: CodeIndexResourceBudget::default(),
            payload_json,
            now_ms: now_millis(),
        })
        .await
        .map_err(storage_api_error)
}

fn pinned_worktree_overlay_payload(
    request: &CodeIndexRequest,
    base_commit: &str,
) -> Result<String, serde_json::Error> {
    let mut payload = request.clone();
    payload.repository.ref_selector = base_commit.to_owned();
    serde_json::to_string(&payload)
}

fn worktree_overlay_input_fingerprint(
    status: &CodeRepositoryStatus,
    request: &CodeIndexRequest,
    base_commit: &str,
    path_filters: &[String],
    language_filters: &[String],
    workspace_detection_json: &str,
    queued_at_ms: u64,
) -> String {
    format!(
        "worktree:{}:{}:{}:{}:{}:{}:{}",
        status.repository_id,
        base_commit,
        serde_json::to_string(path_filters).unwrap_or_default(),
        serde_json::to_string(language_filters).unwrap_or_default(),
        request.repository.ref_selector,
        workspace_detection_json,
        queued_at_ms
    )
}