# Cum funcționează (/ro/docs/resolve/how-it-works)

Ciclul de viață al unui job Resolve de la triaj la atestare, ce dovedește de fapt documentul public de dovadă și ce atinge și ce nu atinge runner-ul izolat.

Un job Resolve este o mică mașină de stări cu o regulă dură la final: **taxa nu poate exista fără atestare**. Tot ce urmează este cum ajunge un job acolo — sau nu.

## Ciclul de viață al job-ului [#ciclul-de-viață-al-job-ului]

```text
 POST /v1/resolve/jobs
        │
        ▼
     triage ──── decline ───▶ declined        (0 $, final)
        │
   quote t7|t19|t49
        │
        ├── nivel gratuit rămas ──▶ accepted   (auto)
        │
        ▼
     quoted ── POST …/accept ──▶ accepted
                                    │
                                    ▼
                                 running ──▶ resolved   (atestare + taxă)
                                    │
                                    ├──────▶ failed     (0 $ — niciun fix verificat în buget)
                                    └──────▶ error      (0 $ — infrastructură; retrimiți fără grijă)
```

| Status | Semnificație | Facturat? |
| --- | --- | --- |
| `triage` | Tranzitoriu — issue-ul este citit. Nu îl vezi niciodată într-un răspuns. | — |
| `declined` | Triajul a considerat un fix verificat improbabil (stack greșit, fără reproducere clară, are nevoie de credențiale…). `decline_reason` spune de ce. | Nu |
| `quoted` | S-a stabilit un preț fix (`tier`, `quote_micro_usd`). Nu rulează nimic până nu accepți. | Încă nu |
| `accepted` | Ai acceptat (sau s-a aplicat nivelul gratuit). În coadă pentru un runner. | Încă nu |
| `running` | Sandbox-ul clonează, reproduce, repară, verifică. | Încă nu |
| `resolved` | Reproducerea a trecut de la eșec la succes, suita de regresie e verde, atestarea scrisă. `attestation_url` este setat. | **Da** — cotația |
| `failed` | Niciun candidat nu a trecut ambele porți în bugetul de ture și timp. `fail_reason` explică. | Nu |
| `error` | Problemă de mediu sau infrastructură (clonă defectă, sandbox indisponibil, worker pierdut). Retrimiți liber. | Nu |

Statusurile merg doar înainte; `resolved`, `failed`, `error` și `declined` sunt terminale. `GET /v1/resolve/jobs/{id}` este limitat la utilizatorul cheii — id-ul de job al altui utilizator este `404`.

### Triaj [#triaj]

Rulează **inline în `POST`-ul tău**, pe modelul `codai`, și răspunde cu exact o linie: `QUOTE t7|t19|t49 <motiv>` sau `DECLINE <motiv>`. Sensul aproximativ al pragurilor: **t7** — mic, bine delimitat (un fișier, reproducere clară); **t19** — moderat (mai multe fișiere, are nevoie de investigație); **t49** — greu dar plauzibil (subtil, transversal). Dacă triajul în sine este indisponibil, job-ul este refuzat cu *triage unavailable, please retry* — Resolve nu cotează niciodată orb.

### Nivelul gratuit [#nivelul-gratuit]

Fiecare utilizator primește **3** remedieri verificate gratuite. Revendicarea este atomică: când sosește un job cotabil și mai ai un loc gratuit, job-ul este creat cu `tier: "free"`, `quote_micro_usd: 0`, `status: "accepted"` și pornește imediat. `free_tier_remaining` din răspuns îți spune câte mai rămân după acesta. Un job gratuit care se termină `failed` sau `error` **dă locul înapoi**.

### Acceptare [#acceptare]

Doar job-urile `quoted` acceptă; orice altceva este `409 job is <status>, only quoted jobs can be accepted`. Acceptarea este momentul în care ești de acord cu cotația — dar plătești în continuare doar dacă se rezolvă.

### Rularea [#rularea]

<Steps>
<Step>

### Reproduce întâi [#reproduce-întâi]

Runner-ul clonează repo-ul la `HEAD` (înregistrat ca `base_commit`). Dacă ai furnizat `test_command`, îl rulează: trebuie să **eșueze** pe commit-ul de bază, altfel job-ul se termină cu *supplied test_command already passes on HEAD — nothing to fix*. Fără comandă, agentul **scrie un test de reproducere** — un singur fișier pytest / vitest / jest — și confirmă că eșuează. Acel output de eșec devine `test_output_before`.

</Step>
<Step>

### Repară [#repară]

