Governare un agente dall’inizio alla fine
Registra un agente, gestisci un’approvazione umana, ispeziona la trace ed esporta Evidence sigillata tramite KLA Control Plane.
Questa guida usa un agente di triage dei reclami che può proporre un rimborso. Il flusso copre la registrazione dell’agente, un’esecuzione governata, l’approvazione umana, l’ispezione della trace e l’esportazione di Evidence.
Gli esempi usano la CLI KLA per le procedure generate del control plane e curl per le route REST Evidence. La CLI legge KLA_ACCESS_TOKEN. Host API e tenant sono espliciti, così gli stessi comandi possono essere eseguiti contro un tenant dev distribuito.
export KLA_API_URL='https://api.dev.kla.digital'
export KLA_ACCESS_TOKEN='<short-lived access token>'
export KLA_TENANT_ID='<tenant external ID>'
# Build the repository CLI while the package is private, then expose it in this shell.
corepack enable
pnpm install --frozen-lockfile
pnpm kla --help
KLA_REPO_ROOT="$(git rev-parse --show-toplevel)"
kla() { KLA_CLI_CWD="$PWD" pnpm --dir "$KLA_REPO_ROOT" --silent kla "$@"; }
kla auth test
kla api doctor
KLA_TENANT_ID configura l’indicazione di routing x-kla-tenant-id del contesto CLI. Il tenant claim verificato resta autorevole. Una richiesta che seleziona un altro tenant usa x-kla-tenant-external-id; il chiamante deve avere un’appartenenza attiva a quel tenant.
Questo percorso presuppone un tenant con una policy approvata e un workflow distribuito che possa creare un’approvazione in sospeso. Registrazione, binding della policy, approvazione del release, distribuzione ed esecuzione scrivono nel tenant. Usa un tenant sotto il tuo controllo e un secondo principal per l’approvazione maker-checker.
sequenceDiagram participant Dev as Developer participant Agent as Claims agent participant KLA as KLA Control Plane participant Desk as Decision Desk participant Room as Evidence Room Dev->>KLA: Register with agents.create Agent->>KLA: Start executions.execute KLA->>Desk: approvals.getPending returns a pending request Desk->>KLA: approvals.decide approves the request Dev->>KLA: Read traces.getTrace and traces.verify Dev->>Room: POST /v1/evidence/export
Passaggio 1: Registrare l’agente
agents.create è una mutation tRPC. Il comando CLI generato la invia tramite il trasporto tRPC autenticato /v1.
- Endpoint:
POST /v1/agents.create
Crea un manifest di registrazione nel formato accettato dall’Agent Registry:
cat > /tmp/claims-triage-registration.yaml <<'YAML'
name: Claims Triage Agent
flowId: claims_triage
description: Triages inbound claims and prepares refund recommendations for review.
model:
provider: openai
name: gpt-4o
pipeline:
- type: llm_generate
name: triage_claim
config:
template: Review the claim and recommend the next action with supporting facts.
owners:
- email: [email protected]
tools:
- lookup_claim
- process_refund
humanOversightRequired: true
YAML
Registralo:
AGENT_JSON=$(kla agent create --file /tmp/claims-triage-registration.yaml)
echo "$AGENT_JSON"
AGENT_ID=$(jq -r '.id // .agentId' <<<"$AGENT_JSON")
Conserva l’identificatore dell’agente restituito. Un agente appena registrato richiede un binding di policy approvato e distribuito prima che executions.execute possa avviare un’esecuzione governata. Il ciclo di vita CLI è:
POLICY_ID='<approved policy ID>'
kla agent policy bind "$AGENT_ID" \
--policy "$POLICY_ID" \
--environment sandbox \
--description 'Attach the refund approval policy.'
Il binding sandbox crea o aggiorna la bozza sandbox in sospeso dell’agente e il relativo workflow gestito. Non rende distribuibile un release di produzione. agents.deployVersion richiede che il manifest del release immutabile contenga un binding governedExecution di produzione.
Crea quel manifest esplicito del release di produzione. Un nuovo UUID dà al workflow di produzione gestito una propria identità; la prima versione del workflow è 1:
WORKFLOW_ID="$(node -e "console.log(require('node:crypto').randomUUID())")"
cp /tmp/claims-triage-registration.yaml /tmp/claims-triage.yaml
cat >> /tmp/claims-triage.yaml <<YAML
governedExecution:
policyId: ${POLICY_ID}
workflowId: ${WORKFLOW_ID}
workflowVersion: 1
environment: production
YAML
RELEASE_JSON=$(kla agent release create "$AGENT_ID" \
--manifest @/tmp/claims-triage.yaml \
--description 'Publish the claims triage release.' \
--justification 'The release adds the governed refund workflow.' \
--change-type minor)
echo "$RELEASE_JSON"
VERSION_ID=$(jq -r '.version.id // .versionId // .id' <<<"$RELEASE_JSON")
Il comando release accetta @/tmp/claims-triage.yaml come input strutturato da file. L’API valida UUID, versione del workflow, ID della policy e ambiente di produzione, poi salva il binding del workflow gestito prima di creare il release immutabile.
La credenziale del reviewer deve identificare un principal autenticato diverso. Il server applica il ruolo del tenant e le regole a due persone a ogni comando del ciclo di vita.
Usa lo stesso URL API e lo stesso tenant con un token reviewer di breve durata limitato al comando di approvazione:
export KLA_REVIEWER_ACCESS_TOKEN='<reviewer short-lived access token>'
KLA_ACCESS_TOKEN="$KLA_REVIEWER_ACCESS_TOKEN" \
kla agent release approve "$VERSION_ID" --yes
Dopo l’approvazione del release da parte del reviewer, ripristina la credenziale maker prima di distribuirlo:
export KLA_ACCESS_TOKEN='<maker short-lived access token>'
kla agent release deploy "$VERSION_ID" --yes
Passaggio 2: Avviare un’esecuzione governata
Esegui l’agente nella sandbox con un reclamo che dovrebbe raggiungere la policy di approvazione:
RUN_JSON=$(kla agent run "$AGENT_ID" \
--environment sandbox \
--input '{"claim_id":"clm_9921","requested_refund":1250.00}')
echo "$RUN_JSON"
EXECUTION_ID=$(jq -r '.executionId' <<<"$RUN_JSON")
kla api executions get --input "{\"id\":\"$EXECUTION_ID\"}"
executions.execute restituisce un executionId. Un esito di policy require_approval lascia l’esecuzione in attesa di un reviewer. Stato ed eventi dell’esecuzione sono disponibili tramite executions.get e executions.getEvents.
Passaggio 3: Trovare e approvare la richiesta in sospeso
approvals.getPending elenca le Decision Requests in sospeso per il tenant. Il filtro dell’esecuzione restituisce la richiesta di questa esecuzione:
- Endpoint:
GET /v1/approvals.getPending
# A 202 PENDING result queues approval creation asynchronously. Retry the lookup until the request
# is visible. Rate limits, 5xx responses, and network failures stay retryable for 60 seconds.
APPROVAL_ID=''
PENDING_JSON='{}'
POLL_DEADLINE=$((SECONDS + 60))
while (( SECONDS < POLL_DEADLINE )); do
PENDING_ATTEMPT_STATUS=0
PENDING_ATTEMPT_STDERR_FILE="$(mktemp)"
PENDING_ATTEMPT_OUTPUT=$(kla api approvals getPending \
--input "{\"executionId\":\"$EXECUTION_ID\",\"limit\":20}" 2>"$PENDING_ATTEMPT_STDERR_FILE") || \
PENDING_ATTEMPT_STATUS=$?
PENDING_ATTEMPT_ERROR="$(cat "$PENDING_ATTEMPT_STDERR_FILE")"
rm -f "$PENDING_ATTEMPT_STDERR_FILE"
if [[ -n "$PENDING_ATTEMPT_ERROR" ]]; then
printf '%s\n' "$PENDING_ATTEMPT_ERROR" >&2
fi
if (( PENDING_ATTEMPT_STATUS != 0 )); then
if printf '%s\n' "$PENDING_ATTEMPT_ERROR" | grep -Eqi \
'(^|[^0-9])(429|5[0-9][0-9])([^0-9]|$)|network|timed out|timeout|ECONNRESET|ECONNREFUSED|ENOTFOUND|fetch failed|socket hang up|temporarily unavailable'; then
echo 'Transient approvals.getPending failure; retrying until the 60-second deadline.' >&2
PENDING_JSON="$PENDING_ATTEMPT_OUTPUT"
sleep 2
continue
fi
echo 'approvals.getPending returned a non-retryable authorization or schema error.' >&2
if [[ -n "$PENDING_ATTEMPT_OUTPUT" ]]; then
printf '%s\n' "$PENDING_ATTEMPT_OUTPUT" >&2
fi
exit 1
fi
PENDING_JSON="$PENDING_ATTEMPT_OUTPUT"
if ! APPROVAL_ID=$(jq -r '.approvals[0].approvalId // empty' <<<"$PENDING_JSON"); then
echo 'approvals.getPending returned invalid JSON.' >&2
exit 1
fi
if [[ -n "$APPROVAL_ID" ]]; then
break
fi
sleep 2
done
if [[ -z "$APPROVAL_ID" ]]; then
echo "No pending approval appeared for $EXECUTION_ID within 60 seconds." >&2
echo "$PENDING_JSON" >&2
exit 1
fi
echo "$PENDING_JSON"
Il reviewer invia approvals.decide con l’identificatore dell’approvazione, una decisione e una motivazione. L’input accetta approve, reject o escalate. Questo esempio usa la seconda credenziale reviewer:
- Endpoint:
POST /v1/approvals.decide
cat > /tmp/approval-decision.json <<JSON
{
"approvalId": "${APPROVAL_ID}",
"decision": "approve",
"decisionAcknowledged": true,
"reason": "Claim documentation reviewed and refund authorized."
}
JSON
KLA_ACCESS_TOKEN="$KLA_REVIEWER_ACCESS_TOKEN" \
kla api approvals decide --input @/tmp/approval-decision.json
# The decision can return before the worker and pending ledger finish. Keep polling the execution
# until it reaches a terminal state. A 202/PENDING response remains expected during this window.
export KLA_ACCESS_TOKEN='<maker short-lived access token>'
EXECUTION_JSON='{}'
EXECUTION_STATUS=''
EXECUTION_STATUS_LOWER=''
EXECUTION_DEADLINE=$((SECONDS + 120))
while (( SECONDS < EXECUTION_DEADLINE )); do
EXECUTION_ATTEMPT_STATUS=0
EXECUTION_ATTEMPT_STDERR_FILE="$(mktemp)"
EXECUTION_ATTEMPT_OUTPUT=$(kla api executions get \
--input "{\"id\":\"$EXECUTION_ID\"}" 2>"$EXECUTION_ATTEMPT_STDERR_FILE") || \
EXECUTION_ATTEMPT_STATUS=$?
EXECUTION_ATTEMPT_ERROR="$(cat "$EXECUTION_ATTEMPT_STDERR_FILE")"
rm -f "$EXECUTION_ATTEMPT_STDERR_FILE"
if [[ -n "$EXECUTION_ATTEMPT_ERROR" ]]; then
printf '%s\n' "$EXECUTION_ATTEMPT_ERROR" >&2
fi
if (( EXECUTION_ATTEMPT_STATUS != 0 )); then
if printf '%s\n' "$EXECUTION_ATTEMPT_ERROR" | grep -Eqi \
'(^|[^0-9])(202|429|5[0-9][0-9])([^0-9]|$)|PENDING|network|timed out|timeout|ECONNRESET|ECONNREFUSED|ENOTFOUND|fetch failed|socket hang up|temporarily unavailable'; then
echo 'Transient execution status response; retrying until the 120-second deadline.' >&2
sleep 2
continue
fi
echo 'executions.get failed while waiting for the approved run.' >&2
if [[ -n "$EXECUTION_ATTEMPT_OUTPUT" ]]; then
printf '%s\n' "$EXECUTION_ATTEMPT_OUTPUT" >&2
fi
exit 1
fi
EXECUTION_JSON="$EXECUTION_ATTEMPT_OUTPUT"
if ! EXECUTION_STATUS=$(jq -r '.status // empty' <<<"$EXECUTION_JSON"); then
echo 'executions.get returned invalid JSON.' >&2
exit 1
fi
EXECUTION_STATUS_LOWER=$(printf '%s' "$EXECUTION_STATUS" | tr '[:upper:]' '[:lower:]')
case "$EXECUTION_STATUS_LOWER" in
completed|failed|blocked|cancelled|timeout|stale)
break
;;
pending|running|gated|waiting_for_input)
sleep 2
;;
'')
echo 'executions.get returned no status.' >&2
echo "$EXECUTION_JSON" >&2
exit 1
;;
*)
echo "executions.get returned an unknown status: $EXECUTION_STATUS" >&2
echo "$EXECUTION_JSON" >&2
exit 1
;;
esac
done
if [[ ! "$EXECUTION_STATUS_LOWER" =~ ^(completed|failed|blocked|cancelled|timeout|stale)$ ]]; then
echo "Execution $EXECUTION_ID did not reach a terminal state within 120 seconds." >&2
echo "$EXECUTION_JSON" >&2
exit 1
fi
if [[ "$EXECUTION_STATUS_LOWER" != 'completed' ]]; then
echo "Execution $EXECUTION_ID ended with status $EXECUTION_STATUS; trace and evidence checks require completed status." >&2
echo "$EXECUTION_JSON" >&2
exit 1
fi
echo "$EXECUTION_JSON"
L’esecuzione riprende secondo il workflow distribuito. Un rifiuto lascia ineseguita l’azione conseguente.
Passaggio 4: Ispezionare e verificare la trace
Il worker può terminare l’esecuzione prima che sia visibile la proiezione del ledger di audit. Leggi la cronologia degli eventi, attendi la voce di audit dell’approvazione e usa gli identificatori restituiti per le chiamate di trace e proof.
- Endpoint:
GET /v1/traces.getTrace - Endpoint:
GET /v1/traces.verify - Endpoint:
GET /v1/audit.listByResource
EVENTS_OUTPUT=$(kla api executions getEvents \
--input "{\"id\":\"$EXECUTION_ID\",\"limit\":200}")
if ! jq -e 'type == "object" and (.events | type == "array")' <<<"$EVENTS_OUTPUT" >/dev/null; then
echo 'executions.getEvents returned invalid JSON or no event list.' >&2
echo "$EVENTS_OUTPUT" >&2
exit 1
fi
EVENTS_JSON="$EVENTS_OUTPUT"
TRACE_ID=$(jq -r --argjson events "$EVENTS_JSON" '
[
.traceId,
.trace_id,
($events.events[]? | .traceId, .trace_id, .metadata?.traceId, .metadata?.trace_id, .data?.traceId, .data?.trace_id)
]
| map(select(type == "string" and length > 0))
| first // empty
' <<<"$EXECUTION_JSON")
if [[ -z "$TRACE_ID" ]]; then
echo "No trace identifier was returned for completed execution $EXECUTION_ID." >&2
echo "$EVENTS_JSON" >&2
exit 1
fi
# Approval decisions and requests receive audit IDs asynchronously. Poll both the approval detail
# and audit projection so the proof call uses a materialized ID from this run.
AUDIT_ID=''
APPROVAL_DETAIL_JSON='{}'
AUDIT_JSON='{}'
LEDGER_DEADLINE=$((SECONDS + 60))
while (( SECONDS < LEDGER_DEADLINE )); do
DETAIL_ATTEMPT_STATUS=0
DETAIL_ATTEMPT_STDERR_FILE="$(mktemp)"
DETAIL_ATTEMPT_OUTPUT=$(kla api approvals getDetail \
--input "{\"approvalId\":\"$APPROVAL_ID\"}" 2>"$DETAIL_ATTEMPT_STDERR_FILE") || \
DETAIL_ATTEMPT_STATUS=$?
DETAIL_ATTEMPT_ERROR="$(cat "$DETAIL_ATTEMPT_STDERR_FILE")"
rm -f "$DETAIL_ATTEMPT_STDERR_FILE"
if [[ -n "$DETAIL_ATTEMPT_ERROR" ]]; then
printf '%s\n' "$DETAIL_ATTEMPT_ERROR" >&2
fi
if (( DETAIL_ATTEMPT_STATUS != 0 )); then
if printf '%s\n' "$DETAIL_ATTEMPT_ERROR" | grep -Eqi \
'(^|[^0-9])(429|5[0-9][0-9])([^0-9]|$)|network|timed out|timeout|ECONNRESET|ECONNREFUSED|ENOTFOUND|fetch failed|socket hang up|temporarily unavailable'; then
echo 'Transient approval detail response; retrying ledger lookup.' >&2
sleep 2
continue
fi
echo 'approvals.getDetail failed while waiting for its audit identifier.' >&2
if [[ -n "$DETAIL_ATTEMPT_OUTPUT" ]]; then
printf '%s\n' "$DETAIL_ATTEMPT_OUTPUT" >&2
fi
exit 1
fi
APPROVAL_DETAIL_JSON="$DETAIL_ATTEMPT_OUTPUT"
if ! jq -e 'type == "object"' <<<"$APPROVAL_DETAIL_JSON" >/dev/null; then
echo 'approvals.getDetail returned invalid JSON.' >&2
echo "$APPROVAL_DETAIL_JSON" >&2
exit 1
fi
AUDIT_ID=$(jq -r '
[
.decisionReceipt.auditEventId,
.metadata.decisionReceipt.auditEventId,
.metadata.requestAuditEventId
]
| map(select(type == "string" and length > 0))
| first // empty
' <<<"$APPROVAL_DETAIL_JSON")
if [[ -n "$AUDIT_ID" ]]; then
break
fi
AUDIT_ATTEMPT_STATUS=0
AUDIT_ATTEMPT_STDERR_FILE="$(mktemp)"
AUDIT_ATTEMPT_OUTPUT=$(kla api audit listByResource \
--input "{\"resource\":\"approval\",\"resourceId\":\"$APPROVAL_ID\",\"limit\":100,\"includeTotal\":true}" 2>"$AUDIT_ATTEMPT_STDERR_FILE") || \
AUDIT_ATTEMPT_STATUS=$?
AUDIT_ATTEMPT_ERROR="$(cat "$AUDIT_ATTEMPT_STDERR_FILE")"
rm -f "$AUDIT_ATTEMPT_STDERR_FILE"
if [[ -n "$AUDIT_ATTEMPT_ERROR" ]]; then
printf '%s\n' "$AUDIT_ATTEMPT_ERROR" >&2
fi
if (( AUDIT_ATTEMPT_STATUS != 0 )); then
if printf '%s\n' "$AUDIT_ATTEMPT_ERROR" | grep -Eqi \
'(^|[^0-9])(429|5[0-9][0-9])([^0-9]|$)|network|timed out|timeout|ECONNRESET|ECONNREFUSED|ENOTFOUND|fetch failed|socket hang up|temporarily unavailable'; then
echo 'Transient audit projection response; retrying ledger lookup.' >&2
sleep 2
continue
fi
echo 'audit.listByResource failed while waiting for its audit identifier.' >&2
if [[ -n "$AUDIT_ATTEMPT_OUTPUT" ]]; then
printf '%s\n' "$AUDIT_ATTEMPT_OUTPUT" >&2
fi
exit 1
fi
AUDIT_JSON="$AUDIT_ATTEMPT_OUTPUT"
if ! jq -e 'type == "object" and (.entries | type == "array")' <<<"$AUDIT_JSON" >/dev/null; then
echo 'audit.listByResource returned invalid JSON.' >&2
echo "$AUDIT_JSON" >&2
exit 1
fi
AUDIT_ID=$(jq -r --arg approval_id "$APPROVAL_ID" '
[
.entries[]?
| select(
(.resourceId == $approval_id) or
(.resource_id == $approval_id) or
(.details?.approvalId == $approval_id) or
(.details?.approval_id == $approval_id)
)
| (.id // .eventId // .event_id)
]
| map(select(type == "string" and length > 0))
| first // empty
' <<<"$AUDIT_JSON")
if [[ -n "$AUDIT_ID" ]]; then
break
fi
sleep 2
done
if [[ -z "$AUDIT_ID" ]]; then
echo "No materialized audit identifier appeared for approval $APPROVAL_ID within 60 seconds." >&2
echo "$APPROVAL_DETAIL_JSON" >&2
echo "$AUDIT_JSON" >&2
exit 1
fi
RESULT_JSON='{}'
MERKLE_ROOT=''
RESULT_DEADLINE=$((SECONDS + 60))
while (( SECONDS < RESULT_DEADLINE )); do
RESULT_ATTEMPT_STATUS=0
RESULT_ATTEMPT_STDERR_FILE="$(mktemp)"
RESULT_ATTEMPT_OUTPUT=$(kla api executions result \
--input "{\"id\":\"$EXECUTION_ID\"}" 2>"$RESULT_ATTEMPT_STDERR_FILE") || \
RESULT_ATTEMPT_STATUS=$?
RESULT_ATTEMPT_ERROR="$(cat "$RESULT_ATTEMPT_STDERR_FILE")"
rm -f "$RESULT_ATTEMPT_STDERR_FILE"
if [[ -n "$RESULT_ATTEMPT_ERROR" ]]; then
printf '%s\n' "$RESULT_ATTEMPT_ERROR" >&2
fi
if (( RESULT_ATTEMPT_STATUS != 0 )); then
if printf '%s\n' "$RESULT_ATTEMPT_ERROR" | grep -Eqi \
'(^|[^0-9])(202|429|5[0-9][0-9])([^0-9]|$)|PENDING|network|timed out|timeout|ECONNRESET|ECONNREFUSED|ENOTFOUND|fetch failed|socket hang up|temporarily unavailable'; then
echo 'Transient execution result response; retrying evidence materialization.' >&2
sleep 2
continue
fi
echo 'executions.result failed while waiting for the evidence anchor.' >&2
if [[ -n "$RESULT_ATTEMPT_OUTPUT" ]]; then
printf '%s\n' "$RESULT_ATTEMPT_OUTPUT" >&2
fi
exit 1
fi
RESULT_JSON="$RESULT_ATTEMPT_OUTPUT"
if ! MERKLE_ROOT=$(jq -r '.evidence.merkleRoot // empty' <<<"$RESULT_JSON"); then
echo 'executions.result returned invalid JSON.' >&2
echo "$RESULT_JSON" >&2
exit 1
fi
if [[ -n "$MERKLE_ROOT" ]]; then
break
fi
sleep 2
done
if [[ -z "$MERKLE_ROOT" ]]; then
echo "The evidence anchor for completed execution $EXECUTION_ID was not materialized within 60 seconds." >&2
echo "$RESULT_JSON" >&2
exit 1
fi
TRACE_INPUT=$(jq -cn \
--arg trace_id "$TRACE_ID" \
--arg tenant_id "$KLA_TENANT_ID" \
'{traceId: $trace_id, tenantId: $tenant_id, includeSensitiveData: false}')
kla api traces getTrace --input "$TRACE_INPUT"
PROOF_INPUT=$(jq -cn --arg tenant "$KLA_TENANT_ID" --arg audit "$AUDIT_ID" \
'{tenant: $tenant, auditId: $audit}')
PROOF_JSON=$(kla api traces verify --input "$PROOF_INPUT")
PROOF_VALID=$(jq -r '.valid // false' <<<"$PROOF_JSON")
PROOF_NONCE=$(jq -r '.nonce // empty' <<<"$PROOF_JSON")
PROOF_TX_ID=$(jq -r '.ledgerAttestation.txId // empty' <<<"$PROOF_JSON")
if [[ "$PROOF_VALID" != 'true' || -z "$PROOF_NONCE" ]]; then
echo "traces.verify did not validate audit $AUDIT_ID." >&2
echo "$PROOF_JSON" >&2
exit 1
fi
echo "$PROOF_JSON"
echo "Trace: $TRACE_ID"
echo "Audit: $AUDIT_ID"
echo "Evidence Merkle root: $MERKLE_ROOT"
echo "Proof nonce: $PROOF_NONCE"
echo "Proof ledger transaction: ${PROOF_TX_ID:-unavailable}"
traces.getTrace legge gli span della trace restituita e mantiene i dati sensibili mascherati per impostazione predefinita. traces.verify genera e verifica una proof per l’identificatore di audit restituito. La risposta della proof include un nonce e, quando disponibile, l’identificatore di transazione dell’attestazione del ledger. Entrambe le chiamate richiedono i permessi trace corrispondenti.
Passaggio 5: Esportare Evidence sigillata
L’esportazione Evidence dell’API è una route REST. Esporta tramite l’identificatore dell’esecuzione, poi leggi il manifest e scarica l’archivio verificato:
- Endpoint:
POST /v1/evidence/export
EXPORT_JSON=$(curl -sS -X POST "$KLA_API_URL/v1/evidence/export" \
-H "Authorization: Bearer $KLA_ACCESS_TOKEN" \
-H "x-kla-tenant-external-id: $KLA_TENANT_ID" \
-H 'Content-Type: application/json' \
-d "{\"runId\":\"$EXECUTION_ID\",\"format\":\"json\",\"includeProofs\":true}")
echo "$EXPORT_JSON"
EXPORT_ID=$(jq -r '.exportId' <<<"$EXPORT_JSON")
La risposta include un exportId, un manifest e un URL di download. L’API espone manifest e archivio tramite queste route:
- Endpoint:
GET /v1/evidence/exports/$EXPORT_ID - Endpoint:
GET /v1/evidence/exports/$EXPORT_ID/download
curl -sS "$KLA_API_URL/v1/evidence/exports/$EXPORT_ID" \
-H "Authorization: Bearer $KLA_ACCESS_TOKEN" \
-H "x-kla-tenant-external-id: $KLA_TENANT_ID" | jq
curl -sS -f "$KLA_API_URL/v1/evidence/exports/$EXPORT_ID/download" \
-H "Authorization: Bearer $KLA_ACCESS_TOKEN" \
-H "x-kla-tenant-external-id: $KLA_TENANT_ID" \
-o "./evidence-$EXPORT_ID.zip"
La CLI incapsula le stesse due route:
kla evidence export --days 7 --out ./evidence
kla evidence verify --bundle "./evidence-$EXPORT_ID.zip" --out ./evidence-report
kla evidence verify funziona sul bundle scaricato e non effettua richieste di rete. Il report registra la specifica del bundle, il digest del manifest, la radice Merkle e il risultato della verifica.
Il ciclo completo è: registrare con agents.create, avviare executions.execute, risolvere la richiesta in sospeso tramite approvals.getPending e approvals.decide, ispezionare e verificare con traces.*, quindi esportare tramite /v1/evidence/*.
