Initial commit on Forgejo
All checks were successful
CI and deploy / Test (push) Successful in 4m52s
CI and deploy / Deploy production (push) Successful in 23s

Fresh repository history for elektrine/tarakan hosted at
https://git.elektrine.com/elektrine/tarakan.
This commit is contained in:
Maxfield Luke 2026-07-29 04:43:40 -04:00
commit af6077b9c3
455 changed files with 78366 additions and 0 deletions

View file

@ -0,0 +1,534 @@
defmodule TarakanWeb.API.WorkController do
use TarakanWeb, :controller
alias Tarakan.PromptSafety
alias Tarakan.Repositories
alias Tarakan.Work
alias Tarakan.Work.ReviewTask
def index(conn, %{"host" => host_slug, "owner" => owner, "name" => name}) do
case visible_repository(host_slug, owner, name, conn.assigns.current_scope) do
nil ->
not_found(conn, "repository is not registered with Tarakan")
repository ->
tasks =
Work.list_tasks(repository,
limit: 100,
scope: conn.assigns.current_scope,
active_only: true
)
encoded = Enum.map(tasks, &task_json/1)
json(conn, %{jobs: encoded})
end
end
@doc """
Global Jobs queue: open / changes_requested, plus the caller's active claims.
"""
def queue(conn, params) do
limit =
case Integer.parse(to_string(params["limit"] || "50")) do
{n, _} when n > 0 -> min(n, 100)
_ -> 50
end
account_id =
case conn.assigns.current_scope do
%{account: %{id: id}} -> id
_ -> nil
end
opts =
[
limit: limit,
account_id: account_id,
min_stars: parse_min_stars(params["min_stars"]),
language: parse_language(params["language"] || params["lang"]),
kind: parse_kind(params["kind"]),
kinds: parse_kinds(params["kinds"])
]
|> Enum.reject(fn {_k, v} -> is_nil(v) end)
tasks = Work.list_open_claimable_tasks(opts)
# What each job is worth, resolved for the whole page in one query, so a
# worker can see where its tokens are actually paid for.
contract_value =
tasks
|> Enum.map(& &1.repository_id)
|> Tarakan.Market.open_value_by_repository()
encoded =
Enum.map(tasks, &Map.put(task_json(&1), :contract, contract_value[&1.repository_id]))
json(conn, %{jobs: encoded})
end
defp parse_min_stars(nil), do: nil
defp parse_min_stars(value) do
case Integer.parse(to_string(value)) do
{n, _} when n > 0 -> n
_ -> nil
end
end
defp parse_language(nil), do: nil
defp parse_language(value) when is_binary(value) do
case String.trim(value) do
"" -> nil
lang -> lang
end
end
defp parse_language(_), do: nil
defp parse_kind(nil), do: nil
defp parse_kind(value) when is_binary(value) do
case String.trim(value) do
"" -> nil
kind -> kind
end
end
defp parse_kind(_), do: nil
# `?kinds=diff_review,refute_finding` - what this worker can actually run.
defp parse_kinds(value) when is_binary(value) do
value
|> String.split(",")
|> Enum.map(&String.trim/1)
|> Enum.reject(&(&1 == ""))
|> case do
[] -> nil
list -> list
end
end
defp parse_kinds(value) when is_list(value), do: parse_kinds(Enum.join(value, ","))
defp parse_kinds(_), do: nil
defp visible_repository(host_slug, owner, name, scope) do
case Tarakan.Hosts.host_for_slug(host_slug) do
{:ok, host} -> Repositories.get_visible_repository(host, owner, name, scope)
:error -> nil
end
end
def show(conn, %{"id" => id}) do
with {:ok, task} <- fetch_visible_task(id, conn.assigns.current_scope) do
json(conn, task_json(task))
else
{:error, :not_found} -> not_found(conn, "job not found")
end
end
def claim(conn, %{"id" => id}) do
with {:ok, task} <- fetch_visible_task(id, conn.assigns.current_scope),
{:ok, task} <- Work.claim_task(task, conn.assigns.current_scope) do
json(conn, task_json(task))
else
error -> lifecycle_error(conn, error)
end
end
def release(conn, %{"id" => id}) do
with {:ok, task} <- fetch_visible_task(id, conn.assigns.current_scope),
{:ok, task} <- Work.release_task(task, conn.assigns.current_scope) do
json(conn, task_json(task))
else
error -> lifecycle_error(conn, error)
end
end
def renew(conn, %{"id" => id}) do
with {:ok, task} <- fetch_visible_task(id, conn.assigns.current_scope),
{:ok, task} <- Work.renew_claim(task, conn.assigns.current_scope) do
json(conn, task_json(task))
else
error -> lifecycle_error(conn, error)
end
end
# Retained for client compatibility. It submits evidence for independent
# review; no client may mark its own contribution accepted.
# Finding-kind Requests accept `document` (Tarakan Review/Scan Format v1).
def complete(conn, %{"id" => id} = params) do
attrs =
params
|> Map.take([
"provenance",
"summary",
"evidence",
"document",
"model",
"prompt_version",
"notes",
"verdict"
])
with {:ok, task} <- fetch_visible_task(id, conn.assigns.current_scope),
{:ok, task} <- Work.submit_task(task, conn.assigns.current_scope, attrs) do
json(conn, task_json(task))
else
error -> lifecycle_error(conn, error)
end
end
def publish(conn, %{"id" => id} = params) do
transition(conn, id, &Work.publish_task/3, decision_attrs(params))
end
def accept(conn, %{"id" => id} = params) do
transition(conn, id, &Work.accept_task/3, decision_attrs(params))
end
def request_changes(conn, %{"id" => id} = params) do
transition(conn, id, &Work.request_changes/3, decision_attrs(params))
end
def reject(conn, %{"id" => id} = params) do
transition(conn, id, &Work.reject_task/3, decision_attrs(params))
end
def cancel(conn, %{"id" => id} = params) do
transition(conn, id, &Work.cancel_task/3, decision_attrs(params))
end
defp transition(conn, id, function, attrs) do
with {:ok, task} <- fetch_visible_task(id, conn.assigns.current_scope),
{:ok, task} <- function.(task, conn.assigns.current_scope, attrs) do
json(conn, task_json(task))
else
error -> lifecycle_error(conn, error)
end
end
defp decision_attrs(params), do: Map.take(params, ["reason", "evidence"])
defp fetch_visible_task(id, scope) do
with {id, ""} <- Integer.parse(id),
%ReviewTask{} = task <- Work.get_visible_task(id, scope) do
{:ok, task}
else
_other -> {:error, :not_found}
end
end
# Titles and descriptions are written by whoever opened the job and are read
# by whoever claims it - specifically, by their agent. Neutralize before the
# payload leaves the server rather than trusting every client to do it.
defp task_json(task) do
%{
id: task.id,
kind: task.kind,
capability: task.capability,
title: PromptSafety.sanitize_line(task.title),
description: PromptSafety.sanitize(task.description, max_bytes: 5_000),
status: task.status,
visibility: task.visibility,
commit_sha: task.commit_sha,
# diff_review reviews base_commit_sha..commit_sha. The worker already has
# the repository, so it diffs locally rather than us shipping the patch.
base_commit_sha: Map.get(task, :base_commit_sha),
target_code_pattern_key: Map.get(task, :target_code_pattern_key),
commit_committed_at: task.commit_committed_at,
repository: repository_json(task.repository),
creator: account_json(task.created_by),
claimant: account_json(task.claimed_by),
reviewer: account_json(task.reviewed_by),
lease: lease_json(task),
contribution: contribution_json(task.contribution),
contributions: Enum.map(task.contributions || [], &contribution_json/1),
decisions: Enum.map(task.decisions || [], &decision_json/1),
linked_review_id: task.linked_review_id,
linked_review: linked_review_json(Map.get(task, :linked_review)),
target_review_id: Map.get(task, :target_review_id),
target_review: linked_review_json(Map.get(task, :target_review)),
target_finding: target_finding_json(Map.get(task, :target_canonical_finding)),
published_at: task.published_at,
submitted_at: task.submitted_at,
reviewed_at: task.reviewed_at,
completed_at: task.completed_at,
disclosed_at: task.disclosed_at,
discloser: account_json(task.disclosed_by),
sensitive_data_reviewed: not is_nil(task.sensitive_data_reviewed_at),
inserted_at: task.inserted_at,
updated_at: task.updated_at,
job_url: url(~p"/jobs/#{task.id}")
}
end
# The finding under attack, reproduction, or re-scoring. Its title and
# description were written by another agent, so they are sanitized on the way
# out for exactly the same reason the job title is.
defp target_finding_json(nil), do: nil
defp target_finding_json(%Ecto.Association.NotLoaded{}), do: nil
defp target_finding_json(finding) do
%{
public_id: finding.public_id,
title: PromptSafety.sanitize_line(finding.title),
description: PromptSafety.sanitize(finding.description, max_bytes: 8_000),
severity: finding.severity,
calibrated_severity: finding.calibrated_severity,
status: finding.status,
file_path: finding.file_path,
line_start: finding.line_start,
line_end: finding.line_end,
first_seen_commit_sha: finding.first_seen_commit_sha,
last_seen_commit_sha: finding.last_seen_commit_sha,
confirmations_count: finding.confirmations_count,
disputes_count: finding.disputes_count,
refutations_count: finding.refutations_count,
survived_refutations_count: finding.survived_refutations_count,
reproductions_count: finding.reproductions_count,
finding_url: url(~p"/findings/#{finding.public_id}")
}
end
defp linked_review_json(nil), do: nil
defp linked_review_json(%Ecto.Association.NotLoaded{}), do: nil
defp linked_review_json(review) do
findings = Map.get(review, :findings)
findings =
case findings do
%Ecto.Association.NotLoaded{} -> []
list when is_list(list) -> list
_ -> []
end
%{
id: review.id,
review_status: review.review_status,
visibility: review.visibility,
findings_count: review.findings_count,
provenance: review.provenance,
review_kind: review.review_kind,
model: review.model,
prompt_version: review.prompt_version,
commit_sha: review.commit_sha,
source_request_id: review.source_request_id,
findings:
Enum.map(findings, fn finding ->
%{
id: finding.id,
file: finding.file_path,
line_start: finding.line_start,
line_end: finding.line_end,
severity: finding.severity,
title: PromptSafety.sanitize_line(finding.title),
description: PromptSafety.sanitize(finding.description, max_bytes: 2_000)
}
end)
}
end
defp repository_json(repository) do
%{
id: repository.id,
host: repository.host,
owner: repository.owner,
name: repository.name,
canonical_url: repository.canonical_url,
participation_mode: repository.participation_mode,
primary_language: repository.primary_language,
stars_count: repository.stars_count,
record_url:
TarakanWeb.Endpoint.url() <> TarakanWeb.RepositoryPaths.repository_path(repository)
}
end
defp account_json(nil), do: nil
defp account_json(account) do
%{
id: account.id,
handle: account.handle
}
end
defp lease_json(%ReviewTask{claimed_by: nil}), do: nil
defp lease_json(task) do
%{
claimed_at: task.claimed_at,
expires_at: task.claim_expires_at,
active: ReviewTask.claim_active?(task)
}
end
defp contribution_json(nil), do: nil
defp contribution_json(contribution) do
%{
id: contribution.id,
version: contribution.version,
provenance: contribution.provenance,
summary: contribution.summary,
evidence: contribution.evidence,
contributor: account_json(contribution.account),
submitted_at: contribution.inserted_at
}
end
defp decision_json(decision) do
%{
id: decision.id,
action: decision.action,
reason: decision.reason,
evidence: decision.evidence,
reviewer: account_json(decision.account),
decided_at: decision.inserted_at
}
end
defp lifecycle_error(conn, {:error, :not_found}),
do: not_found(conn, "job not found")
defp lifecycle_error(conn, {:error, :own_task}),
do: forbidden(conn, "that action is not allowed on this job")
defp lifecycle_error(conn, {:error, :not_independent}),
do: forbidden(conn, "an independent reviewer is required")
defp lifecycle_error(conn, {:error, reason})
when reason in [:forbidden, :unauthorized, :account_inactive, :insufficient_trust],
do: forbidden(conn, "this account is not authorized for that action")
defp lifecycle_error(conn, {:error, :not_claimant}),
do: forbidden(conn, "only the current claimant may perform that action")
defp lifecycle_error(conn, {:error, :claim_expired}),
do: conflict(conn, "the claim has expired; claim the task again")
defp lifecycle_error(conn, {:error, :claim_limit}),
do: conflict(conn, "this account has reached its active claim limit")
defp lifecycle_error(conn, {:error, :claim_rate_limited}),
do: too_many_requests(conn, "too many claim changes; try again shortly")
defp lifecycle_error(conn, {:error, :already_claimed}),
do: conflict(conn, "job has an active claim")
defp lifecycle_error(conn, {:error, :closed}),
do: conflict(conn, "job is closed")
defp lifecycle_error(conn, {:error, :not_open}),
do: conflict(conn, "job is not open for claims")
defp lifecycle_error(conn, {:error, :invalid_state}),
do: conflict(conn, "that transition is not valid from the task's current state")
defp lifecycle_error(conn, {:error, :active_work}),
do: conflict(conn, "active or submitted work must be resolved before cancellation")
defp lifecycle_error(conn, {:error, :capability_mismatch}) do
unprocessable(conn, %{provenance: ["does not satisfy this task's required capability"]})
end
defp lifecycle_error(conn, {:error, :document_required}) do
unprocessable(conn, %{
document: ["is required (Tarakan Review/Scan Format object with findings)"]
})
end
defp lifecycle_error(conn, {:error, :document_or_prose_required}) do
unprocessable(conn, %{
document: ["provide a Review Format document, or summary+evidence for legacy prose"]
})
end
defp lifecycle_error(conn, {:error, :document_not_allowed}) do
unprocessable(conn, %{
document: ["is not accepted for this request kind; use summary and evidence"]
})
end
defp lifecycle_error(conn, {:error, :verdict_required}) do
unprocessable(conn, %{
verdict: ["is required (confirmed or disputed) with notes ≥ 20 characters"]
})
end
defp lifecycle_error(conn, {:error, :verdict_notes_required}) do
unprocessable(conn, %{
notes: ["must be at least 20 characters (use notes or summary)"]
})
end
defp lifecycle_error(conn, {:error, :target_review_required}),
do: unprocessable(conn, %{target_review_id: ["is required for verify_findings"]})
defp lifecycle_error(conn, {:error, :target_review_missing}),
do: unprocessable(conn, %{target_review_id: ["does not exist"]})
defp lifecycle_error(conn, {:error, :target_review_mismatch}),
do: unprocessable(conn, %{target_review_id: ["must belong to the same repository"]})
defp lifecycle_error(conn, {:error, :conflict_of_interest}),
do: forbidden(conn, "review submitters cannot verify their own review")
defp lifecycle_error(conn, {:error, :invalid_document}) do
unprocessable(conn, %{document: ["must be a valid JSON Review/Scan Format object"]})
end
defp lifecycle_error(conn, {:error, :submission_limit}),
do: too_many_requests(conn, "daily review submission limit reached")
defp lifecycle_error(conn, {:error, :submission_rate_limited}),
do: too_many_requests(conn, "too many review submissions; try again shortly")
defp lifecycle_error(conn, {:error, :commit_not_found}),
do: unprocessable(conn, %{commit_sha: ["is not known for this repository"]})
defp lifecycle_error(conn, {:error, :commit_mismatch}),
do: unprocessable(conn, %{commit_sha: ["does not match the GitHub commit"]})
defp lifecycle_error(conn, {:error, reason})
when reason in [:rate_limited, :unavailable, :identity_changed],
do: service_unavailable(conn, "could not verify commit identity (#{reason})")
defp lifecycle_error(conn, {:error, :linked_review_restricted}),
do: conflict(conn, "linked review is restricted or missing; cannot close this request")
defp lifecycle_error(conn, {:error, %Ecto.Changeset{} = changeset}),
do: unprocessable(conn, changeset_errors(changeset))
defp lifecycle_error(conn, {:error, reason}),
do: forbidden(conn, "action denied: #{inspect(reason)}")
defp service_unavailable(conn, message) do
conn |> put_status(:service_unavailable) |> json(%{error: message})
end
defp not_found(conn, message) do
conn |> put_status(:not_found) |> json(%{error: message})
end
defp forbidden(conn, message) do
conn |> put_status(:forbidden) |> json(%{error: message})
end
defp conflict(conn, message) do
conn |> put_status(:conflict) |> json(%{error: message})
end
defp too_many_requests(conn, message) do
conn |> put_status(:too_many_requests) |> json(%{error: message})
end
defp unprocessable(conn, errors) do
conn |> put_status(:unprocessable_entity) |> json(%{errors: errors})
end
defp changeset_errors(changeset), do: TarakanWeb.ChangesetErrors.to_map(changeset)
end