Agentul primește issue-ul, repo-ul și testul care eșuează, și editează cod într-o buclă limitată la cel mult **4 ture** per candidat (editări SEARCH/REPLACE plus citiri de fișiere). Pragurile plătite se ramifică în până la **4 candidați** (primul la temperatură 0, restul la 0,8) și se opresc la primul care trece; nivelul gratuit rulează unul singur. Un candidat trece doar dacă reproducerea trece acum **și** suita proprie a repo-ului trece în continuare.

</Step>
<Step>

### Verifică [#verifică]

Verificarea nu eșuează niciodată permisiv. Două comenzi rulează în sandbox pe `base_commit + patch`: comanda FAIL_TO_PASS (`test_command`) și o comandă de regresie PASS_TO_PASS (`regression_command`) — suita completă a repo-ului cu fișierul de reproducere exclus. Un mediu defect, timeout-urile sau un runner dispărut sunt `error`, niciodată `resolved`.

</Step>
<Step>

### Atestă, apoi taxează [#atestă-apoi-taxează]

Se scrie rândul de atestare, apoi — și doar atunci — rândul `resolve_charges` pentru suma cotată. Dacă ceva eșuează între cele două, ai o dovadă și nicio plată, niciodată invers.

</Step>
</Steps>

## Ce dovedește atestarea [#ce-dovedește-atestarea]

`GET /v1/resolve/attestations/{id}` nu are nevoie de **nicio autentificare**: UUID-ul este capabilitatea. Oricine are linkul poate audita fix-ul. Returnează:

```json
{
  "id": "a45764d8-…",
  "kind": "codai.resolve.attestation.v0",
  "repo_url": "https://github.com/owner/repo",
  "base_commit": "3f9c…",
  "patch": "diff --git a/src/parse.ts b/src/parse.ts\n…",
  "repro_test": { "path": "tests/test_issue_42.py", "content": "…" },
  "test_command": "python -m pytest -q tests/test_issue_42.py",
  "regression_command": "python -m pytest -q --deselect tests/test_issue_42.py --ignore=tests/test_issue_42.py",
  "test_output_before": "… 1 failed …",
  "test_output_after": "… 1 passed …",
  "regression_output": "… 212 passed …",
  "runner_image_digest": "sha256:…",
  "started_at": "2026-09-23T09:12:04.118Z",
  "verified_at": "2026-09-23T09:19:41.402Z"
}
```

| Câmp | Ce îți permite să verifici |
| --- | --- |
| `repo_url` + `base_commit` | Arborele exact pe care a fost verificat fix-ul. `git checkout <base_commit>` și reproduci singur. |
| `patch` | Diff-ul unificat complet. Nimic în afara lui nu a fost schimbat. |
| `repro_test` | Testul care codifică bug-ul (`null` când ai furnizat `test_command`). Îl citești ca să judeci dacă surprinde issue-ul. |
| `test_command` / `test_output_before` | Comanda FAIL_TO_PASS și output-ul ei pe commit-ul de bază — „înainte”. |
| `test_output_after` | Aceeași comandă după patch — „după”. |
| `regression_command` / `regression_output` | Suita proprie a repo-ului, în continuare verde. |
| `runner_image_digest` | Imaginea de container în care au rulat testele, fixată prin digest. |
| `started_at` / `verified_at` | Fereastra de timp a încercării. |

Ce **nu** dovedește: că testul de reproducere este testul *potrivit* pentru issue, sau că fix-ul este cel pe care l-ai fi scris tu. Pentru asta sunt acolo diff-ul și conținutul testului — le citești ca pe un PR, cu avantajul că dovada de execuție este atașată.

Atestarea este imutabilă și rămâne publică; același URL este cel pe care acțiunea GitHub îl leagă din PR.

## Atestări semnate [#atestări-semnate]

