Gouverner un agent de bout en bout
Enregistrer un agent, traiter une approbation humaine, inspecter sa trace et exporter des preuves scellées via KLA Control Plane.
Ce guide utilise un agent de triage des demandes capable de recommander un remboursement. Le parcours couvre l'enregistrement de l'agent, une exécution gouvernée, l'approbation humaine, l'inspection de la trace et l'export Evidence.
Les exemples utilisent la CLI KLA pour les procédures générées du control plane et curl pour les routes REST Evidence. La CLI lit KLA_ACCESS_TOKEN. L'hôte API et le tenant sont explicites afin que les mêmes commandes puissent cibler un tenant dev déployé.
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 configure l'indication de routage x-kla-tenant-id du contexte CLI. Le claim de tenant vérifié reste l'autorité. Une requête qui sélectionne un autre tenant utilise x-kla-tenant-external-id ; l'appelant doit y posséder une adhésion active.
Ce parcours suppose un tenant doté d'une politique approuvée et d'un workflow déployé pouvant créer une approbation en attente. L'enregistrement, la liaison de politique, l'approbation du release, le déploiement et l'exécution écrivent dans le tenant. Utilisez un tenant sous votre contrôle et un second principal pour l'approbation 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
Étape 1 : Enregistrer l'agent
agents.create est une mutation tRPC. La commande CLI générée l'envoie via le transport tRPC authentifié /v1.
- Point de terminaison:
POST /v1/agents.create
Créez un manifeste d'enregistrement au format accepté par l'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
Enregistrez-le :
AGENT_JSON=$(kla agent create --file /tmp/claims-triage-registration.yaml)
echo "$AGENT_JSON"
AGENT_ID=$(jq -r '.id // .agentId' <<<"$AGENT_JSON")
Conservez l'identifiant d'agent renvoyé. Un agent nouvellement enregistré a besoin d'une liaison de politique approuvée et déployée avant qu'executions.execute puisse démarrer une exécution gouvernée. Le cycle de vie CLI est le suivant :
POLICY_ID='<approved policy ID>'
kla agent policy bind "$AGENT_ID" \
--policy "$POLICY_ID" \
--environment sandbox \
--description 'Attach the refund approval policy.'
La liaison sandbox crée ou met à jour le brouillon sandbox en attente de l'agent et son workflow géré. Elle ne rend pas un release de production déployable. agents.deployVersion exige que le manifeste de release immuable porte une liaison governedExecution de production.
Créez ce manifeste explicite de release de production. Un nouvel UUID donne sa propre identité au workflow de production géré ; la première version du workflow est 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")
La commande de release accepte @/tmp/claims-triage.yaml comme entrée de fichier structurée. L'API valide l'UUID, la version du workflow, l'identifiant de politique et l'environnement de production, puis persiste la liaison du workflow géré avant de créer le release immuable.
La credential du reviewer doit identifier un autre principal authentifié. Le serveur applique le rôle du tenant et les règles à deux personnes à chaque commande du cycle de vie.
Utilisez la même URL API et le même tenant avec un jeton reviewer de courte durée limité à la commande d'approbation :
export KLA_REVIEWER_ACCESS_TOKEN='<reviewer short-lived access token>'
KLA_ACCESS_TOKEN="$KLA_REVIEWER_ACCESS_TOKEN" \
kla agent release approve "$VERSION_ID" --yes
Après l'approbation du release par le reviewer, rétablissez la credential maker avant de le déployer :
export KLA_ACCESS_TOKEN='<maker short-lived access token>'
kla agent release deploy "$VERSION_ID" --yes
Étape 2 : Démarrer une exécution gouvernée
Exécutez l'agent dans la sandbox avec une demande qui doit atteindre la politique d'approbation :
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 renvoie un executionId. Le résultat de politique require_approval laisse l'exécution en attente d'un reviewer. Le statut et l'historique des événements sont disponibles via executions.get et executions.getEvents.
Étape 3 : Trouver et approuver la demande en attente
approvals.getPending liste les Decision Requests en attente du tenant. Le filtre d'exécution renvoie la demande de cette exécution :
- Point de terminaison:
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"
Le reviewer envoie approvals.decide avec l'identifiant d'approbation, une décision et un motif. L'entrée accepte approve, reject ou escalate. Cet exemple utilise la seconde credential reviewer :
- Point de terminaison:
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'exécution reprend selon le workflow déployé. Un rejet laisse l'action conséquente inexécutée.
Étape 4 : Inspecter et vérifier la trace
Le worker peut terminer l'exécution avant que la projection du registre d'audit soit visible. Lisez l'historique des événements, attendez l'entrée d'audit de l'approbation et utilisez les identifiants renvoyés pour les appels de trace et de preuve.
- Point de terminaison:
GET /v1/traces.getTrace - Point de terminaison:
GET /v1/traces.verify - Point de terminaison:
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 lit les spans de la trace renvoyée et masque les données sensibles par défaut. traces.verify génère et vérifie une preuve pour l'identifiant d'audit renvoyé. La réponse de preuve contient un nonce et, lorsque l'attestation du registre est disponible, son identifiant de transaction. Les deux appels exigent les permissions de trace correspondantes.
Étape 5 : Exporter les preuves scellées
L'export Evidence de l'API est une route REST. Exportez par identifiant d'exécution, puis lisez le manifeste et téléchargez l'archive vérifiée :
- Point de terminaison:
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 réponse contient un exportId, un manifeste et une URL de téléchargement. L'API expose le manifeste et l'archive via ces routes :
- Point de terminaison:
GET /v1/evidence/exports/$EXPORT_ID - Point de terminaison:
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 enveloppe ces deux mêmes routes :
kla evidence export --days 7 --out ./evidence
kla evidence verify --bundle "./evidence-$EXPORT_ID.zip" --out ./evidence-report
kla evidence verify fonctionne sur le bundle téléchargé et n'effectue aucune requête réseau. Son rapport consigne la spécification du bundle, le digest du manifeste, la racine de Merkle et le résultat de la vérification.
Le parcours terminé est le suivant : enregistrer avec agents.create, démarrer executions.execute, résoudre la demande en attente via approvals.getPending et approvals.decide, inspecter et vérifier avec traces.*, puis exporter via /v1/evidence/*.