Din 2026-09-27, fiecare atestare nouă conține și `signature`: un plic [DSSE v1](https://github.com/secure-systems-lab/dsse) semnat cu o cheie ECDSA P-256 / SHA-256 ținută în Cloud KMS. Nu trebuie să ai încredere în JSON-ul pe care îl servim — îl verifici cu semnătura.

**Ce se semnează** este o declarație canonică (JSON RFC 8785, `payloadType` `application/vnd.codai.resolve.attestation+json;v=1`) care leagă fiecare câmp de mai sus, iar conținuturile lungi prin sha256 (hex cu litere mici al octeților UTF-8):

```json
{
   "kind": "codai.resolve.attestation.v1",
   "attestation_id": "a45764d8-…",
   "repo_url": "https://github.com/owner/repo",
   "base_commit": "3f9c…",
   "patch_sha256": "…",
   "repro_test": { "path": "tests/test_issue_42.py", "sha256": "…" },
   "test_command": "python -m pytest -q tests/test_issue_42.py",
   "regression_command": "python -m pytest -q …",
   "test_output_before_sha256": "…",
   "test_output_after_sha256": "…",
   "regression_output_sha256": "…",
   "runner_image_digest": "sha256:…",
   "started_at": "2026-09-23T09:12:04.118Z",
   "verified_at": "2026-09-23T09:19:41.402Z"
}
```

`repro_test` este `null` când ai furnizat `test_command`; dacă nu s-a rulat suita de regresie, ambele câmpuri de regresie sunt `null`. Plicul este `{ payloadType, payload: base64(declarație), signatures: [{ keyid, sig }] }`, iar `sig` este o semnătură DER (base64) peste PAE-ul DSSE: `"DSSEv1 " + len(payloadType) + " " + payloadType + " " + len(payload) + " " + payload` (lungimi în octeți).

**Unde e cheia:** `GET /v1/resolve/keys` (public, CORS deschis, cache 1 h) întoarce `{ keys: [{ keyid, algorithm: "ECDSA_P256_SHA256", payload_type, encoding, public_key_pem }] }`. Fixează PEM-ul local dacă vrei o verificare care nu depinde deloc de serverele noastre.

O atestare validă trece **ambele** verificări: semnătura se verifică cu o cheie publicată **și** declarația decodată este egală cu declarația recalculată din câmpurile atestării. O semnătură validă peste alt conținut este un eșec (`STATEMENT_MISMATCH`).

**Cum verifici:**

```sh
codaid resolve verify a45764d8-…        # sau URL-ul complet al atestării; nu e nevoie de cheie API
# VERIFIED (key projects/…/cryptoKeyVersions/1)   — cod de ieșire 0
# NOT VERIFIED: STATEMENT_MISMATCH                — cod de ieșire 1
```

```ts
const proof = await codai.resolve.attestation(job.attestation_url!);
const r = await codai.resolve.verifyAttestation(proof); // { keys } ca să fixezi cheile
// { ok: true, keyid } | { ok: false, reason: 'UNSIGNED' | 'BAD_SIGNATURE' | 'STATEMENT_MISMATCH' | 'BAD_PAYLOAD_TYPE' }
```

```python
proof = client.resolve.attestation(job["attestation_url"])
r = client.resolve.verify_attestation(proof)  # pasul de semnătură cere `pip install cryptography`
```

Atestările create **înainte de 2026-09-27 nu sunt semnate** (`signature: null`) și ies `UNSIGNED` la verificare; rămân dovezi de execuție valide, doar că nu sunt legate criptografic.

## Webhook-uri [#webhook-uri]

În loc să interoghezi `GET /v1/resolve/jobs/{id}`, trimite `callback_url` la intake — un URL `https` pe un host public (fără credențiale în URL, fără localhost / adrese private / link-local / de metadate, maximum 2000 de caractere). Răspunsul `201` cu cotația sau `200` cu refuzul conține atunci `callback_secret` (`whsec_…`). **Este întors o singură dată și nu mai este afișat niciodată** — salvează-l împreună cu id-ul job-ului.

```json
{ "repo_url": "https://github.com/owner/repo", "issue_url": "https://github.com/owner/repo/issues/42",
   "callback_url": "https://ci.example.com/hooks/codai-resolve" }
```

### Evenimente [#evenimente]

| Eveniment | Trimis când |
| --- | --- |
| `job.quoted` | O cotație plătită este gata — apelează `/accept`. |
| `job.declined` | Triajul a refuzat; `decline_reason` este setat. |
| `job.resolved` | Fix verificat; `attestation_id` și un `attestation_url` absolut sunt setate. |
| `job.failed` | Niciun fix verificat în buget; suma blocată în portofel este returnată. |
| `job.error` | Eșec de infrastructură; suma blocată este returnată, poți retrimite liniștit. |

Fiecare livrare este un `POST` cu `content-type: application/json` și aceste header-e:

| Header | Valoare |
| --- | --- |
| `x-codai-event` | Numele evenimentului, identic cu `event` din body. |
| `x-codai-delivery` | `<jobId>:<event>` — identic la fiecare reîncercare; folosește-l pentru deduplicare. |
| `x-codai-signature` | `t=<secunde unix>,v1=<hex>`, unde `v1 = HMAC-SHA256(callback_secret, "<t>.<body brut>")`. |

```json
{
   "event": "job.resolved",
   "job": {
      "id": "6f1d2c3e-…", "status": "resolved", "tier": "t19", "quote_micro_usd": 19000000,
      "repo_url": "https://github.com/owner/repo", "decline_reason": null, "fail_reason": null,
      "cost_micro_usd": 1830000, "attestation_id": "a45764d8-…",
      "attestation_url": "https://resolve.codai.ro/v1/resolve/attestations/a45764d8-…",
      "completed_at": "2026-09-27T09:19:41.402Z"
   },
   "sent_at": "2026-09-27T09:19:42.010Z"
}
```

### Verificarea semnăturii [#verificarea-semnăturii]

Verifică pe **body-ul brut al request-ului**, înainte de parsarea JSON; helper-ele din SDK compară în timp constant și resping un timestamp aflat la mai mult de 300 de secunde de momentul curent.

```ts
import { verifyResolveWebhook, type ResolveWebhookEvent } from 'codai-sdk';

export async function POST(req: Request) {
   const raw = await req.text();
   if (!verifyResolveWebhook(process.env.RESOLVE_WHSEC!, raw, req.headers.get('x-codai-signature'))) {
      return new Response('bad signature', { status: 401 });
   }
   const evt = JSON.parse(raw) as ResolveWebhookEvent;
   if (evt.event === 'job.resolved') console.log(evt.job.attestation_url);
   return new Response(null, { status: 204 });
}
```

```python
import json, os
from codai import verify_resolve_webhook

@app.post("/hooks/codai-resolve")  # FastAPI
async def resolve_hook(request: Request):
      raw = await request.body()
      if not verify_resolve_webhook(os.environ["RESOLVE_WHSEC"], raw, request.headers.get("x-codai-signature")):
            raise HTTPException(401, "bad signature")
      evt = json.loads(raw)
      return {"ok": True}
```

### Semantica livrării [#semantica-livrării]

- Până la **3 încercări** per eveniment (backoff 1 s, apoi 4 s) la `5xx`, `429` sau eroare de rețea; orice alt cod non-2xx oprește reîncercările. Fiecare încercare expiră după 10 s. Răspunde rapid cu `2xx` și fă procesarea asincron.
- **Redirect-urile nu sunt urmate**, iar fiecare răspuns DNS pentru host este reverificat ca public la momentul livrării.
- Livrarea **nu schimbă niciodată starea job-ului sau facturarea** — un endpoint mort nu face job-ul să eșueze. `GET /v1/resolve/jobs/{id}` rămâne sursa de adevăr; interoghează-l dacă ai ratat un eveniment.

## Siguranță: ce atinge runner-ul [#siguranță-ce-atinge-runner-ul]

Resolve rulează cod de repository în care nu are încredere — testele tale, scripturile tale de instalare — așa că modelul de execuție este intenționat îngust.

- **Execuție izolată.** Testele, setup-ul și candidații de fix rulează într-un job Cloud Run separat și fără privilegii (`codai-swe-exec`), **fără acces la baza de date și fără credențiale de gateway**; rezultatele revin ca artefacte. Procesul API în sine doar clonează repo-ul, citește fișiere și construiește diff-uri. În producție, API-ul refuză să ruleze cod de repository în proces — o configurare greșită devine `error`, niciodată cod rulând lângă secrete.
- **Repo-uri publice, doar citire.** Resolve preia `https://github.com/<owner>/<repo>` anonim. Nu face niciodată push; acțiunea GitHub face asta cu token-ul *tău*, în workflow-ul *tău*.
- **Schimbări doar prin diff.** Patch-ul din atestare este întreaga întindere a ceea ce s-a schimbat. Agentul editează prin blocuri SEARCH/REPLACE; nu poate rula shell arbitrar în procesul API.
- **Bugete limitate.** Cel mult 4 ture de agent per candidat, cel mult 4 candidați, un timeout per job de 30 de minute cu o limită de 15 minute per rulare în sandbox, și un watchdog de orfani care marchează un job `error` (și returnează un loc gratuit) dacă worker-ul lui dispare.
- **Nimic ascuns.** Apelurile către model se fac pe gateway-ul `codai` sub un id de sesiune `resolve-<jobId>-…`, așa că costul upstream per job este auditabil în `usage_events` ca orice altă cerere.

<Callout type="warn">
Beta privat înseamnă o listă onestă de limite: doar repo-uri publice `github.com`; Python (pytest) și JavaScript/TypeScript (vitest, jest, node:test); repo-urile ale căror teste au nevoie de secrete, servicii sau setup de sistem netrivial vor fi de regulă refuzate sau se termină în `error`.
</Callout>

<Cards>
  <Card title="Prețuri și limite" href="./pricing-and-limits.mdx" description="Praguri, remedieri gratuite, plafoane, cum se păstrează sau se returnează blocarea din portofel." />
  <Card title="Referință API" href="../api/resolve.mdx" description="Referință la nivel de câmp și playground." />
</Cards>
