<a id="api-schema-reference"></a>
# API 요청·응답·완료 의미 참조

이 문서는 261개 등록 operation의 wire 입력, 직접 JSON variant, 오류와 완료 경계를 handler AST와 route registration에서 색인한다. 공유 handler의 method branch와 기존 required 후보 56개는 source control-flow를 별도로 대조했다. 그 밖의 field requiredness와 helper/derived schema는 `P/U`이며, route 261개를 field-level semantic 완료율로 사용하지 않는다. `C`는 해당 행의 직접 branch/shape를 확인했다는 뜻이고 `P`는 일부 helper/derived type이 남았으며 `U`는 직접 shape를 확정하지 못했다는 뜻이다. 실행 성공이나 외부 효과 완료를 뜻하지 않는다.

<a id="api-common-wire"></a>
## 공통 wire 규칙

- `/api/auth/*`와 `/api/health`는 공통 middleware에서 면제된다. change-password는 handler에서 JWT와 현재 암호를 다시 검사한다.
- `extractToken`은 route/method와 무관하게 Bearer header → `artex_token` cookie → query `token` 순으로 JWT를 고른다. 공통 middleware가 보호하는 256개 operation과 middleware 면제지만 handler가 재검증하는 change-password 1개, 합계 257개가 query token을 실제로 수락한다. SSE 전용 제한은 구현돼 있지 않다. auth status/init/login과 health 4개는 JWT를 요구하지 않는다.
- 일반 JSON 오류는 `{ "error": string }`이다. 일부 stream/file/helper 경로는 다른 body 또는 이미 쓴 응답을 가질 수 있어 operation별 상태를 본다.
- 공통 `requireAuth`가 보호하는 256개 operation은 handler 전에 token 누락 `401 {"error":"未授权"}`, invalid/expired `401 {"error":"token 无效或已过期"}`를 반환할 수 있다. operation 표의 handler 직접 오류와 합쳐 해석한다.
- `s.pg(w)`를 직접 호출하는 operation은 PostgreSQL handle이 없을 때 helper가 `503 {"error":"PostgreSQL 不可用"}`를 쓰고 handler를 중단한다. 적용 operation은 아래 협력자와 오류 표에 표시하며 실제 DB health는 실행하지 않았다.
- `encoding/json` decoder는 field 존재 자체를 강제하지 않는다. non-pointer field의 누락/null 구별은 보존되지 않고 Go zero value가 남는다. pointer field는 absent/null 모두 `nil`일 수 있다. “필수”는 absent/zero check가 error response와 return으로 이어지는 control-flow를 확인한 field만 표시한다. default/alias 변환, 제공 시 검증, all-empty patch는 각각 선택 또는 그룹 조건이다.
- unknown-field 거부는 `DisallowUnknownFields`를 호출한 handler만 해당한다. 그 외 JSON object의 미등록 key는 decoder 단계에서 무시될 수 있다.
- path/query/header는 HTTP wire에서 string이다. 정수·boolean·enum 변환과 범위는 validation 식 또는 helper가 소유하며 미해석 helper는 `P/U`로 남긴다.
- write response의 `omitempty` field는 조건부다. map literal key는 해당 variant에서 존재하지만 다른 status/branch의 variant에는 없을 수 있다.

<a id="api-shared-types"></a>
## 공유 nested type

Operation field가 아래 type을 참조하면 이 표가 알려진 JSON member를 소유한다. custom unmarshal/validation은 consumer handler가 추가할 수 있다.

<a id="type-db-trafficref"></a>
### `db.TrafficRef`

| JSON field | wire type | absent/null |
|---|---|---|
| `traffic_id` | `string` (`string`) | zero value; 별도 validation 확인 |
| `role` | `string` (`string`) | zero value; 별도 validation 확인 |
| `note` | `string` (`string`) | zero value; 별도 validation 확인 |

<a id="type-notify-filter"></a>
### `notify.Filter`

| JSON field | wire type | absent/null |
|---|---|---|
| `min_severity` | `string` (`string`) | zero value; 별도 validation 확인 |
| `task_ids` | `array[integer]` (`[]int64`) | zero value; 별도 validation 확인 |
| `asset_ids` | `array[integer]` (`[]int64`) | zero value; 별도 validation 확인 |
| `vulnclass_include` | `array[string]` (`[]string`) | zero value; 별도 validation 확인 |
| `vulnclass_exclude` | `array[string]` (`[]string`) | zero value; 별도 validation 확인 |
| `on_status_change` | `boolean` (`bool`) | zero value; 별도 validation 확인 |

<a id="type-server-chatattachment"></a>
### `server.chatAttachment`

| JSON field | wire type | absent/null |
|---|---|---|
| `name` | `string` (`string`) | zero value; 별도 validation 확인 |
| `path` | `string` (`string`) | zero value; 별도 validation 확인 |
| `size` | `integer` (`int64`) | zero value; 별도 validation 확인 |
| `abs` | `string` (`string`) | zero value; 별도 validation 확인 |

<a id="type-server-taskinterceptrulereq"></a>
### `server.taskInterceptRuleReq`

| JSON field | wire type | absent/null |
|---|---|---|
| `enabled` | `boolean` (`bool`) | zero value; 별도 validation 확인 |
| `action` | `string` (`string`) | zero value; 별도 validation 확인 |
| `kind` | `string` (`string`) | zero value; 별도 validation 확인 |
| `pattern` | `string` (`string`) | zero value; 별도 validation 확인 |
| `note` | `string` (`string`) | zero value; 별도 validation 확인 |

<a id="schema-get-api-auth-status"></a>
## `GET /api/auth/status`

구현: `authStatus` · `server/auth.go` · [정확한 handler 근거](evidence:handler-get-api-auth-status)

### 요청

| 위치/field | wire type | 필수·null | 기본/검증 |
|---|---|---|---|
| body | 없음(직접 decoder/form read 미발견) | 해당 없음 | 해당 없음 |

요청 판정: **C/P** · unknown field 거부 미설정.
직접 validation: `pg == nil`

### 응답·오류

| 종류 | status/body | 해석 상태 |
|---|---|---|
| success/variant | `200` · `initialized:expression` | P/U · `static composite` |
| error | `503` · `{error:string}` · `"PostgreSQL 不可用"` | C for direct writeErr; error helper 내부는 P |

응답 schema 판정: **P** · top-level key는 직접 추출했지만 derived/helper/opaque 또는 nested type이 남음.

### 목록·동시성·완료

- Pagination/filter/sort: 등록된 직접 query key 중 표준 paging/filter key 없음.
- Idempotency/concurrency: read/stream 요청; stream 재연결은 cursor 계약에 따름.
- Side effect/completion: 직접 조회/파일 전송 완료; 외부 조회가 있으면 응답 전 handler 결과만 보장.
- 직접 협력자/실행 단서: `pg.GetSetting`; direct `go` statement는 미발견. 이 목록은 helper 내부의 background launch나 transaction 경계를 펼치지 않으므로 그 부분은 P/U다.
- 종합 의미 판정: **P/U**; route/auth와 위에서 직접 확인한 branch·shape는 C지만, 모든 helper·derived response·중첩 DTO·외부 효과까지 완결됐다는 뜻은 아니다.

<a id="schema-post-api-auth-init"></a>
## `POST /api/auth/init`

구현: `authInit` · `server/auth.go` · [정확한 handler 근거](evidence:handler-post-api-auth-init)

### 요청

| 위치/field | wire type | 필수·null | 기본/검증 |
|---|---|---|---|
| body `password` | `string` (`string`) | 필수(직접 거부 branch 확인); null/absent 구별 안 됨 | `빈 문자열이면 400 후 return` |

요청 판정: **C/P** · unknown field 거부 미설정.
직접 validation: `err != nil \|\| req.Password == ""`

### 응답·오류

| 종류 | status/body | 해석 상태 |
|---|---|---|
| success/variant | `200` · `token:derived` | P/U · `static composite` |
| error | `400` · `{error:string}` · `"密码不能为空"` | C for direct writeErr; error helper 내부는 P |
| error | `403` · `{error:string}` · `"密码已设置"` | C for direct writeErr; error helper 내부는 P |
| error | `500` · `{error:string}` · `"密码加密失败"` / `"保存失败: " + err.Error()` / `"token 生成失败"` | C for direct writeErr; error helper 내부는 P |
| error | `503` · `{error:string}` · `"PostgreSQL 不可用"` | C for direct writeErr; error helper 내부는 P |

응답 schema 판정: **P** · top-level key는 직접 추출했지만 derived/helper/opaque 또는 nested type이 남음.

### 목록·동시성·완료

- Pagination/filter/sort: 등록된 직접 query key 중 표준 paging/filter key 없음.
- Idempotency/concurrency: Idempotency-Key 없음; unique constraint 또는 상태 check가 있는 경우만 중복을 제한(P).
- Side effect/completion: handler의 직접 mutation/호출 후 응답; 외부 전달·agent 실행이 파생되면 별도 상태 확인(P).
- 직접 협력자/실행 단서: `[]byte`, `bcrypt.GenerateFromPassword`, `err.Error`, `pg.GetSetting`, `pg.SetSetting`, `signJWT`; direct `go` statement는 미발견. 이 목록은 helper 내부의 background launch나 transaction 경계를 펼치지 않으므로 그 부분은 P/U다.
- 종합 의미 판정: **P/U**; route/auth와 위에서 직접 확인한 branch·shape는 C지만, 모든 helper·derived response·중첩 DTO·외부 효과까지 완결됐다는 뜻은 아니다.

<a id="schema-post-api-auth-login"></a>
## `POST /api/auth/login`

구현: `authLogin` · `server/auth.go` · [정확한 handler 근거](evidence:handler-post-api-auth-login)

### 요청

| 위치/field | wire type | 필수·null | 기본/검증 |
|---|---|---|---|
| body `username` | `string` (`string`) | 필수(직접 거부 branch 확인); null/absent 구별 안 됨 | `누락/불일치 credential은 401 후 return` |
| body `password` | `string` (`string`) | 필수(직접 거부 branch 확인); null/absent 구별 안 됨 | `누락/불일치 credential은 401 후 return` |

요청 판정: **C/P** · unknown field 거부 미설정.
직접 validation: `req.Username != "ARTEX"`

### 응답·오류

| 종류 | status/body | 해석 상태 |
|---|---|---|
| success/variant | `200` · `token:derived` | P/U · `static composite` |
| error | `400` · `{error:string}` · `"请求格式错误"` | C for direct writeErr; error helper 내부는 P |
| error | `401` · `{error:string}` · `"用户名或密码错误"` | C for direct writeErr; error helper 내부는 P |
| error | `403` · `{error:string}` · `"密码未初始化，请先设置密码"` | C for direct writeErr; error helper 내부는 P |
| error | `500` · `{error:string}` · `"token 生成失败"` | C for direct writeErr; error helper 내부는 P |
| error | `503` · `{error:string}` · `"PostgreSQL 不可用"` | C for direct writeErr; error helper 내부는 P |

응답 schema 판정: **P** · top-level key는 직접 추출했지만 derived/helper/opaque 또는 nested type이 남음.

### 목록·동시성·완료

- Pagination/filter/sort: 등록된 직접 query key 중 표준 paging/filter key 없음.
- Idempotency/concurrency: Idempotency-Key 없음; unique constraint 또는 상태 check가 있는 경우만 중복을 제한(P).
- Side effect/completion: handler의 직접 mutation/호출 후 응답; 외부 전달·agent 실행이 파생되면 별도 상태 확인(P).
- 직접 협력자/실행 단서: `[]byte`, `bcrypt.CompareHashAndPassword`, `pg.GetSetting`, `signJWT`; direct `go` statement는 미발견. 이 목록은 helper 내부의 background launch나 transaction 경계를 펼치지 않으므로 그 부분은 P/U다.
- 종합 의미 판정: **P/U**; route/auth와 위에서 직접 확인한 branch·shape는 C지만, 모든 helper·derived response·중첩 DTO·외부 효과까지 완결됐다는 뜻은 아니다.

<a id="schema-post-api-auth-change-password"></a>
## `POST /api/auth/change-password`

구현: `authChangePassword` · `server/auth.go` · [정확한 handler 근거](evidence:handler-post-api-auth-change-password)

### 요청

| 위치/field | wire type | 필수·null | 기본/검증 |
|---|---|---|---|
| auth credential | Bearer header / `artex_token` cookie / query `token` | 세 위치 중 하나에 유효 JWT 필수 | `extractToken` 우선순위: header→cookie→query; query fallback은 SSE로 제한되지 않음 |
| body `old_password` | `string` (`string`) | 필수(직접 거부 branch 확인); null/absent 구별 안 됨 | `빈 값은 현재 bcrypt hash와 일치하지 않아 401 후 return` |
| body `new_password` | `string` (`string`) | 필수(직접 거부 branch 확인); null/absent 구별 안 됨 | `빈 문자열이면 400 후 return` |

요청 판정: **C/P** · unknown field 거부 미설정.
직접 validation: `req.NewPassword == ""`

### 응답·오류

| 종류 | status/body | 해석 상태 |
|---|---|---|
| success/variant | `200` · `ok:boolean` | C · `static composite` |
| error | `400` · `{error:string}` · `"请求格式错误"` / `"新密码不能为空"` | C for direct writeErr; error helper 내부는 P |
| error | `401` · `{error:string}` · `"未授权"` / `"当前密码错误"` | C for direct writeErr; error helper 내부는 P |
| error | `403` · `{error:string}` · `"密码未初始化，请先设置密码"` | C for direct writeErr; error helper 내부는 P |
| error | `500` · `{error:string}` · `"密码加密失败"` / `"保存失败: " + err.Error()` | C for direct writeErr; error helper 내부는 P |
| error | `503` · `{error:string}` · `"PostgreSQL 不可用"` | C for direct writeErr; error helper 내부는 P |

응답 schema 판정: **C** · 직접 scalar/static top-level key와 field type을 추출; nested 내부 schema는 별도 type 참조가 없으면 P.

### 목록·동시성·완료

- Pagination/filter/sort: 등록된 직접 query key 중 표준 paging/filter key 없음.
- Idempotency/concurrency: Idempotency-Key 없음; unique constraint 또는 상태 check가 있는 경우만 중복을 제한(P).
- Side effect/completion: handler의 직접 mutation/호출 후 응답; 외부 전달·agent 실행이 파생되면 별도 상태 확인(P).
- 직접 협력자/실행 단서: `[]byte`, `bcrypt.CompareHashAndPassword`, `bcrypt.GenerateFromPassword`, `err.Error`, `extractToken`, `pg.GetSetting`, `pg.SetSetting`, `verifyJWT`; direct `go` statement는 미발견. 이 목록은 helper 내부의 background launch나 transaction 경계를 펼치지 않으므로 그 부분은 P/U다.
- 종합 의미 판정: **P/U**; route/auth와 위에서 직접 확인한 branch·shape는 C지만, 모든 helper·derived response·중첩 DTO·외부 효과까지 완결됐다는 뜻은 아니다.

<a id="schema-get-api-health"></a>
## `GET /api/health`

구현: `health` · `server/server.go` · [정확한 handler 근거](evidence:handler-get-api-health)

### 요청

| 위치/field | wire type | 필수·null | 기본/검증 |
|---|---|---|---|
| body | 없음(직접 decoder/form read 미발견) | 해당 없음 | 해당 없음 |

요청 판정: **C/P** · unknown field 거부 미설정.

### 응답·오류

| 종류 | status/body | 해석 상태 |
|---|---|---|
| success/variant | `200` · `ok:boolean`, `service:string`, `version:derived` | P/U · `static composite` |

응답 schema 판정: **P** · top-level key는 직접 추출했지만 derived/helper/opaque 또는 nested type이 남음.

### 목록·동시성·완료

- Pagination/filter/sort: 등록된 직접 query key 중 표준 paging/filter key 없음.
- Idempotency/concurrency: read/stream 요청; stream 재연결은 cursor 계약에 따름.
- Side effect/completion: 직접 조회/파일 전송 완료; 외부 조회가 있으면 응답 전 handler 결과만 보장.
- 직접 협력자/실행 단서: handler AST에서 별도 collaborator call 미발견; direct `go` statement는 미발견. 이 목록은 helper 내부의 background launch나 transaction 경계를 펼치지 않으므로 그 부분은 P/U다.
- 종합 의미 판정: **P/U**; route/auth와 위에서 직접 확인한 branch·shape는 C지만, 모든 helper·derived response·중첩 DTO·외부 효과까지 완결됐다는 뜻은 아니다.

<a id="schema-get-api-stats"></a>
## `GET /api/stats`

구현: `stats` · `server/server.go` · [정확한 handler 근거](evidence:handler-get-api-stats)

### 요청

| 위치/field | wire type | 필수·null | 기본/검증 |
|---|---|---|---|
| query `task` | `string` wire | 선택 | handler/helper 변환·default 미전개(P) |
| auth credential | Bearer header / `artex_token` cookie / query `token` | 세 위치 중 하나에 유효 JWT 필수 | `extractToken` 우선순위: header→cookie→query; query fallback은 SSE로 제한되지 않음 |
| body | 없음(직접 decoder/form read 미발견) | 해당 없음 | 해당 없음 |

요청 판정: **C/P** · unknown field 거부 미설정.
직접 validation: `err == nil` · `g.State == "met"` · `in.State == "running"` · `t == nil` · `taskParam != "" && taskParam != "active"` · `tr != nil`

### 응답·오류

| 종류 | status/body | 해석 상태 |
|---|---|---|
| success/variant | `200` · `out` | P/U · `derived variable` |
| error | `401` · `{error:string}` · `"未授权"` / `"token 无效或已过期"` | C for direct writeErr; error helper 내부는 P |
| error | `404` · `{error:string}` · `"task not found"` | C for direct writeErr; error helper 내부는 P |

응답 schema 판정: **P** · top-level key는 직접 추출했지만 derived/helper/opaque 또는 nested type이 남음.

### 목록·동시성·완료

- Pagination/filter/sort: `task`.
- Idempotency/concurrency: read/stream 요청; stream 재연결은 cursor 계약에 따름.
- Side effect/completion: 직접 조회/파일 전송 완료; 외부 조회가 있으면 응답 전 handler 결과만 보장.
- 직접 협력자/실행 단서: `r.URL.Query`, `r.URL.Query().Get`, `s.engine.ActiveLLMCalls`, `s.engine.IsPaused`, `s.engine.LastActivity`, `s.engine.Ready`, `s.engine.ReadyFor`, `s.engine.Started`, `s.m.Assets`, `s.m.Assets().CountsByType`, `s.m.ResolveTask`, `s.m.Traffic`, `t.Store.ListByKind`, `t.Store.Stats`, `tr.Count`; direct `go` statement는 미발견. 이 목록은 helper 내부의 background launch나 transaction 경계를 펼치지 않으므로 그 부분은 P/U다.
- 종합 의미 판정: **P/U**; route/auth와 위에서 직접 확인한 branch·shape는 C지만, 모든 helper·derived response·중첩 DTO·외부 효과까지 완결됐다는 뜻은 아니다.

<a id="schema-get-api-logs"></a>
## `GET /api/logs`

구현: `getLogs` · `server/server.go` · [정확한 handler 근거](evidence:handler-get-api-logs)

### 요청

| 위치/field | wire type | 필수·null | 기본/검증 |
|---|---|---|---|
| query `limit` | `string` wire | 선택 | handler/helper 변환·default 미전개(P) |
| query `since` | `string` wire | 선택 | handler/helper 변환·default 미전개(P) |
| auth credential | Bearer header / `artex_token` cookie / query `token` | 세 위치 중 하나에 유효 JWT 필수 | `extractToken` 우선순위: header→cookie→query; query fallback은 SSE로 제한되지 않음 |
| body | 없음(직접 decoder/form read 미발견) | 해당 없음 | 해당 없음 |

요청 판정: **C/P** · unknown field 거부 미설정.
직접 parse/default: `atoiDefault(r.URL.Query().Get("limit"), 500)` · `atoiDefault(r.URL.Query().Get("since"), 0)`

### 응답·오류

| 종류 | status/body | 해석 상태 |
|---|---|---|
| success/variant | `200` · `items:derived`, `cursor:derived` | P/U · `static composite` |
| error | `401` · `{error:string}` · `"未授权"` / `"token 无效或已过期"` | C for direct writeErr; error helper 내부는 P |

응답 schema 판정: **P** · top-level key는 직접 추출했지만 derived/helper/opaque 또는 nested type이 남음.

### 목록·동시성·완료

- Pagination/filter/sort: `limit`, `since`.
- Idempotency/concurrency: read/stream 요청; stream 재연결은 cursor 계약에 따름.
- Side effect/completion: 직접 조회/파일 전송 완료; 외부 조회가 있으면 응답 전 handler 결과만 보장.
- 직접 협력자/실행 단서: `atoiDefault`, `logSink.recent`, `r.URL.Query`, `r.URL.Query().Get`; direct `go` statement는 미발견. 이 목록은 helper 내부의 background launch나 transaction 경계를 펼치지 않으므로 그 부분은 P/U다.
- 종합 의미 판정: **P/U**; route/auth와 위에서 직접 확인한 branch·shape는 C지만, 모든 helper·derived response·중첩 DTO·외부 효과까지 완결됐다는 뜻은 아니다.

<a id="schema-get-api-logs-history"></a>
## `GET /api/logs/history`

구현: `getLogsHistory` · `server/server.go` · [정확한 handler 근거](evidence:handler-get-api-logs-history)

### 요청

| 위치/field | wire type | 필수·null | 기본/검증 |
|---|---|---|---|
| query `before` | `string` wire | 선택 | handler/helper 변환·default 미전개(P) |
| query `limit` | `string` wire | 선택 | handler/helper 변환·default 미전개(P) |
| auth credential | Bearer header / `artex_token` cookie / query `token` | 세 위치 중 하나에 유효 JWT 필수 | `extractToken` 우선순위: header→cookie→query; query fallback은 SSE로 제한되지 않음 |
| body | 없음(직접 decoder/form read 미발견) | 해당 없음 | 해당 없음 |

요청 판정: **C/P** · unknown field 거부 미설정.
직접 validation: `before <= 0` · `err != nil` · `limit > 500` · `s.m.pg == nil`
직접 parse/default: `atoiDefault(r.URL.Query().Get("before"), 0)` · `atoiDefault(r.URL.Query().Get("limit"), 200)`

### 응답·오류

| 종류 | status/body | 해석 상태 |
|---|---|---|
| success/variant | `200` · `items:[]any`, `has_more:boolean` | P/U · `static composite` |
| success/variant | `200` · `items:derived`, `has_more:expression` | P/U · `static composite` |
| error | `401` · `{error:string}` · `"未授权"` / `"token 无效或已过期"` | C for direct writeErr; error helper 내부는 P |
| error | `500` · `{error:string}` · `"db: " + err.Error()` | C for direct writeErr; error helper 내부는 P |

응답 schema 판정: **P** · top-level key는 직접 추출했지만 derived/helper/opaque 또는 nested type이 남음.

### 목록·동시성·완료

- Pagination/filter/sort: `before`, `limit`.
- Idempotency/concurrency: read/stream 요청; stream 재연결은 cursor 계약에 따름.
- Side effect/completion: 직접 조회/파일 전송 완료; 외부 조회가 있으면 응답 전 handler 결과만 보장.
- 직접 협력자/실행 단서: `atoiDefault`, `err.Error`, `r.CreatedAt.Format`, `r.URL.Query`, `r.URL.Query().Get`, `s.m.pg.ListLogsBefore`, `s.m.pg.RecentLogs`; direct `go` statement는 미발견. 이 목록은 helper 내부의 background launch나 transaction 경계를 펼치지 않으므로 그 부분은 P/U다.
- 종합 의미 판정: **P/U**; route/auth와 위에서 직접 확인한 branch·shape는 C지만, 모든 helper·derived response·중첩 DTO·외부 효과까지 완결됐다는 뜻은 아니다.

<a id="schema-get-api-logs-stream"></a>
## `GET /api/logs/stream`

구현: `streamLogs` · `server/server.go` · [정확한 handler 근거](evidence:handler-get-api-logs-stream)

### 요청

| 위치/field | wire type | 필수·null | 기본/검증 |
|---|---|---|---|
| query `since` | `string` wire | 선택 | handler/helper 변환·default 미전개(P) |
| auth credential | Bearer header / `artex_token` cookie / query `token` | 세 위치 중 하나에 유효 JWT 필수 | `extractToken` 우선순위: header→cookie→query; query fallback은 SSE로 제한되지 않음 |
| body | 없음(직접 decoder/form read 미발견) | 해당 없음 | 해당 없음 |

요청 판정: **C/P** · unknown field 거부 미설정.
직접 validation: `!ok` · `cursor > since` · `l.Seq <= since`
직접 parse/default: `atoiDefault(r.URL.Query().Get("since"), 0)`

### 응답·오류

| 종류 | status/body | 해석 상태 |
|---|---|---|
| success/variant | `200` · `text/event-stream; Cache-Control:no-cache; Connection:keep-alive; X-Accel-Buffering:no; \`data:\` LogLine JSON, 20 s \`: ping\`` | P/U · `SSE stream` |
| error | `401` · `{error:string}` · `"未授权"` / `"token 无效或已过期"` | C for direct writeErr; error helper 내부는 P |
| error | `500` · `{error:string}` · `"streaming unsupported"` | C for direct writeErr; error helper 내부는 P |

응답 schema 판정: **P** · top-level key는 직접 추출했지만 derived/helper/opaque 또는 nested type이 남음.

### 목록·동시성·완료

- Pagination/filter/sort: `since`.
- Idempotency/concurrency: read/stream 요청; stream 재연결은 cursor 계약에 따름.
- Side effect/completion: SSE 연결 수립; event lifecycle은 stream별 cursor/메모리 상태를 확인.
- 직접 협력자/실행 단서: `atoiDefault`, `ctx.Done`, `flusher.Flush`, `fmt.Fprint`, `fmt.Fprintf`, `logSink.recent`, `logSink.subscribe`, `ping.Stop`, `r.Context`, `r.URL.Query`, `r.URL.Query().Get`, `send`, `unsub`, `w.Header`, `w.Header().Set`; direct `go` statement는 미발견. 이 목록은 helper 내부의 background launch나 transaction 경계를 펼치지 않으므로 그 부분은 P/U다.
- 종합 의미 판정: **P/U**; route/auth와 위에서 직접 확인한 branch·shape는 C지만, 모든 helper·derived response·중첩 DTO·외부 효과까지 완결됐다는 뜻은 아니다.

<a id="schema-get-api-update-check"></a>
## `GET /api/update/check`

구현: `updateCheck` · `server/update.go` · [정확한 handler 근거](evidence:handler-get-api-update-check)

### 요청

| 위치/field | wire type | 필수·null | 기본/검증 |
|---|---|---|---|
| query `force` | `string` wire | 선택 | handler/helper 변환·default 미전개(P) |
| auth credential | Bearer header / `artex_token` cookie / query `token` | 세 위치 중 하나에 유효 JWT 필수 | `extractToken` 우선순위: header→cookie→query; query fallback은 SSE로 제한되지 않음 |
| body | 없음(직접 decoder/form read 미발견) | 해당 없음 | 해당 없음 |

요청 판정: **C/P** · unknown field 거부 미설정.
직접 validation: `!comparable` · `!rel.PublishedAt.IsZero()` · `err != nil` · `ok` · `selfupdate.InDocker()`

### 응답·오류

| 종류 | status/body | 해석 상태 |
|---|---|---|
| success/variant | `200` · `out` | P/U · `derived variable` |
| error | `401` · `{error:string}` · `"未授权"` / `"token 无效或已过期"` | C for direct writeErr; error helper 내부는 P |

응답 schema 판정: **P** · top-level key는 직접 추출했지만 derived/helper/opaque 또는 nested type이 남음.

### 목록·동시성·완료

- Pagination/filter/sort: 등록된 직접 query key 중 표준 paging/filter key 없음.
- Idempotency/concurrency: read/stream 요청; stream 재연결은 cursor 계약에 따름.
- Side effect/completion: 직접 조회/파일 전송 완료; 외부 조회가 있으면 응답 전 handler 결과만 보장.
- 직접 협력자/실행 단서: `bootUpdateState`, `err.Error`, `r.Context`, `r.URL.Query`, `r.URL.Query().Get`, `rel.FindAsset`, `rel.PublishedAt.Format`, `rel.PublishedAt.IsZero`, `relCache.get`, `s.m.GlobalProxy`, `selfupdate.AssetName`, `selfupdate.CompareVersions`, `selfupdate.HasBackup`, `selfupdate.InDocker`, `selfupdate.NewClient`; direct `go` statement는 미발견. 이 목록은 helper 내부의 background launch나 transaction 경계를 펼치지 않으므로 그 부분은 P/U다.
- 종합 의미 판정: **P/U**; route/auth와 위에서 직접 확인한 branch·shape는 C지만, 모든 helper·derived response·중첩 DTO·외부 효과까지 완결됐다는 뜻은 아니다.

<a id="schema-post-api-update-apply"></a>
## `POST /api/update/apply`

구현: `updateApply` · `server/update.go` · [정확한 handler 근거](evidence:handler-post-api-update-apply)

### 요청

| 위치/field | wire type | 필수·null | 기본/검증 |
|---|---|---|---|
| auth credential | Bearer header / `artex_token` cookie / query `token` | 세 위치 중 하나에 유효 JWT 필수 | `extractToken` 우선순위: header→cookie→query; query fallback은 SSE로 제한되지 않음 |
| body | 없음(직접 decoder/form read 미발견) | 해당 없음 | 해당 없음 |

요청 판정: **C/P** · unknown field 거부 미설정.
직접 validation: `!comparable` · `!updHub.begin(rel.TagName)` · `cmp >= 0` · `err != nil`

### 응답·오류

| 종류 | status/body | 해석 상태 |
|---|---|---|
| success/variant | `202` · `ok:boolean`, `target:expression` | P/U · `static composite` |
| error | `400` · `{error:string}` · `fmt.Sprintf("当前版本 %q 不是正式发布版本，已禁用一键更新", current)` / `fmt.Sprintf("当前已是最新版本 %s", current)` | C for direct writeErr; error helper 내부는 P |
| error | `401` · `{error:string}` · `"未授权"` / `"token 无效或已过期"` | C for direct writeErr; error helper 내부는 P |
| error | `409` · `{error:string}` · `"已有一个更新正在进行中"` | C for direct writeErr; error helper 내부는 P |
| error | `502` · `{error:string}` · `err.Error()` | C for direct writeErr; error helper 내부는 P |

응답 schema 판정: **P** · top-level key는 직접 추출했지만 derived/helper/opaque 또는 nested type이 남음.

### 목록·동시성·완료

- Pagination/filter/sort: 등록된 직접 query key 중 표준 paging/filter key 없음.
- Idempotency/concurrency: Idempotency-Key 없음; unique constraint 또는 상태 check가 있는 경우만 중복을 제한(P).
- Side effect/completion: download/apply worker 시작 후 process restart; 새 health/version이 완료 기준.
- 직접 협력자/실행 단서: `err.Error`, `func() { err := selfupdate.Stage(s.ctx, client, rel, current, func(ph selfupdate.Phase, pct int, msg string) { updHub.publish(ph, pct, msg) }) updHub.finish(err) if err != nil { log.Printf("[update] 更新失败：%v", err) return } log.Printf("[update] %s → %s 已暂存，即将退出以完成换装", current, rel.TagName) time.Sleep(1500 * time.Millisecond) requestRestart() }`, `log.Printf`, `r.Context`, `relCache.get`, `requestRestart`, `s.m.GlobalProxy`, `selfupdate.CompareVersions`, `selfupdate.NewClient`, `selfupdate.Stage`, `updHub.begin`, `updHub.finish`, `updHub.publish`; direct `go` statement는 발견. 이 목록은 helper 내부의 background launch나 transaction 경계를 펼치지 않으므로 그 부분은 P/U다.
- 종합 의미 판정: **P/U**; route/auth와 위에서 직접 확인한 branch·shape는 C지만, 모든 helper·derived response·중첩 DTO·외부 효과까지 완결됐다는 뜻은 아니다.

<a id="schema-post-api-update-rollback"></a>
## `POST /api/update/rollback`

구현: `updateRollback` · `server/update.go` · [정확한 handler 근거](evidence:handler-post-api-update-rollback)

### 요청

| 위치/field | wire type | 필수·null | 기본/검증 |
|---|---|---|---|
| auth credential | Bearer header / `artex_token` cookie / query `token` | 세 위치 중 하나에 유효 JWT 필수 | `extractToken` 우선순위: header→cookie→query; query fallback은 SSE로 제한되지 않음 |
| body | 없음(직접 decoder/form read 미발견) | 해당 없음 | 해당 없음 |

요청 판정: **C/P** · unknown field 거부 미설정.
직접 validation: `err != nil` · `running`

### 응답·오류

| 종류 | status/body | 해석 상태 |
|---|---|---|
| success/variant | `202` · `ok:boolean` | C · `static composite` |
| error | `400` · `{error:string}` · `err.Error()` | C for direct writeErr; error helper 내부는 P |
| error | `401` · `{error:string}` · `"未授权"` / `"token 无效或已过期"` | C for direct writeErr; error helper 내부는 P |
| error | `409` · `{error:string}` · `"更新正在进行中，无法回滚"` | C for direct writeErr; error helper 내부는 P |

응답 schema 판정: **C** · 직접 scalar/static top-level key와 field type을 추출; nested 내부 schema는 별도 type 참조가 없으면 P.

### 목록·동시성·완료

- Pagination/filter/sort: 등록된 직접 query key 중 표준 paging/filter key 없음.
- Idempotency/concurrency: Idempotency-Key 없음; unique constraint 또는 상태 check가 있는 경우만 중복을 제한(P).
- Side effect/completion: rollback stage 후 restart 요청; 재기동 version이 완료 기준.
- 직접 협력자/실행 단서: `err.Error`, `func() { time.Sleep(500 * time.Millisecond) requestRestart() }`, `log.Printf`, `requestRestart`, `selfupdate.Rollback`, `updHub.snapshot`; direct `go` statement는 발견. 이 목록은 helper 내부의 background launch나 transaction 경계를 펼치지 않으므로 그 부분은 P/U다.
- 종합 의미 판정: **P/U**; route/auth와 위에서 직접 확인한 branch·shape는 C지만, 모든 helper·derived response·중첩 DTO·외부 효과까지 완결됐다는 뜻은 아니다.

<a id="schema-get-api-update-stream"></a>
## `GET /api/update/stream`

구현: `updateStream` · `server/update.go` · [정확한 handler 근거](evidence:handler-get-api-update-stream)

### 요청

| 위치/field | wire type | 필수·null | 기본/검증 |
|---|---|---|---|
| auth credential | Bearer header / `artex_token` cookie / query `token` | 세 위치 중 하나에 유효 JWT 필수 | `extractToken` 우선순위: header→cookie→query; query fallback은 SSE로 제한되지 않음 |
| body | 없음(직접 decoder/form read 미발견) | 해당 없음 | 해당 없음 |

요청 판정: **C/P** · unknown field 거부 미설정.
직접 validation: `!ok`

### 응답·오류

| 종류 | status/body | 해석 상태 |
|---|---|---|
| success/variant | `200` · `text/event-stream; Cache-Control:no-cache; Connection:keep-alive; X-Accel-Buffering:no; initial snapshot then \`data:\` updateProgress, 20 s \`: ping\`` | P/U · `SSE stream` |
| error | `401` · `{error:string}` · `"未授权"` / `"token 无效或已过期"` | C for direct writeErr; error helper 내부는 P |
| error | `500` · `{error:string}` · `"streaming unsupported"` | C for direct writeErr; error helper 내부는 P |

응답 schema 판정: **P** · top-level key는 직접 추출했지만 derived/helper/opaque 또는 nested type이 남음.

### 목록·동시성·완료

- Pagination/filter/sort: 등록된 직접 query key 중 표준 paging/filter key 없음.
- Idempotency/concurrency: read/stream 요청; stream 재연결은 cursor 계약에 따름.
- Side effect/completion: SSE 연결 수립; event lifecycle은 stream별 cursor/메모리 상태를 확인.
- 직접 협력자/실행 단서: `ctx.Done`, `flusher.Flush`, `fmt.Fprint`, `fmt.Fprintf`, `ping.Stop`, `r.Context`, `send`, `unsub`, `updHub.snapshot`, `updHub.subscribe`, `w.Header`, `w.Header().Set`; direct `go` statement는 미발견. 이 목록은 helper 내부의 background launch나 transaction 경계를 펼치지 않으므로 그 부분은 P/U다.
- 종합 의미 판정: **P/U**; route/auth와 위에서 직접 확인한 branch·shape는 C지만, 모든 helper·derived response·중첩 DTO·외부 효과까지 완결됐다는 뜻은 아니다.

<a id="schema-get-api-tasks"></a>
## `GET /api/tasks`

구현: `listTasks` · `server/server.go` · [정확한 handler 근거](evidence:handler-get-api-tasks)

### 요청

| 위치/field | wire type | 필수·null | 기본/검증 |
|---|---|---|---|
| auth credential | Bearer header / `artex_token` cookie / query `token` | 세 위치 중 하나에 유효 JWT 필수 | `extractToken` 우선순위: header→cookie→query; query fallback은 SSE로 제한되지 않음 |
| body | 없음(직접 decoder/form read 미발견) | 해당 없음 | 해당 없음 |

요청 판정: **C/P** · unknown field 거부 미설정.
직접 validation: `live > dto.LastActivity` · `t != nil`

### 응답·오류

| 종류 | status/body | 해석 상태 |
|---|---|---|
| success/variant | `200` · `tasks:derived`, `active:derived` | P/U · `static composite` |
| error | `401` · `{error:string}` · `"未授权"` / `"token 无效或已过期"` | C for direct writeErr; error helper 내부는 P |

응답 schema 판정: **P** · top-level key는 직접 추출했지만 derived/helper/opaque 또는 nested type이 남음.

### 목록·동시성·완료

- Pagination/filter/sort: 등록된 직접 query key 중 표준 paging/filter key 없음.
- Idempotency/concurrency: read/stream 요청; stream 재연결은 cursor 계약에 따름.
- Side effect/completion: 직접 조회/파일 전송 완료; 외부 조회가 있으면 응답 전 handler 결과만 보장.
- 직접 협력자/실행 단서: `applyTaskArchiveBlocker`, `s.engine.LastActivity`, `s.m.ActiveTask`, `s.m.List`, `s.m.PG`, `s.m.PG().TaskArchiveBlockers`, `s.m.PG().TaskListMetricsAll`, `taskDTO`, `tokenTotalDTO`; direct `go` statement는 미발견. 이 목록은 helper 내부의 background launch나 transaction 경계를 펼치지 않으므로 그 부분은 P/U다.
- 종합 의미 판정: **P/U**; route/auth와 위에서 직접 확인한 branch·shape는 C지만, 모든 helper·derived response·중첩 DTO·외부 효과까지 완결됐다는 뜻은 아니다.

<a id="schema-post-api-tasks"></a>
## `POST /api/tasks`

구현: `createTask` · `server/server.go` · [정확한 handler 근거](evidence:handler-post-api-tasks)

### 요청

| 위치/field | wire type | 필수·null | 기본/검증 |
|---|---|---|---|
| auth credential | Bearer header / `artex_token` cookie / query `token` | 세 위치 중 하나에 유효 JWT 필수 | `extractToken` 우선순위: header→cookie→query; query fallback은 SSE로 제한되지 않음 |
| body `name` | `string` (`string`) | 요구 여부 미확정(P); null/absent 구별 안 됨 | `Go zero value; 별도 기본은 handler/helper 확인` |
| body `category_id` | `integer` (`*int64`) | 요구 여부 미확정(P); absent/null→nil; null→nil | `nil` |
| body `description` | `string` (`string`) | 선택; null/absent 구별 안 됨 | `누락/공백이면 "未命名任务"로 기본화` |
| body `goal` | `string` (`string`) | 요구 여부 미확정(P); null/absent 구별 안 됨 | `Go zero value; 별도 기본은 handler/helper 확인` |
| body `llm_profile_id` | `integer` (`*int64`) | 선택; null→nil | `선택 alias; 제공 시 list가 비었을 때 chain으로 변환` |
| body `llm_profile_ids` | `array[integer]` (`[]int64`) | 선택; null/absent 구별 안 됨 | `빈 list 허용; active configuration을 사용할 수 있음` |
| body `source_task_ids` | `array[string]` (`[]string`) | 요구 여부 미확정(P); absent/null→nil; null/absent 구별 안 됨 | `len(req.SourceTaskIDs) > db.MaxTaskSourceCount` |
| body `company_ids` | `array[integer]` (`[]int64`) | 요구 여부 미확정(P); absent/null→nil; null/absent 구별 안 됨 | `req.CompanyIDs = companyIDs` |
| body `timeout_seconds` | `integer` (`int`) | 선택; null/absent 구별 안 됨 | `음수는 오류가 아니라 0(무제한)으로 정규화` |
| body `plan_heartbeat_seconds` | `integer` (`int`) | 요구 여부 미확정(P); null/absent 구별 안 됨 | `Go zero value; 별도 기본은 handler/helper 확인` |
| body `seed_first_intent` | `boolean` (`*bool`) | 요구 여부 미확정(P); absent/null→nil; null→nil | `nil` |
| body `coverage_enabled` | `boolean` (`*bool`) | 요구 여부 미확정(P); absent/null→nil; null→nil | `nil` |
| body `intercept_rules` | [array[taskInterceptRuleReq]](#type-server-taskinterceptrulereq) (`[]taskInterceptRuleReq`) | 요구 여부 미확정(P); absent/null→nil; null/absent 구별 안 됨 | `nil` |

요청 판정: **C/P** · unknown field 거부 미설정.
직접 validation: `len(req.LLMProfileIDs) == 0 && req.LLMProfileID != nil` · `len(req.SourceTaskIDs) > db.MaxTaskSourceCount` · `req.TimeoutSeconds < 0` · `strings.TrimSpace(req.Description) == ""`
직접 parse/default: `strconv.ParseInt(strings.TrimSpace(raw), 10, 64)`

### 응답·오류

| 종류 | status/body | 해석 상태 |
|---|---|---|
| success/variant | `201` · `taskDTO(t, s.resolvedTaskStatus(t))` | P/U · `helper result` |
| error | `400` · `{error:string}` · `"bad json: " + err.Error()` / `err.Error()` / `fmt.Sprintf("关联任务最多选择 %d 个", db.MaxTaskSourceCount)` / `"关联任务 id 无效或重复"` / `fmt.Sprintf("关联任务 #%d 不存在", id)` / `fmt.Sprintf("关联企业无效：最多选择 %d 个有效企业", db.MaxTaskCompanyCount)` / `"任务级拦截规则无效：" + err.Error()` / `"任务分类不存在或无效"` / `"关联企业不存在或无效"` | C for direct writeErr; error helper 내부는 P |
| error | `401` · `{error:string}` · `"未授权"` / `"token 无效或已过期"` | C for direct writeErr; error helper 내부는 P |
| error | `500` · `{error:string}` · `err.Error()` | C for direct writeErr; error helper 내부는 P |

응답 schema 판정: **P** · top-level key는 직접 추출했지만 derived/helper/opaque 또는 nested type이 남음.

### 목록·동시성·완료

- Pagination/filter/sort: 등록된 직접 query key 중 표준 paging/filter key 없음.
- Idempotency/concurrency: Idempotency-Key 없음; unique constraint 또는 상태 check가 있는 경우만 중복을 제한(P).
- Side effect/completion: task/exploration 저장 후 engine launch; `201`은 탐색 완료가 아님.
- 직접 협력자/실행 단서: `buildTaskInterceptRules`, `db.NormalizeTaskCompanyIDs`, `err.Error`, `json.NewDecoder`, `json.NewDecoder(r.Body).Decode`, `log.Printf`, `s.m.CreateTaskWithOptions`, `s.m.Task`, `taskDTO`; direct `go` statement는 미발견. 이 목록은 helper 내부의 background launch나 transaction 경계를 펼치지 않으므로 그 부분은 P/U다.
- 종합 의미 판정: **P/U**; route/auth와 위에서 직접 확인한 branch·shape는 C지만, 모든 helper·derived response·중첩 DTO·외부 효과까지 완결됐다는 뜻은 아니다.

<a id="schema-get-api-task-categories"></a>
## `GET /api/task-categories`

구현: `pgListTaskCategories` · `server/task_categories.go` · [정확한 handler 근거](evidence:handler-get-api-task-categories)

### 요청

| 위치/field | wire type | 필수·null | 기본/검증 |
|---|---|---|---|
| auth credential | Bearer header / `artex_token` cookie / query `token` | 세 위치 중 하나에 유효 JWT 필수 | `extractToken` 우선순위: header→cookie→query; query fallback은 SSE로 제한되지 않음 |
| body | 없음(직접 decoder/form read 미발견) | 해당 없음 | 해당 없음 |

요청 판정: **C/P** · unknown field 거부 미설정.
직접 validation: `err != nil` · `pg == nil`

### 응답·오류

| 종류 | status/body | 해석 상태 |
|---|---|---|
| success/variant | `200` · `categories:derived` | P/U · `static composite` |
| error | `401` · `{error:string}` · `"未授权"` / `"token 无效或已过期"` | C for direct writeErr; error helper 내부는 P |
| error | `500` · `{error:string}` · `err.Error()` | C for direct writeErr; error helper 내부는 P |
| error | `503` · `{error:string}` · `"PostgreSQL 不可用"` | C for direct writeErr; error helper 내부는 P |

응답 schema 판정: **P** · top-level key는 직접 추출했지만 derived/helper/opaque 또는 nested type이 남음.

### 목록·동시성·완료

- Pagination/filter/sort: 등록된 직접 query key 중 표준 paging/filter key 없음.
- Idempotency/concurrency: read/stream 요청; stream 재연결은 cursor 계약에 따름.
- Side effect/completion: 직접 조회/파일 전송 완료; 외부 조회가 있으면 응답 전 handler 결과만 보장.
- 직접 협력자/실행 단서: `err.Error`, `pg.ListTaskCategories`; direct `go` statement는 미발견. 이 목록은 helper 내부의 background launch나 transaction 경계를 펼치지 않으므로 그 부분은 P/U다.
- 종합 의미 판정: **P/U**; route/auth와 위에서 직접 확인한 branch·shape는 C지만, 모든 helper·derived response·중첩 DTO·외부 효과까지 완결됐다는 뜻은 아니다.

<a id="schema-post-api-task-categories"></a>
## `POST /api/task-categories`

구현: `pgCreateTaskCategory` · `server/task_categories.go` · [정확한 handler 근거](evidence:handler-post-api-task-categories)

### 요청

| 위치/field | wire type | 필수·null | 기본/검증 |
|---|---|---|---|
| auth credential | Bearer header / `artex_token` cookie / query `token` | 세 위치 중 하나에 유효 JWT 필수 | `extractToken` 우선순위: header→cookie→query; query fallback은 SSE로 제한되지 않음 |
| body `name` | `string` (`*string`) | 필수(직접 거부 branch 확인); null→nil | `decodeTaskCategoryRequest가 nil/trim-empty를 400으로 거부` |

요청 body 상한: **16 KiB** (`16384` bytes) · source `server/task_categories.go::decode/direct category request` · 초과/decoder 경로 `413 "请求正文过大"`.

요청 판정: **C/P** · unknown field 거부 미설정.
공유 decoder/helper: `decodeTaskCategoryRequest`; helper 내부 size/presence validation은 같은 source evidence에서 확인하며 operation 단독 AST 판정은 P.

### 응답·오류

| 종류 | status/body | 해석 상태 |
|---|---|---|
| success/variant | `201` · `category` | P/U · `derived variable` |
| error | `400` · `{error:string}` · `err.Error()` / `decode/name/length error` | C for direct writeErr; error helper 내부는 P |
| error | `401` · `{error:string}` · `"未授权"` / `"token 无效或已过期"` | C for direct writeErr; error helper 내부는 P |
| error | `404` · `{error:string}` · `err.Error()` | C for direct writeErr; error helper 내부는 P |
| error | `409` · `{error:string}` · `"分类名称已存在"` | C for direct writeErr; error helper 내부는 P |
| error | `413` · `{error:string}` · `"请求正文过大"` | C for direct writeErr; error helper 내부는 P |
| error | `500` · `{error:string}` · `err.Error()` | C for direct writeErr; error helper 내부는 P |
| error | `503` · `{error:string}` · `"PostgreSQL 不可用"` | C for direct writeErr; error helper 내부는 P |

공통 helper 오류 분기: `writeTaskCategoryError` (`server/task_categories.go::writeTaskCategoryError`: 400/409/404/500), `decodeTaskCategoryRequest` (`server/task_categories.go::decodeTaskCategoryRequest`: 400). Status union은 helper control-flow를 정적으로 펼친 P이며 runtime DB 결과는 관찰하지 않았다.

응답 schema 판정: **P** · top-level key는 직접 추출했지만 derived/helper/opaque 또는 nested type이 남음.

### 목록·동시성·완료

- Pagination/filter/sort: 등록된 직접 query key 중 표준 paging/filter key 없음.
- Idempotency/concurrency: Idempotency-Key 없음; unique constraint 또는 상태 check가 있는 경우만 중복을 제한(P).
- Side effect/completion: handler의 직접 mutation/호출 후 응답; 외부 전달·agent 실행이 파생되면 별도 상태 확인(P).
- 직접 협력자/실행 단서: `decodeTaskCategoryRequest`, `pg.CreateTaskCategory`, `writeTaskCategoryError`; direct `go` statement는 미발견. 이 목록은 helper 내부의 background launch나 transaction 경계를 펼치지 않으므로 그 부분은 P/U다.
- 종합 의미 판정: **P/U**; route/auth와 위에서 직접 확인한 branch·shape는 C지만, 모든 helper·derived response·중첩 DTO·외부 효과까지 완결됐다는 뜻은 아니다.

<a id="schema-patch-api-task-categories-id"></a>
## `PATCH /api/task-categories/{id}`

구현: `pgRenameTaskCategory` · `server/task_categories.go` · [정확한 handler 근거](evidence:handler-patch-api-task-categories-id)

### 요청

| 위치/field | wire type | 필수·null | 기본/검증 |
|---|---|---|---|
| path `id` | `string` | 필수 | handler의 parse/range validation; 미해석 helper는 P |
| auth credential | Bearer header / `artex_token` cookie / query `token` | 세 위치 중 하나에 유효 JWT 필수 | `extractToken` 우선순위: header→cookie→query; query fallback은 SSE로 제한되지 않음 |
| body `name` | `string` (`*string`) | 필수(직접 거부 branch 확인); null→nil | `decodeTaskCategoryRequest가 nil/trim-empty를 400으로 거부` |

요청 body 상한: **16 KiB** (`16384` bytes) · source `server/task_categories.go::decode/direct category request` · 초과/decoder 경로 `413 "请求正文过大"`.

요청 판정: **C/P** · unknown field 거부 미설정.
공유 decoder/helper: `decodeTaskCategoryRequest`; helper 내부 size/presence validation은 같은 source evidence에서 확인하며 operation 단독 AST 판정은 P.

### 응답·오류

| 종류 | status/body | 해석 상태 |
|---|---|---|
| success/variant | `200` · `category` | P/U · `derived variable` |
| error | `400` · `{error:string}` · `"bad task category id"` / `err.Error()` / `decode/name/length error` | C for direct writeErr; error helper 내부는 P |
| error | `401` · `{error:string}` · `"未授权"` / `"token 无效或已过期"` | C for direct writeErr; error helper 내부는 P |
| error | `404` · `{error:string}` · `err.Error()` | C for direct writeErr; error helper 내부는 P |
| error | `409` · `{error:string}` · `"分类名称已存在"` | C for direct writeErr; error helper 내부는 P |
| error | `413` · `{error:string}` · `"请求正文过大"` | C for direct writeErr; error helper 내부는 P |
| error | `500` · `{error:string}` · `err.Error()` | C for direct writeErr; error helper 내부는 P |

공통 helper 오류 분기: `writeTaskCategoryError` (`server/task_categories.go::writeTaskCategoryError`: 400/409/404/500), `decodeTaskCategoryRequest` (`server/task_categories.go::decodeTaskCategoryRequest`: 400). Status union은 helper control-flow를 정적으로 펼친 P이며 runtime DB 결과는 관찰하지 않았다.

응답 schema 판정: **P** · top-level key는 직접 추출했지만 derived/helper/opaque 또는 nested type이 남음.

### 목록·동시성·완료

- Pagination/filter/sort: 등록된 직접 query key 중 표준 paging/filter key 없음.
- Idempotency/concurrency: Idempotency-Key/If-Match 없음; DB upsert/trigger/side effect 때문에 반복 의미를 handler별 확인(P).
- Side effect/completion: handler의 직접 mutation/호출 후 응답; 외부 전달·agent 실행이 파생되면 별도 상태 확인(P).
- 직접 협력자/실행 단서: `decodeTaskCategoryRequest`, `pathInt`, `s.m.RenameTaskCategory`, `writeTaskCategoryError`; direct `go` statement는 미발견. 이 목록은 helper 내부의 background launch나 transaction 경계를 펼치지 않으므로 그 부분은 P/U다.
- 종합 의미 판정: **P/U**; route/auth와 위에서 직접 확인한 branch·shape는 C지만, 모든 helper·derived response·중첩 DTO·외부 효과까지 완결됐다는 뜻은 아니다.

<a id="schema-delete-api-task-categories-id"></a>
## `DELETE /api/task-categories/{id}`

구현: `pgDeleteTaskCategory` · `server/task_categories.go` · [정확한 handler 근거](evidence:handler-delete-api-task-categories-id)

### 요청

| 위치/field | wire type | 필수·null | 기본/검증 |
|---|---|---|---|
| path `id` | `string` | 필수 | handler의 parse/range validation; 미해석 helper는 P |
| auth credential | Bearer header / `artex_token` cookie / query `token` | 세 위치 중 하나에 유효 JWT 필수 | `extractToken` 우선순위: header→cookie→query; query fallback은 SSE로 제한되지 않음 |
| body | 없음(직접 decoder/form read 미발견) | 해당 없음 | 해당 없음 |

요청 판정: **C/P** · unknown field 거부 미설정.
직접 validation: `!deleted` · `!ok` · `err != nil`

### 응답·오류

| 종류 | status/body | 해석 상태 |
|---|---|---|
| success/variant | `200` · `deleted:derived` | P/U · `static composite` |
| error | `400` · `{error:string}` · `"bad task category id"` / `err.Error()` | C for direct writeErr; error helper 내부는 P |
| error | `401` · `{error:string}` · `"未授权"` / `"token 无效或已过期"` | C for direct writeErr; error helper 내부는 P |
| error | `404` · `{error:string}` · `"task category not found"` / `err.Error()` | C for direct writeErr; error helper 내부는 P |
| error | `409` · `{error:string}` · `"分类名称已存在"` | C for direct writeErr; error helper 내부는 P |
| error | `500` · `{error:string}` · `err.Error()` | C for direct writeErr; error helper 내부는 P |

공통 helper 오류 분기: `writeTaskCategoryError` (`server/task_categories.go::writeTaskCategoryError`: 400/409/404/500). Status union은 helper control-flow를 정적으로 펼친 P이며 runtime DB 결과는 관찰하지 않았다.

응답 schema 판정: **P** · top-level key는 직접 추출했지만 derived/helper/opaque 또는 nested type이 남음.

### 목록·동시성·완료

- Pagination/filter/sort: 등록된 직접 query key 중 표준 paging/filter key 없음.
- Idempotency/concurrency: Idempotency-Key/If-Match 없음; 반복 시 응답/존재 검사가 달라질 수 있음.
- Side effect/completion: handler의 직접 mutation/호출 후 응답; 외부 전달·agent 실행이 파생되면 별도 상태 확인(P).
- 직접 협력자/실행 단서: `pathInt`, `s.m.DeleteTaskCategory`, `writeTaskCategoryError`; direct `go` statement는 미발견. 이 목록은 helper 내부의 background launch나 transaction 경계를 펼치지 않으므로 그 부분은 P/U다.
- 종합 의미 판정: **P/U**; route/auth와 위에서 직접 확인한 branch·shape는 C지만, 모든 helper·derived response·중첩 DTO·외부 효과까지 완결됐다는 뜻은 아니다.

<a id="schema-post-api-tasks-category-batch"></a>
## `POST /api/tasks/category/batch`

구현: `updateTasksCategoryBatch` · `server/task_categories.go` · [정확한 handler 근거](evidence:handler-post-api-tasks-category-batch)

### 요청

| 위치/field | wire type | 필수·null | 기본/검증 |
|---|---|---|---|
| auth credential | Bearer header / `artex_token` cookie / query `token` | 세 위치 중 하나에 유효 JWT 필수 | `extractToken` 우선순위: header→cookie→query; query fallback은 SSE로 제한되지 않음 |
| body `task_ids` | `array[string]` (`[]string`) | 필수(직접 거부 branch 확인); null/absent 구별 안 됨 | `normalizeBatchTaskIDs 뒤 0개 또는 상한 초과면 400` |
| body `category_id` | `object/JSON` (`json.RawMessage`) | 필수(직접 거부 branch 확인); absent→400; null→category clear | `RawMessage 길이 0(누락)은 400; 명시 null은 category clear` |

요청 body 상한: **16 KiB** (`16384` bytes) · source `server/task_categories.go::decode/direct category request` · 초과/decoder 경로 `413 "请求正文过大"`.

요청 판정: **C/P** · unknown field 거부 미설정.

### 응답·오류

| 종류 | status/body | 해석 상태 |
|---|---|---|
| success/variant | `200` · `items:derived`, `category:derived` | P/U · `static composite` |
| error | `400` · `{error:string}` · `err.Error()` / `fmt.Sprintf("task_ids 数量必须为 1-%d", db.MaxTaskCategoryBatchSize)` | C for direct writeErr; error helper 내부는 P |
| error | `401` · `{error:string}` · `"未授权"` / `"token 无效或已过期"` | C for direct writeErr; error helper 내부는 P |
| error | `404` · `{error:string}` · `err.Error()` | C for direct writeErr; error helper 내부는 P |
| error | `409` · `{error:string}` · `"分类名称已存在"` | C for direct writeErr; error helper 내부는 P |
| error | `413` · `{error:string}` · `"请求正文过大"` | C for direct writeErr; error helper 내부는 P |
| error | `500` · `{error:string}` · `err.Error()` | C for direct writeErr; error helper 내부는 P |

공통 helper 오류 분기: `writeTaskCategoryError` (`server/task_categories.go::writeTaskCategoryError`: 400/409/404/500). Status union은 helper control-flow를 정적으로 펼친 P이며 runtime DB 결과는 관찰하지 않았다.

응답 schema 판정: **P** · top-level key는 직접 추출했지만 derived/helper/opaque 또는 nested type이 남음.

### 목록·동시성·완료

- Pagination/filter/sort: 등록된 직접 query key 중 표준 paging/filter key 없음.
- Idempotency/concurrency: Idempotency-Key 없음; unique constraint 또는 상태 check가 있는 경우만 중복을 제한(P).
- Side effect/completion: handler의 직접 mutation/호출 후 응답; 외부 전달·agent 실행이 파생되면 별도 상태 확인(P).
- 직접 협력자/실행 단서: `err.Error`, `errors.As`, `normalizeBatchTaskIDs`, `parseCategoryIDField`, `s.m.SetTasksCategory`, `s.m.Task`, `writeTaskCategoryError`; direct `go` statement는 미발견. 이 목록은 helper 내부의 background launch나 transaction 경계를 펼치지 않으므로 그 부분은 P/U다.
- 종합 의미 판정: **P/U**; route/auth와 위에서 직접 확인한 branch·shape는 C지만, 모든 helper·derived response·중첩 DTO·외부 효과까지 완결됐다는 뜻은 아니다.

<a id="schema-get-api-task-templates"></a>
## `GET /api/task-templates`

구현: `pgListTaskTemplates` · `server/task_templates.go` · [정확한 handler 근거](evidence:handler-get-api-task-templates)

### 요청

| 위치/field | wire type | 필수·null | 기본/검증 |
|---|---|---|---|
| auth credential | Bearer header / `artex_token` cookie / query `token` | 세 위치 중 하나에 유효 JWT 필수 | `extractToken` 우선순위: header→cookie→query; query fallback은 SSE로 제한되지 않음 |
| body | 없음(직접 decoder/form read 미발견) | 해당 없음 | 해당 없음 |

요청 판정: **C/P** · unknown field 거부 미설정.
직접 validation: `err != nil` · `pg == nil`

### 응답·오류

| 종류 | status/body | 해석 상태 |
|---|---|---|
| success/variant | `200` · `templates:derived` | P/U · `static composite` |
| error | `401` · `{error:string}` · `"未授权"` / `"token 无效或已过期"` | C for direct writeErr; error helper 내부는 P |
| error | `500` · `{error:string}` · `err.Error()` | C for direct writeErr; error helper 내부는 P |
| error | `503` · `{error:string}` · `"PostgreSQL 不可用"` | C for direct writeErr; error helper 내부는 P |

응답 schema 판정: **P** · top-level key는 직접 추출했지만 derived/helper/opaque 또는 nested type이 남음.

### 목록·동시성·완료

- Pagination/filter/sort: 등록된 직접 query key 중 표준 paging/filter key 없음.
- Idempotency/concurrency: read/stream 요청; stream 재연결은 cursor 계약에 따름.
- Side effect/completion: 직접 조회/파일 전송 완료; 외부 조회가 있으면 응답 전 handler 결과만 보장.
- 직접 협력자/실행 단서: `err.Error`, `pg.ListTaskTemplates`; direct `go` statement는 미발견. 이 목록은 helper 내부의 background launch나 transaction 경계를 펼치지 않으므로 그 부분은 P/U다.
- 종합 의미 판정: **P/U**; route/auth와 위에서 직접 확인한 branch·shape는 C지만, 모든 helper·derived response·중첩 DTO·외부 효과까지 완결됐다는 뜻은 아니다.

<a id="schema-post-api-task-templates"></a>
## `POST /api/task-templates`

구현: `pgCreateTaskTemplate` · `server/task_templates.go` · [정확한 handler 근거](evidence:handler-post-api-task-templates)

### 요청

| 위치/field | wire type | 필수·null | 기본/검증 |
|---|---|---|---|
| auth credential | Bearer header / `artex_token` cookie / query `token` | 세 위치 중 하나에 유효 JWT 필수 | `extractToken` 우선순위: header→cookie→query; query fallback은 SSE로 제한되지 않음 |
| body `name` | `string` (`*string`) | 필수(직접 거부 branch 확인); null→nil | `normalizeTaskTemplateInput/validateTaskTemplateRequest가 nil·trim-empty를 거부` |
| body `description` | `string` (`*string`) | 필수(직접 거부 branch 확인); null→nil | `normalizeTaskTemplateInput/validateTaskTemplateRequest가 nil·trim-empty를 거부` |
| body `goal` | `string` (`*string`) | 필수(직접 거부 branch 확인); null→nil | `normalizeTaskTemplateInput/validateTaskTemplateRequest가 nil·trim-empty를 거부` |
| body `category_id` | `integer` (`*int64`) | 요구 여부 미확정(P); absent/null→nil; null→nil | `nil` |
| body `intercept_rules` | [array[taskInterceptRuleReq]](#type-server-taskinterceptrulereq) (`[]taskInterceptRuleReq`) | 요구 여부 미확정(P); absent/null→nil; null/absent 구별 안 됨 | `nil` |

요청 body 상한: **512 KiB** (`524288` bytes) · source `server/task_templates.go::decodeTaskTemplateRequest` · 초과/decoder 경로 `413 "请求正文过大"`.

요청 판정: **C/P** · unknown field 거부 미설정.
공유 decoder/helper: `decodeTaskTemplateRequest`; helper 내부 size/presence validation은 같은 source evidence에서 확인하며 operation 단독 AST 판정은 P.

### 응답·오류

| 종류 | status/body | 해석 상태 |
|---|---|---|
| success/variant | `201` · `template` | P/U · `derived variable` |
| error | `400` · `{error:string}` · `err.Error()` / `"拦截/允许规则无效：" + err.Error()` | C for direct writeErr; error helper 내부는 P |
| error | `401` · `{error:string}` · `"未授权"` / `"token 无效或已过期"` | C for direct writeErr; error helper 내부는 P |
| error | `404` · `{error:string}` · `"task template not found"` | C for direct writeErr; error helper 내부는 P |
| error | `409` · `{error:string}` · `"模板名称已存在"` | C for direct writeErr; error helper 내부는 P |
| error | `413` · `{error:string}` · `"请求正文过大"` | C for direct writeErr; error helper 내부는 P |
| error | `500` · `{error:string}` · `err.Error()` | C for direct writeErr; error helper 내부는 P |
| error | `503` · `{error:string}` · `"PostgreSQL 不可用"` | C for direct writeErr; error helper 내부는 P |

공통 helper 오류 분기: `writeTaskTemplateErr` (`server/task_templates.go::writeTaskTemplateErr`: 400/409/404/500). Status union은 helper control-flow를 정적으로 펼친 P이며 runtime DB 결과는 관찰하지 않았다.

응답 schema 판정: **P** · top-level key는 직접 추출했지만 derived/helper/opaque 또는 nested type이 남음.

### 목록·동시성·완료

- Pagination/filter/sort: 등록된 직접 query key 중 표준 paging/filter key 없음.
- Idempotency/concurrency: Idempotency-Key 없음; unique constraint 또는 상태 check가 있는 경우만 중복을 제한(P).
- Side effect/completion: handler의 직접 mutation/호출 후 응답; 외부 전달·agent 실행이 파생되면 별도 상태 확인(P).
- 직접 협력자/실행 단서: `buildTaskInterceptRules`, `decodeTaskTemplateRequest`, `err.Error`, `pg.CreateTaskTemplate`, `stringValue`, `validateTaskTemplateRequest`, `writeTaskTemplateErr`; direct `go` statement는 미발견. 이 목록은 helper 내부의 background launch나 transaction 경계를 펼치지 않으므로 그 부분은 P/U다.
- 종합 의미 판정: **P/U**; route/auth와 위에서 직접 확인한 branch·shape는 C지만, 모든 helper·derived response·중첩 DTO·외부 효과까지 완결됐다는 뜻은 아니다.

<a id="schema-patch-api-task-templates-id"></a>
## `PATCH /api/task-templates/{id}`

구현: `pgUpdateTaskTemplate` · `server/task_templates.go` · [정확한 handler 근거](evidence:handler-patch-api-task-templates-id)

### 요청

| 위치/field | wire type | 필수·null | 기본/검증 |
|---|---|---|---|
| path `id` | `string` | 필수 | handler의 parse/range validation; 미해석 helper는 P |
| auth credential | Bearer header / `artex_token` cookie / query `token` | 세 위치 중 하나에 유효 JWT 필수 | `extractToken` 우선순위: header→cookie→query; query fallback은 SSE로 제한되지 않음 |
| body `name` | `string` (`*string`) | 개별 선택(그룹 중 하나 필수); null→nil | `name/description/goal/category_id/intercept_rules 중 하나 필요` |
| body `description` | `string` (`*string`) | 개별 선택(그룹 중 하나 필수); null→nil | `name/description/goal/category_id/intercept_rules 중 하나 필요` |
| body `goal` | `string` (`*string`) | 개별 선택(그룹 중 하나 필수); null→nil | `name/description/goal/category_id/intercept_rules 중 하나 필요` |
| body `category_id` | `integer` (`*int64`) | 개별 선택(그룹 중 하나 필수); absent→변경 없음; null→category clear | `name/description/goal/category_id/intercept_rules 중 하나 필요; 제공 field는 helper가 검증` |
| body `intercept_rules` | [array[taskInterceptRuleReq]](#type-server-taskinterceptrulereq) (`[]taskInterceptRuleReq`) | 개별 선택(그룹 중 하나 필수); absent→변경 없음; null→빈 rule list로 clear | `name/description/goal/category_id/intercept_rules 중 하나 필요; 제공 field는 helper가 검증` |

요청 body 상한: **512 KiB** (`524288` bytes) · source `server/task_templates.go::decodeTaskTemplateRequest` · 초과/decoder 경로 `413 "请求正文过大"`.

요청 판정: **C/P** · unknown field 거부 미설정.
직접 validation: `req.Name == nil && req.Description == nil && req.Goal == nil && !catPresent && !rulesPresent`
공유 decoder/helper: `decodeTaskTemplateRequest`; helper 내부 size/presence validation은 같은 source evidence에서 확인하며 operation 단독 AST 판정은 P.

### 응답·오류

| 종류 | status/body | 해석 상태 |
|---|---|---|
| success/variant | `200` · `template` | P/U · `derived variable` |
| error | `400` · `{error:string}` · `"bad task template id"` / `err.Error()` / `"至少需要提供 name、description、goal、category_id 或 intercept_rules"` / `"拦截/允许规则无效：" + err.Error()` | C for direct writeErr; error helper 내부는 P |
| error | `401` · `{error:string}` · `"未授权"` / `"token 无效或已过期"` | C for direct writeErr; error helper 내부는 P |
| error | `404` · `{error:string}` · `"task template not found"` | C for direct writeErr; error helper 내부는 P |
| error | `409` · `{error:string}` · `"模板名称已存在"` | C for direct writeErr; error helper 내부는 P |
| error | `413` · `{error:string}` · `"请求正文过大"` | C for direct writeErr; error helper 내부는 P |
| error | `500` · `{error:string}` · `err.Error()` | C for direct writeErr; error helper 내부는 P |
| error | `503` · `{error:string}` · `"PostgreSQL 不可用"` | C for direct writeErr; error helper 내부는 P |

공통 helper 오류 분기: `writeTaskTemplateErr` (`server/task_templates.go::writeTaskTemplateErr`: 400/409/404/500). Status union은 helper control-flow를 정적으로 펼친 P이며 runtime DB 결과는 관찰하지 않았다.

응답 schema 판정: **P** · top-level key는 직접 추출했지만 derived/helper/opaque 또는 nested type이 남음.

### 목록·동시성·완료

- Pagination/filter/sort: 등록된 직접 query key 중 표준 paging/filter key 없음.
- Idempotency/concurrency: Idempotency-Key/If-Match 없음; DB upsert/trigger/side effect 때문에 반복 의미를 handler별 확인(P).
- Side effect/completion: handler의 직접 mutation/호출 후 응답; 외부 전달·agent 실행이 파생되면 별도 상태 확인(P).
- 직접 협력자/실행 단서: `buildTaskInterceptRules`, `decodeTaskTemplateRequest`, `err.Error`, `pathInt`, `pg.PatchTaskTemplate`, `validateTaskTemplateRequest`, `writeTaskTemplateErr`; direct `go` statement는 미발견. 이 목록은 helper 내부의 background launch나 transaction 경계를 펼치지 않으므로 그 부분은 P/U다.
- 종합 의미 판정: **P/U**; route/auth와 위에서 직접 확인한 branch·shape는 C지만, 모든 helper·derived response·중첩 DTO·외부 효과까지 완결됐다는 뜻은 아니다.

<a id="schema-delete-api-task-templates-id"></a>
## `DELETE /api/task-templates/{id}`

구현: `pgDeleteTaskTemplate` · `server/task_templates.go` · [정확한 handler 근거](evidence:handler-delete-api-task-templates-id)

### 요청

| 위치/field | wire type | 필수·null | 기본/검증 |
|---|---|---|---|
| path `id` | `string` | 필수 | handler의 parse/range validation; 미해석 helper는 P |
| auth credential | Bearer header / `artex_token` cookie / query `token` | 세 위치 중 하나에 유효 JWT 필수 | `extractToken` 우선순위: header→cookie→query; query fallback은 SSE로 제한되지 않음 |
| body | 없음(직접 decoder/form read 미발견) | 해당 없음 | 해당 없음 |

요청 판정: **C/P** · unknown field 거부 미설정.
직접 validation: `!deleted` · `!ok` · `err != nil` · `pg == nil`

### 응답·오류

| 종류 | status/body | 해석 상태 |
|---|---|---|
| success/variant | `200` · `deleted:derived` | P/U · `static composite` |
| error | `400` · `{error:string}` · `"bad task template id"` | C for direct writeErr; error helper 내부는 P |
| error | `401` · `{error:string}` · `"未授权"` / `"token 无效或已过期"` | C for direct writeErr; error helper 내부는 P |
| error | `404` · `{error:string}` · `"task template not found"` | C for direct writeErr; error helper 내부는 P |
| error | `500` · `{error:string}` · `err.Error()` | C for direct writeErr; error helper 내부는 P |
| error | `503` · `{error:string}` · `"PostgreSQL 不可用"` | C for direct writeErr; error helper 내부는 P |

응답 schema 판정: **P** · top-level key는 직접 추출했지만 derived/helper/opaque 또는 nested type이 남음.

### 목록·동시성·완료

- Pagination/filter/sort: 등록된 직접 query key 중 표준 paging/filter key 없음.
- Idempotency/concurrency: Idempotency-Key/If-Match 없음; 반복 시 응답/존재 검사가 달라질 수 있음.
- Side effect/completion: handler의 직접 mutation/호출 후 응답; 외부 전달·agent 실행이 파생되면 별도 상태 확인(P).
- 직접 협력자/실행 단서: `err.Error`, `pathInt`, `pg.DeleteTaskTemplate`; direct `go` statement는 미발견. 이 목록은 helper 내부의 background launch나 transaction 경계를 펼치지 않으므로 그 부분은 P/U다.
- 종합 의미 판정: **P/U**; route/auth와 위에서 직접 확인한 branch·shape는 C지만, 모든 helper·derived response·중첩 DTO·외부 효과까지 완결됐다는 뜻은 아니다.

<a id="schema-get-api-tasks-id"></a>
## `GET /api/tasks/{id}`

구현: `getTask` · `server/server.go` · [정확한 handler 근거](evidence:handler-get-api-tasks-id)

### 요청

| 위치/field | wire type | 필수·null | 기본/검증 |
|---|---|---|---|
| path `id` | `string` | 필수 | handler의 parse/range validation; 미해석 helper는 P |
| auth credential | Bearer header / `artex_token` cookie / query `token` | 세 위치 중 하나에 유효 JWT 필수 | `extractToken` 우선순위: header→cookie→query; query fallback은 SSE로 제한되지 않음 |
| body | 없음(직접 decoder/form read 미발견) | 해당 없음 | 해당 없음 |

요청 판정: **C/P** · unknown field 거부 미설정.
직접 validation: `!ok`

### 응답·오류

| 종류 | status/body | 해석 상태 |
|---|---|---|
| success/variant | `200` · `dto` | P/U · `derived variable` |
| error | `401` · `{error:string}` · `"未授权"` / `"token 无效或已过期"` | C for direct writeErr; error helper 내부는 P |
| error | `404` · `{error:string}` · `"task not found"` | C for direct writeErr; error helper 내부는 P |

응답 schema 판정: **P** · top-level key는 직접 추출했지만 derived/helper/opaque 또는 nested type이 남음.

### 목록·동시성·완료

- Pagination/filter/sort: 등록된 직접 query key 중 표준 paging/filter key 없음.
- Idempotency/concurrency: read/stream 요청; stream 재연결은 cursor 계약에 따름.
- Side effect/completion: 직접 조회/파일 전송 완료; 외부 조회가 있으면 응답 전 handler 결과만 보장.
- 직접 협력자/실행 단서: `applyTaskArchiveBlocker`, `r.PathValue`, `s.m.PG`, `s.m.PG().TaskArchiveBlockers`, `s.m.Task`, `taskDTO`; direct `go` statement는 미발견. 이 목록은 helper 내부의 background launch나 transaction 경계를 펼치지 않으므로 그 부분은 P/U다.
- 종합 의미 판정: **P/U**; route/auth와 위에서 직접 확인한 branch·shape는 C지만, 모든 helper·derived response·중첩 DTO·외부 효과까지 완결됐다는 뜻은 아니다.

<a id="schema-patch-api-tasks-id"></a>
## `PATCH /api/tasks/{id}`

구현: `updateTaskMetadata` · `server/task_metadata.go` · [정확한 handler 근거](evidence:handler-patch-api-tasks-id)

### 요청

| 위치/field | wire type | 필수·null | 기본/검증 |
|---|---|---|---|
| path `id` | `string` | 필수 | handler의 parse/range validation; 미해석 helper는 P |
| auth credential | Bearer header / `artex_token` cookie / query `token` | 세 위치 중 하나에 유효 JWT 필수 | `extractToken` 우선순위: header→cookie→query; query fallback은 SSE로 제한되지 않음 |
| body `name` | `string` (`*string`) | 개별 선택(그룹 중 하나 필수); null→nil | `name/pinned 중 하나 필요` |
| body `pinned` | `boolean` (`*bool`) | 개별 선택(그룹 중 하나 필수); null→nil | `name/pinned 중 하나 필요` |

요청 body 상한: **16 KiB** (`16384` bytes) · source `server/task_metadata.go::updateTaskMetadata` · 초과/decoder 경로 `413 "请求正文过大"`.

요청 판정: **C/P** · unknown field 거부 미설정.
직접 validation: `request.Name != nil` · `request.Name == nil && request.Pinned == nil`

### 응답·오류

| 종류 | status/body | 해석 상태 |
|---|---|---|
| success/variant | `200` · `taskDTO(task, s.resolvedTaskStatus(task))` | P/U · `helper result` |
| error | `400` · `{error:string}` · `err.Error()` / `"至少需要提供 name 或 pinned"` / `"任务名称不能为空"` / `fmt.Sprintf("任务名称最多 %d 个字符", maxTaskNameRunes)` | C for direct writeErr; error helper 내부는 P |
| error | `401` · `{error:string}` · `"未授权"` / `"token 无效或已过期"` | C for direct writeErr; error helper 내부는 P |
| error | `404` · `{error:string}` · `"task not found"` | C for direct writeErr; error helper 내부는 P |
| error | `413` · `{error:string}` · `"请求正文过大"` | C for direct writeErr; error helper 내부는 P |
| error | `500` · `{error:string}` · `err.Error()` | C for direct writeErr; error helper 내부는 P |

응답 schema 판정: **P** · top-level key는 직접 추출했지만 derived/helper/opaque 또는 nested type이 남음.

### 목록·동시성·완료

- Pagination/filter/sort: 등록된 직접 query key 중 표준 paging/filter key 없음.
- Idempotency/concurrency: Idempotency-Key/If-Match 없음; DB upsert/trigger/side effect 때문에 반복 의미를 handler별 확인(P).
- Side effect/completion: handler의 직접 mutation/호출 후 응답; 외부 전달·agent 실행이 파생되면 별도 상태 확인(P).
- 직접 협력자/실행 단서: `err.Error`, `errors.As`, `r.PathValue`, `s.m.Task`, `s.m.UpdateTaskMetadata`, `taskDTO`, `utf8.RuneCountInString`; direct `go` statement는 미발견. 이 목록은 helper 내부의 background launch나 transaction 경계를 펼치지 않으므로 그 부분은 P/U다.
- 종합 의미 판정: **P/U**; route/auth와 위에서 직접 확인한 branch·shape는 C지만, 모든 helper·derived response·중첩 DTO·외부 효과까지 완결됐다는 뜻은 아니다.

<a id="schema-patch-api-tasks-id-category"></a>
## `PATCH /api/tasks/{id}/category`

구현: `updateTaskCategory` · `server/task_categories.go` · [정확한 handler 근거](evidence:handler-patch-api-tasks-id-category)

### 요청

| 위치/field | wire type | 필수·null | 기본/검증 |
|---|---|---|---|
| path `id` | `string` | 필수 | handler의 parse/range validation; 미해석 helper는 P |
| auth credential | Bearer header / `artex_token` cookie / query `token` | 세 위치 중 하나에 유효 JWT 필수 | `extractToken` 우선순위: header→cookie→query; query fallback은 SSE로 제한되지 않음 |
| body `category_id` | `object/JSON` (`json.RawMessage`) | 필수(직접 거부 branch 확인); absent→400; null→category clear | `RawMessage 길이 0(누락)은 400; 명시 null은 category clear` |

요청 body 상한: **16 KiB** (`16384` bytes) · source `server/task_categories.go::decode/direct category request` · 초과/decoder 경로 `400 err.Error()`.

요청 판정: **C/P** · unknown field 거부 미설정.

### 응답·오류

| 종류 | status/body | 해석 상태 |
|---|---|---|
| success/variant | `200` · `taskDTO(task, s.resolvedTaskStatus(task))` | P/U · `helper result` |
| error | `400` · `{error:string}` · `err.Error()` | C for direct writeErr; error helper 내부는 P |
| error | `401` · `{error:string}` · `"未授权"` / `"token 无效或已过期"` | C for direct writeErr; error helper 내부는 P |
| error | `404` · `{error:string}` · `"task not found"` / `err.Error()` | C for direct writeErr; error helper 내부는 P |
| error | `409` · `{error:string}` · `"分类名称已存在"` | C for direct writeErr; error helper 내부는 P |
| error | `500` · `{error:string}` · `err.Error()` | C for direct writeErr; error helper 내부는 P |

공통 helper 오류 분기: `writeTaskCategoryError` (`server/task_categories.go::writeTaskCategoryError`: 400/409/404/500). Status union은 helper control-flow를 정적으로 펼친 P이며 runtime DB 결과는 관찰하지 않았다.

응답 schema 판정: **P** · top-level key는 직접 추출했지만 derived/helper/opaque 또는 nested type이 남음.

### 목록·동시성·완료

- Pagination/filter/sort: 등록된 직접 query key 중 표준 paging/filter key 없음.
- Idempotency/concurrency: Idempotency-Key/If-Match 없음; DB upsert/trigger/side effect 때문에 반복 의미를 handler별 확인(P).
- Side effect/completion: handler의 직접 mutation/호출 후 응답; 외부 전달·agent 실행이 파생되면 별도 상태 확인(P).
- 직접 협력자/실행 단서: `err.Error`, `parseCategoryIDField`, `r.PathValue`, `s.m.SetTaskCategory`, `s.m.Task`, `taskDTO`, `writeTaskCategoryError`; direct `go` statement는 미발견. 이 목록은 helper 내부의 background launch나 transaction 경계를 펼치지 않으므로 그 부분은 P/U다.
- 종합 의미 판정: **P/U**; route/auth와 위에서 직접 확인한 branch·shape는 C지만, 모든 helper·derived response·중첩 DTO·외부 효과까지 완결됐다는 뜻은 아니다.

<a id="schema-get-api-tasks-id-intercept-rules"></a>
## `GET /api/tasks/{id}/intercept-rules`

구현: `taskInterceptListRules` · `server/task_intercept.go` · [정확한 handler 근거](evidence:handler-get-api-tasks-id-intercept-rules)

### 요청

| 위치/field | wire type | 필수·null | 기본/검증 |
|---|---|---|---|
| path `id` | `string` | 필수 | handler의 parse/range validation; 미해석 helper는 P |
| auth credential | Bearer header / `artex_token` cookie / query `token` | 세 위치 중 하나에 유효 JWT 필수 | `extractToken` 우선순위: header→cookie→query; query fallback은 SSE로 제한되지 않음 |
| body | 없음(직접 decoder/form read 미발견) | 해당 없음 | 해당 없음 |

요청 판정: **C/P** · unknown field 거부 미설정.
직접 validation: `!ok \|\| taskID <= 0` · `err != nil` · `pg == nil` · `rules == nil`

### 응답·오류

| 종류 | status/body | 해석 상태 |
|---|---|---|
| success/variant | `200` · `rules:derived` | P/U · `static composite` |
| error | `400` · `{error:string}` · `"bad task id"` | C for direct writeErr; error helper 내부는 P |
| error | `401` · `{error:string}` · `"未授权"` / `"token 无效或已过期"` | C for direct writeErr; error helper 내부는 P |
| error | `500` · `{error:string}` · `err.Error()` | C for direct writeErr; error helper 내부는 P |
| error | `503` · `{error:string}` · `"PostgreSQL 不可用"` | C for direct writeErr; error helper 내부는 P |

응답 schema 판정: **P** · top-level key는 직접 추출했지만 derived/helper/opaque 또는 nested type이 남음.

### 목록·동시성·완료

- Pagination/filter/sort: 등록된 직접 query key 중 표준 paging/filter key 없음.
- Idempotency/concurrency: read/stream 요청; stream 재연결은 cursor 계약에 따름.
- Side effect/completion: 직접 조회/파일 전송 완료; 외부 조회가 있으면 응답 전 handler 결과만 보장.
- 직접 협력자/실행 단서: `err.Error`, `pathInt`, `pg.Assets`, `pg.Assets().ListTaskInterceptRules`; direct `go` statement는 미발견. 이 목록은 helper 내부의 background launch나 transaction 경계를 펼치지 않으므로 그 부분은 P/U다.
- 종합 의미 판정: **P/U**; route/auth와 위에서 직접 확인한 branch·shape는 C지만, 모든 helper·derived response·중첩 DTO·외부 효과까지 완결됐다는 뜻은 아니다.

<a id="schema-post-api-tasks-id-intercept-rules"></a>
## `POST /api/tasks/{id}/intercept-rules`

구현: `taskInterceptCreateRule` · `server/task_intercept.go` · [정확한 handler 근거](evidence:handler-post-api-tasks-id-intercept-rules)

### 요청

| 위치/field | wire type | 필수·null | 기본/검증 |
|---|---|---|---|
| path `id` | `string` | 필수 | handler의 parse/range validation; 미해석 helper는 P |
| auth credential | Bearer header / `artex_token` cookie / query `token` | 세 위치 중 하나에 유효 JWT 필수 | `extractToken` 우선순위: header→cookie→query; query fallback은 SSE로 제한되지 않음 |
| body `enabled` | `boolean` (`bool`) | 요구 여부 미확정(P); null/absent 구별 안 됨 | `Go zero value; 별도 기본은 handler/helper 확인` |
| body `action` | `string` (`string`) | 요구 여부 미확정(P); null/absent 구별 안 됨 | `Go zero value; 별도 기본은 handler/helper 확인` |
| body `kind` | `string` (`string`) | 요구 여부 미확정(P); null/absent 구별 안 됨 | `Go zero value; 별도 기본은 handler/helper 확인` |
| body `pattern` | `string` (`string`) | 요구 여부 미확정(P); null/absent 구별 안 됨 | `Go zero value; 별도 기본은 handler/helper 확인` |
| body `note` | `string` (`string`) | 요구 여부 미확정(P); null/absent 구별 안 됨 | `Go zero value; 별도 기본은 handler/helper 확인` |

요청 판정: **C/P** · unknown field 거부 미설정.

### 응답·오류

| 종류 | status/body | 해석 상태 |
|---|---|---|
| success/variant | `200` · `rule` | P/U · `derived variable` |
| error | `400` · `{error:string}` · `"bad task id"` / `err.Error()` | C for direct writeErr; error helper 내부는 P |
| error | `401` · `{error:string}` · `"未授权"` / `"token 无效或已过期"` | C for direct writeErr; error helper 내부는 P |
| error | `500` · `{error:string}` · `err.Error()` | C for direct writeErr; error helper 내부는 P |
| error | `503` · `{error:string}` · `"PostgreSQL 不可用"` | C for direct writeErr; error helper 내부는 P |

응답 schema 판정: **P** · top-level key는 직접 추출했지만 derived/helper/opaque 또는 nested type이 남음.

### 목록·동시성·완료

- Pagination/filter/sort: 등록된 직접 query key 중 표준 paging/filter key 없음.
- Idempotency/concurrency: Idempotency-Key 없음; unique constraint 또는 상태 check가 있는 경우만 중복을 제한(P).
- Side effect/completion: handler의 직접 mutation/호출 후 응답; 외부 전달·agent 실행이 파생되면 별도 상태 확인(P).
- 직접 협력자/실행 단서: `err.Error`, `pathInt`, `pg.Assets`, `pg.Assets().CreateTaskInterceptRule`, `validateTaskInterceptRuleReq`; direct `go` statement는 미발견. 이 목록은 helper 내부의 background launch나 transaction 경계를 펼치지 않으므로 그 부분은 P/U다.
- 종합 의미 판정: **P/U**; route/auth와 위에서 직접 확인한 branch·shape는 C지만, 모든 helper·derived response·중첩 DTO·외부 효과까지 완결됐다는 뜻은 아니다.

<a id="schema-put-api-tasks-id-intercept-rules-rid"></a>
## `PUT /api/tasks/{id}/intercept-rules/{rid}`

구현: `taskInterceptUpdateRule` · `server/task_intercept.go` · [정확한 handler 근거](evidence:handler-put-api-tasks-id-intercept-rules-rid)

### 요청

| 위치/field | wire type | 필수·null | 기본/검증 |
|---|---|---|---|
| path `id` | `string` | 필수 | handler의 parse/range validation; 미해석 helper는 P |
| path `rid` | `string` | 필수 | handler의 parse/range validation; 미해석 helper는 P |
| auth credential | Bearer header / `artex_token` cookie / query `token` | 세 위치 중 하나에 유효 JWT 필수 | `extractToken` 우선순위: header→cookie→query; query fallback은 SSE로 제한되지 않음 |
| body `enabled` | `boolean` (`bool`) | 요구 여부 미확정(P); null/absent 구별 안 됨 | `Go zero value; 별도 기본은 handler/helper 확인` |
| body `action` | `string` (`string`) | 요구 여부 미확정(P); null/absent 구별 안 됨 | `Go zero value; 별도 기본은 handler/helper 확인` |
| body `kind` | `string` (`string`) | 요구 여부 미확정(P); null/absent 구별 안 됨 | `Go zero value; 별도 기본은 handler/helper 확인` |
| body `pattern` | `string` (`string`) | 요구 여부 미확정(P); null/absent 구별 안 됨 | `Go zero value; 별도 기본은 handler/helper 확인` |
| body `note` | `string` (`string`) | 요구 여부 미확정(P); null/absent 구별 안 됨 | `Go zero value; 별도 기본은 handler/helper 확인` |

요청 판정: **C/P** · unknown field 거부 미설정.

### 응답·오류

| 종류 | status/body | 해석 상태 |
|---|---|---|
| success/variant | `200` · `rule` | P/U · `derived variable` |
| error | `400` · `{error:string}` · `"bad task id"` / `"bad rule id"` / `err.Error()` | C for direct writeErr; error helper 내부는 P |
| error | `401` · `{error:string}` · `"未授权"` / `"token 无效或已过期"` | C for direct writeErr; error helper 내부는 P |
| error | `500` · `{error:string}` · `err.Error()` | C for direct writeErr; error helper 내부는 P |
| error | `503` · `{error:string}` · `"PostgreSQL 不可用"` | C for direct writeErr; error helper 내부는 P |

응답 schema 판정: **P** · top-level key는 직접 추출했지만 derived/helper/opaque 또는 nested type이 남음.

### 목록·동시성·완료

- Pagination/filter/sort: 등록된 직접 query key 중 표준 paging/filter key 없음.
- Idempotency/concurrency: Idempotency-Key/If-Match 없음; DB upsert/trigger/side effect 때문에 반복 의미를 handler별 확인(P).
- Side effect/completion: handler의 직접 mutation/호출 후 응답; 외부 전달·agent 실행이 파생되면 별도 상태 확인(P).
- 직접 협력자/실행 단서: `err.Error`, `pathInt`, `pg.Assets`, `pg.Assets().UpdateTaskInterceptRule`, `validateTaskInterceptRuleReq`; direct `go` statement는 미발견. 이 목록은 helper 내부의 background launch나 transaction 경계를 펼치지 않으므로 그 부분은 P/U다.
- 종합 의미 판정: **P/U**; route/auth와 위에서 직접 확인한 branch·shape는 C지만, 모든 helper·derived response·중첩 DTO·외부 효과까지 완결됐다는 뜻은 아니다.

<a id="schema-delete-api-tasks-id-intercept-rules-rid"></a>
## `DELETE /api/tasks/{id}/intercept-rules/{rid}`

구현: `taskInterceptDeleteRule` · `server/task_intercept.go` · [정확한 handler 근거](evidence:handler-delete-api-tasks-id-intercept-rules-rid)

### 요청

| 위치/field | wire type | 필수·null | 기본/검증 |
|---|---|---|---|
| path `id` | `string` | 필수 | handler의 parse/range validation; 미해석 helper는 P |
| path `rid` | `string` | 필수 | handler의 parse/range validation; 미해석 helper는 P |
| auth credential | Bearer header / `artex_token` cookie / query `token` | 세 위치 중 하나에 유효 JWT 필수 | `extractToken` 우선순위: header→cookie→query; query fallback은 SSE로 제한되지 않음 |
| body | 없음(직접 decoder/form read 미발견) | 해당 없음 | 해당 없음 |

요청 판정: **C/P** · unknown field 거부 미설정.
직접 validation: `!ok \|\| ruleID <= 0` · `!ok \|\| taskID <= 0` · `err != nil` · `pg == nil`

### 응답·오류

| 종류 | status/body | 해석 상태 |
|---|---|---|
| success/variant | `200` · `deleted:derived` | P/U · `static composite` |
| error | `400` · `{error:string}` · `"bad task id"` / `"bad rule id"` | C for direct writeErr; error helper 내부는 P |
| error | `401` · `{error:string}` · `"未授权"` / `"token 无效或已过期"` | C for direct writeErr; error helper 내부는 P |
| error | `500` · `{error:string}` · `err.Error()` | C for direct writeErr; error helper 내부는 P |
| error | `503` · `{error:string}` · `"PostgreSQL 不可用"` | C for direct writeErr; error helper 내부는 P |

응답 schema 판정: **P** · top-level key는 직접 추출했지만 derived/helper/opaque 또는 nested type이 남음.

### 목록·동시성·완료

- Pagination/filter/sort: 등록된 직접 query key 중 표준 paging/filter key 없음.
- Idempotency/concurrency: Idempotency-Key/If-Match 없음; 반복 시 응답/존재 검사가 달라질 수 있음.
- Side effect/completion: handler의 직접 mutation/호출 후 응답; 외부 전달·agent 실행이 파생되면 별도 상태 확인(P).
- 직접 협력자/실행 단서: `err.Error`, `pathInt`, `pg.Assets`, `pg.Assets().DeleteTaskInterceptRule`; direct `go` statement는 미발견. 이 목록은 helper 내부의 background launch나 transaction 경계를 펼치지 않으므로 그 부분은 P/U다.
- 종합 의미 판정: **P/U**; route/auth와 위에서 직접 확인한 branch·shape는 C지만, 모든 helper·derived response·중첩 DTO·외부 효과까지 완결됐다는 뜻은 아니다.

<a id="schema-post-api-tasks-id-intercept-rules-rid-toggle"></a>
## `POST /api/tasks/{id}/intercept-rules/{rid}/toggle`

구현: `taskInterceptToggleRule` · `server/task_intercept.go` · [정확한 handler 근거](evidence:handler-post-api-tasks-id-intercept-rules-rid-toggle)

### 요청

| 위치/field | wire type | 필수·null | 기본/검증 |
|---|---|---|---|
| path `id` | `string` | 필수 | handler의 parse/range validation; 미해석 helper는 P |
| path `rid` | `string` | 필수 | handler의 parse/range validation; 미해석 helper는 P |
| auth credential | Bearer header / `artex_token` cookie / query `token` | 세 위치 중 하나에 유효 JWT 필수 | `extractToken` 우선순위: header→cookie→query; query fallback은 SSE로 제한되지 않음 |
| body `enabled` | `boolean` (`bool`) | 요구 여부 미확정(P); null/absent 구별 안 됨 | `Go zero value; 별도 기본은 handler/helper 확인` |

요청 판정: **C/P** · unknown field 거부 미설정.

### 응답·오류

| 종류 | status/body | 해석 상태 |
|---|---|---|
| success/variant | `200` · `ok:boolean`, `enabled:expression` | P/U · `static composite` |
| error | `400` · `{error:string}` · `"bad task id"` / `"bad rule id"` / `err.Error()` | C for direct writeErr; error helper 내부는 P |
| error | `401` · `{error:string}` · `"未授权"` / `"token 无效或已过期"` | C for direct writeErr; error helper 내부는 P |
| error | `500` · `{error:string}` · `err.Error()` | C for direct writeErr; error helper 내부는 P |
| error | `503` · `{error:string}` · `"PostgreSQL 不可用"` | C for direct writeErr; error helper 내부는 P |

응답 schema 판정: **P** · top-level key는 직접 추출했지만 derived/helper/opaque 또는 nested type이 남음.

### 목록·동시성·완료

- Pagination/filter/sort: 등록된 직접 query key 중 표준 paging/filter key 없음.
- Idempotency/concurrency: Idempotency-Key 없음; unique constraint 또는 상태 check가 있는 경우만 중복을 제한(P).
- Side effect/completion: handler의 직접 mutation/호출 후 응답; 외부 전달·agent 실행이 파생되면 별도 상태 확인(P).
- 직접 협력자/실행 단서: `err.Error`, `pathInt`, `pg.Assets`, `pg.Assets().ToggleTaskInterceptRule`; direct `go` statement는 미발견. 이 목록은 helper 내부의 background launch나 transaction 경계를 펼치지 않으므로 그 부분은 P/U다.
- 종합 의미 판정: **P/U**; route/auth와 위에서 직접 확인한 branch·shape는 C지만, 모든 helper·derived response·중첩 DTO·외부 효과까지 완결됐다는 뜻은 아니다.

<a id="schema-post-api-tasks-control-batch"></a>
## `POST /api/tasks/control/batch`

구현: `controlTasksBatch` · `server/task_control.go` · [정확한 handler 근거](evidence:handler-post-api-tasks-control-batch)

### 요청

| 위치/field | wire type | 필수·null | 기본/검증 |
|---|---|---|---|
| auth credential | Bearer header / `artex_token` cookie / query `token` | 세 위치 중 하나에 유효 JWT 필수 | `extractToken` 우선순위: header→cookie→query; query fallback은 SSE로 제한되지 않음 |
| body `task_ids` | `array[string]` (`[]string`) | 필수(직접 거부 branch 확인); null/absent 구별 안 됨 | `정규화 뒤 1..100개가 아니면 400` |
| body `action` | `string` (`string`) | 필수(직접 거부 branch 확인); null/absent 구별 안 됨 | `pause/resume가 아니면 400` |

요청 body 상한: **256 KiB** (`262144` bytes) · source `server/task_control.go::controlTasksBatch` · 초과/decoder 경로 `400 "bad json: " + err.Error()`.

요청 판정: **C/P** · unknown field 거부 미설정.
직접 validation: `req.Action != "pause" && req.Action != "resume"`

### 응답·오류

| 종류 | status/body | 해석 상태 |
|---|---|---|
| success/variant | `200` · `items:derived` | P/U · `static composite` |
| error | `400` · `{error:string}` · `"bad json: " + err.Error()` / `"action must be pause\|resume"` / `fmt.Sprintf("task_ids 数量必须为 1-%d", maxBatchControlIDs)` | C for direct writeErr; error helper 내부는 P |
| error | `401` · `{error:string}` · `"未授权"` / `"token 无效或已过期"` | C for direct writeErr; error helper 내부는 P |

응답 schema 판정: **P** · top-level key는 직접 추출했지만 derived/helper/opaque 또는 nested type이 남음.

### 목록·동시성·완료

- Pagination/filter/sort: 등록된 직접 query key 중 표준 paging/filter key 없음.
- Idempotency/concurrency: Idempotency-Key 없음; unique constraint 또는 상태 check가 있는 경우만 중복을 제한(P).
- Side effect/completion: handler의 직접 mutation/호출 후 응답; 외부 전달·agent 실행이 파생되면 별도 상태 확인(P).
- 직접 협력자/실행 단서: `err.Error`, `json.NewDecoder`, `json.NewDecoder(r.Body).Decode`, `normalizeBatchTaskIDs`, `s.m.Task`; direct `go` statement는 미발견. 이 목록은 helper 내부의 background launch나 transaction 경계를 펼치지 않으므로 그 부분은 P/U다.
- 종합 의미 판정: **P/U**; route/auth와 위에서 직접 확인한 branch·shape는 C지만, 모든 helper·derived response·중첩 DTO·외부 효과까지 완결됐다는 뜻은 아니다.

<a id="schema-get-api-task-archives"></a>
## `GET /api/task-archives`

구현: `listTaskArchives` · `server/task_archives.go` · [정확한 handler 근거](evidence:handler-get-api-task-archives)

### 요청

| 위치/field | wire type | 필수·null | 기본/검증 |
|---|---|---|---|
| query `page` | `string` wire | 선택 | handler/helper 변환·default 미전개(P) |
| query `q` | `string` wire | 선택 | handler/helper 변환·default 미전개(P) |
| query `size` | `string` wire | 선택 | handler/helper 변환·default 미전개(P) |
| query `state` | `string` wire | 선택 | handler/helper 변환·default 미전개(P) |
| auth credential | Bearer header / `artex_token` cookie / query `token` | 세 위치 중 하나에 유효 JWT 필수 | `extractToken` 우선순위: header→cookie→query; query fallback은 SSE로 제한되지 않음 |
| body | 없음(직접 decoder/form read 미발견) | 해당 없음 | 해당 없음 |

요청 판정: **C/P** · unknown field 거부 미설정.
직접 validation: `err != nil`

### 응답·오류

| 종류 | status/body | 해석 상태 |
|---|---|---|
| success/variant | `200` · `result` | P/U · `derived variable` |
| error | `401` · `{error:string}` · `"未授权"` / `"token 无效或已过期"` | C for direct writeErr; error helper 내부는 P |
| error | `500` · `{error:string}` · `err.Error()` | C for direct writeErr; error helper 내부는 P |

응답 schema 판정: **P** · top-level key는 직접 추출했지만 derived/helper/opaque 또는 nested type이 남음.

### 목록·동시성·완료

- Pagination/filter/sort: `page`, `q`, `size`.
- Idempotency/concurrency: read/stream 요청; stream 재연결은 cursor 계약에 따름.
- Side effect/completion: 직접 조회/파일 전송 완료; 외부 조회가 있으면 응답 전 handler 결과만 보장.
- 직접 협력자/실행 단서: `err.Error`, `r.URL.Query`, `r.URL.Query().Get`, `s.m.pg.ListTaskArchives`; direct `go` statement는 미발견. 이 목록은 helper 내부의 background launch나 transaction 경계를 펼치지 않으므로 그 부분은 P/U다.
- 종합 의미 판정: **P/U**; route/auth와 위에서 직접 확인한 branch·shape는 C지만, 모든 helper·derived response·중첩 DTO·외부 효과까지 완결됐다는 뜻은 아니다.

<a id="schema-get-api-task-archives-id"></a>
## `GET /api/task-archives/{id}`

구현: `getTaskArchive` · `server/task_archives.go` · [정확한 handler 근거](evidence:handler-get-api-task-archives-id)

### 요청

| 위치/field | wire type | 필수·null | 기본/검증 |
|---|---|---|---|
| path `id` | `string` | 필수 | handler의 parse/range validation; 미해석 helper는 P |
| auth credential | Bearer header / `artex_token` cookie / query `token` | 세 위치 중 하나에 유효 JWT 필수 | `extractToken` 우선순위: header→cookie→query; query fallback은 SSE로 제한되지 않음 |
| body | 없음(직접 decoder/form read 미발견) | 해당 없음 | 해당 없음 |

요청 판정: **C/P** · unknown field 거부 미설정.
직접 validation: `!ok` · `err != nil` · `item == nil`

### 응답·오류

| 종류 | status/body | 해석 상태 |
|---|---|---|
| success/variant | `200` · `item` | P/U · `derived variable` |
| error | `400` · `{error:string}` · `"归档 id 无效"` | C for direct writeErr; error helper 내부는 P |
| error | `401` · `{error:string}` · `"未授权"` / `"token 无效或已过期"` | C for direct writeErr; error helper 내부는 P |
| error | `404` · `{error:string}` · `"归档不存在"` | C for direct writeErr; error helper 내부는 P |
| error | `500` · `{error:string}` · `err.Error()` | C for direct writeErr; error helper 내부는 P |

응답 schema 판정: **P** · top-level key는 직접 추출했지만 derived/helper/opaque 또는 nested type이 남음.

### 목록·동시성·완료

- Pagination/filter/sort: 등록된 직접 query key 중 표준 paging/filter key 없음.
- Idempotency/concurrency: read/stream 요청; stream 재연결은 cursor 계약에 따름.
- Side effect/completion: 직접 조회/파일 전송 완료; 외부 조회가 있으면 응답 전 handler 결과만 보장.
- 직접 협력자/실행 단서: `err.Error`, `pathInt`, `s.m.pg.GetTaskArchive`; direct `go` statement는 미발견. 이 목록은 helper 내부의 background launch나 transaction 경계를 펼치지 않으므로 그 부분은 P/U다.
- 종합 의미 판정: **P/U**; route/auth와 위에서 직접 확인한 branch·shape는 C지만, 모든 helper·derived response·중첩 DTO·외부 효과까지 완결됐다는 뜻은 아니다.

<a id="schema-post-api-tasks-id-archive"></a>
## `POST /api/tasks/{id}/archive`

구현: `queueTaskArchive` · `server/task_archives.go` · [정확한 handler 근거](evidence:handler-post-api-tasks-id-archive)

### 요청

| 위치/field | wire type | 필수·null | 기본/검증 |
|---|---|---|---|
| path `id` | `string` | 필수 | handler의 parse/range validation; 미해석 helper는 P |
| auth credential | Bearer header / `artex_token` cookie / query `token` | 세 위치 중 하나에 유효 JWT 필수 | `extractToken` 우선순위: header→cookie→query; query fallback은 SSE로 제한되지 않음 |
| body | 없음(직접 decoder/form read 미발견) | 해당 없음 | 해당 없음 |

요청 판정: **C/P** · unknown field 거부 미설정.
직접 validation: `!ok` · `err != nil`
직접 parse/default: `strconv.ParseInt(id, 10, 64)`

### 응답·오류

| 종류 | status/body | 해석 상태 |
|---|---|---|
| success/variant | `202` · `item` | P/U · `derived variable` |
| error | `400` · `{error:string}` · `"任务 id 无效"` | C for direct writeErr; error helper 내부는 P |
| error | `401` · `{error:string}` · `"未授权"` / `"token 无效或已过期"` | C for direct writeErr; error helper 내부는 P |
| error | `404` · `{error:string}` · `err.Error()` | C for direct writeErr; error helper 내부는 P |
| error | `409` · `{error:string}` · `err.Error()` | C for direct writeErr; error helper 내부는 P |
| error | `500` · `{error:string}` · `err.Error()` | C for direct writeErr; error helper 내부는 P |

Helper 오류 분기: `writeArchiveError` · `server/task_archives.go::writeArchiveError` · 위 `404, 409, 500` status는 helper control-flow를 정적으로 펼친 P이며 호출 전/후 DB 효과는 operation별로 확인한다.

응답 schema 판정: **P** · top-level key는 직접 추출했지만 derived/helper/opaque 또는 nested type이 남음.

### 목록·동시성·완료

- Pagination/filter/sort: 등록된 직접 query key 중 표준 paging/filter key 없음.
- Idempotency/concurrency: Idempotency-Key 없음; unique constraint 또는 상태 check가 있는 경우만 중복을 제한(P).
- Side effect/completion: persistent archive/restore/delete queue 접수; archive row의 state/phase가 완료 기준.
- 직접 협력자/실행 단서: `canonicalTaskID`, `r.PathValue`, `s.m.pg.QueueTaskArchive`, `writeArchiveError`; direct `go` statement는 미발견. 이 목록은 helper 내부의 background launch나 transaction 경계를 펼치지 않으므로 그 부분은 P/U다.
- 종합 의미 판정: **P/U**; route/auth와 위에서 직접 확인한 branch·shape는 C지만, 모든 helper·derived response·중첩 DTO·외부 효과까지 완결됐다는 뜻은 아니다.

<a id="schema-post-api-tasks-archive-batch"></a>
## `POST /api/tasks/archive/batch`

구현: `queueTaskArchivesBatch` · `server/task_archives.go` · [정확한 handler 근거](evidence:handler-post-api-tasks-archive-batch)

### 요청

| 위치/field | wire type | 필수·null | 기본/검증 |
|---|---|---|---|
| auth credential | Bearer header / `artex_token` cookie / query `token` | 세 위치 중 하나에 유효 JWT 필수 | `extractToken` 우선순위: header→cookie→query; query fallback은 SSE로 제한되지 않음 |
| body `task_ids` | `array[string]` (`[]string`) | 요구 여부 미확정(P); absent/null→nil; null/absent 구별 안 됨 | `nil` |
| body `archive_ids` | `array[integer]` (`[]int64`) | 요구 여부 미확정(P); absent/null→nil; null/absent 구별 안 됨 | `nil` |

요청 판정: **C/P** · unknown field 거부 미설정.
직접 parse/default: `strconv.ParseInt(id, 10, 64)`

### 응답·오류

| 종류 | status/body | 해석 상태 |
|---|---|---|
| success/variant | `202` · `items:derived` | P/U · `static composite` |
| error | `400` · `{error:string}` · `err.Error()` / `"task_ids 不能为空"` / `"一次最多处理 100 个任务"` | C for direct writeErr; error helper 내부는 P |
| error | `401` · `{error:string}` · `"未授权"` / `"token 无效或已过期"` | C for direct writeErr; error helper 내부는 P |

응답 schema 판정: **P** · top-level key는 직접 추출했지만 derived/helper/opaque 또는 nested type이 남음.

### 목록·동시성·완료

- Pagination/filter/sort: 등록된 직접 query key 중 표준 paging/filter key 없음.
- Idempotency/concurrency: Idempotency-Key 없음; unique constraint 또는 상태 check가 있는 경우만 중복을 제한(P).
- Side effect/completion: persistent archive/restore/delete queue 접수; archive row의 state/phase가 완료 기준.
- 직접 협력자/실행 단서: `err.Error`, `normalizeBatchTaskIDs`, `s.m.pg.QueueTaskArchive`; direct `go` statement는 미발견. 이 목록은 helper 내부의 background launch나 transaction 경계를 펼치지 않으므로 그 부분은 P/U다.
- 종합 의미 판정: **P/U**; route/auth와 위에서 직접 확인한 branch·shape는 C지만, 모든 helper·derived response·중첩 DTO·외부 효과까지 완결됐다는 뜻은 아니다.

<a id="schema-post-api-task-archives-id-restore"></a>
## `POST /api/task-archives/{id}/restore`

구현: `queueTaskArchiveRestore` · `server/task_archives.go` · [정확한 handler 근거](evidence:handler-post-api-task-archives-id-restore)

### 요청

| 위치/field | wire type | 필수·null | 기본/검증 |
|---|---|---|---|
| path `id` | `string` | 필수 | handler의 parse/range validation; 미해석 helper는 P |
| auth credential | Bearer header / `artex_token` cookie / query `token` | 세 위치 중 하나에 유효 JWT 필수 | `extractToken` 우선순위: header→cookie→query; query fallback은 SSE로 제한되지 않음 |
| body | 없음(직접 decoder/form read 미발견) | 해당 없음 | 해당 없음 |

요청 판정: **C/P** · unknown field 거부 미설정.
직접 validation: `!ok` · `err != nil`

### 응답·오류

| 종류 | status/body | 해석 상태 |
|---|---|---|
| success/variant | `202` · `item` | P/U · `derived variable` |
| error | `400` · `{error:string}` · `"归档 id 无效"` | C for direct writeErr; error helper 내부는 P |
| error | `401` · `{error:string}` · `"未授权"` / `"token 无效或已过期"` | C for direct writeErr; error helper 내부는 P |
| error | `404` · `{error:string}` · `err.Error()` | C for direct writeErr; error helper 내부는 P |
| error | `409` · `{error:string}` · `err.Error()` | C for direct writeErr; error helper 내부는 P |
| error | `500` · `{error:string}` · `err.Error()` | C for direct writeErr; error helper 내부는 P |

Helper 오류 분기: `writeArchiveError` · `server/task_archives.go::writeArchiveError` · 위 `404, 409, 500` status는 helper control-flow를 정적으로 펼친 P이며 호출 전/후 DB 효과는 operation별로 확인한다.

응답 schema 판정: **P** · top-level key는 직접 추출했지만 derived/helper/opaque 또는 nested type이 남음.

### 목록·동시성·완료

- Pagination/filter/sort: 등록된 직접 query key 중 표준 paging/filter key 없음.
- Idempotency/concurrency: Idempotency-Key 없음; unique constraint 또는 상태 check가 있는 경우만 중복을 제한(P).
- Side effect/completion: persistent archive/restore/delete queue 접수; archive row의 state/phase가 완료 기준.
- 직접 협력자/실행 단서: `pathInt`, `s.m.pg.QueueTaskArchiveRestore`, `writeArchiveError`; direct `go` statement는 미발견. 이 목록은 helper 내부의 background launch나 transaction 경계를 펼치지 않으므로 그 부분은 P/U다.
- 종합 의미 판정: **P/U**; route/auth와 위에서 직접 확인한 branch·shape는 C지만, 모든 helper·derived response·중첩 DTO·외부 효과까지 완결됐다는 뜻은 아니다.

<a id="schema-post-api-task-archives-restore-batch"></a>
## `POST /api/task-archives/restore/batch`

구현: `restoreTaskArchivesBatch` · `server/task_archives.go` · [정확한 handler 근거](evidence:handler-post-api-task-archives-restore-batch)

### 요청

| 위치/field | wire type | 필수·null | 기본/검증 |
|---|---|---|---|
| auth credential | Bearer header / `artex_token` cookie / query `token` | 세 위치 중 하나에 유효 JWT 필수 | `extractToken` 우선순위: header→cookie→query; query fallback은 SSE로 제한되지 않음 |
| body `archive_ids` | `array[integer]` (`[]int64`) | 필수(직접 거부 branch 확인); null/absent 구별 안 됨 | `normalizeArchiveIDs가 1..100개·모든 id>0을 요구하고 중복을 제거; 실패하면 400` |

요청 판정: **C/P** · unknown field 거부 미설정.
직접 validation: `normalizeArchiveIDs: len 1..100, every id > 0; duplicate ids removed preserving first occurrence`

### 응답·오류

| 종류 | status/body | 해석 상태 |
|---|---|---|
| success/variant | `202` · `items:array[archiveBatchItem]` | P/U · `static composite` |
| error | `400` · `{error:string}` · `err.Error()` | C for direct writeErr; error helper 내부는 P |
| error | `401` · `{error:string}` · `"未授权"` / `"token 无效或已过期"` | C for direct writeErr; error helper 내부는 P |

응답 schema 판정: **P** · top-level key는 직접 추출했지만 derived/helper/opaque 또는 nested type이 남음.

### 목록·동시성·완료

- Pagination/filter/sort: 등록된 직접 query key 중 표준 paging/filter key 없음.
- Idempotency/concurrency: Idempotency-Key 없음; unique constraint 또는 상태 check가 있는 경우만 중복을 제한(P).
- Side effect/completion: persistent archive/restore/delete queue 접수; archive row의 state/phase가 완료 기준.
- 직접 협력자/실행 단서: `normalizeArchiveIDs`, `s.m.pg.QueueTaskArchiveRestore`; direct `go` statement는 미발견. 이 목록은 helper 내부의 background launch나 transaction 경계를 펼치지 않으므로 그 부분은 P/U다.
- 종합 의미 판정: **P/U**; route/auth와 위에서 직접 확인한 branch·shape는 C지만, 모든 helper·derived response·중첩 DTO·외부 효과까지 완결됐다는 뜻은 아니다.

<a id="schema-delete-api-task-archives-id"></a>
## `DELETE /api/task-archives/{id}`

구현: `queueTaskArchiveDelete` · `server/task_archives.go` · [정확한 handler 근거](evidence:handler-delete-api-task-archives-id)

### 요청

| 위치/field | wire type | 필수·null | 기본/검증 |
|---|---|---|---|
| path `id` | `string` | 필수 | handler의 parse/range validation; 미해석 helper는 P |
| auth credential | Bearer header / `artex_token` cookie / query `token` | 세 위치 중 하나에 유효 JWT 필수 | `extractToken` 우선순위: header→cookie→query; query fallback은 SSE로 제한되지 않음 |
| body | 없음(직접 decoder/form read 미발견) | 해당 없음 | 해당 없음 |

요청 판정: **C/P** · unknown field 거부 미설정.
직접 validation: `!ok` · `err != nil`

### 응답·오류

| 종류 | status/body | 해석 상태 |
|---|---|---|
| success/variant | `202` · `item` | P/U · `derived variable` |
| error | `400` · `{error:string}` · `"归档 id 无效"` | C for direct writeErr; error helper 내부는 P |
| error | `401` · `{error:string}` · `"未授权"` / `"token 无效或已过期"` | C for direct writeErr; error helper 내부는 P |
| error | `404` · `{error:string}` · `err.Error()` | C for direct writeErr; error helper 내부는 P |
| error | `409` · `{error:string}` · `err.Error()` | C for direct writeErr; error helper 내부는 P |
| error | `500` · `{error:string}` · `err.Error()` | C for direct writeErr; error helper 내부는 P |

Helper 오류 분기: `writeArchiveError` · `server/task_archives.go::writeArchiveError` · 위 `404, 409, 500` status는 helper control-flow를 정적으로 펼친 P이며 호출 전/후 DB 효과는 operation별로 확인한다.

응답 schema 판정: **P** · top-level key는 직접 추출했지만 derived/helper/opaque 또는 nested type이 남음.

### 목록·동시성·완료

- Pagination/filter/sort: 등록된 직접 query key 중 표준 paging/filter key 없음.
- Idempotency/concurrency: Idempotency-Key/If-Match 없음; 반복 시 응답/존재 검사가 달라질 수 있음.
- Side effect/completion: `202` 접수; 후속 state/API를 확인.
- 직접 협력자/실행 단서: `pathInt`, `s.m.pg.QueueTaskArchiveDelete`, `writeArchiveError`; direct `go` statement는 미발견. 이 목록은 helper 내부의 background launch나 transaction 경계를 펼치지 않으므로 그 부분은 P/U다.
- 종합 의미 판정: **P/U**; route/auth와 위에서 직접 확인한 branch·shape는 C지만, 모든 helper·derived response·중첩 DTO·외부 효과까지 완결됐다는 뜻은 아니다.

<a id="schema-post-api-task-archives-delete-batch"></a>
## `POST /api/task-archives/delete/batch`

구현: `deleteTaskArchivesBatch` · `server/task_archives.go` · [정확한 handler 근거](evidence:handler-post-api-task-archives-delete-batch)

### 요청

| 위치/field | wire type | 필수·null | 기본/검증 |
|---|---|---|---|
| auth credential | Bearer header / `artex_token` cookie / query `token` | 세 위치 중 하나에 유효 JWT 필수 | `extractToken` 우선순위: header→cookie→query; query fallback은 SSE로 제한되지 않음 |
| body `archive_ids` | `array[integer]` (`[]int64`) | 필수(직접 거부 branch 확인); null/absent 구별 안 됨 | `normalizeArchiveIDs가 1..100개·모든 id>0을 요구하고 중복을 제거; 실패하면 400` |

요청 판정: **C/P** · unknown field 거부 미설정.
직접 validation: `normalizeArchiveIDs: len 1..100, every id > 0; duplicate ids removed preserving first occurrence`

### 응답·오류

| 종류 | status/body | 해석 상태 |
|---|---|---|
| success/variant | `202` · `items:array[archiveBatchItem]` | P/U · `static composite` |
| error | `400` · `{error:string}` · `err.Error()` | C for direct writeErr; error helper 내부는 P |
| error | `401` · `{error:string}` · `"未授权"` / `"token 无效或已过期"` | C for direct writeErr; error helper 내부는 P |

응답 schema 판정: **P** · top-level key는 직접 추출했지만 derived/helper/opaque 또는 nested type이 남음.

### 목록·동시성·완료

- Pagination/filter/sort: 등록된 직접 query key 중 표준 paging/filter key 없음.
- Idempotency/concurrency: Idempotency-Key 없음; unique constraint 또는 상태 check가 있는 경우만 중복을 제한(P).
- Side effect/completion: persistent archive/restore/delete queue 접수; archive row의 state/phase가 완료 기준.
- 직접 협력자/실행 단서: `normalizeArchiveIDs`, `s.m.pg.QueueTaskArchiveDelete`; direct `go` statement는 미발견. 이 목록은 helper 내부의 background launch나 transaction 경계를 펼치지 않으므로 그 부분은 P/U다.
- 종합 의미 판정: **P/U**; route/auth와 위에서 직접 확인한 branch·shape는 C지만, 모든 helper·derived response·중첩 DTO·외부 효과까지 완결됐다는 뜻은 아니다.

<a id="schema-get-api-tasks-id-coverage"></a>
## `GET /api/tasks/{id}/coverage`

구현: `taskCoverage` · `server/server.go` · [정확한 handler 근거](evidence:handler-get-api-tasks-id-coverage)

### 요청

| 위치/field | wire type | 필수·null | 기본/검증 |
|---|---|---|---|
| path `id` | `string` | 필수 | handler의 parse/range validation; 미해석 helper는 P |
| auth credential | Bearer header / `artex_token` cookie / query `token` | 세 위치 중 하나에 유효 JWT 필수 | `extractToken` 우선순위: header→cookie→query; query fallback은 SSE로 제한되지 않음 |
| body | 없음(직접 decoder/form read 미발견) | 해당 없음 | 해당 없음 |

요청 판정: **C/P** · unknown field 거부 미설정.
직접 validation: `!ok` · `!t.CoverageEnabled` · `as == nil` · `err != nil`
직접 parse/default: `strconv.ParseInt(t.ID, 10, 64)`

### 응답·오류

| 종류 | status/body | 해석 상태 |
|---|---|---|
| success/variant | `200` · `&db.Coverage{Enabled: false, ByType: []db.CoverageByType{}}` | P/U · `expression` |
| success/variant | `200` · `cov` | P/U · `derived variable` |
| error | `401` · `{error:string}` · `"未授权"` / `"token 无效或已过期"` | C for direct writeErr; error helper 내부는 P |
| error | `404` · `{error:string}` · `"task not found"` | C for direct writeErr; error helper 내부는 P |
| error | `500` · `{error:string}` · `err.Error()` | C for direct writeErr; error helper 내부는 P |
| error | `503` · `{error:string}` · `"asset store 未启用"` | C for direct writeErr; error helper 내부는 P |

응답 schema 판정: **P** · top-level key는 직접 추출했지만 derived/helper/opaque 또는 nested type이 남음.

### 목록·동시성·완료

- Pagination/filter/sort: 등록된 직접 query key 중 표준 paging/filter key 없음.
- Idempotency/concurrency: read/stream 요청; stream 재연결은 cursor 계약에 따름.
- Side effect/completion: 직접 조회/파일 전송 완료; 외부 조회가 있으면 응답 전 handler 결과만 보장.
- 직접 협력자/실행 단서: `as.TaskCoverageWithSources`, `err.Error`, `r.PathValue`, `s.m.Assets`, `s.m.Task`; direct `go` statement는 미발견. 이 목록은 helper 내부의 background launch나 transaction 경계를 펼치지 않으므로 그 부분은 P/U다.
- 종합 의미 판정: **P/U**; route/auth와 위에서 직접 확인한 branch·shape는 C지만, 모든 helper·derived response·중첩 DTO·외부 효과까지 완결됐다는 뜻은 아니다.

<a id="schema-get-api-tasks-id-coverage-graph"></a>
## `GET /api/tasks/{id}/coverage-graph`

구현: `taskCoverageGraph` · `server/server.go` · [정확한 handler 근거](evidence:handler-get-api-tasks-id-coverage-graph)

### 요청

| 위치/field | wire type | 필수·null | 기본/검증 |
|---|---|---|---|
| path `id` | `string` | 필수 | handler의 parse/range validation; 미해석 helper는 P |
| auth credential | Bearer header / `artex_token` cookie / query `token` | 세 위치 중 하나에 유효 JWT 필수 | `extractToken` 우선순위: header→cookie→query; query fallback은 SSE로 제한되지 않음 |
| body | 없음(직접 decoder/form read 미발견) | 해당 없음 | 해당 없음 |

요청 판정: **C/P** · unknown field 거부 미설정.
직접 validation: `!ok` · `as == nil` · `err != nil`
직접 parse/default: `strconv.ParseInt(t.ID, 10, 64)`

### 응답·오류

| 종류 | status/body | 해석 상태 |
|---|---|---|
| success/variant | `200` · `g` | P/U · `derived variable` |
| error | `401` · `{error:string}` · `"未授权"` / `"token 无效或已过期"` | C for direct writeErr; error helper 내부는 P |
| error | `404` · `{error:string}` · `"task not found"` | C for direct writeErr; error helper 내부는 P |
| error | `500` · `{error:string}` · `err.Error()` | C for direct writeErr; error helper 내부는 P |
| error | `503` · `{error:string}` · `"asset store 未启用"` | C for direct writeErr; error helper 내부는 P |

응답 schema 판정: **P** · top-level key는 직접 추출했지만 derived/helper/opaque 또는 nested type이 남음.

### 목록·동시성·완료

- Pagination/filter/sort: 등록된 직접 query key 중 표준 paging/filter key 없음.
- Idempotency/concurrency: read/stream 요청; stream 재연결은 cursor 계약에 따름.
- Side effect/completion: 직접 조회/파일 전송 완료; 외부 조회가 있으면 응답 전 handler 결과만 보장.
- 직접 협력자/실행 단서: `as.BuildCoverageGraph`, `err.Error`, `r.PathValue`, `s.m.Assets`, `s.m.Task`; direct `go` statement는 미발견. 이 목록은 helper 내부의 background launch나 transaction 경계를 펼치지 않으므로 그 부분은 P/U다.
- 종합 의미 판정: **P/U**; route/auth와 위에서 직접 확인한 branch·shape는 C지만, 모든 helper·derived response·중첩 DTO·외부 효과까지 완결됐다는 뜻은 아니다.

<a id="schema-get-api-tasks-id-asset-refs"></a>
## `GET /api/tasks/{id}/asset-refs`

구현: `taskAssetRefs` · `server/server.go` · [정확한 handler 근거](evidence:handler-get-api-tasks-id-asset-refs)

### 요청

| 위치/field | wire type | 필수·null | 기본/검증 |
|---|---|---|---|
| path `id` | `string` | 필수 | handler의 parse/range validation; 미해석 helper는 P |
| query `asset_id` | `string` wire | 선택 | handler/helper 변환·default 미전개(P) |
| auth credential | Bearer header / `artex_token` cookie / query `token` | 세 위치 중 하나에 유효 JWT 필수 | `extractToken` 우선순위: header→cookie→query; query fallback은 SSE로 제한되지 않음 |
| body | 없음(직접 decoder/form read 미발견) | 해당 없음 | 해당 없음 |

요청 판정: **C/P** · unknown field 거부 미설정.
직접 validation: `!ok` · `assetID <= 0` · `err != nil`
직접 parse/default: `strconv.ParseInt(r.URL.Query().Get("asset_id"), 10, 64)`

### 응답·오류

| 종류 | status/body | 해석 상태 |
|---|---|---|
| success/variant | `200` · `intents:derived`, `facts:derived`, `findings:derived` | P/U · `static composite` |
| error | `400` · `{error:string}` · `"需要 asset_id"` | C for direct writeErr; error helper 내부는 P |
| error | `401` · `{error:string}` · `"未授权"` / `"token 无效或已过期"` | C for direct writeErr; error helper 내부는 P |
| error | `404` · `{error:string}` · `"task not found"` | C for direct writeErr; error helper 내부는 P |
| error | `500` · `{error:string}` · `err.Error()` | C for direct writeErr; error helper 내부는 P |

응답 schema 판정: **P** · top-level key는 직접 추출했지만 derived/helper/opaque 또는 nested type이 남음.

### 목록·동시성·완료

- Pagination/filter/sort: 등록된 직접 query key 중 표준 paging/filter key 없음.
- Idempotency/concurrency: read/stream 요청; stream 재연결은 cursor 계약에 따름.
- Side effect/completion: 직접 조회/파일 전송 완료; 외부 조회가 있으면 응답 전 handler 결과만 보장.
- 직접 협력자/실행 단서: `coverageAssetRefDTO`, `err.Error`, `r.PathValue`, `r.URL.Query`, `r.URL.Query().Get`, `s.m.Task`, `t.Store.AssetRefsWithSources`; direct `go` statement는 미발견. 이 목록은 helper 내부의 background launch나 transaction 경계를 펼치지 않으므로 그 부분은 P/U다.
- 종합 의미 판정: **P/U**; route/auth와 위에서 직접 확인한 branch·shape는 C지만, 모든 helper·derived response·중첩 DTO·외부 효과까지 완결됐다는 뜻은 아니다.

<a id="schema-post-api-tasks-id-assets"></a>
## `POST /api/tasks/{id}/assets`

구현: `attachTaskAssets` · `server/task_assets.go` · [정확한 handler 근거](evidence:handler-post-api-tasks-id-assets)

### 요청

| 위치/field | wire type | 필수·null | 기본/검증 |
|---|---|---|---|
| path `id` | `string` | 필수 | handler의 parse/range validation; 미해석 helper는 P |
| auth credential | Bearer header / `artex_token` cookie / query `token` | 세 위치 중 하나에 유효 JWT 필수 | `extractToken` 우선순위: header→cookie→query; query fallback은 SSE로 제한되지 않음 |
| body `asset_ids` | `array[integer]` (`[]int64`) | 요구 여부 미확정(P); absent/null→nil; null/absent 구별 안 됨 | `request.Scope != nil && len(request.AssetIDs) > 0` |
| body `source_summary` | `string` (`string`) | 요구 여부 미확정(P); null/absent 구별 안 됨 | `request.SourceSummary = strings.TrimSpace(request.SourceSummary)` |
| body `scope` | `companyScopeInputs` (`companyScopeInputs`) | 요구 여부 미확정(P); null/absent 구별 안 됨 | `request.Scope != nil; request.Scope != nil && len(request.AssetIDs) > 0` |

요청 body 상한: **512 KiB** (`524288` bytes) · source `server/task_assets.go::attachTaskAssets` · 초과/decoder 경로 `413 "请求正文过大"`.

요청 판정: **C/P** · unknown field 거부 미설정.
직접 validation: `request.Scope != nil` · `request.Scope != nil && len(request.AssetIDs) > 0`

### 응답·오류

| 종류 | status/body | 해석 상태 |
|---|---|---|
| success/variant | `200` · `mutation` | P/U · `derived variable` |
| error | `400` · `{error:string}` · `err.Error()` / `"scope 与 asset_ids 不能同时提交"` | C for direct writeErr; error helper 내부는 P |
| error | `401` · `{error:string}` · `"未授权"` / `"token 无效或已过期"` | C for direct writeErr; error helper 내부는 P |
| error | `404` · `{error:string}` · `"task not found"` / `err.Error()` | C for direct writeErr; error helper 내부는 P |
| error | `413` · `{error:string}` · `"请求正文过大"` | C for direct writeErr; error helper 내부는 P |
| error | `500` · `{error:string}` · `err.Error()` | C for direct writeErr; error helper 내부는 P |

공통 helper 오류 분기: `writeTaskAssetError` (`server/task_assets.go::writeTaskAssetError`: 400/404/500). Status union은 helper control-flow를 정적으로 펼친 P이며 runtime DB 결과는 관찰하지 않았다.

응답 schema 판정: **P** · top-level key는 직접 추출했지만 derived/helper/opaque 또는 nested type이 남음.

### 목록·동시성·완료

- Pagination/filter/sort: 등록된 직접 query key 중 표준 paging/filter key 없음.
- Idempotency/concurrency: Idempotency-Key 없음; unique constraint 또는 상태 check가 있는 경우만 중복을 제한(P).
- Side effect/completion: handler의 직접 mutation/호출 후 응답; 외부 전달·agent 실행이 파생되면 별도 상태 확인(P).
- 직접 협력자/실행 단서: `err.Error`, `errors.As`, `parseTaskID`, `r.PathValue`, `s.m.Assets`, `s.m.Assets().AttachAssetsToTask`, `s.m.Assets().RegisterTaskAssetScopes`, `s.m.Task`, `task.Notify`, `writeTaskAssetError`; direct `go` statement는 미발견. 이 목록은 helper 내부의 background launch나 transaction 경계를 펼치지 않으므로 그 부분은 P/U다.
- 종합 의미 판정: **P/U**; route/auth와 위에서 직접 확인한 branch·shape는 C지만, 모든 helper·derived response·중첩 DTO·외부 효과까지 완결됐다는 뜻은 아니다.

<a id="schema-delete-api-tasks-id-assets-assetid"></a>
## `DELETE /api/tasks/{id}/assets/{assetID}`

구현: `detachTaskAsset` · `server/task_assets.go` · [정확한 handler 근거](evidence:handler-delete-api-tasks-id-assets-assetid)

### 요청

| 위치/field | wire type | 필수·null | 기본/검증 |
|---|---|---|---|
| path `assetID` | `string` | 필수 | handler의 parse/range validation; 미해석 helper는 P |
| path `id` | `string` | 필수 | handler의 parse/range validation; 미해석 helper는 P |
| auth credential | Bearer header / `artex_token` cookie / query `token` | 세 위치 중 하나에 유효 JWT 필수 | `extractToken` 우선순위: header→cookie→query; query fallback은 SSE로 제한되지 않음 |
| body | 없음(직접 decoder/form read 미발견) | 해당 없음 | 해당 없음 |

요청 판정: **C/P** · unknown field 거부 미설정.
직접 validation: `!detached` · `!ok` · `err != nil`

### 응답·오류

| 종류 | status/body | 해석 상태 |
|---|---|---|
| success/variant | `200` · `detached:derived` | P/U · `static composite` |
| error | `400` · `{error:string}` · `"bad asset id"` / `err.Error()` | C for direct writeErr; error helper 내부는 P |
| error | `401` · `{error:string}` · `"未授权"` / `"token 无效或已过期"` | C for direct writeErr; error helper 내부는 P |
| error | `404` · `{error:string}` · `"task not found"` / `"asset is not associated with this task"` / `err.Error()` | C for direct writeErr; error helper 내부는 P |
| error | `500` · `{error:string}` · `err.Error()` | C for direct writeErr; error helper 내부는 P |

공통 helper 오류 분기: `writeTaskAssetError` (`server/task_assets.go::writeTaskAssetError`: 400/404/500). Status union은 helper control-flow를 정적으로 펼친 P이며 runtime DB 결과는 관찰하지 않았다.

응답 schema 판정: **P** · top-level key는 직접 추출했지만 derived/helper/opaque 또는 nested type이 남음.

### 목록·동시성·완료

- Pagination/filter/sort: 등록된 직접 query key 중 표준 paging/filter key 없음.
- Idempotency/concurrency: Idempotency-Key/If-Match 없음; 반복 시 응답/존재 검사가 달라질 수 있음.
- Side effect/completion: handler의 직접 mutation/호출 후 응답; 외부 전달·agent 실행이 파생되면 별도 상태 확인(P).
- 직접 협력자/실행 단서: `parseTaskID`, `pathInt`, `r.PathValue`, `s.m.Assets`, `s.m.Assets().DetachAssetFromTask`, `s.m.Task`, `task.Notify`, `writeTaskAssetError`; direct `go` statement는 미발견. 이 목록은 helper 내부의 background launch나 transaction 경계를 펼치지 않으므로 그 부분은 P/U다.
- 종합 의미 판정: **P/U**; route/auth와 위에서 직접 확인한 branch·shape는 C지만, 모든 helper·derived response·중첩 DTO·외부 효과까지 완결됐다는 뜻은 아니다.

<a id="schema-get-api-tasks-id-intent-assets"></a>
## `GET /api/tasks/{id}/intent-assets`

구현: `taskIntentAssets` · `server/task_assets.go` · [정확한 handler 근거](evidence:handler-get-api-tasks-id-intent-assets)

### 요청

| 위치/field | wire type | 필수·null | 기본/검증 |
|---|---|---|---|
| path `id` | `string` | 필수 | handler의 parse/range validation; 미해석 helper는 P |
| auth credential | Bearer header / `artex_token` cookie / query `token` | 세 위치 중 하나에 유효 JWT 필수 | `extractToken` 우선순위: header→cookie→query; query fallback은 SSE로 제한되지 않음 |
| body | 없음(직접 decoder/form read 미발견) | 해당 없음 | 해당 없음 |

요청 판정: **C/P** · unknown field 거부 미설정.
직접 validation: `!ok` · `err != nil`

### 응답·오류

| 종류 | status/body | 해석 상태 |
|---|---|---|
| success/variant | `200` · `assets:derived` | P/U · `static composite` |
| error | `400` · `{error:string}` · `err.Error()` | C for direct writeErr; error helper 내부는 P |
| error | `401` · `{error:string}` · `"未授权"` / `"token 无效或已过期"` | C for direct writeErr; error helper 내부는 P |
| error | `404` · `{error:string}` · `"task not found"` / `err.Error()` | C for direct writeErr; error helper 내부는 P |
| error | `500` · `{error:string}` · `err.Error()` | C for direct writeErr; error helper 내부는 P |

공통 helper 오류 분기: `writeTaskAssetError` (`server/task_assets.go::writeTaskAssetError`: 400/404/500). Status union은 helper control-flow를 정적으로 펼친 P이며 runtime DB 결과는 관찰하지 않았다.

응답 schema 판정: **P** · top-level key는 직접 추출했지만 derived/helper/opaque 또는 nested type이 남음.

### 목록·동시성·완료

- Pagination/filter/sort: 등록된 직접 query key 중 표준 paging/filter key 없음.
- Idempotency/concurrency: read/stream 요청; stream 재연결은 cursor 계약에 따름.
- Side effect/completion: 직접 조회/파일 전송 완료; 외부 조회가 있으면 응답 전 handler 결과만 보장.
- 직접 협력자/실행 단서: `parseTaskID`, `r.PathValue`, `s.m.Assets`, `s.m.Assets().IntentAssets`, `s.m.Task`, `writeTaskAssetError`; direct `go` statement는 미발견. 이 목록은 helper 내부의 background launch나 transaction 경계를 펼치지 않으므로 그 부분은 P/U다.
- 종합 의미 판정: **P/U**; route/auth와 위에서 직접 확인한 branch·shape는 C지만, 모든 helper·derived response·중첩 DTO·외부 효과까지 완결됐다는 뜻은 아니다.

<a id="schema-get-api-workspace-list"></a>
## `GET /api/workspace/list`

구현: `wsList` · `server/workspace.go` · [정확한 handler 근거](evidence:handler-get-api-workspace-list)

### 요청

| 위치/field | wire type | 필수·null | 기본/검증 |
|---|---|---|---|
| query `path` | `string` wire | 선택 | handler/helper 변환·default 미전개(P) |
| auth credential | Bearer header / `artex_token` cookie / query `token` | 세 위치 중 하나에 유효 JWT 필수 | `extractToken` 우선순위: header→cookie→query; query fallback은 SSE로 제한되지 않음 |
| body | 없음(직접 decoder/form read 미발견) | 해당 없음 | 해당 없음 |

요청 판정: **C/P** · unknown field 거부 미설정.
직접 validation: `!fi.IsDir()` · `!ok` · `err != nil` · `out[i].Dir != out[j].Dir`

### 응답·오류

| 종류 | status/body | 해석 상태 |
|---|---|---|
| success/variant | `200` · `path:helper result`, `entries:derived` | P/U · `static composite` |
| error | `400` · `{error:string}` · `"非法路径"` / `"不是目录"` | C for direct writeErr; error helper 내부는 P |
| error | `401` · `{error:string}` · `"未授权"` / `"token 无效或已过期"` | C for direct writeErr; error helper 내부는 P |
| error | `404` · `{error:string}` · `"路径不存在"` | C for direct writeErr; error helper 내부는 P |
| error | `500` · `{error:string}` · `err.Error()` | C for direct writeErr; error helper 내부는 P |

응답 schema 판정: **P** · top-level key는 직접 추출했지만 derived/helper/opaque 또는 nested type이 남음.

### 목록·동시성·완료

- Pagination/filter/sort: 등록된 직접 query key 중 표준 paging/filter key 없음.
- Idempotency/concurrency: read/stream 요청; stream 재연결은 cursor 계약에 따름.
- Side effect/completion: 직접 조회/파일 전송 완료; 외부 조회가 있으면 응답 전 handler 결과만 보장.
- 직접 협력자/실행 단서: `e.Info`, `e.IsDir`, `e.Name`, `err.Error`, `fi.IsDir`, `filepath.Join`, `info.ModTime`, `info.ModTime().UnixMilli`, `info.Size`, `os.ReadDir`, `os.Stat`, `r.URL.Query`, `r.URL.Query().Get`, `sort.Slice`; direct `go` statement는 미발견. 이 목록은 helper 내부의 background launch나 transaction 경계를 펼치지 않으므로 그 부분은 P/U다.
- 종합 의미 판정: **P/U**; route/auth와 위에서 직접 확인한 branch·shape는 C지만, 모든 helper·derived response·중첩 DTO·외부 효과까지 완결됐다는 뜻은 아니다.

<a id="schema-get-api-workspace-read"></a>
## `GET /api/workspace/read`

구현: `wsRead` · `server/workspace.go` · [정확한 handler 근거](evidence:handler-get-api-workspace-read)

### 요청

| 위치/field | wire type | 필수·null | 기본/검증 |
|---|---|---|---|
| query `path` | `string` wire | 성공 응답에 필수 | non-empty relative path required for a success response; empty resolves workspace root, then read/delete→400 and download→404 |
| auth credential | Bearer header / `artex_token` cookie / query `token` | 세 위치 중 하나에 유효 JWT 필수 | `extractToken` 우선순위: header→cookie→query; query fallback은 SSE로 제한되지 않음 |
| body | 없음(직접 decoder/form read 미발견) | 해당 없음 | 해당 없음 |

요청 판정: **C/P** · unknown field 거부 미설정.
직접 validation: `!ok` · `bytes.IndexByte(data, 0) >= 0 \|\| !utf8.Valid(data)` · `err != nil` · `fi.IsDir()` · `fi.Size() > maxWorkspaceRead`

### 응답·오류

| 종류 | status/body | 해석 상태 |
|---|---|---|
| success/variant | `200` · `path:helper result`, `size:helper result`, `too_large:boolean`, `binary:boolean` | P/U · `static composite` |
| success/variant | `200` · `path:helper result`, `size:helper result`, `binary:boolean` | P/U · `static composite` |
| success/variant | `200` · `path:helper result`, `size:helper result`, `binary:boolean`, `content:helper result` | P/U · `static composite` |
| error | `400` · `{error:string}` · `"非法路径"` / `"是目录，不能作为文件读取"` | C for direct writeErr; error helper 내부는 P |
| error | `401` · `{error:string}` · `"未授权"` / `"token 无效或已过期"` | C for direct writeErr; error helper 내부는 P |
| error | `404` · `{error:string}` · `"文件不存在"` | C for direct writeErr; error helper 내부는 P |
| error | `500` · `{error:string}` · `err.Error()` | C for direct writeErr; error helper 내부는 P |

응답 schema 판정: **P** · top-level key는 직접 추출했지만 derived/helper/opaque 또는 nested type이 남음.

### 목록·동시성·완료

- Pagination/filter/sort: 등록된 직접 query key 중 표준 paging/filter key 없음.
- Idempotency/concurrency: read/stream 요청; stream 재연결은 cursor 계약에 따름.
- Side effect/completion: 직접 조회/파일 전송 완료; 외부 조회가 있으면 응답 전 handler 결과만 보장.
- 직접 협력자/실행 단서: `bytes.IndexByte`, `err.Error`, `fi.IsDir`, `fi.Size`, `os.ReadFile`, `os.Stat`, `r.URL.Query`, `r.URL.Query().Get`, `utf8.Valid`; direct `go` statement는 미발견. 이 목록은 helper 내부의 background launch나 transaction 경계를 펼치지 않으므로 그 부분은 P/U다.
- 종합 의미 판정: **P/U**; route/auth와 위에서 직접 확인한 branch·shape는 C지만, 모든 helper·derived response·중첩 DTO·외부 효과까지 완결됐다는 뜻은 아니다.

<a id="schema-post-api-workspace-write"></a>
## `POST /api/workspace/write`

구현: `wsWrite` · `server/workspace.go` · [정확한 handler 근거](evidence:handler-post-api-workspace-write)

### 요청

| 위치/field | wire type | 필수·null | 기본/검증 |
|---|---|---|---|
| auth credential | Bearer header / `artex_token` cookie / query `token` | 세 위치 중 하나에 유효 JWT 필수 | `extractToken` 우선순위: header→cookie→query; query fallback은 SSE로 제한되지 않음 |
| body `path` | `string` (`string`) | 요구 여부 미확정(P); null/absent 구별 안 됨 | `Go zero value; 별도 기본은 handler/helper 확인` |
| body `content` | `string` (`string`) | 요구 여부 미확정(P); null/absent 구별 안 됨 | `Go zero value; 별도 기본은 handler/helper 확인` |

요청 판정: **C/P** · unknown field 거부 미설정.

### 응답·오류

| 종류 | status/body | 해석 상태 |
|---|---|---|
| success/variant | `200` · `ok:boolean`, `path:helper result` | P/U · `static composite` |
| error | `400` · `{error:string}` · `err.Error()` / `"非法路径"` / `"目标是目录"` | C for direct writeErr; error helper 내부는 P |
| error | `401` · `{error:string}` · `"未授权"` / `"token 无效或已过期"` | C for direct writeErr; error helper 내부는 P |
| error | `500` · `{error:string}` · `err.Error()` | C for direct writeErr; error helper 내부는 P |

응답 schema 판정: **P** · top-level key는 직접 추출했지만 derived/helper/opaque 또는 nested type이 남음.

### 목록·동시성·완료

- Pagination/filter/sort: 등록된 직접 query key 중 표준 paging/filter key 없음.
- Idempotency/concurrency: Idempotency-Key 없음; unique constraint 또는 상태 check가 있는 경우만 중복을 제한(P).
- Side effect/completion: handler의 직접 mutation/호출 후 응답; 외부 전달·agent 실행이 파생되면 별도 상태 확인(P).
- 직접 협력자/실행 단서: `[]byte`, `err.Error`, `fi.IsDir`, `filepath.Clean`, `filepath.Dir`, `os.MkdirAll`, `os.Stat`, `os.WriteFile`; direct `go` statement는 미발견. 이 목록은 helper 내부의 background launch나 transaction 경계를 펼치지 않으므로 그 부분은 P/U다.
- 종합 의미 판정: **P/U**; route/auth와 위에서 직접 확인한 branch·shape는 C지만, 모든 helper·derived response·중첩 DTO·외부 효과까지 완결됐다는 뜻은 아니다.

<a id="schema-post-api-workspace-mkdir"></a>
## `POST /api/workspace/mkdir`

구현: `wsMkdir` · `server/workspace.go` · [정확한 handler 근거](evidence:handler-post-api-workspace-mkdir)

### 요청

| 위치/field | wire type | 필수·null | 기본/검증 |
|---|---|---|---|
| auth credential | Bearer header / `artex_token` cookie / query `token` | 세 위치 중 하나에 유효 JWT 필수 | `extractToken` 우선순위: header→cookie→query; query fallback은 SSE로 제한되지 않음 |
| body `path` | `string` (`string`) | 요구 여부 미확정(P); null/absent 구별 안 됨 | `Go zero value; 별도 기본은 handler/helper 확인` |

요청 판정: **C/P** · unknown field 거부 미설정.

### 응답·오류

| 종류 | status/body | 해석 상태 |
|---|---|---|
| success/variant | `200` · `ok:boolean`, `path:helper result` | P/U · `static composite` |
| error | `400` · `{error:string}` · `err.Error()` / `"非法路径"` | C for direct writeErr; error helper 내부는 P |
| error | `401` · `{error:string}` · `"未授权"` / `"token 无效或已过期"` | C for direct writeErr; error helper 내부는 P |
| error | `500` · `{error:string}` · `err.Error()` | C for direct writeErr; error helper 내부는 P |

응답 schema 판정: **P** · top-level key는 직접 추출했지만 derived/helper/opaque 또는 nested type이 남음.

### 목록·동시성·완료

- Pagination/filter/sort: 등록된 직접 query key 중 표준 paging/filter key 없음.
- Idempotency/concurrency: Idempotency-Key 없음; unique constraint 또는 상태 check가 있는 경우만 중복을 제한(P).
- Side effect/completion: handler의 직접 mutation/호출 후 응답; 외부 전달·agent 실행이 파생되면 별도 상태 확인(P).
- 직접 협력자/실행 단서: `err.Error`, `filepath.Clean`, `os.MkdirAll`; direct `go` statement는 미발견. 이 목록은 helper 내부의 background launch나 transaction 경계를 펼치지 않으므로 그 부분은 P/U다.
- 종합 의미 판정: **P/U**; route/auth와 위에서 직접 확인한 branch·shape는 C지만, 모든 helper·derived response·중첩 DTO·외부 효과까지 완결됐다는 뜻은 아니다.

<a id="schema-delete-api-workspace-delete"></a>
## `DELETE /api/workspace/delete`

구현: `wsDelete` · `server/workspace.go` · [정확한 handler 근거](evidence:handler-delete-api-workspace-delete)

### 요청

| 위치/field | wire type | 필수·null | 기본/검증 |
|---|---|---|---|
| query `path` | `string` wire | 성공 응답에 필수 | non-empty relative path required for a success response; empty resolves workspace root, then read/delete→400 and download→404 |
| auth credential | Bearer header / `artex_token` cookie / query `token` | 세 위치 중 하나에 유효 JWT 필수 | `extractToken` 우선순위: header→cookie→query; query fallback은 SSE로 제한되지 않음 |
| body | 없음(직접 decoder/form read 미발견) | 해당 없음 | 해당 없음 |

요청 판정: **C/P** · unknown field 거부 미설정.
직접 validation: `!ok` · `abs == filepath.Clean(s.m.dir)` · `err != nil`

### 응답·오류

| 종류 | status/body | 해석 상태 |
|---|---|---|
| success/variant | `200` · `ok:boolean` | C · `static composite` |
| error | `400` · `{error:string}` · `"非法路径"` / `"不能删除工作区根目录"` | C for direct writeErr; error helper 내부는 P |
| error | `401` · `{error:string}` · `"未授权"` / `"token 无效或已过期"` | C for direct writeErr; error helper 내부는 P |
| error | `404` · `{error:string}` · `"路径不存在"` | C for direct writeErr; error helper 내부는 P |
| error | `500` · `{error:string}` · `err.Error()` | C for direct writeErr; error helper 내부는 P |

응답 schema 판정: **C** · 직접 scalar/static top-level key와 field type을 추출; nested 내부 schema는 별도 type 참조가 없으면 P.

### 목록·동시성·완료

- Pagination/filter/sort: 등록된 직접 query key 중 표준 paging/filter key 없음.
- Idempotency/concurrency: Idempotency-Key/If-Match 없음; 반복 시 응답/존재 검사가 달라질 수 있음.
- Side effect/completion: handler의 직접 mutation/호출 후 응답; 외부 전달·agent 실행이 파생되면 별도 상태 확인(P).
- 직접 협력자/실행 단서: `err.Error`, `filepath.Clean`, `os.RemoveAll`, `os.Stat`, `r.URL.Query`, `r.URL.Query().Get`; direct `go` statement는 미발견. 이 목록은 helper 내부의 background launch나 transaction 경계를 펼치지 않으므로 그 부분은 P/U다.
- 종합 의미 판정: **P/U**; route/auth와 위에서 직접 확인한 branch·shape는 C지만, 모든 helper·derived response·중첩 DTO·외부 효과까지 완결됐다는 뜻은 아니다.

<a id="schema-get-api-workspace-download"></a>
## `GET /api/workspace/download`

구현: `wsDownload` · `server/workspace.go` · [정확한 handler 근거](evidence:handler-get-api-workspace-download)

### 요청

| 위치/field | wire type | 필수·null | 기본/검증 |
|---|---|---|---|
| query `path` | `string` wire | 성공 응답에 필수 | non-empty relative path required for a success response; empty resolves workspace root, then read/delete→400 and download→404 |
| header `Range` | byte-ranges | 선택 | Go stdlib `ServeContent`/`ServeFile` precondition·range contract(P) |
| header `If-Range` | HTTP-date 또는 entity-tag | 선택 | Go stdlib `ServeContent`/`ServeFile` precondition·range contract(P) |
| header `If-Match` | entity-tag list 또는 `*` | 선택 | Go stdlib `ServeContent`/`ServeFile` precondition·range contract(P) |
| header `If-Unmodified-Since` | HTTP-date | 선택 | Go stdlib `ServeContent`/`ServeFile` precondition·range contract(P) |
| header `If-None-Match` | entity-tag list 또는 `*` | 선택 | Go stdlib `ServeContent`/`ServeFile` precondition·range contract(P) |
| header `If-Modified-Since` | HTTP-date | 선택 | Go stdlib `ServeContent`/`ServeFile` precondition·range contract(P) |
| auth credential | Bearer header / `artex_token` cookie / query `token` | 세 위치 중 하나에 유효 JWT 필수 | `extractToken` 우선순위: header→cookie→query; query fallback은 SSE로 제한되지 않음 |
| body | 없음(직접 decoder/form read 미발견) | 해당 없음 | 해당 없음 |

요청 판정: **C/P** · unknown field 거부 미설정.
직접 validation: `!ok` · `err != nil \|\| fi.IsDir()`

### 응답·오류

| 종류 | status/body | 해석 상태 |
|---|---|---|
| success/variant | `200` · `attachment via \`http.ServeFile\`; Accept-Ranges/Content-Length/Last-Modified delegated` | P/U · `stdlib file response` |
| success/variant | `206` · `range response via \`http.ServeFile\`; Content-Range/Length` | P/U · `stdlib conditional response` |
| success/variant | `304` · `conditional response via \`http.ServeFile\`; Last-Modified/ETag preconditions` | P/U · `stdlib conditional response` |
| error | `400` · `{error:string}` · `"非法路径"` | C for direct writeErr; error helper 내부는 P |
| error | `401` · `{error:string}` · `"未授权"` / `"token 无效或已过期"` | C for direct writeErr; error helper 내부는 P |
| error | `404` · `{error:string}` · `"文件不存在"` | C for direct writeErr; error helper 내부는 P |
| error | `412` · body 없음 · `stdlib precondition failure from \`http.ServeFile\`; WriteHeader with no ARTEX JSON body` | P · Go stdlib precondition 처리; ARTEX `writeErr` JSON이 아님 |
| error | `416` · `text/plain` · `stdlib plain-text range error from \`http.ServeFile\`` | P · Go stdlib `ServeContent`/`ServeFile`가 직접 생성; ARTEX `writeErr` JSON이 아님 |

응답 schema 판정: **P** · top-level key는 직접 추출했지만 derived/helper/opaque 또는 nested type이 남음.

### 목록·동시성·완료

- Pagination/filter/sort: 등록된 직접 query key 중 표준 paging/filter key 없음.
- Idempotency/concurrency: read/stream 요청; stream 재연결은 cursor 계약에 따름.
- Side effect/completion: 직접 조회/파일 전송 완료; 외부 조회가 있으면 응답 전 handler 결과만 보장.
- 직접 협력자/실행 단서: `fi.IsDir`, `filepath.Base`, `os.Stat`, `r.URL.Query`, `r.URL.Query().Get`, `sanitizeFilename`, `w.Header`, `w.Header().Set`; direct `go` statement는 미발견. 이 목록은 helper 내부의 background launch나 transaction 경계를 펼치지 않으므로 그 부분은 P/U다.
- 종합 의미 판정: **P/U**; route/auth와 위에서 직접 확인한 branch·shape는 C지만, 모든 helper·derived response·중첩 DTO·외부 효과까지 완결됐다는 뜻은 아니다.

<a id="schema-post-api-workspace-upload"></a>
## `POST /api/workspace/upload`

구현: `wsUpload` · `server/workspace.go` · [정확한 handler 근거](evidence:handler-post-api-workspace-upload)

### 요청

| 위치/field | wire type | 필수·null | 기본/검증 |
|---|---|---|---|
| query `path` | `string` wire | 선택 | handler/helper 변환·default 미전개(P) |
| auth credential | Bearer header / `artex_token` cookie / query `token` | 세 위치 중 하나에 유효 JWT 필수 | `extractToken` 우선순위: header→cookie→query; query fallback은 SSE로 제한되지 않음 |
| multipart `file` (repeatable) | binary file | 1개 이상 필수 | query `path` directory; request/body size cap |

요청 body 상한: **512 MiB** (`536870912` bytes) · source `server/workspace.go::wsUpload` · 초과/decoder 경로 `400 "解析上传失败或超出大小限制：" + err.Error()`.

요청 판정: **C/P** · unknown field 거부 미설정.
직접 validation: `!ok` · `!okd` · `err != nil` · `err != nil \|\| !fi.IsDir()` · `len(files) == 0` · `name == "" \|\| name == "." \|\| name == ".."`

### 응답·오류

| 종류 | status/body | 해석 상태 |
|---|---|---|
| success/variant | `200` · `uploaded:derived` | P/U · `static composite` |
| error | `400` · `{error:string}` · `"非法路径"` / `"目标目录不存在"` / `"解析上传失败或超出大小限制：" + err.Error()` / `"缺少上传文件(表单字段 file)"` | C for direct writeErr; error helper 내부는 P |
| error | `401` · `{error:string}` · `"未授权"` / `"token 无效或已过期"` | C for direct writeErr; error helper 내부는 P |
| error | `500` · `{error:string}` · `err.Error()` | C for direct writeErr; error helper 내부는 P |

응답 schema 판정: **P** · top-level key는 직접 추출했지만 derived/helper/opaque 또는 nested type이 남음.

### 목록·동시성·완료

- Pagination/filter/sort: 등록된 직접 query key 중 표준 paging/filter key 없음.
- Idempotency/concurrency: Idempotency-Key 없음; unique constraint 또는 상태 check가 있는 경우만 중복을 제한(P).
- Side effect/completion: handler의 직접 mutation/호출 후 응답; 외부 전달·agent 실행이 파생되면 별도 상태 확인(P).
- 직접 협력자/실행 단서: `err.Error`, `fi.IsDir`, `filepath.Base`, `filepath.Join`, `os.Stat`, `r.ParseMultipartForm`, `r.URL.Query`, `r.URL.Query().Get`, `saveUpload`; direct `go` statement는 미발견. 이 목록은 helper 내부의 background launch나 transaction 경계를 펼치지 않으므로 그 부분은 P/U다.
- 종합 의미 판정: **P/U**; route/auth와 위에서 직접 확인한 branch·shape는 C지만, 모든 helper·derived response·중첩 DTO·외부 효과까지 완결됐다는 뜻은 아니다.

<a id="schema-get-api-tasks-id-scope"></a>
## `GET /api/tasks/{id}/scope`

구현: `taskScopeList` · `server/server.go` · [정확한 handler 근거](evidence:handler-get-api-tasks-id-scope)

### 요청

| 위치/field | wire type | 필수·null | 기본/검증 |
|---|---|---|---|
| path `id` | `string` | 필수 | handler의 parse/range validation; 미해석 helper는 P |
| auth credential | Bearer header / `artex_token` cookie / query `token` | 세 위치 중 하나에 유효 JWT 필수 | `extractToken` 우선순위: header→cookie→query; query fallback은 SSE로 제한되지 않음 |
| body | 없음(직접 decoder/form read 미발견) | 해당 없음 | 해당 없음 |

요청 판정: **C/P** · unknown field 거부 미설정.
직접 validation: `!ok` · `as == nil` · `err != nil`
직접 parse/default: `strconv.ParseInt(t.ID, 10, 64)`

### 응답·오류

| 종류 | status/body | 해석 상태 |
|---|---|---|
| success/variant | `200` · `scope:derived` | P/U · `static composite` |
| error | `401` · `{error:string}` · `"未授权"` / `"token 无效或已过期"` | C for direct writeErr; error helper 내부는 P |
| error | `404` · `{error:string}` · `"task not found"` | C for direct writeErr; error helper 내부는 P |
| error | `500` · `{error:string}` · `err.Error()` | C for direct writeErr; error helper 내부는 P |
| error | `503` · `{error:string}` · `"asset store 未启用"` | C for direct writeErr; error helper 내부는 P |

응답 schema 판정: **P** · top-level key는 직접 추출했지만 derived/helper/opaque 또는 nested type이 남음.

### 목록·동시성·완료

- Pagination/filter/sort: 등록된 직접 query key 중 표준 paging/filter key 없음.
- Idempotency/concurrency: read/stream 요청; stream 재연결은 cursor 계약에 따름.
- Side effect/completion: 직접 조회/파일 전송 완료; 외부 조회가 있으면 응답 전 handler 결과만 보장.
- 직접 협력자/실행 단서: `as.ListTaskScopeWithSources`, `err.Error`, `r.PathValue`, `s.m.Assets`, `s.m.Task`; direct `go` statement는 미발견. 이 목록은 helper 내부의 background launch나 transaction 경계를 펼치지 않으므로 그 부분은 P/U다.
- 종합 의미 판정: **P/U**; route/auth와 위에서 직접 확인한 branch·shape는 C지만, 모든 helper·derived response·중첩 DTO·외부 효과까지 완결됐다는 뜻은 아니다.

<a id="schema-post-api-tasks-id-scope"></a>
## `POST /api/tasks/{id}/scope`

구현: `taskScopeAdd` · `server/server.go` · [정확한 handler 근거](evidence:handler-post-api-tasks-id-scope)

### 요청

| 위치/field | wire type | 필수·null | 기본/검증 |
|---|---|---|---|
| path `id` | `string` | 필수 | handler의 parse/range validation; 미해석 helper는 P |
| auth credential | Bearer header / `artex_token` cookie / query `token` | 세 위치 중 하나에 유효 JWT 필수 | `extractToken` 우선순위: header→cookie→query; query fallback은 SSE로 제한되지 않음 |
| body `kind` | `string` (`string`) | 요구 여부 미확정(P); null/absent 구별 안 됨 | `Go zero value; 별도 기본은 handler/helper 확인` |
| body `value` | `string` (`string`) | 요구 여부 미확정(P); null/absent 구별 안 됨 | `Go zero value; 별도 기본은 handler/helper 확인` |
| body `reason` | `string` (`string`) | 요구 여부 미확정(P); null/absent 구별 안 됨 | `Go zero value; 별도 기본은 handler/helper 확인` |

요청 판정: **C/P** · unknown field 거부 미설정.
직접 parse/default: `strconv.ParseInt(t.ID, 10, 64)`

### 응답·오류

| 종류 | status/body | 해석 상태 |
|---|---|---|
| success/variant | `200` · `ts` | P/U · `derived variable` |
| error | `400` · `{error:string}` · `"invalid JSON"` / `err.Error()` | C for direct writeErr; error helper 내부는 P |
| error | `401` · `{error:string}` · `"未授权"` / `"token 无效或已过期"` | C for direct writeErr; error helper 내부는 P |
| error | `404` · `{error:string}` · `"task not found"` | C for direct writeErr; error helper 내부는 P |
| error | `503` · `{error:string}` · `"asset store 未启用"` | C for direct writeErr; error helper 내부는 P |

응답 schema 판정: **P** · top-level key는 직접 추출했지만 derived/helper/opaque 또는 nested type이 남음.

### 목록·동시성·완료

- Pagination/filter/sort: 등록된 직접 query key 중 표준 paging/filter key 없음.
- Idempotency/concurrency: Idempotency-Key 없음; unique constraint 또는 상태 check가 있는 경우만 중복을 제한(P).
- Side effect/completion: handler의 직접 mutation/호출 후 응답; 외부 전달·agent 실행이 파생되면 별도 상태 확인(P).
- 직접 협력자/실행 단서: `as.AddAgentScope`, `err.Error`, `json.NewDecoder`, `json.NewDecoder(r.Body).Decode`, `r.PathValue`, `s.m.Assets`, `s.m.Task`; direct `go` statement는 미발견. 이 목록은 helper 내부의 background launch나 transaction 경계를 펼치지 않으므로 그 부분은 P/U다.
- 종합 의미 판정: **P/U**; route/auth와 위에서 직접 확인한 branch·shape는 C지만, 모든 helper·derived response·중첩 DTO·외부 효과까지 완결됐다는 뜻은 아니다.

<a id="schema-delete-api-tasks-id-scope-sid"></a>
## `DELETE /api/tasks/{id}/scope/{sid}`

구현: `taskScopeDelete` · `server/server.go` · [정확한 handler 근거](evidence:handler-delete-api-tasks-id-scope-sid)

### 요청

| 위치/field | wire type | 필수·null | 기본/검증 |
|---|---|---|---|
| path `id` | `string` | 필수 | handler의 parse/range validation; 미해석 helper는 P |
| path `sid` | `string` | 필수 | handler의 parse/range validation; 미해석 helper는 P |
| auth credential | Bearer header / `artex_token` cookie / query `token` | 세 위치 중 하나에 유효 JWT 필수 | `extractToken` 우선순위: header→cookie→query; query fallback은 SSE로 제한되지 않음 |
| body | 없음(직접 decoder/form read 미발견) | 해당 없음 | 해당 없음 |

요청 판정: **C/P** · unknown field 거부 미설정.
직접 validation: `!deleted` · `!ok` · `as == nil` · `err != nil`
직접 parse/default: `strconv.ParseInt(r.PathValue("sid"), 10, 64)` · `strconv.ParseInt(t.ID, 10, 64)`

### 응답·오류

| 종류 | status/body | 해석 상태 |
|---|---|---|
| success/variant | `200` · `ok:boolean` | C · `static composite` |
| error | `400` · `{error:string}` · `"invalid scope id"` | C for direct writeErr; error helper 내부는 P |
| error | `401` · `{error:string}` · `"未授权"` / `"token 无效或已过期"` | C for direct writeErr; error helper 내부는 P |
| error | `404` · `{error:string}` · `"task not found"` / `"scope row not found"` | C for direct writeErr; error helper 내부는 P |
| error | `500` · `{error:string}` · `err.Error()` | C for direct writeErr; error helper 내부는 P |
| error | `503` · `{error:string}` · `"asset store 未启用"` | C for direct writeErr; error helper 내부는 P |

응답 schema 판정: **C** · 직접 scalar/static top-level key와 field type을 추출; nested 내부 schema는 별도 type 참조가 없으면 P.

### 목록·동시성·완료

- Pagination/filter/sort: 등록된 직접 query key 중 표준 paging/filter key 없음.
- Idempotency/concurrency: Idempotency-Key/If-Match 없음; 반복 시 응답/존재 검사가 달라질 수 있음.
- Side effect/completion: handler의 직접 mutation/호출 후 응답; 외부 전달·agent 실행이 파생되면 별도 상태 확인(P).
- 직접 협력자/실행 단서: `as.DeleteTaskScope`, `err.Error`, `r.PathValue`, `s.m.Assets`, `s.m.Task`; direct `go` statement는 미발견. 이 목록은 helper 내부의 background launch나 transaction 경계를 펼치지 않으므로 그 부분은 P/U다.
- 종합 의미 판정: **P/U**; route/auth와 위에서 직접 확인한 branch·shape는 C지만, 모든 helper·derived response·중첩 DTO·외부 효과까지 완결됐다는 뜻은 아니다.

<a id="schema-get-api-tasks-id-goals"></a>
## `GET /api/tasks/{id}/goals`

구현: `listGoals` · `server/goals_api.go` · [정확한 handler 근거](evidence:handler-get-api-tasks-id-goals)

### 요청

| 위치/field | wire type | 필수·null | 기본/검증 |
|---|---|---|---|
| path `id` | `string` | 필수 | handler의 parse/range validation; 미해석 helper는 P |
| auth credential | Bearer header / `artex_token` cookie / query `token` | 세 위치 중 하나에 유효 JWT 필수 | `extractToken` 우선순위: header→cookie→query; query fallback은 SSE로 제한되지 않음 |
| body | 없음(직접 decoder/form read 미발견) | 해당 없음 | 해당 없음 |

요청 판정: **C/P** · unknown field 거부 미설정.
직접 validation: `!ok` · `err != nil`

### 응답·오류

| 종류 | status/body | 해석 상태 |
|---|---|---|
| success/variant | `200` · `goals:helper result` | P/U · `static composite` |
| error | `401` · `{error:string}` · `"未授权"` / `"token 无效或已过期"` | C for direct writeErr; error helper 내부는 P |
| error | `404` · `{error:string}` · `"task not found"` | C for direct writeErr; error helper 내부는 P |
| error | `500` · `{error:string}` · `err.Error()` | C for direct writeErr; error helper 내부는 P |

응답 schema 판정: **P** · top-level key는 직접 추출했지만 derived/helper/opaque 또는 nested type이 남음.

### 목록·동시성·완료

- Pagination/filter/sort: 등록된 직접 query key 중 표준 paging/filter key 없음.
- Idempotency/concurrency: read/stream 요청; stream 재연결은 cursor 계약에 따름.
- Side effect/completion: 직접 조회/파일 전송 완료; 외부 조회가 있으면 응답 전 handler 결과만 보장.
- 직접 협력자/실행 단서: `err.Error`, `goalDTOs`, `r.PathValue`, `s.m.Task`, `t.Store.ListByKind`; direct `go` statement는 미발견. 이 목록은 helper 내부의 background launch나 transaction 경계를 펼치지 않으므로 그 부분은 P/U다.
- 종합 의미 판정: **P/U**; route/auth와 위에서 직접 확인한 branch·shape는 C지만, 모든 helper·derived response·중첩 DTO·외부 효과까지 완결됐다는 뜻은 아니다.

<a id="schema-post-api-tasks-id-goals"></a>
## `POST /api/tasks/{id}/goals`

구현: `addGoal` · `server/goals_api.go` · [정확한 handler 근거](evidence:handler-post-api-tasks-id-goals)

### 요청

| 위치/field | wire type | 필수·null | 기본/검증 |
|---|---|---|---|
| path `id` | `string` | 필수 | handler의 parse/range validation; 미해석 helper는 P |
| auth credential | Bearer header / `artex_token` cookie / query `token` | 세 위치 중 하나에 유효 JWT 필수 | `extractToken` 우선순위: header→cookie→query; query fallback은 SSE로 제한되지 않음 |
| body `text` | `string` (`string`) | 필수(직접 거부 branch 확인); null/absent 구별 안 됨 | `trim 후 빈 문자열이면 400` |
| body `vulnclass` | `string` (`string`) | 요구 여부 미확정(P); null/absent 구별 안 됨 | `Go zero value; 별도 기본은 handler/helper 확인` |

요청 판정: **C/P** · unknown field 거부 미설정.

### 응답·오류

| 종류 | status/body | 해석 상태 |
|---|---|---|
| success/variant | `200` · `goalDTO(node)` | P/U · `helper result` |
| error | `400` · `{error:string}` · `"invalid JSON"` / `"目标内容不能为空"` | C for direct writeErr; error helper 내부는 P |
| error | `401` · `{error:string}` · `"未授权"` / `"token 无效或已过期"` | C for direct writeErr; error helper 내부는 P |
| error | `404` · `{error:string}` · `"task not found"` | C for direct writeErr; error helper 내부는 P |
| error | `409` · `{error:string}` · `"任务正在删除,无法新增目标"` | C for direct writeErr; error helper 내부는 P |
| error | `500` · `{error:string}` · `err.Error()` / `"目标写入后读取失败"` | C for direct writeErr; error helper 내부는 P |

응답 schema 판정: **P** · top-level key는 직접 추출했지만 derived/helper/opaque 또는 nested type이 남음.

### 목록·동시성·완료

- Pagination/filter/sort: 등록된 직접 query key 중 표준 paging/filter key 없음.
- Idempotency/concurrency: Idempotency-Key 없음; unique constraint 또는 상태 check가 있는 경우만 중복을 제한(P).
- Side effect/completion: handler의 직접 mutation/호출 후 응답; 외부 전달·agent 실행이 파생되면 별도 상태 확인(P).
- 직접 협력자/실행 단서: `err.Error`, `goalDTO`, `json.NewDecoder`, `json.NewDecoder(r.Body).Decode`, `r.PathValue`, `s.engine.beginTaskOperation`, `s.engine.decInflight`, `s.m.Task`, `t.NotifyGoal`, `t.Store.AddGoal`, `t.Store.GetNode`, `t.Store.Link`, `t.Store.OriginFactID`; direct `go` statement는 미발견. 이 목록은 helper 내부의 background launch나 transaction 경계를 펼치지 않으므로 그 부분은 P/U다.
- 종합 의미 판정: **P/U**; route/auth와 위에서 직접 확인한 branch·shape는 C지만, 모든 helper·derived response·중첩 DTO·외부 효과까지 완결됐다는 뜻은 아니다.

<a id="schema-patch-api-tasks-id-goals-gid"></a>
## `PATCH /api/tasks/{id}/goals/{gid}`

구현: `editGoal` · `server/goals_api.go` · [정확한 handler 근거](evidence:handler-patch-api-tasks-id-goals-gid)

### 요청

| 위치/field | wire type | 필수·null | 기본/검증 |
|---|---|---|---|
| path `gid` | `string` | 필수 | handler의 parse/range validation; 미해석 helper는 P |
| path `id` | `string` | 필수 | handler의 parse/range validation; 미해석 helper는 P |
| auth credential | Bearer header / `artex_token` cookie / query `token` | 세 위치 중 하나에 유효 JWT 필수 | `extractToken` 우선순위: header→cookie→query; query fallback은 SSE로 제한되지 않음 |
| body `text` | `string` (`string`) | 필수(직접 거부 branch 확인); null/absent 구별 안 됨 | `trim 후 빈 문자열이면 400` |
| body `vulnclass` | `string` (`string`) | 요구 여부 미확정(P); null/absent 구별 안 됨 | `Go zero value; 별도 기본은 handler/helper 확인` |

요청 판정: **C/P** · unknown field 거부 미설정.
직접 parse/default: `strconv.ParseInt(r.PathValue("gid"), 10, 64)`

### 응답·오류

| 종류 | status/body | 해석 상태 |
|---|---|---|
| success/variant | `200` · `goalDTO(updated)` | P/U · `helper result` |
| error | `400` · `{error:string}` · `"bad goal id"` / `"invalid JSON"` / `"目标内容不能为空"` | C for direct writeErr; error helper 내부는 P |
| error | `401` · `{error:string}` · `"未授权"` / `"token 无效或已过期"` | C for direct writeErr; error helper 내부는 P |
| error | `404` · `{error:string}` · `"task not found"` / `"目标不存在"` | C for direct writeErr; error helper 내부는 P |
| error | `409` · `{error:string}` · `"任务正在删除,无法修改目标"` | C for direct writeErr; error helper 내부는 P |
| error | `500` · `{error:string}` · `err.Error()` / `"目标更新后读取失败"` | C for direct writeErr; error helper 내부는 P |

응답 schema 판정: **P** · top-level key는 직접 추출했지만 derived/helper/opaque 또는 nested type이 남음.

### 목록·동시성·완료

- Pagination/filter/sort: 등록된 직접 query key 중 표준 paging/filter key 없음.
- Idempotency/concurrency: Idempotency-Key/If-Match 없음; DB upsert/trigger/side effect 때문에 반복 의미를 handler별 확인(P).
- Side effect/completion: handler의 직접 mutation/호출 후 응답; 외부 전달·agent 실행이 파생되면 별도 상태 확인(P).
- 직접 협력자/실행 단서: `err.Error`, `goalDTO`, `json.NewDecoder`, `json.NewDecoder(r.Body).Decode`, `r.PathValue`, `s.engine.beginTaskOperation`, `s.engine.decInflight`, `s.m.Task`, `t.NotifyGoalEdited`, `t.Store.GetNode`, `t.Store.UpdateGoalPayload`; direct `go` statement는 미발견. 이 목록은 helper 내부의 background launch나 transaction 경계를 펼치지 않으므로 그 부분은 P/U다.
- 종합 의미 판정: **P/U**; route/auth와 위에서 직접 확인한 branch·shape는 C지만, 모든 helper·derived response·중첩 DTO·외부 효과까지 완결됐다는 뜻은 아니다.

<a id="schema-delete-api-tasks-id-goals-gid"></a>
## `DELETE /api/tasks/{id}/goals/{gid}`

구현: `deleteGoal` · `server/goals_api.go` · [정확한 handler 근거](evidence:handler-delete-api-tasks-id-goals-gid)

### 요청

| 위치/field | wire type | 필수·null | 기본/검증 |
|---|---|---|---|
| path `gid` | `string` | 필수 | handler의 parse/range validation; 미해석 helper는 P |
| path `id` | `string` | 필수 | handler의 parse/range validation; 미해석 helper는 P |
| auth credential | Bearer header / `artex_token` cookie / query `token` | 세 위치 중 하나에 유효 JWT 필수 | `extractToken` 우선순위: header→cookie→query; query fallback은 SSE로 제한되지 않음 |
| body | 없음(직접 decoder/form read 미발견) | 해당 없음 | 해당 없음 |

요청 판정: **C/P** · unknown field 거부 미설정.
직접 validation: `!ok` · `!s.engine.beginTaskOperation(t.ID)` · `err != nil` · `err != nil \|\| gid <= 0` · `node == nil \|\| node.Kind != db.KindGoal`
직접 parse/default: `strconv.ParseInt(r.PathValue("gid"), 10, 64)`

### 응답·오류

| 종류 | status/body | 해석 상태 |
|---|---|---|
| success/variant | `200` · `ok:boolean` | C · `static composite` |
| error | `400` · `{error:string}` · `"bad goal id"` | C for direct writeErr; error helper 내부는 P |
| error | `401` · `{error:string}` · `"未授权"` / `"token 无效或已过期"` | C for direct writeErr; error helper 내부는 P |
| error | `404` · `{error:string}` · `"task not found"` / `"目标不存在"` | C for direct writeErr; error helper 내부는 P |
| error | `409` · `{error:string}` · `"任务正在删除,无法删除目标"` | C for direct writeErr; error helper 내부는 P |
| error | `500` · `{error:string}` · `err.Error()` | C for direct writeErr; error helper 내부는 P |

응답 schema 판정: **C** · 직접 scalar/static top-level key와 field type을 추출; nested 내부 schema는 별도 type 참조가 없으면 P.

### 목록·동시성·완료

- Pagination/filter/sort: 등록된 직접 query key 중 표준 paging/filter key 없음.
- Idempotency/concurrency: Idempotency-Key/If-Match 없음; 반복 시 응답/존재 검사가 달라질 수 있음.
- Side effect/completion: handler의 직접 mutation/호출 후 응답; 외부 전달·agent 실행이 파생되면 별도 상태 확인(P).
- 직접 협력자/실행 단서: `err.Error`, `goalDTO`, `r.PathValue`, `s.engine.beginTaskOperation`, `s.engine.decInflight`, `s.m.Task`, `t.NotifyGoalDeleted`, `t.Store.DeleteGoal`, `t.Store.GetNode`; direct `go` statement는 미발견. 이 목록은 helper 내부의 background launch나 transaction 경계를 펼치지 않으므로 그 부분은 P/U다.
- 종합 의미 판정: **P/U**; route/auth와 위에서 직접 확인한 branch·shape는 C지만, 모든 helper·derived response·중첩 DTO·외부 효과까지 완결됐다는 뜻은 아니다.

<a id="schema-get-api-tasks-id-constraints"></a>
## `GET /api/tasks/{id}/constraints`

구현: `listConstraints` · `server/constraints_api.go` · [정확한 handler 근거](evidence:handler-get-api-tasks-id-constraints)

### 요청

| 위치/field | wire type | 필수·null | 기본/검증 |
|---|---|---|---|
| path `id` | `string` | 필수 | handler의 parse/range validation; 미해석 helper는 P |
| auth credential | Bearer header / `artex_token` cookie / query `token` | 세 위치 중 하나에 유효 JWT 필수 | `extractToken` 우선순위: header→cookie→query; query fallback은 SSE로 제한되지 않음 |
| body | 없음(직접 decoder/form read 미발견) | 해당 없음 | 해당 없음 |

요청 판정: **C/P** · unknown field 거부 미설정.
직접 validation: `!ok` · `err != nil`

### 응답·오류

| 종류 | status/body | 해석 상태 |
|---|---|---|
| success/variant | `200` · `constraints:helper result` | P/U · `static composite` |
| error | `401` · `{error:string}` · `"未授权"` / `"token 无效或已过期"` | C for direct writeErr; error helper 내부는 P |
| error | `404` · `{error:string}` · `"task not found"` | C for direct writeErr; error helper 내부는 P |
| error | `500` · `{error:string}` · `err.Error()` | C for direct writeErr; error helper 내부는 P |

응답 schema 판정: **P** · top-level key는 직접 추출했지만 derived/helper/opaque 또는 nested type이 남음.

### 목록·동시성·완료

- Pagination/filter/sort: 등록된 직접 query key 중 표준 paging/filter key 없음.
- Idempotency/concurrency: read/stream 요청; stream 재연결은 cursor 계약에 따름.
- Side effect/completion: 직접 조회/파일 전송 완료; 외부 조회가 있으면 응답 전 handler 결과만 보장.
- 직접 협력자/실행 단서: `constraintDTOs`, `err.Error`, `r.PathValue`, `s.m.Task`, `t.Store.ListConstraints`; direct `go` statement는 미발견. 이 목록은 helper 내부의 background launch나 transaction 경계를 펼치지 않으므로 그 부분은 P/U다.
- 종합 의미 판정: **P/U**; route/auth와 위에서 직접 확인한 branch·shape는 C지만, 모든 helper·derived response·중첩 DTO·외부 효과까지 완결됐다는 뜻은 아니다.

<a id="schema-post-api-tasks-id-constraints"></a>
## `POST /api/tasks/{id}/constraints`

구현: `addConstraint` · `server/constraints_api.go` · [정확한 handler 근거](evidence:handler-post-api-tasks-id-constraints)

### 요청

| 위치/field | wire type | 필수·null | 기본/검증 |
|---|---|---|---|
| path `id` | `string` | 필수 | handler의 parse/range validation; 미해석 helper는 P |
| auth credential | Bearer header / `artex_token` cookie / query `token` | 세 위치 중 하나에 유효 JWT 필수 | `extractToken` 우선순위: header→cookie→query; query fallback은 SSE로 제한되지 않음 |
| body `text` | `string` (`string`) | 필수(직접 거부 branch 확인); null/absent 구별 안 됨 | `trim 후 빈 문자열이면 400` |
| body `kind` | `string` (`string`) | 필수(직접 거부 branch 확인); null/absent 구별 안 됨 | `normalizeConstraintKind 결과가 allow/deny가 아니면 400` |

요청 판정: **C/P** · unknown field 거부 미설정.

### 응답·오류

| 종류 | status/body | 해석 상태 |
|---|---|---|
| success/variant | `200` · `id:string`, `kind:string`, `text:string`, `origin:string`, `ts:string` | C · `static composite` |
| error | `400` · `{error:string}` · `"invalid JSON"` / `"约束内容不能为空"` / `"kind 必须是 allow 或 deny"` | C for direct writeErr; error helper 내부는 P |
| error | `401` · `{error:string}` · `"未授权"` / `"token 无效或已过期"` | C for direct writeErr; error helper 내부는 P |
| error | `404` · `{error:string}` · `"task not found"` | C for direct writeErr; error helper 내부는 P |
| error | `409` · `{error:string}` · `"任务正在删除,无法新增约束"` | C for direct writeErr; error helper 내부는 P |
| error | `500` · `{error:string}` · `err.Error()` | C for direct writeErr; error helper 내부는 P |

응답 schema 판정: **C** · 직접 scalar/static top-level key와 field type을 추출; nested 내부 schema는 별도 type 참조가 없으면 P.

### 목록·동시성·완료

- Pagination/filter/sort: 등록된 직접 query key 중 표준 paging/filter key 없음.
- Idempotency/concurrency: Idempotency-Key 없음; unique constraint 또는 상태 check가 있는 경우만 중복을 제한(P).
- Side effect/completion: handler의 직접 mutation/호출 후 응답; 외부 전달·agent 실행이 파생되면 별도 상태 확인(P).
- 직접 협력자/실행 단서: `err.Error`, `json.NewDecoder`, `json.NewDecoder(r.Body).Decode`, `normalizeConstraintKind`, `r.PathValue`, `s.engine.beginTaskOperation`, `s.engine.decInflight`, `s.m.Task`, `t.Store.AddConstraint`; direct `go` statement는 미발견. 이 목록은 helper 내부의 background launch나 transaction 경계를 펼치지 않으므로 그 부분은 P/U다.
- 종합 의미 판정: **P/U**; route/auth와 위에서 직접 확인한 branch·shape는 C지만, 모든 helper·derived response·중첩 DTO·외부 효과까지 완결됐다는 뜻은 아니다.

<a id="schema-patch-api-tasks-id-constraints-cid"></a>
## `PATCH /api/tasks/{id}/constraints/{cid}`

구현: `editConstraint` · `server/constraints_api.go` · [정확한 handler 근거](evidence:handler-patch-api-tasks-id-constraints-cid)

### 요청

| 위치/field | wire type | 필수·null | 기본/검증 |
|---|---|---|---|
| path `cid` | `string` | 필수 | handler의 parse/range validation; 미해석 helper는 P |
| path `id` | `string` | 필수 | handler의 parse/range validation; 미해석 helper는 P |
| auth credential | Bearer header / `artex_token` cookie / query `token` | 세 위치 중 하나에 유효 JWT 필수 | `extractToken` 우선순위: header→cookie→query; query fallback은 SSE로 제한되지 않음 |
| body `text` | `string` (`string`) | 필수(직접 거부 branch 확인); null/absent 구별 안 됨 | `trim 후 빈 문자열이면 400` |
| body `kind` | `string` (`string`) | 필수(직접 거부 branch 확인); null/absent 구별 안 됨 | `normalizeConstraintKind 결과가 allow/deny가 아니면 400` |

요청 판정: **C/P** · unknown field 거부 미설정.
직접 parse/default: `strconv.ParseInt(r.PathValue("cid"), 10, 64)`

### 응답·오류

| 종류 | status/body | 해석 상태 |
|---|---|---|
| success/variant | `200` · `id:string`, `kind:string`, `text:string`, `origin:string`, `ts:string` | C · `static composite` |
| error | `400` · `{error:string}` · `"bad constraint id"` / `"invalid JSON"` / `"约束内容不能为空"` / `"kind 必须是 allow 或 deny"` | C for direct writeErr; error helper 내부는 P |
| error | `401` · `{error:string}` · `"未授权"` / `"token 无效或已过期"` | C for direct writeErr; error helper 내부는 P |
| error | `404` · `{error:string}` · `"task not found"` | C for direct writeErr; error helper 내부는 P |
| error | `409` · `{error:string}` · `"任务正在删除,无法修改约束"` | C for direct writeErr; error helper 내부는 P |
| error | `500` · `{error:string}` · `err.Error()` | C for direct writeErr; error helper 내부는 P |

응답 schema 판정: **C** · 직접 scalar/static top-level key와 field type을 추출; nested 내부 schema는 별도 type 참조가 없으면 P.

### 목록·동시성·완료

- Pagination/filter/sort: 등록된 직접 query key 중 표준 paging/filter key 없음.
- Idempotency/concurrency: Idempotency-Key/If-Match 없음; DB upsert/trigger/side effect 때문에 반복 의미를 handler별 확인(P).
- Side effect/completion: handler의 직접 mutation/호출 후 응답; 외부 전달·agent 실행이 파생되면 별도 상태 확인(P).
- 직접 협력자/실행 단서: `err.Error`, `json.NewDecoder`, `json.NewDecoder(r.Body).Decode`, `normalizeConstraintKind`, `r.PathValue`, `s.engine.beginTaskOperation`, `s.engine.decInflight`, `s.m.Task`, `t.Store.UpdateConstraint`; direct `go` statement는 미발견. 이 목록은 helper 내부의 background launch나 transaction 경계를 펼치지 않으므로 그 부분은 P/U다.
- 종합 의미 판정: **P/U**; route/auth와 위에서 직접 확인한 branch·shape는 C지만, 모든 helper·derived response·중첩 DTO·외부 효과까지 완결됐다는 뜻은 아니다.

<a id="schema-delete-api-tasks-id-constraints-cid"></a>
## `DELETE /api/tasks/{id}/constraints/{cid}`

구현: `deleteConstraint` · `server/constraints_api.go` · [정확한 handler 근거](evidence:handler-delete-api-tasks-id-constraints-cid)

### 요청

| 위치/field | wire type | 필수·null | 기본/검증 |
|---|---|---|---|
| path `cid` | `string` | 필수 | handler의 parse/range validation; 미해석 helper는 P |
| path `id` | `string` | 필수 | handler의 parse/range validation; 미해석 helper는 P |
| auth credential | Bearer header / `artex_token` cookie / query `token` | 세 위치 중 하나에 유효 JWT 필수 | `extractToken` 우선순위: header→cookie→query; query fallback은 SSE로 제한되지 않음 |
| body | 없음(직접 decoder/form read 미발견) | 해당 없음 | 해당 없음 |

요청 판정: **C/P** · unknown field 거부 미설정.
직접 validation: `!ok` · `!s.engine.beginTaskOperation(t.ID)` · `err != nil` · `err != nil \|\| cid <= 0`
직접 parse/default: `strconv.ParseInt(r.PathValue("cid"), 10, 64)`

### 응답·오류

| 종류 | status/body | 해석 상태 |
|---|---|---|
| success/variant | `200` · `ok:boolean` | C · `static composite` |
| error | `400` · `{error:string}` · `"bad constraint id"` | C for direct writeErr; error helper 내부는 P |
| error | `401` · `{error:string}` · `"未授权"` / `"token 无效或已过期"` | C for direct writeErr; error helper 내부는 P |
| error | `404` · `{error:string}` · `"task not found"` | C for direct writeErr; error helper 내부는 P |
| error | `409` · `{error:string}` · `"任务正在删除,无法删除约束"` | C for direct writeErr; error helper 내부는 P |
| error | `500` · `{error:string}` · `err.Error()` | C for direct writeErr; error helper 내부는 P |

응답 schema 판정: **C** · 직접 scalar/static top-level key와 field type을 추출; nested 내부 schema는 별도 type 참조가 없으면 P.

### 목록·동시성·완료

- Pagination/filter/sort: 등록된 직접 query key 중 표준 paging/filter key 없음.
- Idempotency/concurrency: Idempotency-Key/If-Match 없음; 반복 시 응답/존재 검사가 달라질 수 있음.
- Side effect/completion: handler의 직접 mutation/호출 후 응답; 외부 전달·agent 실행이 파생되면 별도 상태 확인(P).
- 직접 협력자/실행 단서: `err.Error`, `r.PathValue`, `s.engine.beginTaskOperation`, `s.engine.decInflight`, `s.m.Task`, `t.Store.DeleteConstraint`; direct `go` statement는 미발견. 이 목록은 helper 내부의 background launch나 transaction 경계를 펼치지 않으므로 그 부분은 P/U다.
- 종합 의미 판정: **P/U**; route/auth와 위에서 직접 확인한 branch·shape는 C지만, 모든 helper·derived response·중첩 DTO·외부 효과까지 완결됐다는 뜻은 아니다.

<a id="schema-post-api-tasks-id-control"></a>
## `POST /api/tasks/{id}/control`

구현: `control` · `server/server.go` · [정확한 handler 근거](evidence:handler-post-api-tasks-id-control)

### 요청

| 위치/field | wire type | 필수·null | 기본/검증 |
|---|---|---|---|
| path `id` | `string` | 필수 | handler의 parse/range validation; 미해석 helper는 P |
| auth credential | Bearer header / `artex_token` cookie / query `token` | 세 위치 중 하나에 유효 JWT 필수 | `extractToken` 우선순위: header→cookie→query; query fallback은 SSE로 제한되지 않음 |
| body `action` | `string` (`string`) | 필수(직접 거부 branch 확인); null/absent 구별 안 됨 | `pause/resume가 아니면 400` |

요청 판정: **C/P** · unknown field 거부 미설정.
직접 validation: `req.Action != "pause" && req.Action != "resume"`

### 응답·오류

| 종류 | status/body | 해석 상태 |
|---|---|---|
| success/variant | `200` · `result` | P/U · `derived variable` |
| error | `400` · `{error:string}` · `err.Error()` / `"action must be pause\|resume"` | C for direct writeErr; error helper 내부는 P |
| error | `401` · `{error:string}` · `"未授权"` / `"token 无效或已过期"` | C for direct writeErr; error helper 내부는 P |
| error | `404` · `{error:string}` · `"task not found"` | C for direct writeErr; error helper 내부는 P |
| error | `409` · `{error:string}` · `err.Error()` | C for direct writeErr; error helper 내부는 P |

응답 schema 판정: **P** · top-level key는 직접 추출했지만 derived/helper/opaque 또는 nested type이 남음.

### 목록·동시성·완료

- Pagination/filter/sort: 등록된 직접 query key 중 표준 paging/filter key 없음.
- Idempotency/concurrency: Idempotency-Key 없음; unique constraint 또는 상태 check가 있는 경우만 중복을 제한(P).
- Side effect/completion: handler의 직접 mutation/호출 후 응답; 외부 전달·agent 실행이 파생되면 별도 상태 확인(P).
- 직접 협력자/실행 단서: `err.Error`, `json.NewDecoder`, `json.NewDecoder(r.Body).Decode`, `r.PathValue`, `s.m.Task`; direct `go` statement는 미발견. 이 목록은 helper 내부의 background launch나 transaction 경계를 펼치지 않으므로 그 부분은 P/U다.
- 종합 의미 판정: **P/U**; route/auth와 위에서 직접 확인한 branch·shape는 C지만, 모든 helper·derived response·중첩 DTO·외부 효과까지 완결됐다는 뜻은 아니다.

<a id="schema-put-api-tasks-id-llm"></a>
## `PUT /api/tasks/{id}/llm`

구현: `updateTaskLLMProfiles` · `server/server.go` · [정확한 handler 근거](evidence:handler-put-api-tasks-id-llm)

### 요청

| 위치/field | wire type | 필수·null | 기본/검증 |
|---|---|---|---|
| path `id` | `string` | 필수 | handler의 parse/range validation; 미해석 helper는 P |
| auth credential | Bearer header / `artex_token` cookie / query `token` | 세 위치 중 하나에 유효 JWT 필수 | `extractToken` 우선순위: header→cookie→query; query fallback은 SSE로 제한되지 않음 |
| body `llm_profile_ids` | `array[integer]` (`[]int64`) | 요구 여부 미확정(P); absent/null→nil; null/absent 구별 안 됨 | `nil` |
| body `active_llm_profile_id` | `integer` (`*int64`) | 요구 여부 미확정(P); absent/null→nil; null→nil | `req.ActiveLLMProfileID != nil` |

요청 판정: **C/P** · unknown field 거부 미설정.
직접 validation: `req.ActiveLLMProfileID != nil`

### 응답·오류

| 종류 | status/body | 해석 상태 |
|---|---|---|
| success/variant | `200` · `result` | P/U · `derived variable` |
| error | `400` · `{error:string}` · `"bad json: " + err.Error()` / `err.Error()` | C for direct writeErr; error helper 내부는 P |
| error | `401` · `{error:string}` · `"未授权"` / `"token 无效或已过期"` | C for direct writeErr; error helper 내부는 P |
| error | `404` · `{error:string}` · `"task not found"` | C for direct writeErr; error helper 내부는 P |

응답 schema 판정: **P** · top-level key는 직접 추출했지만 derived/helper/opaque 또는 nested type이 남음.

### 목록·동시성·완료

- Pagination/filter/sort: 등록된 직접 query key 중 표준 paging/filter key 없음.
- Idempotency/concurrency: Idempotency-Key/If-Match 없음; DB upsert/trigger/side effect 때문에 반복 의미를 handler별 확인(P).
- Side effect/completion: handler의 직접 mutation/호출 후 응답; 외부 전달·agent 실행이 파생되면 별도 상태 확인(P).
- 직접 협력자/실행 단서: `activityDTO`, `err.Error`, `json.NewDecoder`, `json.NewDecoder(r.Body).Decode`, `r.PathValue`, `s.m.ReplaceTaskLLMProfiles`, `s.m.Task`, `sameOptionalID`, `t.Notify`, `t.llmStateSnapshot`; direct `go` statement는 발견. 이 목록은 helper 내부의 background launch나 transaction 경계를 펼치지 않으므로 그 부분은 P/U다.
- 종합 의미 판정: **P/U**; route/auth와 위에서 직접 확인한 branch·shape는 C지만, 모든 helper·derived response·중첩 DTO·외부 효과까지 완결됐다는 뜻은 아니다.

<a id="schema-get-api-tasks-id-llm-resolution"></a>
## `GET /api/tasks/{id}/llm/resolution`

구현: `taskLLMResolutionHandler` · `server/task_resolution.go` · [정확한 handler 근거](evidence:handler-get-api-tasks-id-llm-resolution)

### 요청

| 위치/field | wire type | 필수·null | 기본/검증 |
|---|---|---|---|
| path `id` | `string` | 필수 | handler의 parse/range validation; 미해석 helper는 P |
| auth credential | Bearer header / `artex_token` cookie / query `token` | 세 위치 중 하나에 유효 JWT 필수 | `extractToken` 우선순위: header→cookie→query; query fallback은 SSE로 제한되지 않음 |
| body | 없음(직접 decoder/form read 미발견) | 해당 없음 | 해당 없음 |

요청 판정: **C/P** · unknown field 거부 미설정.
직접 validation: `!ok` · `err != nil`

### 응답·오류

| 종류 | status/body | 해석 상태 |
|---|---|---|
| success/variant | `200` · `roles` | P/U · `derived variable` |
| error | `401` · `{error:string}` · `"未授权"` / `"token 无效或已过期"` | C for direct writeErr; error helper 내부는 P |
| error | `404` · `{error:string}` · `"task not found"` | C for direct writeErr; error helper 내부는 P |
| error | `500` · `{error:string}` · `err.Error()` | C for direct writeErr; error helper 내부는 P |

응답 schema 판정: **P** · top-level key는 직접 추출했지만 derived/helper/opaque 또는 nested type이 남음.

### 목록·동시성·완료

- Pagination/filter/sort: 등록된 직접 query key 중 표준 paging/filter key 없음.
- Idempotency/concurrency: read/stream 요청; stream 재연결은 cursor 계약에 따름.
- Side effect/completion: 직접 조회/파일 전송 완료; 외부 조회가 있으면 응답 전 handler 결과만 보장.
- 직접 협력자/실행 단서: `err.Error`, `r.PathValue`, `s.m.Task`; direct `go` statement는 미발견. 이 목록은 helper 내부의 background launch나 transaction 경계를 펼치지 않으므로 그 부분은 P/U다.
- 종합 의미 판정: **P/U**; route/auth와 위에서 직접 확인한 branch·shape는 C지만, 모든 helper·derived response·중첩 DTO·외부 효과까지 완결됐다는 뜻은 아니다.

<a id="schema-post-api-tasks-id-intents-iid-control"></a>
## `POST /api/tasks/{id}/intents/{iid}/control`

구현: `controlIntent` · `server/server.go` · [정확한 handler 근거](evidence:handler-post-api-tasks-id-intents-iid-control)

### 요청

| 위치/field | wire type | 필수·null | 기본/검증 |
|---|---|---|---|
| path `id` | `string` | 필수 | handler의 parse/range validation; 미해석 helper는 P |
| path `iid` | `string` | 필수 | handler의 parse/range validation; 미해석 helper는 P |
| auth credential | Bearer header / `artex_token` cookie / query `token` | 세 위치 중 하나에 유효 JWT 필수 | `extractToken` 우선순위: header→cookie→query; query fallback은 SSE로 제한되지 않음 |
| body `action` | `string` (`string`) | 필수(직접 거부 branch 확인); null/absent 구별 안 됨 | `pause/resume/cancel이 아니면 400` |
| body `reason` | `string` (`string`) | 조건부 필수; null/absent 구별 안 됨 | `action=cancel일 때 trim-empty이면 applyIntentControl 오류로 409` |
| body `mode` | `string` (`string`) | 선택; null/absent 구별 안 됨 | `action=cancel에서 hard만 물리 삭제; 누락 및 그 밖의 값은 soft 경로` |

요청 판정: **C/P** · unknown field 거부 미설정.
직접 validation: `req.Action != "pause" && req.Action != "resume" && req.Action != "cancel"`
직접 parse/default: `strconv.ParseInt(r.PathValue("iid"), 10, 64)`

### 응답·오류

| 종류 | status/body | 해석 상태 |
|---|---|---|
| success/variant | `200` · `result` | P/U · `derived variable` |
| error | `400` · `{error:string}` · `"bad intent id"` / `"bad json: " + err.Error()` / `"action must be pause\|resume\|cancel"` | C for direct writeErr; error helper 내부는 P |
| error | `401` · `{error:string}` · `"未授权"` / `"token 无效或已过期"` | C for direct writeErr; error helper 내부는 P |
| error | `404` · `{error:string}` · `"task not found"` | C for direct writeErr; error helper 내부는 P |
| error | `409` · `{error:string}` · `"任务正在删除，无法控制意图"` / `err.Error()` | C for direct writeErr; error helper 내부는 P |

응답 schema 판정: **P** · top-level key는 직접 추출했지만 derived/helper/opaque 또는 nested type이 남음.

### 목록·동시성·완료

- Pagination/filter/sort: 등록된 직접 query key 중 표준 paging/filter key 없음.
- Idempotency/concurrency: Idempotency-Key 없음; unique constraint 또는 상태 check가 있는 경우만 중복을 제한(P).
- Side effect/completion: handler의 직접 mutation/호출 후 응답; 외부 전달·agent 실행이 파생되면 별도 상태 확인(P).
- 직접 협력자/실행 단서: `err.Error`, `json.NewDecoder`, `json.NewDecoder(r.Body).Decode`, `r.Context`, `r.PathValue`, `s.engine.beginTaskOperation`, `s.engine.decInflight`, `s.m.Task`; direct `go` statement는 미발견. 이 목록은 helper 내부의 background launch나 transaction 경계를 펼치지 않으므로 그 부분은 P/U다.
- 종합 의미 판정: **P/U**; route/auth와 위에서 직접 확인한 branch·shape는 C지만, 모든 helper·derived response·중첩 DTO·외부 효과까지 완결됐다는 뜻은 아니다.

<a id="schema-post-api-tasks-id-intents-iid-messages"></a>
## `POST /api/tasks/{id}/intents/{iid}/messages`

구현: `sendWorkerMessage` · `server/intent_intervention.go` · [정확한 handler 근거](evidence:handler-post-api-tasks-id-intents-iid-messages)

### 요청

| 위치/field | wire type | 필수·null | 기본/검증 |
|---|---|---|---|
| path `id` | `string` | 필수 | handler의 parse/range validation; 미해석 helper는 P |
| path `iid` | `string` | 필수 | handler의 parse/range validation; 미해석 helper는 P |
| auth credential | Bearer header / `artex_token` cookie / query `token` | 세 위치 중 하나에 유효 JWT 필수 | `extractToken` 우선순위: header→cookie→query; query fallback은 SSE로 제한되지 않음 |
| body `message` | `string` (`string`) | 필수(직접 거부 branch 확인); null/absent 구별 안 됨 | `trim-empty 또는 4000자 초과면 400` |
| body `request_id` | `string` (`string`) | 필수(직접 거부 branch 확인); null/absent 구별 안 됨 | `1..128자 허용문자 검증 실패면 400` |

요청 body 상한: **64 KiB** (`65536` bytes) · source `server/intent_intervention.go::sendWorkerMessage` · 초과/decoder 경로 `413 "请求体过大"`.

요청 판정: **C/P** · unknown field 거부 미설정.
직접 parse/default: `strconv.ParseInt(r.PathValue("iid"), 10, 64)`

### 응답·오류

| 종류 | status/body | 해석 상태 |
|---|---|---|
| success/variant | `200` · `id:derived`, `state:string`, `accepted:boolean`, `request_id:derived` | P/U · `static composite` |
| error | `400` · `{error:string}` · `"bad intent id"` / `"bad json: " + err.Error()` / `"消息不能为空"` / `"消息不能超过 4000 个字符"` / `"request_id 必须是 1-128 位字母、数字、-、_、. 或 :"` / `err.Error()` | C for direct writeErr; error helper 내부는 P |
| error | `401` · `{error:string}` · `"未授权"` / `"token 无效或已过期"` | C for direct writeErr; error helper 내부는 P |
| error | `404` · `{error:string}` · `"task not found"` / `"intent not found"` | C for direct writeErr; error helper 내부는 P |
| error | `409` · `{error:string}` · `"任务正在删除，无法向 Worker 发送消息"` / `"任务已暂停，请先恢复任务再向 Worker 发送消息"` / `"排队中的任务无法向 Worker 发送消息"` / `"终态任务无法向 Worker 发送消息"` / `"任务正在收尾，无法向 Worker 发送消息"` / `"继承意图为只读，不能发送 Worker 消息"` / `"node is not an intent"` / `"仅已暂停的 Worker 可以发送消息，请先暂停"` / `err.Error()` | C for direct writeErr; error helper 내부는 P |
| error | `413` · `{error:string}` · `"请求体过大"` | C for direct writeErr; error helper 내부는 P |
| error | `500` · `{error:string}` · `err.Error()` | C for direct writeErr; error helper 내부는 P |

Helper 오류 분기: `prepareChatMentionMessage` · `server/chat_mentions.go::Server.prepareChatMentionMessage` · 위 `400, 500` status는 helper control-flow를 정적으로 펼친 P이며 호출 전/후 DB 효과는 operation별로 확인한다.

응답 schema 판정: **P** · top-level key는 직접 추출했지만 derived/helper/opaque 또는 nested type이 남음.

### 목록·동시성·완료

- Pagination/filter/sort: 등록된 직접 query key 중 표준 paging/filter key 없음.
- Idempotency/concurrency: Idempotency-Key 없음; unique constraint 또는 상태 check가 있는 경우만 중복을 제한(P).
- Side effect/completion: agent turn을 background context에서 실행할 수 있음; activity/stop/terminal event를 확인.
- 직접 협력자/실행 단서: `[]rune`, `err.Error`, `errors.As`, `isTerminalStatus`, `json.NewDecoder`, `json.NewDecoder(r.Body).Decode`, `r.PathValue`, `s.engine.IsDeleting`, `s.engine.IsPaused`, `s.engine.isSettling`, `s.engine.runDetachedIntent`, `s.m.Task`, `t.Store.GetNode`, `t.Store.GetNodeWithSources`, `t.lifecycleSnapshot`, `validWorkerMessageRequestID`; direct `go` statement는 미발견. 이 목록은 helper 내부의 background launch나 transaction 경계를 펼치지 않으므로 그 부분은 P/U다.
- 종합 의미 판정: **P/U**; route/auth와 위에서 직접 확인한 branch·shape는 C지만, 모든 helper·derived response·중첩 DTO·외부 효과까지 완결됐다는 뜻은 아니다.

<a id="schema-post-api-tasks-id-intents-iid-rerun"></a>
## `POST /api/tasks/{id}/intents/{iid}/rerun`

구현: `rerunIntent` · `server/server.go` · [정확한 handler 근거](evidence:handler-post-api-tasks-id-intents-iid-rerun)

### 요청

| 위치/field | wire type | 필수·null | 기본/검증 |
|---|---|---|---|
| path `id` | `string` | 필수 | handler의 parse/range validation; 미해석 helper는 P |
| path `iid` | `string` | 필수 | handler의 parse/range validation; 미해석 helper는 P |
| auth credential | Bearer header / `artex_token` cookie / query `token` | 세 위치 중 하나에 유효 JWT 필수 | `extractToken` 우선순위: header→cookie→query; query fallback은 SSE로 제한되지 않음 |
| body | 없음(직접 decoder/form read 미발견) | 해당 없음 | 해당 없음 |

요청 판정: **C/P** · unknown field 거부 미설정.
직접 validation: `!ok` · `!reopened` · `!s.engine.beginTaskOperation(t.ID)` · `err != nil` · `rollbackErr != nil`
직접 parse/default: `strconv.ParseInt(r.PathValue("iid"), 10, 64)`

### 응답·오류

| 종류 | status/body | 해석 상태 |
|---|---|---|
| success/variant | `200` · `id:expression`, `reopened:derived`, `queued:derived` | P/U · `static composite` |
| error | `400` · `{error:string}` · `"bad intent id"` | C for direct writeErr; error helper 내부는 P |
| error | `401` · `{error:string}` · `"未授权"` / `"token 无效或已过期"` | C for direct writeErr; error helper 내부는 P |
| error | `404` · `{error:string}` · `"task not found"` | C for direct writeErr; error helper 내부는 P |
| error | `409` · `{error:string}` · `"任务正在删除，无法重跑意图"` / `"该意图不是可重跑状态(仅 blocked/exhausted/stopped 可重跑)"` | C for direct writeErr; error helper 내부는 P |
| error | `500` · `{error:string}` · `err.Error()` | C for direct writeErr; error helper 내부는 P |

응답 schema 판정: **P** · top-level key는 직접 추출했지만 derived/helper/opaque 또는 nested type이 남음.

### 목록·동시성·완료

- Pagination/filter/sort: 등록된 직접 query key 중 표준 paging/filter key 없음.
- Idempotency/concurrency: Idempotency-Key 없음; unique constraint 또는 상태 check가 있는 경우만 중복을 제한(P).
- Side effect/completion: handler의 직접 mutation/호출 후 응답; 외부 전달·agent 실행이 파생되면 별도 상태 확인(P).
- 직접 협력자/실행 단서: `err.Error`, `log.Printf`, `r.PathValue`, `restoreRerunIntent`, `s.engine.beginTaskOperation`, `s.engine.decInflight`, `s.m.Task`, `t.Store.GetNode`, `t.Store.ReopenIntent`; direct `go` statement는 미발견. 이 목록은 helper 내부의 background launch나 transaction 경계를 펼치지 않으므로 그 부분은 P/U다.
- 종합 의미 판정: **P/U**; route/auth와 위에서 직접 확인한 branch·shape는 C지만, 모든 helper·derived response·중첩 DTO·외부 효과까지 완결됐다는 뜻은 아니다.

<a id="schema-post-api-tasks-id-intents-rerun-blocked"></a>
## `POST /api/tasks/{id}/intents/rerun-blocked`

구현: `rerunBlocked` · `server/server.go` · [정확한 handler 근거](evidence:handler-post-api-tasks-id-intents-rerun-blocked)

### 요청

| 위치/field | wire type | 필수·null | 기본/검증 |
|---|---|---|---|
| path `id` | `string` | 필수 | handler의 parse/range validation; 미해석 helper는 P |
| auth credential | Bearer header / `artex_token` cookie / query `token` | 세 위치 중 하나에 유효 JWT 필수 | `extractToken` 우선순위: header→cookie→query; query fallback은 SSE로 제한되지 않음 |
| body | 없음(직접 decoder/form read 미발견) | 해당 없음 | 해당 없음 |

요청 판정: **C/P** · unknown field 거부 미설정.
직접 validation: `!ok` · `!s.engine.beginTaskOperation(t.ID)` · `err != nil` · `intent.State == "blocked"` · `len(rollbackErrors) > 0` · `n > 0` · `rollbackErr != nil`

### 응답·오류

| 종류 | status/body | 해석 상태 |
|---|---|---|
| success/variant | `200` · `id:expression`, `reopened:derived`, `queued:derived` | P/U · `static composite` |
| error | `401` · `{error:string}` · `"未授权"` / `"token 无效或已过期"` | C for direct writeErr; error helper 내부는 P |
| error | `404` · `{error:string}` · `"task not found"` | C for direct writeErr; error helper 내부는 P |
| error | `409` · `{error:string}` · `"任务正在删除，无法重跑意图"` | C for direct writeErr; error helper 내부는 P |
| error | `500` · `{error:string}` · `err.Error()` | C for direct writeErr; error helper 내부는 P |

응답 schema 판정: **P** · top-level key는 직접 추출했지만 derived/helper/opaque 또는 nested type이 남음.

### 목록·동시성·완료

- Pagination/filter/sort: 등록된 직접 query key 중 표준 paging/filter key 없음.
- Idempotency/concurrency: Idempotency-Key 없음; unique constraint 또는 상태 check가 있는 경우만 중복을 제한(P).
- Side effect/completion: handler의 직접 mutation/호출 후 응답; 외부 전달·agent 실행이 파생되면 별도 상태 확인(P).
- 직접 협력자/실행 단서: `err.Error`, `log.Printf`, `r.PathValue`, `restoreRerunIntent`, `s.engine.beginTaskOperation`, `s.engine.decInflight`, `s.m.Task`, `t.Store.ListByKind`, `t.Store.ReopenBlockedIntents`; direct `go` statement는 미발견. 이 목록은 helper 내부의 background launch나 transaction 경계를 펼치지 않으므로 그 부분은 P/U다.
- 종합 의미 판정: **P/U**; route/auth와 위에서 직접 확인한 branch·shape는 C지만, 모든 helper·derived response·중첩 DTO·외부 효과까지 완결됐다는 뜻은 아니다.

<a id="schema-post-api-active"></a>
## `POST /api/active`

구현: `setActive` · `server/server.go` · [정확한 handler 근거](evidence:handler-post-api-active)

### 요청

| 위치/field | wire type | 필수·null | 기본/검증 |
|---|---|---|---|
| auth credential | Bearer header / `artex_token` cookie / query `token` | 세 위치 중 하나에 유효 JWT 필수 | `extractToken` 우선순위: header→cookie→query; query fallback은 SSE로 제한되지 않음 |
| body `id` | `string` (`string`) | 필수(직접 거부 branch 확인); null/absent 구별 안 됨 | `SetActive가 id를 찾지 못하면 404` |

요청 판정: **C/P** · unknown field 거부 미설정.
직접 validation: `!s.m.SetActive(req.ID)`

### 응답·오류

| 종류 | status/body | 해석 상태 |
|---|---|---|
| success/variant | `200` · `active:expression` | P/U · `static composite` |
| error | `400` · `{error:string}` · `err.Error()` | C for direct writeErr; error helper 내부는 P |
| error | `401` · `{error:string}` · `"未授权"` / `"token 无效或已过期"` | C for direct writeErr; error helper 내부는 P |
| error | `404` · `{error:string}` · `"task not found"` | C for direct writeErr; error helper 내부는 P |

응답 schema 판정: **P** · top-level key는 직접 추출했지만 derived/helper/opaque 또는 nested type이 남음.

### 목록·동시성·완료

- Pagination/filter/sort: 등록된 직접 query key 중 표준 paging/filter key 없음.
- Idempotency/concurrency: Idempotency-Key 없음; unique constraint 또는 상태 check가 있는 경우만 중복을 제한(P).
- Side effect/completion: handler의 직접 mutation/호출 후 응답; 외부 전달·agent 실행이 파생되면 별도 상태 확인(P).
- 직접 협력자/실행 단서: `err.Error`, `json.NewDecoder`, `json.NewDecoder(r.Body).Decode`, `s.engine.Run`, `s.m.SetActive`, `s.m.Task`, `t.lifecycleSnapshot`; direct `go` statement는 미발견. 이 목록은 helper 내부의 background launch나 transaction 경계를 펼치지 않으므로 그 부분은 P/U다.
- 종합 의미 판정: **P/U**; route/auth와 위에서 직접 확인한 branch·shape는 C지만, 모든 helper·derived response·중첩 DTO·외부 효과까지 완결됐다는 뜻은 아니다.

<a id="schema-get-api-llm"></a>
## `GET /api/llm`

구현: `getLLM` · `server/server.go` · [정확한 handler 근거](evidence:handler-get-api-llm)

### 요청

| 위치/field | wire type | 필수·null | 기본/검증 |
|---|---|---|---|
| auth credential | Bearer header / `artex_token` cookie / query `token` | 세 위치 중 하나에 유효 JWT 필수 | `extractToken` 우선순위: header→cookie→query; query fallback은 SSE로 제한되지 않음 |
| body | 없음(직접 decoder/form read 미발견) | 해당 없음 | 해당 없음 |

요청 판정: **C/P** · unknown field 거부 미설정.

### 응답·오류

| 종류 | status/body | 해석 상태 |
|---|---|---|
| success/variant | `200` · `configured:expression`, `provider:helper result`, `model:expression`, `base_url:expression`, `proxy:expression`, `key_set:expression`, `rate_per_second:expression`, `rate_per_minute:expression`, `context_window_k:expression`, `thinking_type:expression`, `reasoning_effort:expression` | P/U · `static composite` |
| error | `401` · `{error:string}` · `"未授权"` / `"token 无效或已过期"` | C for direct writeErr; error helper 내부는 P |

응답 schema 판정: **P** · top-level key는 직접 추출했지만 derived/helper/opaque 또는 nested type이 남음.

### 목록·동시성·완료

- Pagination/filter/sort: 등록된 직접 query key 중 표준 paging/filter key 없음.
- Idempotency/concurrency: read/stream 요청; stream 재연결은 cursor 계약에 따름.
- Side effect/completion: 직접 조회/파일 전송 완료; 외부 조회가 있으면 응답 전 handler 결과만 보장.
- 직접 협력자/실행 단서: `s.cfgMu.Lock`, `s.cfgMu.Unlock`, `s.llmCfg.Provider`; direct `go` statement는 미발견. 이 목록은 helper 내부의 background launch나 transaction 경계를 펼치지 않으므로 그 부분은 P/U다.
- 종합 의미 판정: **P/U**; route/auth와 위에서 직접 확인한 branch·shape는 C지만, 모든 helper·derived response·중첩 DTO·외부 효과까지 완결됐다는 뜻은 아니다.

<a id="schema-post-api-llm"></a>
## `POST /api/llm`

구현: `setLLM` · `server/server.go` · [정확한 handler 근거](evidence:handler-post-api-llm)

### 요청

| 위치/field | wire type | 필수·null | 기본/검증 |
|---|---|---|---|
| auth credential | Bearer header / `artex_token` cookie / query `token` | 세 위치 중 하나에 유효 JWT 필수 | `extractToken` 우선순위: header→cookie→query; query fallback은 SSE로 제한되지 않음 |
| body `provider` | `string` (`string`) | 요구 여부 미확정(P); null/absent 구별 안 됨 | `Go zero value; 별도 기본은 handler/helper 확인` |
| body `model` | `string` (`string`) | 요구 여부 미확정(P); null/absent 구별 안 됨 | `Go zero value; 별도 기본은 handler/helper 확인` |
| body `base_url` | `string` (`string`) | 요구 여부 미확정(P); null/absent 구별 안 됨 | `Go zero value; 별도 기본은 handler/helper 확인` |
| body `proxy` | `string` (`string`) | 요구 여부 미확정(P); null/absent 구별 안 됨 | `Go zero value; 별도 기본은 handler/helper 확인` |
| body `api_key` | `string` (`string`) | 요구 여부 미확정(P); null/absent 구별 안 됨 | `Go zero value; 별도 기본은 handler/helper 확인` |
| body `rate_per_second` | `number` (`float64`) | 요구 여부 미확정(P); null/absent 구별 안 됨 | `cfg.RatePerSecond, cfg.RatePerMinute = req.RatePerSecond, req.RatePerMinute` |
| body `rate_per_minute` | `number` (`float64`) | 요구 여부 미확정(P); null/absent 구별 안 됨 | `cfg.RatePerSecond, cfg.RatePerMinute = req.RatePerSecond, req.RatePerMinute` |
| body `context_window_k` | `integer` (`int`) | 요구 여부 미확정(P); null/absent 구별 안 됨 | `Go zero value; 별도 기본은 handler/helper 확인` |
| body `thinking_type` | `string` (`string`) | 요구 여부 미확정(P); null/absent 구별 안 됨 | `cfg.ThinkingType = req.ThinkingType` |
| body `reasoning_effort` | `string` (`string`) | 요구 여부 미확정(P); null/absent 구별 안 됨 | `cfg.ReasoningEffort = req.ReasoningEffort` |

요청 판정: **C/P** · unknown field 거부 미설정.

### 응답·오류

| 종류 | status/body | 해석 상태 |
|---|---|---|
| success/variant | `200` · `configured:expression`, `provider:helper result`, `model:expression`, `base_url:expression`, `proxy:expression`, `key_set:expression`, `rate_per_second:expression`, `rate_per_minute:expression`, `context_window_k:expression`, `thinking_type:expression`, `reasoning_effort:expression` | P/U · `static composite` |
| error | `400` · `{error:string}` · `err.Error()` / `"api_key required"` / `"provider init failed: " + err.Error()` | C for direct writeErr; error helper 내부는 P |
| error | `401` · `{error:string}` · `"未授权"` / `"token 无效或已过期"` | C for direct writeErr; error helper 내부는 P |
| error | `500` · `{error:string}` · `"persist provider failed: " + err.Error()` / `"saved provider is unavailable"` | C for direct writeErr; error helper 내부는 P |

응답 schema 판정: **P** · top-level key는 직접 추출했지만 derived/helper/opaque 또는 nested type이 남음.

### 목록·동시성·완료

- Pagination/filter/sort: 등록된 직접 query key 중 표준 paging/filter key 없음.
- Idempotency/concurrency: Idempotency-Key 없음; unique constraint 또는 상태 check가 있는 경우만 중복을 제한(P).
- Side effect/completion: handler의 직접 mutation/호출 후 응답; 외부 전달·agent 실행이 파생되면 별도 상태 확인(P).
- 직접 협력자/실행 단서: `agent.ConfigFrom`, `cfg.NewProvider`, `cfg.Provider`, `err.Error`, `json.NewDecoder`, `json.NewDecoder(r.Body).Decode`, `log.Printf`, `s.cfgMu.Lock`, `s.cfgMu.Unlock`; direct `go` statement는 미발견. 이 목록은 helper 내부의 background launch나 transaction 경계를 펼치지 않으므로 그 부분은 P/U다.
- 종합 의미 판정: **P/U**; route/auth와 위에서 직접 확인한 branch·shape는 C지만, 모든 helper·derived response·중첩 DTO·외부 효과까지 완결됐다는 뜻은 아니다.

<a id="schema-post-api-llm-test"></a>
## `POST /api/llm/test`

구현: `testLLM` · `server/server.go` · [정확한 handler 근거](evidence:handler-post-api-llm-test)

### 요청

| 위치/field | wire type | 필수·null | 기본/검증 |
|---|---|---|---|
| auth credential | Bearer header / `artex_token` cookie / query `token` | 세 위치 중 하나에 유효 JWT 필수 | `extractToken` 우선순위: header→cookie→query; query fallback은 SSE로 제한되지 않음 |
| body `provider` | `string` (`string`) | 요구 여부 미확정(P); null/absent 구별 안 됨 | `Go zero value; 별도 기본은 handler/helper 확인` |
| body `model` | `string` (`string`) | 요구 여부 미확정(P); null/absent 구별 안 됨 | `Go zero value; 별도 기본은 handler/helper 확인` |
| body `base_url` | `string` (`string`) | 요구 여부 미확정(P); null/absent 구별 안 됨 | `Go zero value; 별도 기본은 handler/helper 확인` |
| body `proxy` | `string` (`string`) | 요구 여부 미확정(P); null/absent 구별 안 됨 | `Go zero value; 별도 기본은 handler/helper 확인` |
| body `api_key` | `string` (`string`) | 요구 여부 미확정(P); null/absent 구별 안 됨 | `Go zero value; 별도 기본은 handler/helper 확인` |
| body `thinking_type` | `string` (`string`) | 요구 여부 미확정(P); null/absent 구별 안 됨 | `cfg.ThinkingType = req.ThinkingType` |
| body `reasoning_effort` | `string` (`string`) | 요구 여부 미확정(P); null/absent 구별 안 됨 | `cfg.ReasoningEffort = req.ReasoningEffort` |
| body `profile_id` | `integer` (`*int64`) | 선택; null→nil | `제공 시 저장 profile credential fallback에만 사용` |
| body `streaming` | `boolean` (`*bool`) | 요구 여부 미확정(P); absent/null→nil; null→nil | `req.Streaming != nil` |
| body `session_header_key` | `string` (`string`) | 요구 여부 미확정(P); null/absent 구별 안 됨 | `cfg.SessionHeaderKey = req.SessionHeaderKey` |

요청 판정: **C/P** · unknown field 거부 미설정.
직접 validation: `req.ProfileID != nil && (cfg.APIKey == "" \|\| cfg.SessionHeaderKey == "")` · `req.Streaming != nil`

### 응답·오류

| 종류 | status/body | 해석 상태 |
|---|---|---|
| success/variant | `200` · `ok:boolean`, `error:string` | C · `static composite` |
| success/variant | `200` · `ok:boolean`, `error:helper result` | P/U · `static composite` |
| success/variant | `200` · `ok:boolean`, `latency_ms:helper result`, `model:expression`, `reply:helper result` | P/U · `static composite` |
| error | `400` · `{error:string}` · `err.Error()` | C for direct writeErr; error helper 내부는 P |
| error | `401` · `{error:string}` · `"未授权"` / `"token 无效或已过期"` | C for direct writeErr; error helper 내부는 P |

응답 schema 판정: **P** · top-level key는 직접 추출했지만 derived/helper/opaque 또는 nested type이 남음.

### 목록·동시성·완료

- Pagination/filter/sort: 등록된 직접 query key 중 표준 paging/filter key 없음.
- Idempotency/concurrency: Idempotency-Key 없음; unique constraint 또는 상태 check가 있는 경우만 중복을 제한(P).
- Side effect/completion: handler의 직접 mutation/호출 후 응답; 외부 전달·agent 실행이 파생되면 별도 상태 확인(P).
- 직접 협력자/실행 단서: `agent.ConfigFrom`, `agent.TestConnection`, `err.Error`, `json.NewDecoder`, `json.NewDecoder(r.Body).Decode`, `lat.Milliseconds`, `r.Context`, `s.cfgMu.Lock`, `s.cfgMu.Unlock`, `s.m.pg.ProfileByID`, `truncateReply`; direct `go` statement는 미발견. 이 목록은 helper 내부의 background launch나 transaction 경계를 펼치지 않으므로 그 부분은 P/U다.
- 종합 의미 판정: **P/U**; route/auth와 위에서 직접 확인한 branch·shape는 C지만, 모든 helper·derived response·중첩 DTO·외부 효과까지 완결됐다는 뜻은 아니다.

<a id="schema-get-api-assets"></a>
## `GET /api/assets`

구현: `listAssets` · `server/assets.go` · [정확한 handler 근거](evidence:handler-get-api-assets)

### 요청

| 위치/field | wire type | 필수·null | 기본/검증 |
|---|---|---|---|
| query `company_id` | `string` wire | 선택 | handler/helper 변환·default 미전개(P) |
| query `dsl` | `string` wire | 선택 | handler/helper 변환·default 미전개(P) |
| query `limit` | `string` wire | 선택 | handler/helper 변환·default 미전개(P) |
| query `offset` | `string` wire | 선택 | handler/helper 변환·default 미전개(P) |
| query `task_id` | `string` wire | 선택 | handler/helper 변환·default 미전개(P) |
| query `type` | `string` wire | 선택 | handler/helper 변환·default 미전개(P) |
| auth credential | Bearer header / `artex_token` cookie / query `token` | 세 위치 중 하나에 유효 JWT 필수 | `extractToken` 우선순위: header→cookie→query; query fallback은 SSE로 제한되지 않음 |
| body | 없음(직접 decoder/form read 미발견) | 해당 없음 | 해당 없음 |

요청 판정: **C/P** · unknown field 거부 미설정.
직접 validation: `as == nil` · `dsl != ""` · `err != nil` · `err == nil && offset < total` · `limit <= 0` · `limit > maxAssetPageSize` · `offset < 0` · `typ == ""`
직접 parse/default: `strconv.ParseInt(q.Get("company_id"), 10, 64)` · `strconv.ParseInt(q.Get("task_id"), 10, 64)`

### 응답·오류

| 종류 | status/body | 해석 상태 |
|---|---|---|
| success/variant | `200` · `count:helper result`, `total:derived`, `assets:derived` | P/U · `static composite` |
| error | `400` · `{error:string}` · `err.Error()` | C for direct writeErr; error helper 내부는 P |
| error | `401` · `{error:string}` · `"未授权"` / `"token 无效或已过期"` | C for direct writeErr; error helper 내부는 P |
| error | `500` · `{error:string}` · `err.Error()` | C for direct writeErr; error helper 내부는 P |
| error | `503` · `{error:string}` · `"database unavailable"` | C for direct writeErr; error helper 내부는 P |

응답 schema 판정: **P** · top-level key는 직접 추출했지만 derived/helper/opaque 또는 nested type이 남음.

### 목록·동시성·완료

- Pagination/filter/sort: `limit`, `offset`.
- Idempotency/concurrency: read/stream 요청; stream 재연결은 cursor 계약에 따름.
- Side effect/completion: 직접 조회/파일 전송 완료; 외부 조회가 있으면 응답 전 handler 결과만 보장.
- 직접 협력자/실행 단서: `as.CountByCompany`, `as.CountByTask`, `as.CountByType`, `as.CountDSL`, `as.QueryByCompany`, `as.QueryByTask`, `as.QueryByType`, `as.QueryDSL`, `db.ValidateDSL`, `err.Error`, `q.Get`, `r.URL.Query`; direct `go` statement는 미발견. 이 목록은 helper 내부의 background launch나 transaction 경계를 펼치지 않으므로 그 부분은 P/U다.
- 종합 의미 판정: **P/U**; route/auth와 위에서 직접 확인한 branch·shape는 C지만, 모든 helper·derived response·중첩 DTO·외부 효과까지 완결됐다는 뜻은 아니다.

<a id="schema-get-api-assets-counts"></a>
## `GET /api/assets/counts`

구현: `assetCounts` · `server/assets.go` · [정확한 handler 근거](evidence:handler-get-api-assets-counts)

### 요청

| 위치/field | wire type | 필수·null | 기본/검증 |
|---|---|---|---|
| query `task_id` | `string` wire | 선택 | handler/helper 변환·default 미전개(P) |
| auth credential | Bearer header / `artex_token` cookie / query `token` | 세 위치 중 하나에 유효 JWT 필수 | `extractToken` 우선순위: header→cookie→query; query fallback은 SSE로 제한되지 않음 |
| body | 없음(직접 decoder/form read 미발견) | 해당 없음 | 해당 없음 |

요청 판정: **C/P** · unknown field 거부 미설정.
직접 validation: `as == nil` · `err != nil` · `taskID > 0`
직접 parse/default: `strconv.ParseInt(r.URL.Query().Get("task_id"), 10, 64)`

### 응답·오류

| 종류 | status/body | 해석 상태 |
|---|---|---|
| success/variant | `200` · `counts` | P/U · `derived variable` |
| error | `401` · `{error:string}` · `"未授权"` / `"token 无效或已过期"` | C for direct writeErr; error helper 내부는 P |
| error | `500` · `{error:string}` · `err.Error()` | C for direct writeErr; error helper 내부는 P |
| error | `503` · `{error:string}` · `"database unavailable"` | C for direct writeErr; error helper 내부는 P |

응답 schema 판정: **P** · top-level key는 직접 추출했지만 derived/helper/opaque 또는 nested type이 남음.

### 목록·동시성·완료

- Pagination/filter/sort: 등록된 직접 query key 중 표준 paging/filter key 없음.
- Idempotency/concurrency: read/stream 요청; stream 재연결은 cursor 계약에 따름.
- Side effect/completion: 직접 조회/파일 전송 완료; 외부 조회가 있으면 응답 전 handler 결과만 보장.
- 직접 협력자/실행 단서: `as.CountsByType`, `as.CountsByTypeForTask`, `err.Error`, `r.URL.Query`, `r.URL.Query().Get`; direct `go` statement는 미발견. 이 목록은 helper 내부의 background launch나 transaction 경계를 펼치지 않으므로 그 부분은 P/U다.
- 종합 의미 판정: **P/U**; route/auth와 위에서 직접 확인한 branch·shape는 C지만, 모든 helper·derived response·중첩 DTO·외부 효과까지 완결됐다는 뜻은 아니다.

<a id="schema-post-api-assets"></a>
## `POST /api/assets`

구현: `insertAssets` · `server/assets.go` · [정확한 handler 근거](evidence:handler-post-api-assets)

### 요청

| 위치/field | wire type | 필수·null | 기본/검증 |
|---|---|---|---|
| auth credential | Bearer header / `artex_token` cookie / query `token` | 세 위치 중 하나에 유효 JWT 필수 | `extractToken` 우선순위: header→cookie→query; query fallback은 SSE로 제한되지 않음 |
| body `task_id` | `integer` (`int64`) | 요구 여부 미확정(P); null/absent 구별 안 됨 | `req.TaskID > 0` |
| body `assets` | `array[struct {
	Type string `json:"type"`
	// root_domain / subdomain
	Domain      string   `json:"domain"`
	ICP         string   `json:"icp"`
	RecordType  string   `json:"record_type"`
	RecordValue []string `json:"record_value"`
	// ip
	IP           string           `json:"ip"`
	BoundDomains []string         `json:"bound_domains"`
	OpenPorts    []db.PortService `json:"open_ports"`
	// app
	AppName     string `json:"app_name"`
	BundleID    string `json:"bundle_id"`
	Category    string `json:"category"`
	Description string `json:"description"`
	AppICP      string `json:"app_icp"`
	// service http
	URL           string           `json:"url"`
	Technologies  []string         `json:"technologies"`
	StatusCode    *int             `json:"status_code"`
	ContentLength *int64           `json:"content_length"`
	PageTitle     string           `json:"page_title"`
	FaviconMMH3   string           `json:"favicon_mmh3"`
	Auth          []map[string]any `json:"auth"`
	ServiceIP     string           `json:"service_ip"`
	// service other
	Port        int    `json:"port"`
	ServiceName string `json:"service_name"`
	// endpoint
	Method string           `json:"method"`
	Params []map[string]any `json:"params"`
}]` (`[]struct { Type string `json:"type"` // root_domain / subdomain Domain string `json:"domain"` ICP string `json:"icp"` RecordType string `json:"record_type"` RecordValue []string `json:"record_value"` // ip IP string `json:"ip"` BoundDomains []string `json:"bound_domains"` OpenPorts []db.PortService `json:"open_ports"` // app AppName string `json:"app_name"` BundleID string `json:"bundle_id"` Category string `json:"category"` Description string `json:"description"` AppICP string `json:"app_icp"` // service http URL string `json:"url"` Technologies []string `json:"technologies"` StatusCode *int `json:"status_code"` ContentLength *int64 `json:"content_length"` PageTitle string `json:"page_title"` FaviconMMH3 string `json:"favicon_mmh3"` Auth []map[string]any `json:"auth"` ServiceIP string `json:"service_ip"` // service other Port int `json:"port"` ServiceName string `json:"service_name"` // endpoint Method string `json:"method"` Params []map[string]any `json:"params"` }`) | 요구 여부 미확정(P); absent/null→nil; null/absent 구별 안 됨 | `nil` |

요청 판정: **C/P** · unknown field 거부 미설정.
직접 validation: `req.TaskID > 0`

### 응답·오류

| 종류 | status/body | 해석 상태 |
|---|---|---|
| success/variant | `200` · `results:derived`, `errors:derived` | P/U · `static composite` |
| error | `400` · `{error:string}` · `"invalid JSON: " + err.Error()` | C for direct writeErr; error helper 내부는 P |
| error | `401` · `{error:string}` · `"未授权"` / `"token 无效或已过期"` | C for direct writeErr; error helper 내부는 P |
| error | `503` · `{error:string}` · `"database unavailable"` | C for direct writeErr; error helper 내부는 P |

응답 schema 판정: **P** · top-level key는 직접 추출했지만 derived/helper/opaque 또는 nested type이 남음.

### 목록·동시성·완료

- Pagination/filter/sort: 등록된 직접 query key 중 표준 paging/filter key 없음.
- Idempotency/concurrency: Idempotency-Key 없음; unique constraint 또는 상태 check가 있는 경우만 중복을 제한(P).
- Side effect/completion: handler의 직접 mutation/호출 후 응답; 외부 전달·agent 실행이 파생되면 별도 상태 확인(P).
- 직접 협력자/실행 단서: `as.SetTaskAssetSource`, `as.UpsertApp`, `as.UpsertEndpoint`, `as.UpsertHTTPService`, `as.UpsertIP`, `as.UpsertOtherService`, `as.UpsertRootDomain`, `as.UpsertSubdomain`, `err.Error`, `json.NewDecoder`, `json.NewDecoder(r.Body).Decode`; direct `go` statement는 미발견. 이 목록은 helper 내부의 background launch나 transaction 경계를 펼치지 않으므로 그 부분은 P/U다.
- 종합 의미 판정: **P/U**; route/auth와 위에서 직접 확인한 branch·shape는 C지만, 모든 helper·derived response·중첩 DTO·외부 효과까지 완결됐다는 뜻은 아니다.

<a id="schema-delete-api-assets"></a>
## `DELETE /api/assets`

구현: `deleteAssets` · `server/assets.go` · [정확한 handler 근거](evidence:handler-delete-api-assets)

### 요청

| 위치/field | wire type | 필수·null | 기본/검증 |
|---|---|---|---|
| auth credential | Bearer header / `artex_token` cookie / query `token` | 세 위치 중 하나에 유효 JWT 필수 | `extractToken` 우선순위: header→cookie→query; query fallback은 SSE로 제한되지 않음 |
| body `ids` | `array[integer]` (`[]int64`) | 필수(직접 거부 branch 확인); null/absent 구별 안 됨 | `빈 배열이면 400 후 return` |

요청 판정: **C/P** · unknown field 거부 미설정.
직접 validation: `len(req.IDs) == 0`

### 응답·오류

| 종류 | status/body | 해석 상태 |
|---|---|---|
| success/variant | `200` · `deleted:derived` | P/U · `static composite` |
| error | `400` · `{error:string}` · `"invalid JSON: " + err.Error()` / `"ids required"` | C for direct writeErr; error helper 내부는 P |
| error | `401` · `{error:string}` · `"未授权"` / `"token 无效或已过期"` | C for direct writeErr; error helper 내부는 P |
| error | `500` · `{error:string}` · `err.Error()` | C for direct writeErr; error helper 내부는 P |
| error | `503` · `{error:string}` · `"database unavailable"` | C for direct writeErr; error helper 내부는 P |

응답 schema 판정: **P** · top-level key는 직접 추출했지만 derived/helper/opaque 또는 nested type이 남음.

### 목록·동시성·완료

- Pagination/filter/sort: 등록된 직접 query key 중 표준 paging/filter key 없음.
- Idempotency/concurrency: Idempotency-Key/If-Match 없음; 반복 시 응답/존재 검사가 달라질 수 있음.
- Side effect/completion: handler의 직접 mutation/호출 후 응답; 외부 전달·agent 실행이 파생되면 별도 상태 확인(P).
- 직접 협력자/실행 단서: `as.DeleteByIDs`, `err.Error`, `json.NewDecoder`, `json.NewDecoder(r.Body).Decode`; direct `go` statement는 미발견. 이 목록은 helper 내부의 background launch나 transaction 경계를 펼치지 않으므로 그 부분은 P/U다.
- 종합 의미 판정: **P/U**; route/auth와 위에서 직접 확인한 branch·shape는 C지만, 모든 helper·derived response·중첩 DTO·외부 효과까지 완결됐다는 뜻은 아니다.

<a id="schema-get-api-companies"></a>
## `GET /api/companies`

구현: `listCompanies` · `server/assets.go` · [정확한 handler 근거](evidence:handler-get-api-companies)

### 요청

| 위치/field | wire type | 필수·null | 기본/검증 |
|---|---|---|---|
| auth credential | Bearer header / `artex_token` cookie / query `token` | 세 위치 중 하나에 유효 JWT 필수 | `extractToken` 우선순위: header→cookie→query; query fallback은 SSE로 제한되지 않음 |
| body | 없음(직접 decoder/form read 미발견) | 해당 없음 | 해당 없음 |

요청 판정: **C/P** · unknown field 거부 미설정.
직접 validation: `cs == nil` · `err != nil`

### 응답·오류

| 종류 | status/body | 해석 상태 |
|---|---|---|
| success/variant | `200` · `companies` | P/U · `derived variable` |
| error | `401` · `{error:string}` · `"未授权"` / `"token 无效或已过期"` | C for direct writeErr; error helper 내부는 P |
| error | `500` · `{error:string}` · `err.Error()` | C for direct writeErr; error helper 내부는 P |
| error | `503` · `{error:string}` · `"database unavailable"` | C for direct writeErr; error helper 내부는 P |

응답 schema 판정: **P** · top-level key는 직접 추출했지만 derived/helper/opaque 또는 nested type이 남음.

### 목록·동시성·완료

- Pagination/filter/sort: 등록된 직접 query key 중 표준 paging/filter key 없음.
- Idempotency/concurrency: read/stream 요청; stream 재연결은 cursor 계약에 따름.
- Side effect/completion: 직접 조회/파일 전송 완료; 외부 조회가 있으면 응답 전 handler 결과만 보장.
- 직접 협력자/실행 단서: `cs.ListCompanies`, `err.Error`; direct `go` statement는 미발견. 이 목록은 helper 내부의 background launch나 transaction 경계를 펼치지 않으므로 그 부분은 P/U다.
- 종합 의미 판정: **P/U**; route/auth와 위에서 직접 확인한 branch·shape는 C지만, 모든 helper·derived response·중첩 DTO·외부 효과까지 완결됐다는 뜻은 아니다.

<a id="schema-post-api-companies"></a>
## `POST /api/companies`

구현: `createCompany` · `server/assets.go` · [정확한 handler 근거](evidence:handler-post-api-companies)

### 요청

| 위치/field | wire type | 필수·null | 기본/검증 |
|---|---|---|---|
| auth credential | Bearer header / `artex_token` cookie / query `token` | 세 위치 중 하나에 유효 JWT 필수 | `extractToken` 우선순위: header→cookie→query; query fallback은 SSE로 제한되지 않음 |
| body `name` | `string` (`string`) | 필수(직접 거부 branch 확인); null/absent 구별 안 됨 | `trim 후 빈 문자열이면 400 후 return` |
| body `logo` | `string` (`string`) | 요구 여부 미확정(P); null/absent 구별 안 됨 | `Go zero value; 별도 기본은 handler/helper 확인` |
| body `scope` | `companyScopeInputs` (`companyScopeInputs`) | 요구 여부 미확정(P); null/absent 구별 안 됨 | `Go zero value; 별도 기본은 handler/helper 확인` |

요청 body 상한: **2 MiB** (`2097152` bytes) · source `server/assets.go::decodeCompanyMutationRequest` · 초과/decoder 경로 `413 "请求正文过大"`.

요청 판정: **C/P** · unknown field 거부 미설정.
직접 validation: `req.Name == ""`
공유 decoder/helper: `decodeCompanyMutationRequest`; helper 내부 size/presence validation은 같은 source evidence에서 확인하며 operation 단독 AST 판정은 P.

### 응답·오류

| 종류 | status/body | 해석 상태 |
|---|---|---|
| success/variant | `201` · `out` | P/U · `derived variable` |
| error | `400` · `{error:string}` · `"name required"` / `err.Error()` / `validationErr.Error()` | C for direct writeErr; error helper 내부는 P |
| error | `401` · `{error:string}` · `"未授权"` / `"token 无效或已过期"` | C for direct writeErr; error helper 내부는 P |
| error | `409` · `{error:string}` · `"企业名称已存在"` | C for direct writeErr; error helper 내부는 P |
| error | `413` · `{error:string}` · `"请求正文过大"` | C for direct writeErr; error helper 내부는 P |
| error | `500` · `{error:string}` · `err.Error()` | C for direct writeErr; error helper 내부는 P |
| error | `503` · `{error:string}` · `"database unavailable"` | C for direct writeErr; error helper 내부는 P |

응답 schema 판정: **P** · top-level key는 직접 추출했지만 derived/helper/opaque 또는 nested type이 남음.

### 목록·동시성·완료

- Pagination/filter/sort: 등록된 직접 query key 중 표준 paging/filter key 없음.
- Idempotency/concurrency: Idempotency-Key 없음; unique constraint 또는 상태 check가 있는 경우만 중복을 제한(P).
- Side effect/completion: handler의 직접 mutation/호출 후 응답; 외부 전달·agent 실행이 파생되면 별도 상태 확인(P).
- 직접 협력자/실행 단서: `cs.CreateCompanyWithScope`, `db.ValidateCompanyScopeInputBounds`, `decodeCompanyMutationRequest`, `err.Error`, `errors.As`, `validationErr.Error`; direct `go` statement는 미발견. 이 목록은 helper 내부의 background launch나 transaction 경계를 펼치지 않으므로 그 부분은 P/U다.
- 종합 의미 판정: **P/U**; route/auth와 위에서 직접 확인한 branch·shape는 C지만, 모든 helper·derived response·중첩 DTO·외부 효과까지 완결됐다는 뜻은 아니다.

<a id="schema-get-api-companies-id"></a>
## `GET /api/companies/{id}`

구현: `getCompany` · `server/assets.go` · [정확한 handler 근거](evidence:handler-get-api-companies-id)

### 요청

| 위치/field | wire type | 필수·null | 기본/검증 |
|---|---|---|---|
| path `id` | `string` | 필수 | handler의 parse/range validation; 미해석 helper는 P |
| auth credential | Bearer header / `artex_token` cookie / query `token` | 세 위치 중 하나에 유효 JWT 필수 | `extractToken` 우선순위: header→cookie→query; query fallback은 SSE로 제한되지 않음 |
| body | 없음(직접 decoder/form read 미발견) | 해당 없음 | 해당 없음 |

요청 판정: **C/P** · unknown field 거부 미설정.
직접 validation: `c == nil` · `cs == nil` · `err != nil`
직접 parse/default: `strconv.ParseInt(r.PathValue("id"), 10, 64)`

### 응답·오류

| 종류 | status/body | 해석 상태 |
|---|---|---|
| success/variant | `200` · `company:derived`, `scope:derived` | P/U · `static composite` |
| error | `400` · `{error:string}` · `"invalid id"` | C for direct writeErr; error helper 내부는 P |
| error | `401` · `{error:string}` · `"未授权"` / `"token 无效或已过期"` | C for direct writeErr; error helper 내부는 P |
| error | `404` · `{error:string}` · `"company not found"` | C for direct writeErr; error helper 내부는 P |
| error | `500` · `{error:string}` · `err.Error()` | C for direct writeErr; error helper 내부는 P |
| error | `503` · `{error:string}` · `"database unavailable"` | C for direct writeErr; error helper 내부는 P |

응답 schema 판정: **P** · top-level key는 직접 추출했지만 derived/helper/opaque 또는 nested type이 남음.

### 목록·동시성·완료

- Pagination/filter/sort: 등록된 직접 query key 중 표준 paging/filter key 없음.
- Idempotency/concurrency: read/stream 요청; stream 재연결은 cursor 계약에 따름.
- Side effect/completion: 직접 조회/파일 전송 완료; 외부 조회가 있으면 응답 전 handler 결과만 보장.
- 직접 협력자/실행 단서: `cs.GetCompany`, `cs.GetScope`, `err.Error`, `r.PathValue`; direct `go` statement는 미발견. 이 목록은 helper 내부의 background launch나 transaction 경계를 펼치지 않으므로 그 부분은 P/U다.
- 종합 의미 판정: **P/U**; route/auth와 위에서 직접 확인한 branch·shape는 C지만, 모든 helper·derived response·중첩 DTO·외부 효과까지 완결됐다는 뜻은 아니다.

<a id="schema-delete-api-companies-id"></a>
## `DELETE /api/companies/{id}`

구현: `deleteCompany` · `server/assets.go` · [정확한 handler 근거](evidence:handler-delete-api-companies-id)

### 요청

| 위치/field | wire type | 필수·null | 기본/검증 |
|---|---|---|---|
| path `id` | `string` | 필수 | handler의 parse/range validation; 미해석 helper는 P |
| auth credential | Bearer header / `artex_token` cookie / query `token` | 세 위치 중 하나에 유효 JWT 필수 | `extractToken` 우선순위: header→cookie→query; query fallback은 SSE로 제한되지 않음 |
| body `delete_assets` | `boolean` (`bool`) | 요구 여부 미확정(P); null/absent 구별 안 됨 | `Go zero value; 별도 기본은 handler/helper 확인` |

요청 판정: **C/P** · unknown field 거부 미설정.
직접 parse/default: `strconv.ParseInt(r.PathValue("id"), 10, 64)`
공유 decoder/helper: `decoder.Decode`; helper 내부 size/presence validation은 같은 source evidence에서 확인하며 operation 단독 AST 판정은 P.

### 응답·오류

| 종류 | status/body | 해석 상태 |
|---|---|---|
| success/variant | `200` · `deleted:number`, `assets_deleted:derived` | P/U · `static composite` |
| error | `400` · `{error:string}` · `"invalid id"` / `"invalid JSON: " + err.Error()` / `"invalid JSON: multiple values"` | C for direct writeErr; error helper 내부는 P |
| error | `401` · `{error:string}` · `"未授权"` / `"token 无效或已过期"` | C for direct writeErr; error helper 내부는 P |
| error | `404` · `{error:string}` · `"company not found"` | C for direct writeErr; error helper 내부는 P |
| error | `500` · `{error:string}` · `err.Error()` | C for direct writeErr; error helper 내부는 P |
| error | `503` · `{error:string}` · `"database unavailable"` | C for direct writeErr; error helper 내부는 P |

응답 schema 판정: **P** · top-level key는 직접 추출했지만 derived/helper/opaque 또는 nested type이 남음.

### 목록·동시성·완료

- Pagination/filter/sort: 등록된 직접 query key 중 표준 paging/filter key 없음.
- Idempotency/concurrency: Idempotency-Key/If-Match 없음; 반복 시 응답/존재 검사가 달라질 수 있음.
- Side effect/completion: handler의 직접 mutation/호출 후 응답; 외부 전달·agent 실행이 파생되면 별도 상태 확인(P).
- 직접 협력자/실행 단서: `decoder.Decode`, `err.Error`, `json.NewDecoder`, `r.PathValue`, `s.m.DeleteCompanyWithAssets`; direct `go` statement는 미발견. 이 목록은 helper 내부의 background launch나 transaction 경계를 펼치지 않으므로 그 부분은 P/U다.
- 종합 의미 판정: **P/U**; route/auth와 위에서 직접 확인한 branch·shape는 C지만, 모든 helper·derived response·중첩 DTO·외부 효과까지 완결됐다는 뜻은 아니다.

<a id="schema-post-api-companies-id-scope"></a>
## `POST /api/companies/{id}/scope`

구현: `addCompanyScope` · `server/assets.go` · [정확한 handler 근거](evidence:handler-post-api-companies-id-scope)

### 요청

| 위치/field | wire type | 필수·null | 기본/검증 |
|---|---|---|---|
| path `id` | `string` | 필수 | handler의 parse/range validation; 미해석 helper는 P |
| auth credential | Bearer header / `artex_token` cookie / query `token` | 세 위치 중 하나에 유효 JWT 필수 | `extractToken` 우선순위: header→cookie→query; query fallback은 SSE로 제한되지 않음 |
| body `scope` | `companyScopeInputs` (`companyScopeInputs`) | 요구 여부 미확정(P); null/absent 구별 안 됨 | `Go zero value; 별도 기본은 handler/helper 확인` |
| body `reason` | `string` (`string`) | 요구 여부 미확정(P); null/absent 구별 안 됨 | `Go zero value; 별도 기본은 handler/helper 확인` |
| body `reset` | `boolean` (`bool`) | 요구 여부 미확정(P); null/absent 구별 안 됨 | `req.Reset` |

요청 body 상한: **2 MiB** (`2097152` bytes) · source `server/assets.go::decodeCompanyMutationRequest` · 초과/decoder 경로 `413 "请求正文过大"`.

요청 판정: **C/P** · unknown field 거부 미설정.
직접 validation: `req.Reset`
직접 parse/default: `strconv.ParseInt(r.PathValue("id"), 10, 64)`
공유 decoder/helper: `decodeCompanyMutationRequest`; helper 내부 size/presence validation은 같은 source evidence에서 확인하며 operation 단독 AST 판정은 P.

### 응답·오류

| 종류 | status/body | 해석 상태 |
|---|---|---|
| success/variant | `200` · `out` | P/U · `derived variable` |
| error | `400` · `{error:string}` · `"invalid company id"` / `err.Error()` / `validationErr.Error()` | C for direct writeErr; error helper 내부는 P |
| error | `401` · `{error:string}` · `"未授权"` / `"token 无效或已过期"` | C for direct writeErr; error helper 내부는 P |
| error | `404` · `{error:string}` · `"company not found"` | C for direct writeErr; error helper 내부는 P |
| error | `413` · `{error:string}` · `"请求正文过大"` | C for direct writeErr; error helper 내부는 P |
| error | `500` · `{error:string}` · `mutationErr.Error()` | C for direct writeErr; error helper 내부는 P |
| error | `503` · `{error:string}` · `"database unavailable"` | C for direct writeErr; error helper 내부는 P |

응답 schema 판정: **P** · top-level key는 직접 추출했지만 derived/helper/opaque 또는 nested type이 남음.

### 목록·동시성·완료

- Pagination/filter/sort: 등록된 직접 query key 중 표준 paging/filter key 없음.
- Idempotency/concurrency: Idempotency-Key 없음; unique constraint 또는 상태 check가 있는 경우만 중복을 제한(P).
- Side effect/completion: handler의 직접 mutation/호출 후 응답; 외부 전달·agent 실행이 파생되면 별도 상태 확인(P).
- 직접 협력자/실행 단서: `cs.AddScopeInputsChecked`, `cs.MalformedIPAssetWarning`, `cs.UpdateScopeInputsChecked`, `db.ValidateCompanyScopeInputBounds`, `decodeCompanyMutationRequest`, `err.Error`, `errors.As`, `mutationErr.Error`, `r.PathValue`, `validationErr.Error`; direct `go` statement는 미발견. 이 목록은 helper 내부의 background launch나 transaction 경계를 펼치지 않으므로 그 부분은 P/U다.
- 종합 의미 판정: **P/U**; route/auth와 위에서 직접 확인한 branch·shape는 C지만, 모든 helper·derived response·중첩 DTO·외부 효과까지 완결됐다는 뜻은 아니다.

<a id="schema-post-api-companies-reattribute"></a>
## `POST /api/companies/reattribute`

구현: `reattribute` · `server/assets.go` · [정확한 handler 근거](evidence:handler-post-api-companies-reattribute)

### 요청

| 위치/field | wire type | 필수·null | 기본/검증 |
|---|---|---|---|
| auth credential | Bearer header / `artex_token` cookie / query `token` | 세 위치 중 하나에 유효 JWT 필수 | `extractToken` 우선순위: header→cookie→query; query fallback은 SSE로 제한되지 않음 |
| body | 없음(직접 decoder/form read 미발견) | 해당 없음 | 해당 없음 |

요청 판정: **C/P** · unknown field 거부 미설정.
직접 validation: `cs == nil` · `err != nil`

### 응답·오류

| 종류 | status/body | 해석 상태 |
|---|---|---|
| success/variant | `200` · `ok:boolean` | C · `static composite` |
| error | `401` · `{error:string}` · `"未授权"` / `"token 无效或已过期"` | C for direct writeErr; error helper 내부는 P |
| error | `500` · `{error:string}` · `err.Error()` | C for direct writeErr; error helper 내부는 P |
| error | `503` · `{error:string}` · `"database unavailable"` | C for direct writeErr; error helper 내부는 P |

응답 schema 판정: **C** · 직접 scalar/static top-level key와 field type을 추출; nested 내부 schema는 별도 type 참조가 없으면 P.

### 목록·동시성·완료

- Pagination/filter/sort: 등록된 직접 query key 중 표준 paging/filter key 없음.
- Idempotency/concurrency: Idempotency-Key 없음; unique constraint 또는 상태 check가 있는 경우만 중복을 제한(P).
- Side effect/completion: handler의 직접 mutation/호출 후 응답; 외부 전달·agent 실행이 파생되면 별도 상태 확인(P).
- 직접 협력자/실행 단서: `cs.RecomputeAttribution`, `err.Error`; direct `go` statement는 미발견. 이 목록은 helper 내부의 background launch나 transaction 경계를 펼치지 않으므로 그 부분은 P/U다.
- 종합 의미 판정: **P/U**; route/auth와 위에서 직접 확인한 branch·shape는 C지만, 모든 helper·derived response·중첩 DTO·외부 효과까지 완결됐다는 뜻은 아니다.

<a id="schema-get-api-exploration-frontier"></a>
## `GET /api/exploration/frontier`

구현: `frontier` · `server/server.go` · [정확한 handler 근거](evidence:handler-get-api-exploration-frontier)

### 요청

| 위치/field | wire type | 필수·null | 기본/검증 |
|---|---|---|---|
| query `limit` | `string` wire | 선택 | handler/helper 변환·default 미전개(P) |
| query `task` | `string` wire | 선택 | handler/helper 변환·default 미전개(P) |
| auth credential | Bearer header / `artex_token` cookie / query `token` | 세 위치 중 하나에 유효 JWT 필수 | `extractToken` 우선순위: header→cookie→query; query fallback은 SSE로 제한되지 않음 |
| body | 없음(직접 decoder/form read 미발견) | 해당 없음 | 해당 없음 |

요청 판정: **C/P** · unknown field 거부 미설정.
직접 validation: `t == nil`
직접 parse/default: `atoiDefault(r.URL.Query().Get("limit"), 100)`

### 응답·오류

| 종류 | status/body | 해석 상태 |
|---|---|---|
| success/variant | `200` · `[]any{}` | P/U · `static composite` |
| success/variant | `200` · `taskNodeDTOs(fr)` | P/U · `helper result` |
| error | `401` · `{error:string}` · `"未授权"` / `"token 无效或已过期"` | C for direct writeErr; error helper 내부는 P |

응답 schema 판정: **P** · top-level key는 직접 추출했지만 derived/helper/opaque 또는 nested type이 남음.

### 목록·동시성·완료

- Pagination/filter/sort: `limit`, `task`.
- Idempotency/concurrency: read/stream 요청; stream 재연결은 cursor 계약에 따름.
- Side effect/completion: 직접 조회/파일 전송 완료; 외부 조회가 있으면 응답 전 handler 결과만 보장.
- 직접 협력자/실행 단서: `atoiDefault`, `r.URL.Query`, `r.URL.Query().Get`, `s.m.ResolveTask`, `t.Store.Frontier`, `taskNodeDTOs`; direct `go` statement는 미발견. 이 목록은 helper 내부의 background launch나 transaction 경계를 펼치지 않으므로 그 부분은 P/U다.
- 종합 의미 판정: **P/U**; route/auth와 위에서 직접 확인한 branch·shape는 C지만, 모든 helper·derived response·중첩 DTO·외부 효과까지 완결됐다는 뜻은 아니다.

<a id="schema-get-api-exploration-findings"></a>
## `GET /api/exploration/findings`

구현: `findings` · `server/server.go` · [정확한 handler 근거](evidence:handler-get-api-exploration-findings)

### 요청

| 위치/field | wire type | 필수·null | 기본/검증 |
|---|---|---|---|
| query `asset_scope` | `string` wire | 선택 | trimmed asset-tree key; empty→no filter, `__none__`→unassigned, missing key→empty result |
| query `limit` | `string` wire | 선택 | handler/helper 변환·default 미전개(P) |
| query `page` | `string` wire | 선택 | handler/helper 변환·default 미전개(P) |
| query `q` | `string` wire | 선택 | string; DB trims, empty→no filter; escaped literal substring ILIKE over name/class/summary/evidence/report |
| query `severity` | `string` wire | 선택 | string; `all`/empty→no filter, otherwise exact equality without enum rejection |
| query `sort` | `string` wire | 선택 | `severity`→severity rank then newest; every other value→newest first |
| query `status` | `string` wire | 선택 | string; `all`/empty→no filter, otherwise exact equality without enum rejection |
| query `task` | `string` wire | 선택 | handler/helper 변환·default 미전개(P) |
| query `task_id` | `string` wire | 선택 | positive int string→task filter; `__unassigned__`→NULL/orphan; empty/invalid→no filter |
| query `vulnclass` | `string` wire | 선택 | string; `all`/empty→no filter, otherwise exact equality |
| auth credential | Bearer header / `artex_token` cookie / query `token` | 세 위치 중 하나에 유효 JWT 필수 | `extractToken` 우선순위: header→cookie→query; query fallback은 SSE로 제한되지 않음 |
| body | 없음(직접 decoder/form read 미발견) | 해당 없음 | 해당 없음 |

요청 판정: **C/P** · unknown field 거부 미설정.
직접 validation: `err != nil` · `listErr != nil` · `metaErr != nil` · `q.Get("page") == "" && q.Get("limit") == ""` · `t == nil` · `taskParam == ""`
직접 parse/default: `strconv.ParseInt(t.ID, 10, 64)`

### 응답·오류

| 종류 | status/body | 해석 상태 |
|---|---|---|
| success/variant | `200` · `out` | P/U · `derived variable` |
| success/variant | `200` · `items:derived`, `total:derived`, `page:derived`, `page_size:derived` | P/U · `static composite` |
| success/variant | `200` · `[]any{}` | P/U · `static composite` |
| error | `401` · `{error:string}` · `"未授权"` / `"token 无效或已过期"` | C for direct writeErr; error helper 내부는 P |
| error | `500` · `{error:string}` · `err.Error()` / `listErr.Error()` | C for direct writeErr; error helper 내부는 P |

응답 schema 판정: **P** · top-level key는 직접 추출했지만 derived/helper/opaque 또는 nested type이 남음.

### 목록·동시성·완료

- Pagination/filter/sort: `limit`, `page`, `q`, `severity`, `sort`, `status`, `task`.
- Idempotency/concurrency: read/stream 요청; stream 재연결은 cursor 계약에 따름.
- Side effect/completion: 직접 조회/파일 전송 완료; 외부 조회가 있으면 응답 전 handler 결과만 보장.
- 직접 협력자/실행 단서: `err.Error`, `findingDTOsForOwner`, `findingDTOsForTask`, `findingFilterFromQuery`, `findingFromDB`, `findingPaginationParam`, `i64s`, `listErr.Error`, `log.Printf`, `q.Get`, `r.URL.Query`, `s.m.ResolveTask`, `s.m.pg.FindingMetaByNodeID`, `s.m.pg.ListFindings`, `s.m.pg.ListFindingsPage`, `source.Store.ListByKind`, `t.Store.DirectSourceStores`, `t.Store.ListByKind`; direct `go` statement는 미발견. 이 목록은 helper 내부의 background launch나 transaction 경계를 펼치지 않으므로 그 부분은 P/U다.
- 종합 의미 판정: **P/U**; route/auth와 위에서 직접 확인한 branch·shape는 C지만, 모든 helper·derived response·중첩 DTO·외부 효과까지 완결됐다는 뜻은 아니다.

<a id="schema-get-api-exploration-findings-groups"></a>
## `GET /api/exploration/findings/groups`

구현: `findingGroups` · `server/findings_groups.go` · [정확한 handler 근거](evidence:handler-get-api-exploration-findings-groups)

### 요청

| 위치/field | wire type | 필수·null | 기본/검증 |
|---|---|---|---|
| query `asset_scope` | `string` wire | 선택 | trimmed asset-tree key; empty→no filter, `__none__`→unassigned, missing key→empty result |
| query `limit` | `string` wire | 선택 | handler/helper 변환·default 미전개(P) |
| query `page` | `string` wire | 선택 | handler/helper 변환·default 미전개(P) |
| query `q` | `string` wire | 선택 | string; DB trims, empty→no filter; escaped literal substring ILIKE over name/class/summary/evidence/report |
| query `severity` | `string` wire | 선택 | string; `all`/empty→no filter, otherwise exact equality without enum rejection |
| query `sort` | `string` wire | 선택 | `severity`→severity rank then newest; every other value→newest first |
| query `status` | `string` wire | 선택 | string; `all`/empty→no filter, otherwise exact equality without enum rejection |
| query `task_id` | `string` wire | 선택 | positive int string→task filter; `__unassigned__`→NULL/orphan; empty/invalid→no filter |
| query `vulnclass` | `string` wire | 선택 | string; `all`/empty→no filter, otherwise exact equality |
| auth credential | Bearer header / `artex_token` cookie / query `token` | 세 위치 중 하나에 유효 JWT 필수 | `extractToken` 우선순위: header→cookie→query; query fallback은 SSE로 제한되지 않음 |
| body | 없음(직접 decoder/form read 미발견) | 해당 없음 | 해당 없음 |

요청 판정: **C/P** · unknown field 거부 미설정.
직접 validation: `err != nil` · `groups[i].TaskID == nil` · `ok` · `s.engine != nil`

### 응답·오류

| 종류 | status/body | 해석 상태 |
|---|---|---|
| success/variant | `200` · `items:derived`, `total:derived`, `finding_total:derived`, `page:derived`, `page_size:derived` | P/U · `static composite` |
| error | `401` · `{error:string}` · `"未授权"` / `"token 无效或已过期"` | C for direct writeErr; error helper 내부는 P |
| error | `500` · `{error:string}` · `err.Error()` | C for direct writeErr; error helper 내부는 P |

응답 schema 판정: **P** · top-level key는 직접 추출했지만 derived/helper/opaque 또는 nested type이 남음.

### 목록·동시성·완료

- Pagination/filter/sort: `limit`, `page`, `q`, `severity`, `sort`, `status`.
- Idempotency/concurrency: read/stream 요청; stream 재연결은 cursor 계약에 따름.
- Side effect/completion: 직접 조회/파일 전송 완료; 외부 조회가 있으면 응답 전 handler 결과만 보장.
- 직접 협력자/실행 단서: `err.Error`, `findingFilterFromQuery`, `findingPaginationParam`, `i64s`, `q.Get`, `r.URL.Query`, `s.m.Task`, `s.m.pg.ListFindingGroups`; direct `go` statement는 미발견. 이 목록은 helper 내부의 background launch나 transaction 경계를 펼치지 않으므로 그 부분은 P/U다.
- 종합 의미 판정: **P/U**; route/auth와 위에서 직접 확인한 branch·shape는 C지만, 모든 helper·derived response·중첩 DTO·외부 효과까지 완결됐다는 뜻은 아니다.

<a id="schema-get-api-exploration-findings-asset-tree"></a>
## `GET /api/exploration/findings/asset-tree`

구현: `findingAssetTree` · `server/findings_groups.go` · [정확한 handler 근거](evidence:handler-get-api-exploration-findings-asset-tree)

### 요청

| 위치/field | wire type | 필수·null | 기본/검증 |
|---|---|---|---|
| query `asset_scope` | `string` wire | 선택 | trimmed asset-tree key; empty→no filter, `__none__`→unassigned, missing key→empty result |
| query `q` | `string` wire | 선택 | string; DB trims, empty→no filter; escaped literal substring ILIKE over name/class/summary/evidence/report |
| query `severity` | `string` wire | 선택 | string; `all`/empty→no filter, otherwise exact equality without enum rejection |
| query `sort` | `string` wire | 선택 | `severity`→severity rank then newest; every other value→newest first |
| query `status` | `string` wire | 선택 | string; `all`/empty→no filter, otherwise exact equality without enum rejection |
| query `task_id` | `string` wire | 선택 | positive int string→task filter; `__unassigned__`→NULL/orphan; empty/invalid→no filter |
| query `vulnclass` | `string` wire | 선택 | string; `all`/empty→no filter, otherwise exact equality |
| auth credential | Bearer header / `artex_token` cookie / query `token` | 세 위치 중 하나에 유효 JWT 필수 | `extractToken` 우선순위: header→cookie→query; query fallback은 SSE로 제한되지 않음 |
| body | 없음(직접 decoder/form read 미발견) | 해당 없음 | 해당 없음 |

요청 판정: **C/P** · unknown field 거부 미설정.
직접 validation: `err != nil`

### 응답·오류

| 종류 | status/body | 해석 상태 |
|---|---|---|
| success/variant | `200` · `tree` | P/U · `derived variable` |
| error | `401` · `{error:string}` · `"未授权"` / `"token 无效或已过期"` | C for direct writeErr; error helper 내부는 P |
| error | `500` · `{error:string}` · `err.Error()` | C for direct writeErr; error helper 내부는 P |

응답 schema 판정: **P** · top-level key는 직접 추출했지만 derived/helper/opaque 또는 nested type이 남음.

### 목록·동시성·완료

- Pagination/filter/sort: `q`, `severity`, `sort`, `status`.
- Idempotency/concurrency: read/stream 요청; stream 재연결은 cursor 계약에 따름.
- Side effect/completion: 직접 조회/파일 전송 완료; 외부 조회가 있으면 응답 전 handler 결과만 보장.
- 직접 협력자/실행 단서: `err.Error`, `findingFilterFromQuery`, `r.URL.Query`, `s.m.pg.BuildFindingAssetTree`; direct `go` statement는 미발견. 이 목록은 helper 내부의 background launch나 transaction 경계를 펼치지 않으므로 그 부분은 P/U다.
- 종합 의미 판정: **P/U**; route/auth와 위에서 직접 확인한 branch·shape는 C지만, 모든 helper·derived response·중첩 DTO·외부 효과까지 완결됐다는 뜻은 아니다.

<a id="schema-get-api-exploration-findings-stats"></a>
## `GET /api/exploration/findings/stats`

구현: `findingStats` · `server/server.go` · [정확한 handler 근거](evidence:handler-get-api-exploration-findings-stats)

### 요청

| 위치/field | wire type | 필수·null | 기본/검증 |
|---|---|---|---|
| auth credential | Bearer header / `artex_token` cookie / query `token` | 세 위치 중 하나에 유효 JWT 필수 | `extractToken` 우선순위: header→cookie→query; query fallback은 SSE로 제한되지 않음 |
| body | 없음(직접 decoder/form read 미발견) | 해당 없음 | 해당 없음 |

요청 판정: **C/P** · unknown field 거부 미설정.
직접 validation: `err != nil`

### 응답·오류

| 종류 | status/body | 해석 상태 |
|---|---|---|
| success/variant | `200` · `st` | P/U · `derived variable` |
| error | `401` · `{error:string}` · `"未授权"` / `"token 无效或已过期"` | C for direct writeErr; error helper 내부는 P |
| error | `500` · `{error:string}` · `err.Error()` | C for direct writeErr; error helper 내부는 P |

응답 schema 판정: **P** · top-level key는 직접 추출했지만 derived/helper/opaque 또는 nested type이 남음.

### 목록·동시성·완료

- Pagination/filter/sort: 등록된 직접 query key 중 표준 paging/filter key 없음.
- Idempotency/concurrency: read/stream 요청; stream 재연결은 cursor 계약에 따름.
- Side effect/completion: 직접 조회/파일 전송 완료; 외부 조회가 있으면 응답 전 handler 결과만 보장.
- 직접 협력자/실행 단서: `err.Error`, `s.m.pg.FindingStats`; direct `go` statement는 미발견. 이 목록은 helper 내부의 background launch나 transaction 경계를 펼치지 않으므로 그 부분은 P/U다.
- 종합 의미 판정: **P/U**; route/auth와 위에서 직접 확인한 branch·shape는 C지만, 모든 helper·derived response·중첩 DTO·외부 효과까지 완결됐다는 뜻은 아니다.

<a id="schema-get-api-exploration-findings-export"></a>
## `GET /api/exploration/findings/export`

구현: `findingsExport` · `server/server.go` · [정확한 handler 근거](evidence:handler-get-api-exploration-findings-export)

### 요청

| 위치/field | wire type | 필수·null | 기본/검증 |
|---|---|---|---|
| query `asset_scope` | `string` wire | 선택 | handler/helper 변환·default 미전개(P) |
| query `format` | `string` wire | 필수 | required enum `md-single\|md-zip\|csv\|json`; missing/other→400 |
| query `ids` | `string` wire | 조건부 필수(scope=selected) | required only when scope=`selected`; comma-separated positive int64 values; empty/invalid→400 |
| query `q` | `string` wire | 선택 | handler/helper 변환·default 미전개(P) |
| query `scope` | `string` wire | 선택 | optional enum `filtered\|all\|selected`; missing/empty→`filtered`; other→400 |
| query `severity` | `string` wire | 선택 | handler/helper 변환·default 미전개(P) |
| query `sort` | `string` wire | 선택 | handler/helper 변환·default 미전개(P) |
| query `status` | `string` wire | 선택 | handler/helper 변환·default 미전개(P) |
| query `task_id` | `string` wire | 선택 | handler/helper 변환·default 미전개(P) |
| query `vulnclass` | `string` wire | 선택 | handler/helper 변환·default 미전개(P) |
| header `Range` | byte-ranges | 선택 | Go stdlib `ServeContent`/`ServeFile` precondition·range contract(P); `format=md-zip` branch에서만 소비하고 md-single/csv/json은 무시 |
| header `If-Range` | HTTP-date 또는 entity-tag | 선택 | Go stdlib `ServeContent`/`ServeFile` precondition·range contract(P); `format=md-zip` branch에서만 소비하고 md-single/csv/json은 무시 |
| header `If-Match` | entity-tag list 또는 `*` | 선택 | Go stdlib `ServeContent`/`ServeFile` precondition·range contract(P); `format=md-zip` branch에서만 소비하고 md-single/csv/json은 무시 |
| header `If-Unmodified-Since` | HTTP-date | 선택 | Go stdlib `ServeContent`/`ServeFile` precondition·range contract(P); `format=md-zip` branch에서만 소비하고 md-single/csv/json은 무시 |
| header `If-None-Match` | entity-tag list 또는 `*` | 선택 | Go stdlib `ServeContent`/`ServeFile` precondition·range contract(P); `format=md-zip` branch에서만 소비하고 md-single/csv/json은 무시 |
| header `If-Modified-Since` | HTTP-date | 선택 | Go stdlib `ServeContent`/`ServeFile` precondition·range contract(P); `format=md-zip` branch에서만 소비하고 md-single/csv/json은 무시 |
| auth credential | Bearer header / `artex_token` cookie / query `token` | 세 위치 중 하나에 유효 JWT 필수 | `extractToken` 우선순위: header→cookie→query; query fallback은 SSE로 제한되지 않음 |
| body | 없음(직접 decoder/form read 미발견) | 해당 없음 | 해당 없음 |

요청 판정: **C/P** · unknown field 거부 미설정.
직접 validation: `err != nil` · `err != nil \|\| id <= 0` · `len(ids) == 0` · `part == ""`
직접 parse/default: `strconv.ParseInt(part, 10, 64)`

### 응답·오류

| 종류 | status/body | 해석 상태 |
|---|---|---|
| success/variant | `200` · `format별 markdown/zip/csv/json attachment; md-zip headers are stdlib delegated` | P/U · `format-selected file/raw response` |
| success/variant | `206` · `md-zip \`http.ServeContent\` range response; Content-Range/Length` | P/U · `stdlib conditional response` |
| success/variant | `304` · `md-zip \`http.ServeContent\` conditional response; Last-Modified/ETag preconditions` | P/U · `stdlib conditional response` |
| error | `400` · `{error:string}` · `"bad finding id: " + part` / `"no findings selected"` / `"bad scope: " + scope` / `"bad format: " + format` | C for direct writeErr; error helper 내부는 P |
| error | `401` · `{error:string}` · `"未授权"` / `"token 无效或已过期"` | C for direct writeErr; error helper 내부는 P |
| error | `404` · `{error:string}` · `err.Error()` | C for direct writeErr; error helper 내부는 P |
| error | `409` · `{error:string}` · `err.Error()` | C for direct writeErr; error helper 내부는 P |
| error | `412` · body 없음 · `\`format=md-zip\` only: stdlib precondition failure from \`http.ServeContent\`; WriteHeader with no ARTEX JSON body` | P · Go stdlib precondition 처리; ARTEX `writeErr` JSON이 아님 |
| error | `416` · `text/plain` · `\`format=md-zip\` only: stdlib plain-text range error from \`http.ServeContent\`` | P · Go stdlib `ServeContent`/`ServeFile`가 직접 생성; ARTEX `writeErr` JSON이 아님 |
| error | `422` · `{error:string}` · `err.Error()` | C for direct writeErr; error helper 내부는 P |
| error | `500` · `{error:string}` · `err.Error()` | C for direct writeErr; error helper 내부는 P |

Helper 오류 분기: `evidenceError` · `server/finding_traffic.go::evidenceError` · 위 `404, 409, 422` status는 helper control-flow를 정적으로 펼친 P이며 호출 전/후 DB 효과는 operation별로 확인한다.

응답 schema 판정: **P** · top-level key는 직접 추출했지만 derived/helper/opaque 또는 nested type이 남음.

### 목록·동시성·완료

- Pagination/filter/sort: `q`, `severity`, `sort`, `status`.
- Idempotency/concurrency: read/stream 요청; stream 재연결은 cursor 계약에 따름.
- Side effect/completion: 직접 조회/파일 전송 완료; 외부 조회가 있으면 응답 전 handler 결과만 보장.
- 직접 협력자/실행 단서: `[]byte`, `buildFindingsEvidenceZip`, `enc.Encode`, `enc.SetIndent`, `err.Error`, `evidenceError`, `file.Close`, `filepath.Join`, `findingFilterFromQuery`, `findingFromDB`, `json.NewEncoder`, `now.Format`, `os.MkdirTemp`, `os.Open`, `os.RemoveAll`, `q.Get`, `r.Context`, `r.URL.Query` 외 8개; direct `go` statement는 미발견. 이 목록은 helper 내부의 background launch나 transaction 경계를 펼치지 않으므로 그 부분은 P/U다.
- 종합 의미 판정: **P/U**; route/auth와 위에서 직접 확인한 branch·shape는 C지만, 모든 helper·derived response·중첩 DTO·외부 효과까지 완결됐다는 뜻은 아니다.

<a id="schema-get-api-exploration-findings-id"></a>
## `GET /api/exploration/findings/{id}`

구현: `getFinding` · `server/server.go` · [정확한 handler 근거](evidence:handler-get-api-exploration-findings-id)

### 요청

| 위치/field | wire type | 필수·null | 기본/검증 |
|---|---|---|---|
| path `id` | `string` | 필수 | handler의 parse/range validation; 미해석 helper는 P |
| query `context_task` | `string` wire | 선택 | handler/helper 변환·default 미전개(P) |
| auth credential | Bearer header / `artex_token` cookie / query `token` | 세 위치 중 하나에 유효 JWT 필수 | `extractToken` 우선순위: header→cookie→query; query fallback은 SSE로 제한되지 않음 |
| body | 없음(직접 decoder/form read 미발견) | 해당 없음 | 해당 없음 |

요청 판정: **C/P** · unknown field 거부 미설정.
직접 validation: `!allowed` · `contextTask == nil` · `contextTaskID != ""` · `err != nil` · `f == nil` · `id <= 0`
직접 parse/default: `atoiDefault(r.PathValue("id"), 0)`

### 응답·오류

| 종류 | status/body | 해석 상태 |
|---|---|---|
| success/variant | `200` · `dto` | P/U · `derived variable` |
| error | `400` · `{error:string}` · `"bad finding id"` | C for direct writeErr; error helper 내부는 P |
| error | `401` · `{error:string}` · `"未授权"` / `"token 无效或已过期"` | C for direct writeErr; error helper 내부는 P |
| error | `404` · `{error:string}` · `"finding not found"` / `"context task not found"` / `"finding not available in task context"` | C for direct writeErr; error helper 내부는 P |
| error | `500` · `{error:string}` · `err.Error()` | C for direct writeErr; error helper 내부는 P |

응답 schema 판정: **P** · top-level key는 직접 추출했지만 derived/helper/opaque 또는 nested type이 남음.

### 목록·동시성·완료

- Pagination/filter/sort: 등록된 직접 query key 중 표준 paging/filter key 없음.
- Idempotency/concurrency: read/stream 요청; stream 재연결은 cursor 계약에 따름.
- Side effect/completion: 직접 조회/파일 전송 완료; 외부 조회가 있으면 응답 전 handler 결과만 보장.
- 직접 협력자/실행 단서: `atoiDefault`, `err.Error`, `findingFromDB`, `findingProvenanceInTask`, `r.PathValue`, `r.URL.Query`, `r.URL.Query().Get`, `s.m.ResolveTask`, `s.m.pg.GetFinding`; direct `go` statement는 미발견. 이 목록은 helper 내부의 background launch나 transaction 경계를 펼치지 않으므로 그 부분은 P/U다.
- 종합 의미 판정: **P/U**; route/auth와 위에서 직접 확인한 branch·shape는 C지만, 모든 helper·derived response·중첩 DTO·외부 효과까지 완결됐다는 뜻은 아니다.

<a id="schema-get-api-exploration-findings-id-lineage"></a>
## `GET /api/exploration/findings/{id}/lineage`

구현: `findingLineage` · `server/server.go` · [정확한 handler 근거](evidence:handler-get-api-exploration-findings-id-lineage)

### 요청

| 위치/field | wire type | 필수·null | 기본/검증 |
|---|---|---|---|
| path `id` | `string` | 필수 | handler의 parse/range validation; 미해석 helper는 P |
| auth credential | Bearer header / `artex_token` cookie / query `token` | 세 위치 중 하나에 유효 JWT 필수 | `extractToken` 우선순위: header→cookie→query; query fallback은 SSE로 제한되지 않음 |
| body | 없음(직접 decoder/form read 미발견) | 해당 없음 | 해당 없음 |

요청 판정: **C/P** · unknown field 거부 미설정.
직접 validation: `err != nil` · `f == nil` · `f.NodeID == nil \|\| f.TaskID == nil` · `id <= 0` · `t == nil`
직접 parse/default: `atoiDefault(r.PathValue("id"), 0)`

### 응답·오류

| 종류 | status/body | 해석 상태 |
|---|---|---|
| success/variant | `200` · `empty` | P/U · `derived variable` |
| success/variant | `200` · `nodes:helper result`, `edges:helper result` | P/U · `static composite` |
| error | `400` · `{error:string}` · `"bad finding id"` | C for direct writeErr; error helper 내부는 P |
| error | `401` · `{error:string}` · `"未授权"` / `"token 无效或已过期"` | C for direct writeErr; error helper 내부는 P |
| error | `404` · `{error:string}` · `"finding not found"` | C for direct writeErr; error helper 내부는 P |
| error | `500` · `{error:string}` · `err.Error()` | C for direct writeErr; error helper 내부는 P |

응답 schema 판정: **P** · top-level key는 직접 추출했지만 derived/helper/opaque 또는 nested type이 남음.

### 목록·동시성·완료

- Pagination/filter/sort: 등록된 직접 query key 중 표준 paging/filter key 없음.
- Idempotency/concurrency: read/stream 요청; stream 재연결은 cursor 계약에 따름.
- Side effect/completion: 직접 조회/파일 전송 완료; 외부 조회가 있으면 응답 전 handler 결과만 보장.
- 직접 협력자/실행 단서: `atoiDefault`, `edgeDTOs`, `err.Error`, `i64s`, `r.PathValue`, `s.m.ResolveTask`, `s.m.pg.GetFinding`, `t.Store.FindingLineage`, `taskNodeDTOs`; direct `go` statement는 미발견. 이 목록은 helper 내부의 background launch나 transaction 경계를 펼치지 않으므로 그 부분은 P/U다.
- 종합 의미 판정: **P/U**; route/auth와 위에서 직접 확인한 branch·shape는 C지만, 모든 helper·derived response·중첩 DTO·외부 효과까지 완결됐다는 뜻은 아니다.

<a id="schema-post-api-exploration-findings-id-deepen"></a>
## `POST /api/exploration/findings/{id}/deepen`

구현: `deepenFinding` · `server/findings_groups.go` · [정확한 handler 근거](evidence:handler-post-api-exploration-findings-id-deepen)

### 요청

| 위치/field | wire type | 필수·null | 기본/검증 |
|---|---|---|---|
| path `id` | `string` | 필수 | handler의 parse/range validation; 미해석 helper는 P |
| auth credential | Bearer header / `artex_token` cookie / query `token` | 세 위치 중 하나에 유효 JWT 필수 | `extractToken` 우선순위: header→cookie→query; query fallback은 SSE로 제한되지 않음 |
| body `description` | `string` (`string`) | 필수(직접 거부 branch 확인); null/absent 구별 안 됨 | `trim-empty이면 400; 4000자 초과도 400` |

요청 body 상한: **32 KiB** (`32768` bytes) · source `server/findings_groups.go::deepenFinding` · 초과/decoder 경로 `413 "请求正文过大"`.

요청 판정: **C/P** · unknown field 거부 미설정.
직접 parse/default: `atoiDefault(r.PathValue("id"), 0)`

### 응답·오류

| 종류 | status/body | 해석 상태 |
|---|---|---|
| success/variant | `200` · `task_id:expression`, `intent_id:helper result`, `state:derived`, `queued:derived` | P/U · `static composite` |
| error | `400` · `{error:string}` · `"bad finding id"` / `"bad json: " + err.Error()` / `"description is required"` / `fmt.Sprintf("description must be at most %d characters", maxFindingFollowUpRunes)` | C for direct writeErr; error helper 내부는 P |
| error | `401` · `{error:string}` · `"未授权"` / `"token 无效或已过期"` | C for direct writeErr; error helper 내부는 P |
| error | `404` · `{error:string}` · `"finding not found"` | C for direct writeErr; error helper 내부는 P |
| error | `409` · `{error:string}` · `"finding origin task or node is no longer available"` / `"finding origin task is no longer available"` / `"task is being deleted"` | C for direct writeErr; error helper 내부는 P |
| error | `413` · `{error:string}` · `"请求正文过大"` | C for direct writeErr; error helper 내부는 P |
| error | `500` · `{error:string}` · `err.Error()` | C for direct writeErr; error helper 내부는 P |

응답 schema 판정: **P** · top-level key는 직접 추출했지만 derived/helper/opaque 또는 nested type이 남음.

### 목록·동시성·완료

- Pagination/filter/sort: 등록된 직접 query key 중 표준 paging/filter key 없음.
- Idempotency/concurrency: Idempotency-Key 없음; unique constraint 또는 상태 check가 있는 경우만 중복을 제한(P).
- Side effect/completion: 후속 intent queue 접수; intent terminal state가 완료 기준.
- 직접 협력자/실행 단서: `atoiDefault`, `err.Error`, `errors.As`, `errors.Join`, `i64s`, `json.NewDecoder`, `json.NewDecoder(r.Body).Decode`, `r.PathValue`, `s.engine.Broadcaster`, `s.engine.Broadcaster().Publish`, `s.engine.beginTaskOperation`, `s.engine.decInflight`, `s.engine.touch`, `s.m.Task`, `s.m.pg.GetFinding`, `t.Store.AddFindingFollowUpIntent`, `t.Store.DiscardOpenIntent`, `t.Store.GetNode` 외 1개; direct `go` statement는 미발견. 이 목록은 helper 내부의 background launch나 transaction 경계를 펼치지 않으므로 그 부분은 P/U다.
- 종합 의미 판정: **P/U**; route/auth와 위에서 직접 확인한 branch·shape는 C지만, 모든 helper·derived response·중첩 DTO·외부 효과까지 완결됐다는 뜻은 아니다.

<a id="schema-get-api-exploration-findings-id-retests"></a>
## `GET /api/exploration/findings/{id}/retests`

구현: `listFindingRetests` · `server/finding_retests.go` · [정확한 handler 근거](evidence:handler-get-api-exploration-findings-id-retests)

### 요청

| 위치/field | wire type | 필수·null | 기본/검증 |
|---|---|---|---|
| path `id` | `string` | 필수 | handler의 parse/range validation; 미해석 helper는 P |
| auth credential | Bearer header / `artex_token` cookie / query `token` | 세 위치 중 하나에 유효 JWT 필수 | `extractToken` 우선순위: header→cookie→query; query fallback은 SSE로 제한되지 않음 |
| body | 없음(직접 decoder/form read 미발견) | 해당 없음 | 해당 없음 |

요청 판정: **C/P** · unknown field 거부 미설정.
직접 validation: `!ok \|\| id <= 0` · `err != nil` · `f == nil` · `pg == nil`

### 응답·오류

| 종류 | status/body | 해석 상태 |
|---|---|---|
| success/variant | `200` · `retests:derived` | P/U · `static composite` |
| error | `400` · `{error:string}` · `"bad finding id"` | C for direct writeErr; error helper 내부는 P |
| error | `401` · `{error:string}` · `"未授权"` / `"token 无效或已过期"` | C for direct writeErr; error helper 내부는 P |
| error | `404` · `{error:string}` · `"finding not found"` | C for direct writeErr; error helper 내부는 P |
| error | `500` · `{error:string}` · `err.Error()` | C for direct writeErr; error helper 내부는 P |
| error | `503` · `{error:string}` · `"PostgreSQL 不可用"` | C for direct writeErr; error helper 내부는 P |

응답 schema 판정: **P** · top-level key는 직접 추출했지만 derived/helper/opaque 또는 nested type이 남음.

### 목록·동시성·완료

- Pagination/filter/sort: 등록된 직접 query key 중 표준 paging/filter key 없음.
- Idempotency/concurrency: read/stream 요청; stream 재연결은 cursor 계약에 따름.
- Side effect/completion: 직접 조회/파일 전송 완료; 외부 조회가 있으면 응답 전 handler 결과만 보장.
- 직접 협력자/실행 단서: `err.Error`, `pathInt`, `pg.GetFinding`, `pg.ListFindingRetests`; direct `go` statement는 미발견. 이 목록은 helper 내부의 background launch나 transaction 경계를 펼치지 않으므로 그 부분은 P/U다.
- 종합 의미 판정: **P/U**; route/auth와 위에서 직접 확인한 branch·shape는 C지만, 모든 helper·derived response·중첩 DTO·외부 효과까지 완결됐다는 뜻은 아니다.

<a id="schema-get-api-exploration-findings-retests-active"></a>
## `GET /api/exploration/findings/retests/active`

구현: `listActiveFindingRetests` · `server/finding_retests.go` · [정확한 handler 근거](evidence:handler-get-api-exploration-findings-retests-active)

### 요청

| 위치/field | wire type | 필수·null | 기본/검증 |
|---|---|---|---|
| auth credential | Bearer header / `artex_token` cookie / query `token` | 세 위치 중 하나에 유효 JWT 필수 | `extractToken` 우선순위: header→cookie→query; query fallback은 SSE로 제한되지 않음 |
| body | 없음(직접 decoder/form read 미발견) | 해당 없음 | 해당 없음 |

요청 판정: **C/P** · unknown field 거부 미설정.
직접 validation: `err != nil` · `pg == nil`

### 응답·오류

| 종류 | status/body | 해석 상태 |
|---|---|---|
| success/variant | `200` · `retests:derived` | P/U · `static composite` |
| error | `401` · `{error:string}` · `"未授权"` / `"token 无效或已过期"` | C for direct writeErr; error helper 내부는 P |
| error | `500` · `{error:string}` · `err.Error()` | C for direct writeErr; error helper 내부는 P |
| error | `503` · `{error:string}` · `"PostgreSQL 不可用"` | C for direct writeErr; error helper 내부는 P |

응답 schema 판정: **P** · top-level key는 직접 추출했지만 derived/helper/opaque 또는 nested type이 남음.

### 목록·동시성·완료

- Pagination/filter/sort: 등록된 직접 query key 중 표준 paging/filter key 없음.
- Idempotency/concurrency: read/stream 요청; stream 재연결은 cursor 계약에 따름.
- Side effect/completion: 직접 조회/파일 전송 완료; 외부 조회가 있으면 응답 전 handler 결과만 보장.
- 직접 협력자/실행 단서: `err.Error`, `pg.ListActiveFindingRetests`, `r.Context`; direct `go` statement는 미발견. 이 목록은 helper 내부의 background launch나 transaction 경계를 펼치지 않으므로 그 부분은 P/U다.
- 종합 의미 판정: **P/U**; route/auth와 위에서 직접 확인한 branch·shape는 C지만, 모든 helper·derived response·중첩 DTO·외부 효과까지 완결됐다는 뜻은 아니다.

<a id="schema-post-api-exploration-findings-id-retests"></a>
## `POST /api/exploration/findings/{id}/retests`

구현: `startFindingRetest` · `server/finding_retests.go` · [정확한 handler 근거](evidence:handler-post-api-exploration-findings-id-retests)

### 요청

| 위치/field | wire type | 필수·null | 기본/검증 |
|---|---|---|---|
| path `id` | `string` | 필수 | handler의 parse/range validation; 미해석 helper는 P |
| auth credential | Bearer header / `artex_token` cookie / query `token` | 세 위치 중 하나에 유효 JWT 필수 | `extractToken` 우선순위: header→cookie→query; query fallback은 SSE로 제한되지 않음 |
| body `notes` | `string` (`string`) | 요구 여부 미확정(P); null/absent 구별 안 됨 | `utf8.RuneCountInString(req.Notes) > 4000` |

요청 body 상한: **64 KiB** (`65536` bytes) · source `server/conversations.go::decodeConversationRequest` · 초과/decoder 경로 `413 "请求正文过大"`.

요청 판정: **C/P** · unknown field 거부 미설정.
직접 validation: `utf8.RuneCountInString(req.Notes) > 4000`
공유 decoder/helper: `decodeConversationRequest`; helper 내부 size/presence validation은 같은 source evidence에서 확인하며 operation 단독 AST 판정은 P.

### 응답·오류

| 종류 | status/body | 해석 상태 |
|---|---|---|
| success/variant | `200` · `retest:derived`, `created:boolean(false)` | P/U · `static composite` |
| success/variant | `202` · `retest:derived`, `created:boolean(true)` | P/U · `static composite` |
| error | `400` · `{error:string}` · `"bad finding id"` / `"复测补充说明最多 4000 个字符"` | C for direct writeErr; error helper 내부는 P |
| error | `401` · `{error:string}` · `"未授权"` / `"token 无效或已过期"` | C for direct writeErr; error helper 내부는 P |
| error | `404` · `{error:string}` · `"finding not found"` | C for direct writeErr; error helper 내부는 P |
| error | `409` · `{error:string}` · `"漏洞复测 Agent 不存在或未启用，请在 Agent 管理中配置 retester"` / `"请为复测 Agent 启用并绑定工具：" + key` | C for direct writeErr; error helper 내부는 P |
| error | `413` · `{error:string}` · `"请求正文过大"` | C for direct writeErr; error helper 내부는 P |
| error | `500` · `{error:string}` · `err.Error()` | C for direct writeErr; error helper 내부는 P |
| error | `503` · `{error:string}` · `s.chatUnavailableReason()` / `"服务正在停止"` / `"PostgreSQL 不可用"` | C for direct writeErr; error helper 내부는 P |

응답 schema 판정: **P** · top-level key는 직접 추출했지만 derived/helper/opaque 또는 nested type이 남음.

### 목록·동시성·완료

- Pagination/filter/sort: 등록된 직접 query key 중 표준 paging/filter key 없음.
- Idempotency/concurrency: Idempotency-Key 없음; unique constraint 또는 상태 check가 있는 경우만 중복을 제한(P).
- Side effect/completion: retest row/conversation 시작; retest terminal state가 완료 기준.
- 직접 협력자/실행 단서: `decodeConversationRequest`, `err.Error`, `pathInt`, `pg.CreateFindingRetest`, `pg.GetAgentByKey`, `pg.GetFinding`, `pg.GetTool`, `r.Context`, `retest.InitialMessage`, `s.chatMu.Lock`, `s.chatMu.Unlock`, `s.ctx.Err`, `slices.Contains`, `utf8.RuneCountInString`; direct `go` statement는 미발견. 이 목록은 helper 내부의 background launch나 transaction 경계를 펼치지 않으므로 그 부분은 P/U다.
- 종합 의미 판정: **P/U**; route/auth와 위에서 직접 확인한 branch·shape는 C지만, 모든 helper·derived response·중첩 DTO·외부 효과까지 완결됐다는 뜻은 아니다.

<a id="schema-patch-api-exploration-findings-id"></a>
## `PATCH /api/exploration/findings/{id}`

구현: `patchFinding` · `server/server.go` · [정확한 handler 근거](evidence:handler-patch-api-exploration-findings-id)

### 요청

| 위치/field | wire type | 필수·null | 기본/검증 |
|---|---|---|---|
| path `id` | `string` | 필수 | handler의 parse/range validation; 미해석 helper는 P |
| auth credential | Bearer header / `artex_token` cookie / query `token` | 세 위치 중 하나에 유효 JWT 필수 | `extractToken` 우선순위: header→cookie→query; query fallback은 SSE로 제한되지 않음 |
| body `status` | `string` (`*string`) | 개별 선택(그룹 중 하나 필수); null→nil | `status/severity/name/vulnclass 중 하나 필요; 제공 field만 검증/수정` |
| body `severity` | `string` (`*string`) | 개별 선택(그룹 중 하나 필수); null→nil | `status/severity/name/vulnclass 중 하나 필요; 제공 field만 검증/수정` |
| body `name` | `string` (`*string`) | 개별 선택(그룹 중 하나 필수); null→nil | `status/severity/name/vulnclass 중 하나 필요; 제공 field만 검증/수정` |
| body `vulnclass` | `string` (`*string`) | 개별 선택(그룹 중 하나 필수); null→nil | `status/severity/name/vulnclass 중 하나 필요; 제공 field만 검증/수정` |

요청 판정: **C/P** · unknown field 거부 미설정.
직접 validation: `!db.ValidFindingStatus(*body.Status)` · `!db.ValidSeverity(*body.Severity)` · `!notified && from != *body.Status` · `body.Name != nil` · `body.Severity != nil` · `body.Status != nil` · `body.Status == nil && body.Severity == nil && body.Name == nil && body.VulnClass == nil` · `body.VulnClass != nil`
직접 parse/default: `atoiDefault(r.PathValue("id"), 0)`

### 응답·오류

| 종류 | status/body | 해석 상태 |
|---|---|---|
| success/variant | `200` · `findingFromDB(f, s.resolveAssetIDs(f.AssetIDs))` | P/U · `helper result` |
| error | `400` · `{error:string}` · `"bad finding id"` / `"bad json: " + err.Error()` / `"nothing to update: provide status/severity/name/vulnclass"` / `"bad status: " + *body.Status` / `"bad severity: " + *body.Severity` | C for direct writeErr; error helper 내부는 P |
| error | `401` · `{error:string}` · `"未授权"` / `"token 无效或已过期"` | C for direct writeErr; error helper 내부는 P |
| error | `404` · `{error:string}` · `"finding not found"` | C for direct writeErr; error helper 내부는 P |
| error | `500` · `{error:string}` · `err.Error()` | C for direct writeErr; error helper 내부는 P |

응답 schema 판정: **P** · top-level key는 직접 추출했지만 derived/helper/opaque 또는 nested type이 남음.

### 목록·동시성·완료

- Pagination/filter/sort: 등록된 직접 query key 중 표준 paging/filter key 없음.
- Idempotency/concurrency: Idempotency-Key/If-Match 없음; DB upsert/trigger/side effect 때문에 반복 의미를 handler별 확인(P).
- Side effect/completion: handler의 직접 mutation/호출 후 응답; 외부 전달·agent 실행이 파생되면 별도 상태 확인(P).
- 직접 협력자/실행 단서: `atoiDefault`, `db.ValidFindingStatus`, `db.ValidSeverity`, `err.Error`, `findingFromDB`, `json.NewDecoder`, `json.NewDecoder(r.Body).Decode`, `log.Printf`, `r.Context`, `r.PathValue`, `s.m.pg.GetFinding`, `s.m.pg.SetFindingName`, `s.m.pg.SetFindingSeverity`, `s.m.pg.SetFindingStatusWithNotify`, `s.m.pg.SetFindingVulnClass`; direct `go` statement는 미발견. 이 목록은 helper 내부의 background launch나 transaction 경계를 펼치지 않으므로 그 부분은 P/U다.
- 종합 의미 판정: **P/U**; route/auth와 위에서 직접 확인한 branch·shape는 C지만, 모든 helper·derived response·중첩 DTO·외부 효과까지 완결됐다는 뜻은 아니다.

<a id="schema-delete-api-exploration-findings-id"></a>
## `DELETE /api/exploration/findings/{id}`

구현: `deleteFinding` · `server/server.go` · [정확한 handler 근거](evidence:handler-delete-api-exploration-findings-id)

### 요청

| 위치/field | wire type | 필수·null | 기본/검증 |
|---|---|---|---|
| path `id` | `string` | 필수 | handler의 parse/range validation; 미해석 helper는 P |
| auth credential | Bearer header / `artex_token` cookie / query `token` | 세 위치 중 하나에 유효 JWT 필수 | `extractToken` 우선순위: header→cookie→query; query fallback은 SSE로 제한되지 않음 |
| body | 없음(직접 decoder/form read 미발견) | 해당 없음 | 해당 없음 |

요청 판정: **C/P** · unknown field 거부 미설정.
직접 validation: `err != nil` · `id <= 0` · `n == 0`
직접 parse/default: `atoiDefault(r.PathValue("id"), 0)`

### 응답·오류

| 종류 | status/body | 해석 상태 |
|---|---|---|
| success/variant | `200` · `deleted:boolean`, `id:derived` | P/U · `static composite` |
| error | `400` · `{error:string}` · `"bad finding id"` | C for direct writeErr; error helper 내부는 P |
| error | `401` · `{error:string}` · `"未授权"` / `"token 无效或已过期"` | C for direct writeErr; error helper 내부는 P |
| error | `404` · `{error:string}` · `"finding not found"` | C for direct writeErr; error helper 내부는 P |
| error | `500` · `{error:string}` · `err.Error()` | C for direct writeErr; error helper 내부는 P |

응답 schema 판정: **P** · top-level key는 직접 추출했지만 derived/helper/opaque 또는 nested type이 남음.

### 목록·동시성·완료

- Pagination/filter/sort: 등록된 직접 query key 중 표준 paging/filter key 없음.
- Idempotency/concurrency: Idempotency-Key/If-Match 없음; 반복 시 응답/존재 검사가 달라질 수 있음.
- Side effect/completion: handler의 직접 mutation/호출 후 응답; 외부 전달·agent 실행이 파생되면 별도 상태 확인(P).
- 직접 협력자/실행 단서: `atoiDefault`, `err.Error`, `r.PathValue`, `s.m.pg.DeleteFinding`; direct `go` statement는 미발견. 이 목록은 helper 내부의 background launch나 transaction 경계를 펼치지 않으므로 그 부분은 P/U다.
- 종합 의미 판정: **P/U**; route/auth와 위에서 직접 확인한 branch·shape는 C지만, 모든 helper·derived response·중첩 DTO·외부 효과까지 완결됐다는 뜻은 아니다.

<a id="schema-get-api-exploration-intents"></a>
## `GET /api/exploration/intents`

구현: `intents` · `server/server.go` · [정확한 handler 근거](evidence:handler-get-api-exploration-intents)

### 요청

| 위치/field | wire type | 필수·null | 기본/검증 |
|---|---|---|---|
| query `before` | `string` wire | 선택 | handler/helper 변환·default 미전개(P) |
| query `limit` | `string` wire | 선택 | handler/helper 변환·default 미전개(P) |
| query `page` | `string` wire | 선택 | handler/helper 변환·default 미전개(P) |
| query `task` | `string` wire | 선택 | handler/helper 변환·default 미전개(P) |
| auth credential | Bearer header / `artex_token` cookie / query `token` | 세 위치 중 하나에 유효 JWT 필수 | `extractToken` 우선순위: header→cookie→query; query fallback은 SSE로 제한되지 않음 |
| body | 없음(직접 decoder/form read 미발견) | 해당 없음 | 해당 없음 |

요청 판정: **C/P** · unknown field 거부 미설정.
직접 validation: `err != nil` · `len(in) > limit` · `q.Get("before") == "" && q.Get("page") == ""` · `sourceErr != nil` · `t == nil`
직접 parse/default: `atoiDefault(q.Get("before"), 0)` · `atoiDefault(q.Get("limit"), 300)`

### 응답·오류

| 종류 | status/body | 해석 상태 |
|---|---|---|
| success/variant | `200` · `[]any{}` | P/U · `static composite` |
| success/variant | `200` · `taskNodeDTOs(in)` | P/U · `helper result` |
| success/variant | `200` · `items:helper result`, `has_more:derived` | P/U · `static composite` |
| error | `401` · `{error:string}` · `"未授权"` / `"token 无效或已过期"` | C for direct writeErr; error helper 내부는 P |
| error | `500` · `{error:string}` · `err.Error()` / `sourceErr.Error()` | C for direct writeErr; error helper 내부는 P |

응답 schema 판정: **P** · top-level key는 직접 추출했지만 derived/helper/opaque 또는 nested type이 남음.

### 목록·동시성·완료

- Pagination/filter/sort: `before`, `limit`, `page`, `task`.
- Idempotency/concurrency: read/stream 요청; stream 재연결은 cursor 계약에 따름.
- Side effect/completion: 직접 조회/파일 전송 완료; 외부 조회가 있으면 응답 전 handler 결과만 보장.
- 직접 협력자/실행 단서: `atoiDefault`, `err.Error`, `log.Printf`, `min`, `q.Get`, `r.URL.Query`, `r.URL.Query().Get`, `s.m.ResolveTask`, `sort.Slice`, `sourceErr.Error`, `t.Store.DirectSourceStores`, `t.Store.ListByKind`, `taskIntentHistoryPage`, `taskNodeDTOs`; direct `go` statement는 미발견. 이 목록은 helper 내부의 background launch나 transaction 경계를 펼치지 않으므로 그 부분은 P/U다.
- 종합 의미 판정: **P/U**; route/auth와 위에서 직접 확인한 branch·shape는 C지만, 모든 helper·derived response·중첩 DTO·외부 효과까지 완결됐다는 뜻은 아니다.

<a id="schema-get-api-exploration-graph"></a>
## `GET /api/exploration/graph`

구현: `explorationGraph` · `server/server.go` · [정확한 handler 근거](evidence:handler-get-api-exploration-graph)

### 요청

| 위치/field | wire type | 필수·null | 기본/검증 |
|---|---|---|---|
| query `task` | `string` wire | 선택 | handler/helper 변환·default 미전개(P) |
| auth credential | Bearer header / `artex_token` cookie / query `token` | 세 위치 중 하나에 유효 JWT 필수 | `extractToken` 우선순위: header→cookie→query; query fallback은 SSE로 제한되지 않음 |
| body | 없음(직접 decoder/form read 미발견) | 해당 없음 | 해당 없음 |

요청 판정: **C/P** · unknown field 거부 미설정.
직접 validation: `edgeErr != nil` · `err != nil` · `nodeErr != nil` · `t == nil`

### 응답·오류

| 종류 | status/body | 해석 상태 |
|---|---|---|
| success/variant | `200` · `nodes:[]any`, `edges:[]any` | P/U · `static composite` |
| success/variant | `200` · `nodes:helper result`, `edges:helper result` | P/U · `static composite` |
| error | `401` · `{error:string}` · `"未授权"` / `"token 无效或已过期"` | C for direct writeErr; error helper 내부는 P |
| error | `500` · `{error:string}` · `err.Error()` / `nodeErr.Error()` / `edgeErr.Error()` | C for direct writeErr; error helper 내부는 P |

응답 schema 판정: **P** · top-level key는 직접 추출했지만 derived/helper/opaque 또는 nested type이 남음.

### 목록·동시성·완료

- Pagination/filter/sort: `task`.
- Idempotency/concurrency: read/stream 요청; stream 재연결은 cursor 계약에 따름.
- Side effect/completion: 직접 조회/파일 전송 완료; 외부 조회가 있으면 응답 전 handler 결과만 보장.
- 직접 협력자/실행 단서: `edgeDTOs`, `edgeErr.Error`, `err.Error`, `inheritedGraphSnapshot`, `nodeErr.Error`, `r.URL.Query`, `r.URL.Query().Get`, `s.m.ResolveTask`, `source.Store.Edges`, `source.Store.Nodes`, `t.Store.DirectSourceStores`, `t.Store.Edges`, `t.Store.Nodes`, `taskNodeDTOs`; direct `go` statement는 미발견. 이 목록은 helper 내부의 background launch나 transaction 경계를 펼치지 않으므로 그 부분은 P/U다.
- 종합 의미 판정: **P/U**; route/auth와 위에서 직접 확인한 branch·shape는 C지만, 모든 helper·derived response·중첩 DTO·외부 효과까지 완결됐다는 뜻은 아니다.

<a id="schema-get-api-exploration-nodes"></a>
## `GET /api/exploration/nodes`

구현: `explorationNodes` · `server/server.go` · [정확한 handler 근거](evidence:handler-get-api-exploration-nodes)

### 요청

| 위치/field | wire type | 필수·null | 기본/검증 |
|---|---|---|---|
| query `kind` | `string` wire | 선택 | handler/helper 변환·default 미전개(P) |
| query `order` | `string` wire | 선택 | handler/helper 변환·default 미전개(P) |
| query `page` | `string` wire | 선택 | handler/helper 변환·default 미전개(P) |
| query `q` | `string` wire | 선택 | handler/helper 변환·default 미전개(P) |
| query `size` | `string` wire | 선택 | handler/helper 변환·default 미전개(P) |
| query `state` | `string` wire | 선택 | handler/helper 변환·default 미전개(P) |
| query `task` | `string` wire | 선택 | handler/helper 변환·default 미전개(P) |
| auth credential | Bearer header / `artex_token` cookie / query `token` | 세 위치 중 하나에 유효 JWT 필수 | `extractToken` 우선순위: header→cookie→query; query fallback은 SSE로 제한되지 않음 |
| body | 없음(직접 decoder/form read 미발견) | 해당 없음 | 해당 없음 |

요청 판정: **C/P** · unknown field 거부 미설정.
직접 validation: `!onPage[e.From]` · `!onPage[e.To]` · `err != nil` · `t == nil`
직접 parse/default: `atoiDefault(q.Get("page"), 1)` · `atoiDefault(q.Get("size"), 20)`

### 응답·오류

| 종류 | status/body | 해석 상태 |
|---|---|---|
| success/variant | `200` · `items:[]any`, `total:number`, `page:derived`, `size:derived`, `edges:[]any`, `refs:map[string]any` | P/U · `static composite` |
| success/variant | `200` · `items:helper result`, `total:derived`, `page:derived`, `size:derived`, `edges:helper result`, `refs:derived`, `assets:helper result` | P/U · `static composite` |
| error | `401` · `{error:string}` · `"未授权"` / `"token 无效或已过期"` | C for direct writeErr; error helper 내부는 P |
| error | `500` · `{error:string}` · `err.Error()` | C for direct writeErr; error helper 내부는 P |

응답 schema 판정: **P** · top-level key는 직접 추출했지만 derived/helper/opaque 또는 nested type이 남음.

### 목록·동시성·완료

- Pagination/filter/sort: `order`, `page`, `q`, `size`, `task`.
- Idempotency/concurrency: read/stream 요청; stream 재연결은 cursor 계약에 따름.
- Side effect/completion: 직접 조회/파일 전송 완료; 외부 조회가 있으면 응답 전 handler 결과만 보장.
- 직접 협력자/실행 단서: `atoiDefault`, `csvValues`, `edgeDTOs`, `err.Error`, `i64s`, `q.Get`, `r.URL.Query`, `s.m.ResolveTask`, `t.Store.EdgesTouching`, `t.Store.NodesByIDs`, `t.Store.NodesPage`, `taskNodeDTO`, `taskNodeDTOs`; direct `go` statement는 미발견. 이 목록은 helper 내부의 background launch나 transaction 경계를 펼치지 않으므로 그 부분은 P/U다.
- 종합 의미 판정: **P/U**; route/auth와 위에서 직접 확인한 branch·shape는 C지만, 모든 helper·derived response·중첩 DTO·외부 효과까지 완결됐다는 뜻은 아니다.

<a id="schema-get-api-exploration-activity"></a>
## `GET /api/exploration/activity`

구현: `activity` · `server/server.go` · [정확한 handler 근거](evidence:handler-get-api-exploration-activity)

### 요청

| 위치/field | wire type | 필수·null | 기본/검증 |
|---|---|---|---|
| query `intent` | `string` wire | 선택 | handler/helper 변환·default 미전개(P) |
| query `limit` | `string` wire | 선택 | handler/helper 변환·default 미전개(P) |
| query `since` | `string` wire | 선택 | handler/helper 변환·default 미전개(P) |
| query `task` | `string` wire | 선택 | handler/helper 변환·default 미전개(P) |
| auth credential | Bearer header / `artex_token` cookie / query `token` | 세 위치 중 하나에 유효 JWT 필수 | `extractToken` 우선순위: header→cookie→query; query fallback은 SSE로 제한되지 않음 |
| body | 없음(직접 decoder/form read 미발견) | 해당 없음 | 해당 없음 |

요청 판정: **C/P** · unknown field 거부 미설정.
직접 validation: `err != nil` · `err == nil` · `intentPtr != nil` · `iv != ""` · `t == nil`
직접 parse/default: `atoiDefault(r.URL.Query().Get("limit"), 300)` · `atoiDefault(r.URL.Query().Get("since"), 0)` · `strconv.ParseInt(iv, 10, 64)`

### 응답·오류

| 종류 | status/body | 해석 상태 |
|---|---|---|
| success/variant | `200` · `items:[]any`, `cursor:number` | P/U · `static composite` |
| success/variant | `200` · `items:helper result`, `cursor:derived` | P/U · `static composite` |
| error | `401` · `{error:string}` · `"未授权"` / `"token 无效或已过期"` | C for direct writeErr; error helper 내부는 P |
| error | `500` · `{error:string}` · `err.Error()` | C for direct writeErr; error helper 내부는 P |

응답 schema 판정: **P** · top-level key는 직접 추출했지만 derived/helper/opaque 또는 nested type이 남음.

### 목록·동시성·완료

- Pagination/filter/sort: `limit`, `since`, `task`.
- Idempotency/concurrency: read/stream 요청; stream 재연결은 cursor 계약에 따름.
- Side effect/completion: 직접 조회/파일 전송 완료; 외부 조회가 있으면 응답 전 handler 결과만 보장.
- 직접 협력자/실행 단서: `activityDTOs`, `atoiDefault`, `err.Error`, `log.Printf`, `r.URL.Query`, `r.URL.Query().Get`, `s.m.ResolveTask`, `t.Store.ActivityList`, `t.Store.ActivityListWithSources`; direct `go` statement는 미발견. 이 목록은 helper 내부의 background launch나 transaction 경계를 펼치지 않으므로 그 부분은 P/U다.
- 종합 의미 판정: **P/U**; route/auth와 위에서 직접 확인한 branch·shape는 C지만, 모든 helper·derived response·중첩 DTO·외부 효과까지 완결됐다는 뜻은 아니다.

<a id="schema-get-api-exploration-activity-history"></a>
## `GET /api/exploration/activity/history`

구현: `activityHistory` · `server/server.go` · [정확한 handler 근거](evidence:handler-get-api-exploration-activity-history)

### 요청

| 위치/field | wire type | 필수·null | 기본/검증 |
|---|---|---|---|
| query `before` | `string` wire | 선택 | handler/helper 변환·default 미전개(P) |
| query `limit` | `string` wire | 선택 | handler/helper 변환·default 미전개(P) |
| query `session` | `string` wire | 선택 | handler/helper 변환·default 미전개(P) |
| query `task` | `string` wire | 선택 | handler/helper 변환·default 미전개(P) |
| auth credential | Bearer header / `artex_token` cookie / query `token` | 세 위치 중 하나에 유효 JWT 필수 | `extractToken` 우선순위: header→cookie→query; query fallback은 SSE로 제한되지 않음 |
| body | 없음(직접 decoder/form read 미발견) | 해당 없음 | 해당 없음 |

요청 판정: **C/P** · unknown field 거부 미설정.
직접 validation: `!ok` · `err != nil` · `filter.Main && filter.MainSeg == nil` · `len(items) > 0` · `sourceTaskID > 0` · `store == nil` · `t == nil`
직접 parse/default: `atoiDefault(q.Get("before"), 0)` · `atoiDefault(q.Get("limit"), 200)`

### 응답·오류

| 종류 | status/body | 해석 상태 |
|---|---|---|
| success/variant | `200` · `items:helper result`, `snapshot_cursor:derived`, `earliest_cursor:derived`, `has_more:derived` | P/U · `static composite` |
| error | `400` · `{error:string}` · `"bad session"` | C for direct writeErr; error helper 내부는 P |
| error | `401` · `{error:string}` · `"未授权"` / `"token 无效或已过期"` | C for direct writeErr; error helper 내부는 P |
| error | `404` · `{error:string}` · `"task not found"` / `"session not found"` | C for direct writeErr; error helper 내부는 P |
| error | `500` · `{error:string}` · `err.Error()` | C for direct writeErr; error helper 내부는 P |

응답 schema 판정: **P** · top-level key는 직접 추출했지만 derived/helper/opaque 또는 nested type이 남음.

### 목록·동시성·완료

- Pagination/filter/sort: `before`, `limit`, `task`.
- Idempotency/concurrency: read/stream 요청; stream 재연결은 cursor 계약에 따름.
- Side effect/completion: 직접 조회/파일 전송 완료; 외부 조회가 있으면 응답 전 handler 결과만 보장.
- 직접 협력자/실행 단서: `activityDTOs`, `activitySessionStore`, `atoiDefault`, `err.Error`, `log.Printf`, `min`, `parseActivitySession`, `q.Get`, `r.URL.Query`, `r.URL.Query().Get`, `s.m.ResolveTask`, `store.ActivityPage`, `store.ActivityPageForTerminalIntent`, `t.Store.ActivityMaxID`, `t.Store.CurrentMainSeg`; direct `go` statement는 미발견. 이 목록은 helper 내부의 background launch나 transaction 경계를 펼치지 않으므로 그 부분은 P/U다.
- 종합 의미 판정: **P/U**; route/auth와 위에서 직접 확인한 branch·shape는 C지만, 모든 helper·derived response·중첩 DTO·외부 효과까지 완결됐다는 뜻은 아니다.

<a id="schema-get-api-exploration-main-sessions"></a>
## `GET /api/exploration/main-sessions`

구현: `mainSessions` · `server/server.go` · [정확한 handler 근거](evidence:handler-get-api-exploration-main-sessions)

### 요청

| 위치/field | wire type | 필수·null | 기본/검증 |
|---|---|---|---|
| query `task` | `string` wire | 선택 | handler/helper 변환·default 미전개(P) |
| auth credential | Bearer header / `artex_token` cookie / query `token` | 세 위치 중 하나에 유효 JWT 필수 | `extractToken` 우선순위: header→cookie→query; query fallback은 SSE로 제한되지 않음 |
| body | 없음(직접 decoder/form read 미발견) | 해당 없음 | 해당 없음 |

요청 판정: **C/P** · unknown field 거부 미설정.
직접 validation: `err != nil` · `len(list) > 0` · `t == nil`

### 응답·오류

| 종류 | status/body | 해석 상태 |
|---|---|---|
| success/variant | `200` · `sessions:derived`, `current:derived` | P/U · `static composite` |
| error | `401` · `{error:string}` · `"未授权"` / `"token 无效或已过期"` | C for direct writeErr; error helper 내부는 P |
| error | `404` · `{error:string}` · `"task not found"` | C for direct writeErr; error helper 내부는 P |
| error | `500` · `{error:string}` · `err.Error()` | C for direct writeErr; error helper 내부는 P |

응답 schema 판정: **P** · top-level key는 직접 추출했지만 derived/helper/opaque 또는 nested type이 남음.

### 목록·동시성·완료

- Pagination/filter/sort: `task`.
- Idempotency/concurrency: read/stream 요청; stream 재연결은 cursor 계약에 따름.
- Side effect/completion: 직접 조회/파일 전송 완료; 외부 조회가 있으면 응답 전 handler 결과만 보장.
- 직접 협력자/실행 단서: `err.Error`, `r.URL.Query`, `r.URL.Query().Get`, `s.m.ResolveTask`, `t.Store.ListMainSessions`; direct `go` statement는 미발견. 이 목록은 helper 내부의 background launch나 transaction 경계를 펼치지 않으므로 그 부분은 P/U다.
- 종합 의미 판정: **P/U**; route/auth와 위에서 직접 확인한 branch·shape는 C지만, 모든 helper·derived response·중첩 DTO·외부 효과까지 완결됐다는 뜻은 아니다.

<a id="schema-post-api-exploration-main-session-new"></a>
## `POST /api/exploration/main-session/new`

구현: `newMainSession` · `server/server.go` · [정확한 handler 근거](evidence:handler-post-api-exploration-main-session-new)

### 요청

| 위치/field | wire type | 필수·null | 기본/검증 |
|---|---|---|---|
| query `task` | `string` wire | 선택 | handler/helper 변환·default 미전개(P) |
| auth credential | Bearer header / `artex_token` cookie / query `token` | 세 위치 중 하나에 유효 JWT 필수 | `extractToken` 우선순위: header→cookie→query; query fallback은 SSE로 제한되지 않음 |
| body | 없음(직접 decoder/form read 미발견) | 해당 없음 | 해당 없음 |

요청 판정: **C/P** · unknown field 거부 미설정.
직접 validation: `err != nil` · `s.engine.IsDeleting(t.ID)` · `t == nil`

### 응답·오류

| 종류 | status/body | 해석 상태 |
|---|---|---|
| success/variant | `200` · `seq:expression`, `created_at:helper result`, `current:expression` | P/U · `static composite` |
| error | `401` · `{error:string}` · `"未授权"` / `"token 无效或已过期"` | C for direct writeErr; error helper 내부는 P |
| error | `404` · `{error:string}` · `"task not found"` | C for direct writeErr; error helper 내부는 P |
| error | `409` · `{error:string}` · `"任务正在删除，无法新建会话"` | C for direct writeErr; error helper 내부는 P |
| error | `500` · `{error:string}` · `err.Error()` | C for direct writeErr; error helper 내부는 P |

응답 schema 판정: **P** · top-level key는 직접 추출했지만 derived/helper/opaque 또는 nested type이 남음.

### 목록·동시성·완료

- Pagination/filter/sort: `task`.
- Idempotency/concurrency: Idempotency-Key 없음; unique constraint 또는 상태 check가 있는 경우만 중복을 제한(P).
- Side effect/completion: handler의 직접 mutation/호출 후 응답; 외부 전달·agent 실행이 파생되면 별도 상태 확인(P).
- 직접 협력자/실행 단서: `err.Error`, `r.URL.Query`, `r.URL.Query().Get`, `rfc3339`, `s.engine.IsDeleting`, `s.m.ResolveTask`, `t.Store.NewMainSession`; direct `go` statement는 미발견. 이 목록은 helper 내부의 background launch나 transaction 경계를 펼치지 않으므로 그 부분은 P/U다.
- 종합 의미 판정: **P/U**; route/auth와 위에서 직접 확인한 branch·shape는 C지만, 모든 helper·derived response·중첩 DTO·외부 효과까지 완결됐다는 뜻은 아니다.

<a id="schema-get-api-exploration-activity-stream"></a>
## `GET /api/exploration/activity/stream`

구현: `streamActivity` · `server/server.go` · [정확한 handler 근거](evidence:handler-get-api-exploration-activity-stream)

### 요청

| 위치/field | wire type | 필수·null | 기본/검증 |
|---|---|---|---|
| query `intent` | `string` wire | 선택 | handler/helper 변환·default 미전개(P) |
| query `since` | `string` wire | 선택 | handler/helper 변환·default 미전개(P) |
| query `task` | `string` wire | 선택 | handler/helper 변환·default 미전개(P) |
| header `Last-Event-ID` | `int64` string | 선택 | valid 값이면 query `since`(기본 0)보다 우선; invalid이면 `since` 유지 |
| auth credential | Bearer header / `artex_token` cookie / query `token` | 세 위치 중 하나에 유효 JWT 필수 | `extractToken` 우선순위: header→cookie→query; query fallback은 SSE로 제한되지 않음 |
| body | 없음(직접 decoder/form read 미발견) | 해당 없음 | 해당 없음 |

요청 판정: **C/P** · unknown field 거부 미설정.
직접 validation: `!ok` · `a.ID <= since` · `cursor > since` · `err != nil` · `err == nil` · `intentPtr != nil && (a.NodeID == nil \|\| *a.NodeID != *intentPtr)` · `iv != ""` · `le != ""` · `len(items) < replayBatch` · `t == nil`
직접 parse/default: `atoiDefault(r.URL.Query().Get("since"), 0)` · `strconv.ParseInt(iv, 10, 64)` · `strconv.ParseInt(le, 10, 64)`

### 응답·오류

| 종류 | status/body | 해석 상태 |
|---|---|---|
| success/variant | `200` · `text/event-stream; Cache-Control:no-cache; Connection:keep-alive; X-Accel-Buffering:no; \`id:\` activity ID + \`data:\` activityDTO, replay cursor, 20 s \`: ping\`` | P/U · `SSE stream` |
| error | `401` · `{error:string}` · `"未授权"` / `"token 无效或已过期"` | C for direct writeErr; error helper 내부는 P |
| error | `404` · `{error:string}` · `"task not found"` | C for direct writeErr; error helper 내부는 P |
| error | `500` · `{error:string}` · `"streaming unsupported"` | C for direct writeErr; error helper 내부는 P |

응답 schema 판정: **P** · top-level key는 직접 추출했지만 derived/helper/opaque 또는 nested type이 남음.

### 목록·동시성·완료

- Pagination/filter/sort: `since`, `task`.
- Idempotency/concurrency: read/stream 요청; stream 재연결은 cursor 계약에 따름.
- Side effect/completion: SSE 연결 수립; event lifecycle은 stream별 cursor/메모리 상태를 확인.
- 직접 협력자/실행 단서: `activityDTO`, `atoiDefault`, `ctx.Done`, `flusher.Flush`, `fmt.Fprint`, `fmt.Fprintf`, `log.Printf`, `ping.Stop`, `r.Context`, `r.Header.Get`, `r.URL.Query`, `r.URL.Query().Get`, `s.engine.Broadcaster`, `s.engine.Broadcaster().Subscribe`, `s.m.ResolveTask`, `sendSSE`, `t.Store.ActivityList`, `unsub` 외 2개; direct `go` statement는 미발견. 이 목록은 helper 내부의 background launch나 transaction 경계를 펼치지 않으므로 그 부분은 P/U다.
- 종합 의미 판정: **P/U**; route/auth와 위에서 직접 확인한 branch·shape는 C지만, 모든 helper·derived response·중첩 DTO·외부 효과까지 완결됐다는 뜻은 아니다.

<a id="schema-get-api-exploration-activity-seq"></a>
## `GET /api/exploration/activity/{seq}`

구현: `activityDetail` · `server/server.go` · [정확한 handler 근거](evidence:handler-get-api-exploration-activity-seq)

### 요청

| 위치/field | wire type | 필수·null | 기본/검증 |
|---|---|---|---|
| path `seq` | `string` | 필수 | handler의 parse/range validation; 미해석 helper는 P |
| query `task` | `string` wire | 선택 | handler/helper 변환·default 미전개(P) |
| auth credential | Bearer header / `artex_token` cookie / query `token` | 세 위치 중 하나에 유효 JWT 필수 | `extractToken` 우선순위: header→cookie→query; query fallback은 SSE로 제한되지 않음 |
| body | 없음(직접 decoder/form read 미발견) | 해당 없음 | 해당 없음 |

요청 판정: **C/P** · unknown field 거부 미설정.
직접 validation: `err != nil` · `t == nil`
직접 parse/default: `strconv.ParseInt(r.PathValue("seq"), 10, 64)`

### 응답·오류

| 종류 | status/body | 해석 상태 |
|---|---|---|
| success/variant | `200` · `detail:derived` | P/U · `static composite` |
| error | `401` · `{error:string}` · `"未授权"` / `"token 无效或已过期"` | C for direct writeErr; error helper 내부는 P |
| error | `404` · `{error:string}` · `"no task"` | C for direct writeErr; error helper 내부는 P |
| error | `500` · `{error:string}` · `err.Error()` | C for direct writeErr; error helper 내부는 P |

응답 schema 판정: **P** · top-level key는 직접 추출했지만 derived/helper/opaque 또는 nested type이 남음.

### 목록·동시성·완료

- Pagination/filter/sort: `task`.
- Idempotency/concurrency: read/stream 요청; stream 재연결은 cursor 계약에 따름.
- Side effect/completion: 직접 조회/파일 전송 완료; 외부 조회가 있으면 응답 전 handler 결과만 보장.
- 직접 협력자/실행 단서: `err.Error`, `r.PathValue`, `r.URL.Query`, `r.URL.Query().Get`, `s.m.ResolveTask`, `taskActivityDetail`; direct `go` statement는 미발견. 이 목록은 helper 내부의 background launch나 transaction 경계를 펼치지 않으므로 그 부분은 P/U다.
- 종합 의미 판정: **P/U**; route/auth와 위에서 직접 확인한 branch·shape는 C지만, 모든 helper·derived response·중첩 DTO·외부 효과까지 완결됐다는 뜻은 아니다.

<a id="schema-get-api-exploration-tokens"></a>
## `GET /api/exploration/tokens`

구현: `tokenStats` · `server/server.go` · [정확한 handler 근거](evidence:handler-get-api-exploration-tokens)

### 요청

| 위치/field | wire type | 필수·null | 기본/검증 |
|---|---|---|---|
| query `task` | `string` wire | 선택 | handler/helper 변환·default 미전개(P) |
| auth credential | Bearer header / `artex_token` cookie / query `token` | 세 위치 중 하나에 유효 JWT 필수 | `extractToken` 우선순위: header→cookie→query; query fallback은 SSE로 제한되지 않음 |
| body | 없음(직접 decoder/form read 미발견) | 해당 없음 | 해당 없음 |

요청 판정: **C/P** · unknown field 거부 미설정.
직접 validation: `err != nil` · `t == nil`

### 응답·오류

| 종류 | status/body | 해석 상태 |
|---|---|---|
| success/variant | `200` · `workers:[]db.TokenUsage`, `sessions:[]db.SessionTokenUsage`, `total:helper result` | P/U · `static composite` |
| success/variant | `200` · `workers:derived`, `sessions:derived`, `total:helper result` | P/U · `static composite` |
| error | `401` · `{error:string}` · `"未授权"` / `"token 无效或已过期"` | C for direct writeErr; error helper 내부는 P |
| error | `500` · `{error:string}` · `err.Error()` | C for direct writeErr; error helper 내부는 P |

응답 schema 판정: **P** · top-level key는 직접 추출했지만 derived/helper/opaque 또는 nested type이 남음.

### 목록·동시성·완료

- Pagination/filter/sort: `task`.
- Idempotency/concurrency: read/stream 요청; stream 재연결은 cursor 계약에 따름.
- Side effect/completion: 직접 조회/파일 전송 완료; 외부 조회가 있으면 응답 전 handler 결과만 보장.
- 직접 협력자/실행 단서: `err.Error`, `r.URL.Query`, `r.URL.Query().Get`, `s.m.ResolveTask`, `t.Store.TokenStatsBySession`, `t.Store.TokenStatsByWorker`, `t.Store.TokenTotal`, `tokenTotalDTO`; direct `go` statement는 미발견. 이 목록은 helper 내부의 background launch나 transaction 경계를 펼치지 않으므로 그 부분은 P/U다.
- 종합 의미 판정: **P/U**; route/auth와 위에서 직접 확인한 branch·shape는 C지만, 모든 helper·derived response·중첩 DTO·외부 효과까지 완결됐다는 뜻은 아니다.

<a id="schema-get-api-tokens-daily"></a>
## `GET /api/tokens/daily`

구현: `tokenDailyStats` · `server/server.go` · [정확한 handler 근거](evidence:handler-get-api-tokens-daily)

### 요청

| 위치/field | wire type | 필수·null | 기본/검증 |
|---|---|---|---|
| query `days` | `string` wire | 선택 | handler/helper 변환·default 미전개(P) |
| auth credential | Bearer header / `artex_token` cookie / query `token` | 세 위치 중 하나에 유효 JWT 필수 | `extractToken` 우선순위: header→cookie→query; query fallback은 SSE로 제한되지 않음 |
| body | 없음(직접 decoder/form read 미발견) | 해당 없음 | 해당 없음 |

요청 판정: **C/P** · unknown field 거부 미설정.
직접 validation: `buckets == nil` · `err != nil` · `s.m.pg == nil`
직접 parse/default: `atoiDefault(r.URL.Query().Get("days"), 30)`

### 응답·오류

| 종류 | status/body | 해석 상태 |
|---|---|---|
| success/variant | `200` · `[]any{}` | P/U · `static composite` |
| success/variant | `200` · `buckets` | P/U · `derived variable` |
| error | `401` · `{error:string}` · `"未授权"` / `"token 无效或已过期"` | C for direct writeErr; error helper 내부는 P |
| error | `500` · `{error:string}` · `err.Error()` | C for direct writeErr; error helper 내부는 P |

응답 schema 판정: **P** · top-level key는 직접 추출했지만 derived/helper/opaque 또는 nested type이 남음.

### 목록·동시성·완료

- Pagination/filter/sort: `days`.
- Idempotency/concurrency: read/stream 요청; stream 재연결은 cursor 계약에 따름.
- Side effect/completion: 직접 조회/파일 전송 완료; 외부 조회가 있으면 응답 전 handler 결과만 보장.
- 직접 협력자/실행 단서: `atoiDefault`, `err.Error`, `r.URL.Query`, `r.URL.Query().Get`, `s.m.pg.TokenDailyAll`; direct `go` statement는 미발견. 이 목록은 helper 내부의 background launch나 transaction 경계를 펼치지 않으므로 그 부분은 P/U다.
- 종합 의미 판정: **P/U**; route/auth와 위에서 직접 확인한 branch·shape는 C지만, 모든 helper·derived response·중첩 DTO·외부 효과까지 완결됐다는 뜻은 아니다.

<a id="schema-get-api-tokens-conversations"></a>
## `GET /api/tokens/conversations`

구현: `conversationTokens` · `server/server.go` · [정확한 handler 근거](evidence:handler-get-api-tokens-conversations)

### 요청

| 위치/field | wire type | 필수·null | 기본/검증 |
|---|---|---|---|
| auth credential | Bearer header / `artex_token` cookie / query `token` | 세 위치 중 하나에 유효 JWT 필수 | `extractToken` 우선순위: header→cookie→query; query fallback은 SSE로 제한되지 않음 |
| body | 없음(직접 decoder/form read 미발견) | 해당 없음 | 해당 없음 |

요청 판정: **C/P** · unknown field 거부 미설정.
직접 validation: `err != nil` · `s.m.pg == nil`

### 응답·오류

| 종류 | status/body | 해석 상태 |
|---|---|---|
| success/variant | `200` · `[]any{}` | P/U · `static composite` |
| success/variant | `200` · `rows` | P/U · `derived variable` |
| error | `401` · `{error:string}` · `"未授权"` / `"token 无效或已过期"` | C for direct writeErr; error helper 내부는 P |
| error | `500` · `{error:string}` · `err.Error()` | C for direct writeErr; error helper 내부는 P |

응답 schema 판정: **P** · top-level key는 직접 추출했지만 derived/helper/opaque 또는 nested type이 남음.

### 목록·동시성·완료

- Pagination/filter/sort: 등록된 직접 query key 중 표준 paging/filter key 없음.
- Idempotency/concurrency: read/stream 요청; stream 재연결은 cursor 계약에 따름.
- Side effect/completion: 직접 조회/파일 전송 완료; 외부 조회가 있으면 응답 전 handler 결과만 보장.
- 직접 협력자/실행 단서: `err.Error`, `s.m.pg.ConversationTokenSummaries`; direct `go` statement는 미발견. 이 목록은 helper 내부의 background launch나 transaction 경계를 펼치지 않으므로 그 부분은 P/U다.
- 종합 의미 판정: **P/U**; route/auth와 위에서 직접 확인한 branch·shape는 C지만, 모든 helper·derived response·중첩 DTO·외부 효과까지 완결됐다는 뜻은 아니다.

<a id="schema-get-api-tokens-usage"></a>
## `GET /api/tokens/usage`

구현: `pgUsageStats` · `server/commands.go` · [정확한 handler 근거](evidence:handler-get-api-tokens-usage)

### 요청

| 위치/field | wire type | 필수·null | 기본/검증 |
|---|---|---|---|
| query `days` | `string` wire | 선택 | handler/helper 변환·default 미전개(P) |
| auth credential | Bearer header / `artex_token` cookie / query `token` | 세 위치 중 하나에 유효 JWT 필수 | `extractToken` 우선순위: header→cookie→query; query fallback은 SSE로 제한되지 않음 |
| body | 없음(직접 decoder/form read 미발견) | 해당 없음 | 해당 없음 |

요청 판정: **C/P** · unknown field 거부 미설정.
직접 validation: `err != nil` · `pg == nil`
직접 parse/default: `atoiDefault(r.URL.Query().Get("days"), 365)`

### 응답·오류

| 종류 | status/body | 해석 상태 |
|---|---|---|
| success/variant | `200` · `by_profile:[]db.ProfileUsage`, `daily:[]db.ProfileDayUsage` | P/U · `static composite` |
| success/variant | `200` · `by_profile:derived`, `daily:derived` | P/U · `static composite` |
| error | `401` · `{error:string}` · `"未授权"` / `"token 无效或已过期"` | C for direct writeErr; error helper 내부는 P |
| error | `500` · `{error:string}` · `err.Error()` | C for direct writeErr; error helper 내부는 P |

응답 schema 판정: **P** · top-level key는 직접 추출했지만 derived/helper/opaque 또는 nested type이 남음.

### 목록·동시성·완료

- Pagination/filter/sort: `days`.
- Idempotency/concurrency: read/stream 요청; stream 재연결은 cursor 계약에 따름.
- Side effect/completion: 직접 조회/파일 전송 완료; 외부 조회가 있으면 응답 전 handler 결과만 보장.
- 직접 협력자/실행 단서: `atoiDefault`, `err.Error`, `pg.UsageByProfile`, `pg.UsageDaily`, `r.URL.Query`, `r.URL.Query().Get`, `s.m.PG`; direct `go` statement는 미발견. 이 목록은 helper 내부의 background launch나 transaction 경계를 펼치지 않으므로 그 부분은 P/U다.
- 종합 의미 판정: **P/U**; route/auth와 위에서 직접 확인한 branch·shape는 C지만, 모든 helper·derived response·중첩 DTO·외부 효과까지 완결됐다는 뜻은 아니다.

<a id="schema-get-api-audit"></a>
## `GET /api/audit`

구현: `getAudit` · `server/server.go` · [정확한 handler 근거](evidence:handler-get-api-audit)

### 요청

| 위치/field | wire type | 필수·null | 기본/검증 |
|---|---|---|---|
| query `task` | `string` wire | 선택 | handler/helper 변환·default 미전개(P) |
| auth credential | Bearer header / `artex_token` cookie / query `token` | 세 위치 중 하나에 유효 JWT 필수 | `extractToken` 우선순위: header→cookie→query; query fallback은 SSE로 제한되지 않음 |
| body | 없음(직접 decoder/form read 미발견) | 해당 없음 | 해당 없음 |

요청 판정: **C/P** · unknown field 거부 미설정.
직접 validation: `t == nil`

### 응답·오류

| 종류 | status/body | 해석 상태 |
|---|---|---|
| success/variant | `200` · `[]any{}` | P/U · `static composite` |
| success/variant | `200` · `entries:helper result`, `attributions:helper result` | P/U · `static composite` |
| error | `401` · `{error:string}` · `"未授权"` / `"token 无效或已过期"` | C for direct writeErr; error helper 내부는 P |

응답 schema 판정: **P** · top-level key는 직접 추출했지만 derived/helper/opaque 또는 nested type이 남음.

### 목록·동시성·완료

- Pagination/filter/sort: `task`.
- Idempotency/concurrency: read/stream 요청; stream 재연결은 cursor 계약에 따름.
- Side effect/completion: 직접 조회/파일 전송 완료; 외부 조회가 있으면 응답 전 handler 결과만 보장.
- 직접 협력자/실행 단서: `r.URL.Query`, `r.URL.Query().Get`, `s.m.ResolveTask`, `t.Guard.Attributions`, `t.Guard.Audit`; direct `go` statement는 미발견. 이 목록은 helper 내부의 background launch나 transaction 경계를 펼치지 않으므로 그 부분은 P/U다.
- 종합 의미 판정: **P/U**; route/auth와 위에서 직접 확인한 branch·shape는 C지만, 모든 helper·derived response·중첩 DTO·외부 효과까지 완결됐다는 뜻은 아니다.

<a id="schema-post-api-gc"></a>
## `POST /api/gc`

구현: `gc` · `server/server.go` · [정확한 handler 근거](evidence:handler-post-api-gc)

### 요청

| 위치/field | wire type | 필수·null | 기본/검증 |
|---|---|---|---|
| auth credential | Bearer header / `artex_token` cookie / query `token` | 세 위치 중 하나에 유효 JWT 필수 | `extractToken` 우선순위: header→cookie→query; query fallback은 SSE로 제한되지 않음 |
| body | 없음(직접 decoder/form read 미발견) | 해당 없음 | 해당 없음 |

요청 판정: **C/P** · unknown field 거부 미설정.

### 응답·오류

| 종류 | status/body | 해석 상태 |
|---|---|---|
| success/variant | `200` · `removed:number` | C · `static composite` |
| error | `401` · `{error:string}` · `"未授权"` / `"token 无效或已过期"` | C for direct writeErr; error helper 내부는 P |

응답 schema 판정: **C** · 직접 scalar/static top-level key와 field type을 추출; nested 내부 schema는 별도 type 참조가 없으면 P.

### 목록·동시성·완료

- Pagination/filter/sort: 등록된 직접 query key 중 표준 paging/filter key 없음.
- Idempotency/concurrency: Idempotency-Key 없음; unique constraint 또는 상태 check가 있는 경우만 중복을 제한(P).
- Side effect/completion: handler의 직접 mutation/호출 후 응답; 외부 전달·agent 실행이 파생되면 별도 상태 확인(P).
- 직접 협력자/실행 단서: handler AST에서 별도 collaborator call 미발견; direct `go` statement는 미발견. 이 목록은 helper 내부의 background launch나 transaction 경계를 펼치지 않으므로 그 부분은 P/U다.
- 종합 의미 판정: **P/U**; route/auth와 위에서 직접 확인한 branch·shape는 C지만, 모든 helper·derived response·중첩 DTO·외부 효과까지 완결됐다는 뜻은 아니다.

<a id="schema-get-api-traffic"></a>
## `GET /api/traffic`

구현: `getTraffic` · `server/server.go` · [정확한 handler 근거](evidence:handler-get-api-traffic)

### 요청

| 위치/field | wire type | 필수·null | 기본/검증 |
|---|---|---|---|
| query `body` | `string` wire | 선택 | handler/helper 변환·default 미전개(P) |
| query `host` | `string` wire | 선택 | handler/helper 변환·default 미전개(P) |
| query `method` | `string` wire | 선택 | handler/helper 변환·default 미전개(P) |
| query `order` | `string` wire | 선택 | handler/helper 변환·default 미전개(P) |
| query `page` | `string` wire | 선택 | handler/helper 변환·default 미전개(P) |
| query `path` | `string` wire | 선택 | handler/helper 변환·default 미전개(P) |
| query `q` | `string` wire | 선택 | handler/helper 변환·default 미전개(P) |
| query `resp_max` | `string` wire | 선택 | handler/helper 변환·default 미전개(P) |
| query `resp_min` | `string` wire | 선택 | handler/helper 변환·default 미전개(P) |
| query `size` | `string` wire | 선택 | handler/helper 변환·default 미전개(P) |
| query `sort` | `string` wire | 선택 | handler/helper 변환·default 미전개(P) |
| query `status` | `string` wire | 선택 | handler/helper 변환·default 미전개(P) |
| auth credential | Bearer header / `artex_token` cookie / query `token` | 세 위치 중 하나에 유효 JWT 필수 | `extractToken` 우선순위: header→cookie→query; query fallback은 SSE로 제한되지 않음 |
| body | 없음(직접 decoder/form read 미발견) | 해당 없음 | 해당 없음 |

요청 판정: **C/P** · unknown field 거부 미설정.
직접 validation: `tr == nil`
직접 parse/default: `atoiDefault(q.Get("page"), 0)` · `atoiDefault(q.Get("resp_max"), -1)` · `atoiDefault(q.Get("resp_min"), -1)` · `atoiDefault(q.Get("size"), 100)`

### 응답·오류

| 종류 | status/body | 해석 상태 |
|---|---|---|
| success/variant | `200` · `enabled:boolean`, `exchanges:[]any` | P/U · `static composite` |
| success/variant | `200` · `enabled:helper result`, `proxy:helper result`, `count:derived`, `total:derived`, `page:derived`, `size:derived`, `exchanges:helper result` | P/U · `static composite` |
| error | `401` · `{error:string}` · `"未授权"` / `"token 无效或已过期"` | C for direct writeErr; error helper 내부는 P |

응답 schema 판정: **P** · top-level key는 직접 추출했지만 derived/helper/opaque 또는 nested type이 남음.

### 목록·동시성·완료

- Pagination/filter/sort: `host`, `order`, `page`, `q`, `size`, `sort`, `status`.
- Idempotency/concurrency: read/stream 요청; stream 재연결은 cursor 계약에 따름.
- Side effect/completion: 직접 조회/파일 전송 완료; 외부 조회가 있으면 응답 전 handler 결과만 보장.
- 직접 협력자/실행 단서: `atoiDefault`, `q.Get`, `r.URL.Query`, `s.m.ProxyAddr`, `s.m.Traffic`, `s.m.TrafficEnabled`, `tr.Count`, `tr.Page`, `trafficDTOs`; direct `go` statement는 미발견. 이 목록은 helper 내부의 background launch나 transaction 경계를 펼치지 않으므로 그 부분은 P/U다.
- 종합 의미 판정: **P/U**; route/auth와 위에서 직접 확인한 branch·shape는 C지만, 모든 helper·derived response·중첩 DTO·외부 효과까지 완결됐다는 뜻은 아니다.

<a id="schema-get-api-traffic-hosts"></a>
## `GET /api/traffic/hosts`

구현: `getTrafficHosts` · `server/server.go` · [정확한 handler 근거](evidence:handler-get-api-traffic-hosts)

### 요청

| 위치/field | wire type | 필수·null | 기본/검증 |
|---|---|---|---|
| auth credential | Bearer header / `artex_token` cookie / query `token` | 세 위치 중 하나에 유효 JWT 필수 | `extractToken` 우선순위: header→cookie→query; query fallback은 SSE로 제한되지 않음 |
| body | 없음(직접 decoder/form read 미발견) | 해당 없음 | 해당 없음 |

요청 판정: **C/P** · unknown field 거부 미설정.
직접 validation: `err != nil` · `tr == nil`

### 응답·오류

| 종류 | status/body | 해석 상태 |
|---|---|---|
| success/variant | `200` · `hosts:[]any` | P/U · `static composite` |
| success/variant | `200` · `hosts:derived` | P/U · `static composite` |
| error | `401` · `{error:string}` · `"未授权"` / `"token 无效或已过期"` | C for direct writeErr; error helper 내부는 P |
| error | `500` · `{error:string}` · `err.Error()` | C for direct writeErr; error helper 내부는 P |

응답 schema 판정: **P** · top-level key는 직접 추출했지만 derived/helper/opaque 또는 nested type이 남음.

### 목록·동시성·완료

- Pagination/filter/sort: 등록된 직접 query key 중 표준 paging/filter key 없음.
- Idempotency/concurrency: read/stream 요청; stream 재연결은 cursor 계약에 따름.
- Side effect/completion: 직접 조회/파일 전송 완료; 외부 조회가 있으면 응답 전 handler 결과만 보장.
- 직접 협력자/실행 단서: `err.Error`, `s.m.Traffic`, `tr.Hosts`; direct `go` statement는 미발견. 이 목록은 helper 내부의 background launch나 transaction 경계를 펼치지 않으므로 그 부분은 P/U다.
- 종합 의미 판정: **P/U**; route/auth와 위에서 직접 확인한 branch·shape는 C지만, 모든 helper·derived response·중첩 DTO·외부 효과까지 완결됐다는 뜻은 아니다.

<a id="schema-delete-api-traffic"></a>
## `DELETE /api/traffic`

구현: `deleteTraffic` · `server/server.go` · [정확한 handler 근거](evidence:handler-delete-api-traffic)

### 요청

| 위치/field | wire type | 필수·null | 기본/검증 |
|---|---|---|---|
| query `host` | `string` wire | 성공 응답에 필수 | required non-empty trimmed host substring; missing/empty→400 |
| auth credential | Bearer header / `artex_token` cookie / query `token` | 세 위치 중 하나에 유효 JWT 필수 | `extractToken` 우선순위: header→cookie→query; query fallback은 SSE로 제한되지 않음 |
| body | 없음(직접 decoder/form read 미발견) | 해당 없음 | 해당 없음 |

요청 판정: **C/P** · unknown field 거부 미설정.
직접 validation: `err != nil` · `host == ""` · `tr == nil`

### 응답·오류

| 종류 | status/body | 해석 상태 |
|---|---|---|
| success/variant | `200` · `deleted:derived` | P/U · `static composite` |
| error | `400` · `{error:string}` · `"missing host"` | C for direct writeErr; error helper 내부는 P |
| error | `401` · `{error:string}` · `"未授权"` / `"token 无效或已过期"` | C for direct writeErr; error helper 내부는 P |
| error | `404` · `{error:string}` · `"traffic disabled"` | C for direct writeErr; error helper 내부는 P |
| error | `500` · `{error:string}` · `err.Error()` | C for direct writeErr; error helper 내부는 P |

응답 schema 판정: **P** · top-level key는 직접 추출했지만 derived/helper/opaque 또는 nested type이 남음.

### 목록·동시성·완료

- Pagination/filter/sort: `host`.
- Idempotency/concurrency: Idempotency-Key/If-Match 없음; 반복 시 응답/존재 검사가 달라질 수 있음.
- Side effect/completion: handler의 직접 mutation/호출 후 응답; 외부 전달·agent 실행이 파생되면 별도 상태 확인(P).
- 직접 협력자/실행 단서: `err.Error`, `r.URL.Query`, `r.URL.Query().Get`, `s.m.Traffic`, `tr.DeleteHost`; direct `go` statement는 미발견. 이 목록은 helper 내부의 background launch나 transaction 경계를 펼치지 않으므로 그 부분은 P/U다.
- 종합 의미 판정: **P/U**; route/auth와 위에서 직접 확인한 branch·shape는 C지만, 모든 helper·derived response·중첩 DTO·외부 효과까지 완결됐다는 뜻은 아니다.

<a id="schema-delete-api-traffic-hosts"></a>
## `DELETE /api/traffic/hosts`

구현: `deleteTrafficHosts` · `server/server.go` · [정확한 handler 근거](evidence:handler-delete-api-traffic-hosts)

### 요청

| 위치/field | wire type | 필수·null | 기본/검증 |
|---|---|---|---|
| auth credential | Bearer header / `artex_token` cookie / query `token` | 세 위치 중 하나에 유효 JWT 필수 | `extractToken` 우선순위: header→cookie→query; query fallback은 SSE로 제한되지 않음 |
| body `hosts` | `array[string]` (`[]string`) | 필수(직접 거부 branch 확인); null/absent 구별 안 됨 | `trim 뒤 빈 배열이면 400` |

요청 판정: **C/P** · unknown field 거부 미설정.

### 응답·오류

| 종류 | status/body | 해석 상태 |
|---|---|---|
| success/variant | `200` · `deleted:derived` | P/U · `static composite` |
| error | `400` · `{error:string}` · `"invalid body"` / `"missing hosts"` | C for direct writeErr; error helper 내부는 P |
| error | `401` · `{error:string}` · `"未授权"` / `"token 无效或已过期"` | C for direct writeErr; error helper 내부는 P |
| error | `404` · `{error:string}` · `"traffic disabled"` | C for direct writeErr; error helper 내부는 P |
| error | `500` · `{error:string}` · `err.Error()` | C for direct writeErr; error helper 내부는 P |

응답 schema 판정: **P** · top-level key는 직접 추출했지만 derived/helper/opaque 또는 nested type이 남음.

### 목록·동시성·완료

- Pagination/filter/sort: 등록된 직접 query key 중 표준 paging/filter key 없음.
- Idempotency/concurrency: Idempotency-Key/If-Match 없음; 반복 시 응답/존재 검사가 달라질 수 있음.
- Side effect/completion: handler의 직접 mutation/호출 후 응답; 외부 전달·agent 실행이 파생되면 별도 상태 확인(P).
- 직접 협력자/실행 단서: `err.Error`, `json.NewDecoder`, `json.NewDecoder(r.Body).Decode`, `s.m.Traffic`, `tr.DeleteHostsExact`; direct `go` statement는 미발견. 이 목록은 helper 내부의 background launch나 transaction 경계를 펼치지 않으므로 그 부분은 P/U다.
- 종합 의미 판정: **P/U**; route/auth와 위에서 직접 확인한 branch·shape는 C지만, 모든 helper·derived response·중첩 DTO·외부 효과까지 완결됐다는 뜻은 아니다.

<a id="schema-delete-api-traffic-all"></a>
## `DELETE /api/traffic/all`

구현: `deleteAllTraffic` · `server/server.go` · [정확한 handler 근거](evidence:handler-delete-api-traffic-all)

### 요청

| 위치/field | wire type | 필수·null | 기본/검증 |
|---|---|---|---|
| auth credential | Bearer header / `artex_token` cookie / query `token` | 세 위치 중 하나에 유효 JWT 필수 | `extractToken` 우선순위: header→cookie→query; query fallback은 SSE로 제한되지 않음 |
| body | 없음(직접 decoder/form read 미발견) | 해당 없음 | 해당 없음 |

요청 판정: **C/P** · unknown field 거부 미설정.
직접 validation: `err != nil` · `tr == nil`

### 응답·오류

| 종류 | status/body | 해석 상태 |
|---|---|---|
| success/variant | `200` · `deleted:derived`, `reclaimed:derived` | P/U · `static composite` |
| error | `401` · `{error:string}` · `"未授权"` / `"token 无效或已过期"` | C for direct writeErr; error helper 내부는 P |
| error | `404` · `{error:string}` · `"traffic disabled"` | C for direct writeErr; error helper 내부는 P |
| error | `500` · `{error:string}` · `err.Error()` | C for direct writeErr; error helper 내부는 P |

응답 schema 판정: **P** · top-level key는 직접 추출했지만 derived/helper/opaque 또는 nested type이 남음.

### 목록·동시성·완료

- Pagination/filter/sort: 등록된 직접 query key 중 표준 paging/filter key 없음.
- Idempotency/concurrency: Idempotency-Key/If-Match 없음; 반복 시 응답/존재 검사가 달라질 수 있음.
- Side effect/completion: handler의 직접 mutation/호출 후 응답; 외부 전달·agent 실행이 파생되면 별도 상태 확인(P).
- 직접 협력자/실행 단서: `err.Error`, `s.m.Traffic`, `tr.DeleteAll`; direct `go` statement는 미발견. 이 목록은 helper 내부의 background launch나 transaction 경계를 펼치지 않으므로 그 부분은 P/U다.
- 종합 의미 판정: **P/U**; route/auth와 위에서 직접 확인한 branch·shape는 C지만, 모든 helper·derived response·중첩 DTO·외부 효과까지 완결됐다는 뜻은 아니다.

<a id="schema-get-api-traffic-exchange"></a>
## `GET /api/traffic/exchange`

구현: `getTrafficExchange` · `server/server.go` · [정확한 handler 근거](evidence:handler-get-api-traffic-exchange)

### 요청

| 위치/field | wire type | 필수·null | 기본/검증 |
|---|---|---|---|
| query `id` | `string` wire | 성공 응답에 필수 | required non-empty exchange identifier; missing→400 |
| auth credential | Bearer header / `artex_token` cookie / query `token` | 세 위치 중 하나에 유효 JWT 필수 | `extractToken` 우선순위: header→cookie→query; query fallback은 SSE로 제한되지 않음 |
| body | 없음(직접 decoder/form read 미발견) | 해당 없음 | 해당 없음 |

요청 판정: **C/P** · unknown field 거부 미설정.
직접 validation: `err != nil` · `id == ""` · `tr == nil`

### 응답·오류

| 종류 | status/body | 해석 상태 |
|---|---|---|
| success/variant | `200` · `req:derived`, `resp:derived` | P/U · `static composite` |
| error | `400` · `{error:string}` · `"missing id"` | C for direct writeErr; error helper 내부는 P |
| error | `401` · `{error:string}` · `"未授权"` / `"token 无效或已过期"` | C for direct writeErr; error helper 내부는 P |
| error | `404` · `{error:string}` · `"traffic disabled"` / `err.Error()` | C for direct writeErr; error helper 내부는 P |

응답 schema 판정: **P** · top-level key는 직접 추출했지만 derived/helper/opaque 또는 nested type이 남음.

### 목록·동시성·완료

- Pagination/filter/sort: 등록된 직접 query key 중 표준 paging/filter key 없음.
- Idempotency/concurrency: read/stream 요청; stream 재연결은 cursor 계약에 따름.
- Side effect/completion: 직접 조회/파일 전송 완료; 외부 조회가 있으면 응답 전 handler 결과만 보장.
- 직접 협력자/실행 단서: `err.Error`, `r.URL.Query`, `r.URL.Query().Get`, `s.m.Traffic`, `tr.Get`; direct `go` statement는 미발견. 이 목록은 helper 내부의 background launch나 transaction 경계를 펼치지 않으므로 그 부분은 P/U다.
- 종합 의미 판정: **P/U**; route/auth와 위에서 직접 확인한 branch·shape는 C지만, 모든 helper·derived response·중첩 DTO·외부 효과까지 완결됐다는 뜻은 아니다.

<a id="schema-get-api-traffic-blob"></a>
## `GET /api/traffic/blob`

구현: `getTrafficBlob` · `server/server.go` · [정확한 handler 근거](evidence:handler-get-api-traffic-blob)

### 요청

| 위치/field | wire type | 필수·null | 기본/검증 |
|---|---|---|---|
| query `hash` | `string` wire | 성공 응답에 필수 | required non-empty trimmed SHA-256 blob key; missing→400; Blob helper validates lookup |
| auth credential | Bearer header / `artex_token` cookie / query `token` | 세 위치 중 하나에 유효 JWT 필수 | `extractToken` 우선순위: header→cookie→query; query fallback은 SSE로 제한되지 않음 |
| body | 없음(직접 decoder/form read 미발견) | 해당 없음 | 해당 없음 |

요청 판정: **C/P** · unknown field 거부 미설정.
직접 validation: `err != nil` · `hash == ""` · `tr == nil`

### 응답·오류

| 종류 | status/body | 해석 상태 |
|---|---|---|
| success/variant | `200` · `application/octet-stream; Content-Length; attachment filename \`<hash>.bin\`` | P/U · `binary stream` |
| error | `400` · `{error:string}` · `"missing hash"` | C for direct writeErr; error helper 내부는 P |
| error | `401` · `{error:string}` · `"未授权"` / `"token 无效或已过期"` | C for direct writeErr; error helper 내부는 P |
| error | `404` · `{error:string}` · `"traffic disabled"` / `err.Error()` | C for direct writeErr; error helper 내부는 P |

응답 schema 판정: **P** · top-level key는 직접 추출했지만 derived/helper/opaque 또는 nested type이 남음.

### 목록·동시성·완료

- Pagination/filter/sort: 등록된 직접 query key 중 표준 paging/filter key 없음.
- Idempotency/concurrency: read/stream 요청; stream 재연결은 cursor 계약에 따름.
- Side effect/completion: 직접 조회/파일 전송 완료; 외부 조회가 있으면 응답 전 handler 결과만 보장.
- 직접 협력자/실행 단서: `err.Error`, `f.Close`, `io.Copy`, `log.Printf`, `r.URL.Query`, `r.URL.Query().Get`, `s.m.Traffic`, `tr.Blob`, `w.Header`, `w.Header().Set`; direct `go` statement는 미발견. 이 목록은 helper 내부의 background launch나 transaction 경계를 펼치지 않으므로 그 부분은 P/U다.
- 종합 의미 판정: **P/U**; route/auth와 위에서 직접 확인한 branch·shape는 C지만, 모든 helper·derived response·중첩 DTO·외부 효과까지 완결됐다는 뜻은 아니다.

<a id="schema-get-api-commands"></a>
## `GET /api/commands`

구현: `pgListCommands` · `server/commands.go` · [정확한 handler 근거](evidence:handler-get-api-commands)

### 요청

| 위치/field | wire type | 필수·null | 기본/검증 |
|---|---|---|---|
| query `page` | `string` wire | 선택 | handler/helper 변환·default 미전개(P) |
| query `q` | `string` wire | 선택 | handler/helper 변환·default 미전개(P) |
| query `size` | `string` wire | 선택 | handler/helper 변환·default 미전개(P) |
| query `task` | `string` wire | 선택 | handler/helper 변환·default 미전개(P) |
| auth credential | Bearer header / `artex_token` cookie / query `token` | 세 위치 중 하나에 유효 JWT 필수 | `extractToken` 우선순위: header→cookie→query; query fallback은 SSE로 제한되지 않음 |
| body | 없음(직접 decoder/form read 미발견) | 해당 없음 | 해당 없음 |

요청 판정: **C/P** · unknown field 거부 미설정.
직접 validation: `err != nil`
직접 parse/default: `atoiDefault(q.Get("page"), 0)` · `atoiDefault(q.Get("size"), 50)`

### 응답·오류

| 종류 | status/body | 해석 상태 |
|---|---|---|
| success/variant | `200` · `commands:derived`, `total:derived` | P/U · `static composite` |
| error | `401` · `{error:string}` · `"未授权"` / `"token 无效或已过期"` | C for direct writeErr; error helper 내부는 P |
| error | `500` · `{error:string}` · `err.Error()` | C for direct writeErr; error helper 내부는 P |

응답 schema 판정: **P** · top-level key는 직접 추출했지만 derived/helper/opaque 또는 nested type이 남음.

### 목록·동시성·완료

- Pagination/filter/sort: `page`, `q`, `size`, `task`.
- Idempotency/concurrency: read/stream 요청; stream 재연결은 cursor 계약에 따름.
- Side effect/completion: 직접 조회/파일 전송 완료; 외부 조회가 있으면 응답 전 handler 결과만 보장.
- 직접 협력자/실행 단서: `atoiDefault`, `commandTaskFilter`, `err.Error`, `q.Get`, `r.URL.Query`, `s.m.PG`, `s.m.PG().ListCommands`; direct `go` statement는 미발견. 이 목록은 helper 내부의 background launch나 transaction 경계를 펼치지 않으므로 그 부분은 P/U다.
- 종합 의미 판정: **P/U**; route/auth와 위에서 직접 확인한 branch·shape는 C지만, 모든 helper·derived response·중첩 DTO·외부 효과까지 완결됐다는 뜻은 아니다.

<a id="schema-get-api-commands-stats"></a>
## `GET /api/commands/stats`

구현: `pgToolStats` · `server/commands.go` · [정확한 handler 근거](evidence:handler-get-api-commands-stats)

### 요청

| 위치/field | wire type | 필수·null | 기본/검증 |
|---|---|---|---|
| query `q` | `string` wire | 선택 | handler/helper 변환·default 미전개(P) |
| query `task` | `string` wire | 선택 | handler/helper 변환·default 미전개(P) |
| auth credential | Bearer header / `artex_token` cookie / query `token` | 세 위치 중 하나에 유효 JWT 필수 | `extractToken` 우선순위: header→cookie→query; query fallback은 SSE로 제한되지 않음 |
| body | 없음(직접 decoder/form read 미발견) | 해당 없음 | 해당 없음 |

요청 판정: **C/P** · unknown field 거부 미설정.
직접 validation: `err != nil` · `pg == nil`

### 응답·오류

| 종류 | status/body | 해석 상태 |
|---|---|---|
| success/variant | `200` · `stats:[]db.ToolStat` | P/U · `static composite` |
| success/variant | `200` · `stats:derived` | P/U · `static composite` |
| error | `401` · `{error:string}` · `"未授权"` / `"token 无效或已过期"` | C for direct writeErr; error helper 내부는 P |
| error | `500` · `{error:string}` · `err.Error()` | C for direct writeErr; error helper 내부는 P |

응답 schema 판정: **P** · top-level key는 직접 추출했지만 derived/helper/opaque 또는 nested type이 남음.

### 목록·동시성·완료

- Pagination/filter/sort: `q`, `task`.
- Idempotency/concurrency: read/stream 요청; stream 재연결은 cursor 계약에 따름.
- Side effect/completion: 직접 조회/파일 전송 완료; 외부 조회가 있으면 응답 전 handler 결과만 보장.
- 직접 협력자/실행 단서: `commandTaskFilter`, `err.Error`, `pg.ToolStats`, `q.Get`, `r.URL.Query`, `s.m.PG`; direct `go` statement는 미발견. 이 목록은 helper 내부의 background launch나 transaction 경계를 펼치지 않으므로 그 부분은 P/U다.
- 종합 의미 판정: **P/U**; route/auth와 위에서 직접 확인한 branch·shape는 C지만, 모든 helper·derived response·중첩 DTO·외부 효과까지 완결됐다는 뜻은 아니다.

<a id="schema-get-api-llm-records"></a>
## `GET /api/llm/records`

구현: `pgListLLMRecords` · `server/commands.go` · [정확한 handler 근거](evidence:handler-get-api-llm-records)

### 요청

| 위치/field | wire type | 필수·null | 기본/검증 |
|---|---|---|---|
| query `model` | `string` wire | 선택 | handler/helper 변환·default 미전개(P) |
| query `page` | `string` wire | 선택 | handler/helper 변환·default 미전개(P) |
| query `session` | `string` wire | 선택 | handler/helper 변환·default 미전개(P) |
| query `size` | `string` wire | 선택 | handler/helper 변환·default 미전개(P) |
| query `task` | `string` wire | 선택 | handler/helper 변환·default 미전개(P) |
| auth credential | Bearer header / `artex_token` cookie / query `token` | 세 위치 중 하나에 유효 JWT 필수 | `extractToken` 우선순위: header→cookie→query; query fallback은 SSE로 제한되지 않음 |
| body | 없음(직접 decoder/form read 미발견) | 해당 없음 | 해당 없음 |

요청 판정: **C/P** · unknown field 거부 미설정.
직접 validation: `err != nil`
직접 parse/default: `atoiDefault(q.Get("page"), 0)` · `atoiDefault(q.Get("size"), 50)`

### 응답·오류

| 종류 | status/body | 해석 상태 |
|---|---|---|
| success/variant | `200` · `records:derived`, `total:derived` | P/U · `static composite` |
| error | `401` · `{error:string}` · `"未授权"` / `"token 无效或已过期"` | C for direct writeErr; error helper 내부는 P |
| error | `500` · `{error:string}` · `err.Error()` | C for direct writeErr; error helper 내부는 P |

응답 schema 판정: **P** · top-level key는 직접 추출했지만 derived/helper/opaque 또는 nested type이 남음.

### 목록·동시성·완료

- Pagination/filter/sort: `page`, `size`, `task`.
- Idempotency/concurrency: read/stream 요청; stream 재연결은 cursor 계약에 따름.
- Side effect/completion: 직접 조회/파일 전송 완료; 외부 조회가 있으면 응답 전 handler 결과만 보장.
- 직접 협력자/실행 단서: `atoiDefault`, `err.Error`, `q.Get`, `r.URL.Query`, `s.m.PG`, `s.m.PG().ListLLMRecords`; direct `go` statement는 미발견. 이 목록은 helper 내부의 background launch나 transaction 경계를 펼치지 않으므로 그 부분은 P/U다.
- 종합 의미 판정: **P/U**; route/auth와 위에서 직접 확인한 branch·shape는 C지만, 모든 helper·derived response·중첩 DTO·외부 효과까지 완결됐다는 뜻은 아니다.

<a id="schema-delete-api-llm-records"></a>
## `DELETE /api/llm/records`

구현: `pgDeleteLLMRecords` · `server/commands.go` · [정확한 handler 근거](evidence:handler-delete-api-llm-records)

### 요청

| 위치/field | wire type | 필수·null | 기본/검증 |
|---|---|---|---|
| query `task` | `string` wire | 성공 응답에 필수 | required non-empty trimmed task identifier; missing/empty→400 |
| auth credential | Bearer header / `artex_token` cookie / query `token` | 세 위치 중 하나에 유효 JWT 필수 | `extractToken` 우선순위: header→cookie→query; query fallback은 SSE로 제한되지 않음 |
| body | 없음(직접 decoder/form read 미발견) | 해당 없음 | 해당 없음 |

요청 판정: **C/P** · unknown field 거부 미설정.
직접 validation: `err != nil` · `task == ""`

### 응답·오류

| 종류 | status/body | 해석 상태 |
|---|---|---|
| success/variant | `200` · `deleted:derived` | P/U · `static composite` |
| error | `400` · `{error:string}` · `"missing task"` | C for direct writeErr; error helper 내부는 P |
| error | `401` · `{error:string}` · `"未授权"` / `"token 无效或已过期"` | C for direct writeErr; error helper 내부는 P |
| error | `500` · `{error:string}` · `err.Error()` | C for direct writeErr; error helper 내부는 P |

응답 schema 판정: **P** · top-level key는 직접 추출했지만 derived/helper/opaque 또는 nested type이 남음.

### 목록·동시성·완료

- Pagination/filter/sort: `task`.
- Idempotency/concurrency: Idempotency-Key/If-Match 없음; 반복 시 응답/존재 검사가 달라질 수 있음.
- Side effect/completion: handler의 직접 mutation/호출 후 응답; 외부 전달·agent 실행이 파생되면 별도 상태 확인(P).
- 직접 협력자/실행 단서: `err.Error`, `r.URL.Query`, `r.URL.Query().Get`, `s.m.PG`, `s.m.PG().DeleteLLMRecords`; direct `go` statement는 미발견. 이 목록은 helper 내부의 background launch나 transaction 경계를 펼치지 않으므로 그 부분은 P/U다.
- 종합 의미 판정: **P/U**; route/auth와 위에서 직접 확인한 branch·shape는 C지만, 모든 helper·derived response·중첩 DTO·외부 효과까지 완결됐다는 뜻은 아니다.

<a id="schema-get-api-llm-records-tasks"></a>
## `GET /api/llm/records/tasks`

구현: `pgLLMTasks` · `server/commands.go` · [정확한 handler 근거](evidence:handler-get-api-llm-records-tasks)

### 요청

| 위치/field | wire type | 필수·null | 기본/검증 |
|---|---|---|---|
| auth credential | Bearer header / `artex_token` cookie / query `token` | 세 위치 중 하나에 유효 JWT 필수 | `extractToken` 우선순위: header→cookie→query; query fallback은 SSE로 제한되지 않음 |
| body | 없음(직접 decoder/form read 미발견) | 해당 없음 | 해당 없음 |

요청 판정: **C/P** · unknown field 거부 미설정.
직접 validation: `err != nil`

### 응답·오류

| 종류 | status/body | 해석 상태 |
|---|---|---|
| success/variant | `200` · `tasks:derived` | P/U · `static composite` |
| error | `401` · `{error:string}` · `"未授权"` / `"token 无效或已过期"` | C for direct writeErr; error helper 내부는 P |
| error | `500` · `{error:string}` · `err.Error()` | C for direct writeErr; error helper 내부는 P |

응답 schema 판정: **P** · top-level key는 직접 추출했지만 derived/helper/opaque 또는 nested type이 남음.

### 목록·동시성·완료

- Pagination/filter/sort: 등록된 직접 query key 중 표준 paging/filter key 없음.
- Idempotency/concurrency: read/stream 요청; stream 재연결은 cursor 계약에 따름.
- Side effect/completion: 직접 조회/파일 전송 완료; 외부 조회가 있으면 응답 전 handler 결과만 보장.
- 직접 협력자/실행 단서: `err.Error`, `s.m.PG`, `s.m.PG().LLMTasks`; direct `go` statement는 미발견. 이 목록은 helper 내부의 background launch나 transaction 경계를 펼치지 않으므로 그 부분은 P/U다.
- 종합 의미 판정: **P/U**; route/auth와 위에서 직접 확인한 branch·shape는 C지만, 모든 helper·derived response·중첩 DTO·외부 효과까지 완결됐다는 뜻은 아니다.

<a id="schema-get-api-llm-records-by-model"></a>
## `GET /api/llm/records/by-model`

구현: `pgTokenByModel` · `server/commands.go` · [정확한 handler 근거](evidence:handler-get-api-llm-records-by-model)

### 요청

| 위치/field | wire type | 필수·null | 기본/검증 |
|---|---|---|---|
| query `task` | `string` wire | 성공 응답에 필수 | required non-empty trimmed task identifier; missing/empty→400 |
| auth credential | Bearer header / `artex_token` cookie / query `token` | 세 위치 중 하나에 유효 JWT 필수 | `extractToken` 우선순위: header→cookie→query; query fallback은 SSE로 제한되지 않음 |
| body | 없음(직접 decoder/form read 미발견) | 해당 없음 | 해당 없음 |

요청 판정: **C/P** · unknown field 거부 미설정.
직접 validation: `err != nil` · `id == ""` · `pg == nil`

### 응답·오류

| 종류 | status/body | 해석 상태 |
|---|---|---|
| success/variant | `200` · `models:[]db.ModelTokenStat` | P/U · `static composite` |
| success/variant | `200` · `models:derived` | P/U · `static composite` |
| error | `400` · `{error:string}` · `"missing task"` | C for direct writeErr; error helper 내부는 P |
| error | `401` · `{error:string}` · `"未授权"` / `"token 无效或已过期"` | C for direct writeErr; error helper 내부는 P |
| error | `500` · `{error:string}` · `err.Error()` | C for direct writeErr; error helper 내부는 P |

응답 schema 판정: **P** · top-level key는 직접 추출했지만 derived/helper/opaque 또는 nested type이 남음.

### 목록·동시성·완료

- Pagination/filter/sort: `task`.
- Idempotency/concurrency: read/stream 요청; stream 재연결은 cursor 계약에 따름.
- Side effect/completion: 직접 조회/파일 전송 완료; 외부 조회가 있으면 응답 전 handler 결과만 보장.
- 직접 협력자/실행 단서: `err.Error`, `pg.TokenByModel`, `r.URL.Query`, `r.URL.Query().Get`, `s.m.PG`; direct `go` statement는 미발견. 이 목록은 helper 내부의 background launch나 transaction 경계를 펼치지 않으므로 그 부분은 P/U다.
- 종합 의미 판정: **P/U**; route/auth와 위에서 직접 확인한 branch·shape는 C지만, 모든 helper·derived response·중첩 DTO·외부 효과까지 완결됐다는 뜻은 아니다.

<a id="schema-get-api-llm-records-id"></a>
## `GET /api/llm/records/{id}`

구현: `pgGetLLMRecord` · `server/commands.go` · [정확한 handler 근거](evidence:handler-get-api-llm-records-id)

### 요청

| 위치/field | wire type | 필수·null | 기본/검증 |
|---|---|---|---|
| path `id` | `string` | 필수 | handler의 parse/range validation; 미해석 helper는 P |
| auth credential | Bearer header / `artex_token` cookie / query `token` | 세 위치 중 하나에 유효 JWT 필수 | `extractToken` 우선순위: header→cookie→query; query fallback은 SSE로 제한되지 않음 |
| body | 없음(직접 decoder/form read 미발견) | 해당 없음 | 해당 없음 |

요청 판정: **C/P** · unknown field 거부 미설정.
직접 validation: `err != nil`
직접 parse/default: `strconv.ParseInt(r.PathValue("id"), 10, 64)`

### 응답·오류

| 종류 | status/body | 해석 상태 |
|---|---|---|
| success/variant | `200` · `rec` | P/U · `derived variable` |
| error | `400` · `{error:string}` · `"invalid id"` | C for direct writeErr; error helper 내부는 P |
| error | `401` · `{error:string}` · `"未授权"` / `"token 无效或已过期"` | C for direct writeErr; error helper 내부는 P |
| error | `404` · `{error:string}` · `"not found"` | C for direct writeErr; error helper 내부는 P |

응답 schema 판정: **P** · top-level key는 직접 추출했지만 derived/helper/opaque 또는 nested type이 남음.

### 목록·동시성·완료

- Pagination/filter/sort: 등록된 직접 query key 중 표준 paging/filter key 없음.
- Idempotency/concurrency: read/stream 요청; stream 재연결은 cursor 계약에 따름.
- Side effect/completion: 직접 조회/파일 전송 완료; 외부 조회가 있으면 응답 전 handler 결과만 보장.
- 직접 협력자/실행 단서: `r.PathValue`, `s.m.PG`, `s.m.PG().GetLLMRecord`; direct `go` statement는 미발견. 이 목록은 helper 내부의 background launch나 transaction 경계를 펼치지 않으므로 그 부분은 P/U다.
- 종합 의미 판정: **P/U**; route/auth와 위에서 직접 확인한 branch·shape는 C지만, 모든 helper·derived response·중첩 DTO·외부 효과까지 완결됐다는 뜻은 아니다.

<a id="schema-get-api-settings"></a>
## `GET /api/settings`

구현: `getSettings` · `server/server.go` · [정확한 handler 근거](evidence:handler-get-api-settings)

### 요청

| 위치/field | wire type | 필수·null | 기본/검증 |
|---|---|---|---|
| auth credential | Bearer header / `artex_token` cookie / query `token` | 세 위치 중 하나에 유효 JWT 필수 | `extractToken` 우선순위: header→cookie→query; query fallback은 SSE로 제한되지 않음 |
| body | 없음(직접 decoder/form read 미발견) | 해당 없음 | 해당 없음 |

요청 판정: **C/P** · unknown field 거부 미설정.

### 응답·오류

| 종류 | status/body | 해석 상태 |
|---|---|---|
| success/variant | `200` · `s.settingsPayload()` | P/U · `helper result` |
| error | `401` · `{error:string}` · `"未授权"` / `"token 无效或已过期"` | C for direct writeErr; error helper 내부는 P |

응답 schema 판정: **P** · top-level key는 직접 추출했지만 derived/helper/opaque 또는 nested type이 남음.

### 목록·동시성·완료

- Pagination/filter/sort: 등록된 직접 query key 중 표준 paging/filter key 없음.
- Idempotency/concurrency: read/stream 요청; stream 재연결은 cursor 계약에 따름.
- Side effect/completion: 직접 조회/파일 전송 완료; 외부 조회가 있으면 응답 전 handler 결과만 보장.
- 직접 협력자/실행 단서: handler AST에서 별도 collaborator call 미발견; direct `go` statement는 미발견. 이 목록은 helper 내부의 background launch나 transaction 경계를 펼치지 않으므로 그 부분은 P/U다.
- 종합 의미 판정: **P/U**; route/auth와 위에서 직접 확인한 branch·shape는 C지만, 모든 helper·derived response·중첩 DTO·외부 효과까지 완결됐다는 뜻은 아니다.

<a id="schema-put-api-settings"></a>
## `PUT /api/settings`

구현: `putSettings` · `server/server.go` · [정확한 handler 근거](evidence:handler-put-api-settings)

### 요청

| 위치/field | wire type | 필수·null | 기본/검증 |
|---|---|---|---|
| auth credential | Bearer header / `artex_token` cookie / query `token` | 세 위치 중 하나에 유효 JWT 필수 | `extractToken` 우선순위: header→cookie→query; query fallback은 SSE로 제한되지 않음 |
| body `traffic_capture` | `boolean` (`*bool`) | 요구 여부 미확정(P); absent/null→nil; null→nil | `req.TrafficCapture != nil` |
| body `agent_traffic_binding` | `boolean` (`*bool`) | 요구 여부 미확정(P); absent/null→nil; null→nil | `req.AgentTrafficBinding != nil` |
| body `llm_record` | `boolean` (`*bool`) | 요구 여부 미확정(P); absent/null→nil; null→nil | `req.LLMRecord != nil` |
| body `web_search_enabled` | `boolean` (`*bool`) | 요구 여부 미확정(P); absent/null→nil; null→nil | `req.WebSearchEnabled != nil; req.WebSearchEnabled != nil \|\| req.WebSearchBackend != nil \|\| req.BraveKey != nil \|\| req.TavilyKey != nil \|\| req.WebSearchProxy != nil` |
| body `web_search_backend` | `string` (`*string`) | 요구 여부 미확정(P); absent/null→nil; null→nil | `req.WebSearchBackend != nil; req.WebSearchEnabled != nil \|\| req.WebSearchBackend != nil \|\| req.BraveKey != nil \|\| req.TavilyKey != nil \|\| req.WebSearchProxy != nil` |
| body `brave_search_api_key` | `string` (`*string`) | 요구 여부 미확정(P); absent/null→nil; null→nil | `req.WebSearchEnabled != nil \|\| req.WebSearchBackend != nil \|\| req.BraveKey != nil \|\| req.TavilyKey != nil \|\| req.WebSearchProxy != nil` |
| body `tavily_search_api_key` | `string` (`*string`) | 요구 여부 미확정(P); absent/null→nil; null→nil | `req.WebSearchEnabled != nil \|\| req.WebSearchBackend != nil \|\| req.BraveKey != nil \|\| req.TavilyKey != nil \|\| req.WebSearchProxy != nil` |
| body `web_search_proxy` | `string` (`*string`) | 요구 여부 미확정(P); absent/null→nil; null→nil | `req.WebSearchEnabled != nil \|\| req.WebSearchBackend != nil \|\| req.BraveKey != nil \|\| req.TavilyKey != nil \|\| req.WebSearchProxy != nil` |
| body `global_proxy` | `string` (`*string`) | 요구 여부 미확정(P); absent/null→nil; null→nil | `req.GlobalProxy != nil` |
| body `python_interpreter` | `string` (`*string`) | 요구 여부 미확정(P); absent/null→nil; null→nil | `req.PythonInterp != nil` |
| body `workers` | `integer` (`*int`) | 요구 여부 미확정(P); absent/null→nil; null→nil | `req.Workers != nil` |
| body `task_concurrency_enabled` | `boolean` (`*bool`) | 요구 여부 미확정(P); absent/null→nil; null→nil | `req.ConcurrencyEnabled != nil; req.ConcurrencyEnabled != nil \|\| req.ConcurrencyLimit != nil` |
| body `task_concurrency_limit` | `integer` (`*int`) | 요구 여부 미확정(P); absent/null→nil; null→nil | `req.ConcurrencyEnabled != nil \|\| req.ConcurrencyLimit != nil; req.ConcurrencyLimit != nil` |
| body `llm_pool_enabled` | `boolean` (`*bool`) | 요구 여부 미확정(P); absent/null→nil; null→nil | `req.LLMPoolEnabled != nil; req.LLMPoolEnabled != nil \|\| req.LLMPoolBindFallback != nil` |
| body `llm_pool_bind_fallback` | `boolean` (`*bool`) | 요구 여부 미확정(P); absent/null→nil; null→nil | `req.LLMPoolBindFallback != nil; req.LLMPoolEnabled != nil \|\| req.LLMPoolBindFallback != nil` |
| body `constraints_inject_planner` | `boolean` (`*bool`) | 요구 여부 미확정(P); absent/null→nil; null→nil | `req.ConstraintsInjectPlanner != nil` |
| body `constraints_inject_worker` | `boolean` (`*bool`) | 요구 여부 미확정(P); absent/null→nil; null→nil | `req.ConstraintsInjectWorker != nil` |
| body `noa_compaction` | `boolean` (`*bool`) | 요구 여부 미확정(P); absent/null→nil; null→nil | `req.NoaCompaction != nil` |
| body `notify_enabled` | `boolean` (`*bool`) | 요구 여부 미확정(P); absent/null→nil; null→nil | `req.NotifyEnabled != nil` |
| body `notify_public_base_url` | `string` (`*string`) | 요구 여부 미확정(P); absent/null→nil; null→nil | `req.NotifyBaseURL != nil` |
| body `notify_digest_interval_min` | `integer` (`*int`) | 요구 여부 미확정(P); absent/null→nil; null→nil | `*req.NotifyDigestMins < 1 \|\| *req.NotifyDigestMins > 24*60; req.NotifyDigestMins != nil` |

요청 판정: **C/P** · unknown field 거부 미설정.
직접 validation: `*req.NotifyDigestMins < 1 \|\| *req.NotifyDigestMins > 24*60` · `req.AgentTrafficBinding != nil` · `req.ConcurrencyEnabled != nil` · `req.ConcurrencyEnabled != nil \|\| req.ConcurrencyLimit != nil` · `req.ConcurrencyLimit != nil` · `req.ConstraintsInjectPlanner != nil` · `req.ConstraintsInjectWorker != nil` · `req.GlobalProxy != nil` · `req.LLMPoolBindFallback != nil` · `req.LLMPoolEnabled != nil` · `req.LLMPoolEnabled != nil \|\| req.LLMPoolBindFallback != nil` · `req.LLMRecord != nil` · `req.NoaCompaction != nil` · `req.NotifyBaseURL != nil` · `req.NotifyDigestMins != nil` · `req.NotifyEnabled != nil` · `req.PythonInterp != nil` · `req.TrafficCapture != nil` · `req.WebSearchBackend != nil` · `req.WebSearchEnabled != nil` · `req.WebSearchEnabled != nil \|\| req.WebSearchBackend != nil \|\| req.BraveKey != nil \|\| req.TavilyKey != nil \|\| req.WebSearchProxy != nil` · `req.Workers != nil`

### 응답·오류

| 종류 | status/body | 해석 상태 |
|---|---|---|
| success/variant | `200` · `s.settingsPayload()` | P/U · `helper result` |
| error | `400` · `{error:string}` · `err.Error()` / `"回链地址需以 http:// 或 https:// 开头"` / `"汇总周期需在 1 到 1440 分钟之间"` | C for direct writeErr; error helper 내부는 P |
| error | `401` · `{error:string}` · `"未授权"` / `"token 无效或已过期"` | C for direct writeErr; error helper 내부는 P |
| error | `500` · `{error:string}` · `err.Error()` | C for direct writeErr; error helper 내부는 P |

응답 schema 판정: **P** · top-level key는 직접 추출했지만 derived/helper/opaque 또는 nested type이 남음.

### 목록·동시성·완료

- Pagination/filter/sort: 등록된 직접 query key 중 표준 paging/filter key 없음.
- Idempotency/concurrency: Idempotency-Key/If-Match 없음; DB upsert/trigger/side effect 때문에 반복 의미를 handler별 확인(P).
- Side effect/completion: handler의 직접 mutation/호출 후 응답; 외부 전달·agent 실행이 파생되면 별도 상태 확인(P).
- 직접 협력자/실행 단서: `err.Error`, `json.NewDecoder`, `json.NewDecoder(r.Body).Decode`, `s.cfgMu.Lock`, `s.cfgMu.Unlock`, `s.m.ConcurrencyLimit`, `s.m.SetConcurrency`, `s.m.SetGlobalProxy`, `s.m.SetLLMPoolBindFallback`, `s.m.SetLLMPoolEnabled`, `s.m.SetLLMRecordEnabled`, `s.m.SetNoaCompaction`, `s.m.SetTrafficEnabled`, `s.m.SetWebSearch`, `s.m.SetWorkers`, `s.m.WebSearch`, `s.m.pg.SetBool`, `s.m.pg.SetSetting` 외 1개; direct `go` statement는 발견. 이 목록은 helper 내부의 background launch나 transaction 경계를 펼치지 않으므로 그 부분은 P/U다.
- 종합 의미 판정: **P/U**; route/auth와 위에서 직접 확인한 branch·shape는 C지만, 모든 helper·derived response·중첩 DTO·외부 효과까지 완결됐다는 뜻은 아니다.

<a id="schema-get-api-notify-meta"></a>
## `GET /api/notify/meta`

구현: `notifyMeta` · `server/notify_api.go` · [정확한 handler 근거](evidence:handler-get-api-notify-meta)

### 요청

| 위치/field | wire type | 필수·null | 기본/검증 |
|---|---|---|---|
| auth credential | Bearer header / `artex_token` cookie / query `token` | 세 위치 중 하나에 유효 JWT 필수 | `extractToken` 우선순위: header→cookie→query; query fallback은 SSE로 제한되지 않음 |
| body | 없음(직접 decoder/form read 미발견) | 해당 없음 | 해당 없음 |

요청 판정: **C/P** · unknown field 거부 미설정.
직접 validation: `err != nil` · `pg == nil`

### 응답·오류

| 종류 | status/body | 해석 상태 |
|---|---|---|
| success/variant | `200` · `kinds:derived`, `enabled:helper result`, `public_base_url:derived`, `digest_interval_min:derived`, `defaults:map[string]any`, `stats:derived` | P/U · `static composite` |
| error | `401` · `{error:string}` · `"未授权"` / `"token 无效或已过期"` | C for direct writeErr; error helper 내부는 P |
| error | `500` · `{error:string}` · `err.Error()` | C for direct writeErr; error helper 내부는 P |
| error | `503` · `{error:string}` · `"PostgreSQL 不可用"` | C for direct writeErr; error helper 내부는 P |

응답 schema 판정: **P** · top-level key는 직접 추출했지만 derived/helper/opaque 또는 nested type이 남음.

### 목록·동시성·완료

- Pagination/filter/sort: 등록된 직접 query key 중 표준 paging/filter key 없음.
- Idempotency/concurrency: read/stream 요청; stream 재연결은 cursor 계약에 따름.
- Side effect/completion: 직접 조회/파일 전송 완료; 외부 조회가 있으면 응답 전 handler 결과만 보장.
- 직접 협력자/실행 단서: `ch.DefaultRatePerMin`, `ch.SecretKeys`, `err.Error`, `notify.Get`, `notify.Kinds`, `pg.GetBool`, `pg.GetSetting`, `pg.NotificationStatsSnapshot`, `r.Context`; direct `go` statement는 미발견. 이 목록은 helper 내부의 background launch나 transaction 경계를 펼치지 않으므로 그 부분은 P/U다.
- 종합 의미 판정: **P/U**; route/auth와 위에서 직접 확인한 branch·shape는 C지만, 모든 helper·derived response·중첩 DTO·외부 효과까지 완결됐다는 뜻은 아니다.

<a id="schema-get-api-notify-channels"></a>
## `GET /api/notify/channels`

구현: `notifyListChannels` · `server/notify_api.go` · [정확한 handler 근거](evidence:handler-get-api-notify-channels)

### 요청

| 위치/field | wire type | 필수·null | 기본/검증 |
|---|---|---|---|
| auth credential | Bearer header / `artex_token` cookie / query `token` | 세 위치 중 하나에 유효 JWT 필수 | `extractToken` 우선순위: header→cookie→query; query fallback은 SSE로 제한되지 않음 |
| body | 없음(직접 decoder/form read 미발견) | 해당 없음 | 해당 없음 |

요청 판정: **C/P** · unknown field 거부 미설정.
직접 validation: `err != nil` · `pg == nil`

### 응답·오류

| 종류 | status/body | 해석 상태 |
|---|---|---|
| success/variant | `200` · `channels:derived` | P/U · `static composite` |
| error | `401` · `{error:string}` · `"未授权"` / `"token 无效或已过期"` | C for direct writeErr; error helper 내부는 P |
| error | `500` · `{error:string}` · `err.Error()` | C for direct writeErr; error helper 내부는 P |
| error | `503` · `{error:string}` · `"PostgreSQL 不可用"` | C for direct writeErr; error helper 내부는 P |

응답 schema 판정: **P** · top-level key는 직접 추출했지만 derived/helper/opaque 또는 nested type이 남음.

### 목록·동시성·완료

- Pagination/filter/sort: 등록된 직접 query key 중 표준 paging/filter key 없음.
- Idempotency/concurrency: read/stream 요청; stream 재연결은 cursor 계약에 따름.
- Side effect/completion: 직접 조회/파일 전송 완료; 외부 조회가 있으면 응답 전 handler 결과만 보장.
- 직접 협력자/실행 단서: `err.Error`, `pg.ListNotificationChannels`, `r.Context`, `toNotifyChannelDTO`; direct `go` statement는 미발견. 이 목록은 helper 내부의 background launch나 transaction 경계를 펼치지 않으므로 그 부분은 P/U다.
- 종합 의미 판정: **P/U**; route/auth와 위에서 직접 확인한 branch·shape는 C지만, 모든 helper·derived response·중첩 DTO·외부 효과까지 완결됐다는 뜻은 아니다.

<a id="schema-post-api-notify-channels"></a>
## `POST /api/notify/channels`

구현: `notifyCreateChannel` · `server/notify_api.go` · [정확한 handler 근거](evidence:handler-post-api-notify-channels)

### 요청

| 위치/field | wire type | 필수·null | 기본/검증 |
|---|---|---|---|
| auth credential | Bearer header / `artex_token` cookie / query `token` | 세 위치 중 하나에 유효 JWT 필수 | `extractToken` 우선순위: header→cookie→query; query fallback은 SSE로 제한되지 않음 |
| body `name` | `string` (`*string`) | 필수(직접 거부 branch 확인); null→nil | `nil/trim-empty이면 400 후 return` |
| body `kind` | `string` (`*string`) | 필수(직접 거부 branch 확인); null→nil | `nil 또는 미지원 kind이면 400 후 return` |
| body `enabled` | `boolean` (`*bool`) | 요구 여부 미확정(P); absent/null→nil; null→nil | `nil` |
| body `mode` | `string` (`*string`) | 요구 여부 미확정(P); absent/null→nil; null→nil | `!db.ValidNotifyMode(*req.Mode); req.Mode != nil` |
| body `config` | `object/JSON` (`map[string]any`) | 요구 여부 미확정(P); absent/null→nil; null/absent 구별 안 됨 | `nil` |
| body `filter` | [notify.Filter](#type-notify-filter) (`*notify.Filter`) | 요구 여부 미확정(P); absent/null→nil; null→nil | `req.Filter != nil` |
| body `rate_per_min` | `integer` (`*int`) | 선택; null→nil | `누락 시 channel default; 제공 시 음수만 거부` |

요청 판정: **C/P** · unknown field 거부 미설정.
직접 validation: `!db.ValidNotifyMode(*req.Mode)` · `*req.RatePerMin < 0` · `req.Filter != nil` · `req.Kind == nil \|\| !notify.ValidKind(*req.Kind)` · `req.Mode != nil` · `req.Name != nil` · `req.RatePerMin != nil` · `req.RatePerMin == nil`

### 응답·오류

| 종류 | status/body | 해석 상태 |
|---|---|---|
| success/variant | `200` · `id:derived` | P/U · `static composite` |
| error | `400` · `{error:string}` · `"请求体不是合法 JSON: " + err.Error()` / `fmt.Sprintf("渠道类型无效，可选：%s", strings.Join(notify.Kinds(), " / "))` / `"缺少渠道名称"` / `err.Error()` / `"推送模式无效，可选：realtime / digest"` / `"限流值不能为负"` | C for direct writeErr; error helper 내부는 P |
| error | `401` · `{error:string}` · `"未授权"` / `"token 无效或已过期"` | C for direct writeErr; error helper 내부는 P |
| error | `500` · `{error:string}` · `err.Error()` | C for direct writeErr; error helper 내부는 P |
| error | `503` · `{error:string}` · `"PostgreSQL 不可用"` | C for direct writeErr; error helper 내부는 P |

응답 schema 판정: **P** · top-level key는 직접 추출했지만 derived/helper/opaque 또는 nested type이 남음.

### 목록·동시성·완료

- Pagination/filter/sort: 등록된 직접 query key 중 표준 paging/filter key 없음.
- Idempotency/concurrency: Idempotency-Key 없음; unique constraint 또는 상태 check가 있는 경우만 중복을 제한(P).
- Side effect/completion: delivery/test effect는 remote channel 결과와 DB delivery state를 함께 확인.
- 직접 협력자/실행 단서: `channel.DefaultRatePerMin`, `channel.Validate`, `db.ValidNotifyMode`, `err.Error`, `json.NewDecoder`, `json.NewDecoder(r.Body).Decode`, `notify.Get`, `notify.Kinds`, `notify.ValidKind`, `pg.SaveNotificationChannel`, `r.Context`, `req.Filter.Validate`; direct `go` statement는 미발견. 이 목록은 helper 내부의 background launch나 transaction 경계를 펼치지 않으므로 그 부분은 P/U다.
- 종합 의미 판정: **P/U**; route/auth와 위에서 직접 확인한 branch·shape는 C지만, 모든 helper·derived response·중첩 DTO·외부 효과까지 완결됐다는 뜻은 아니다.

<a id="schema-patch-api-notify-channels-id"></a>
## `PATCH /api/notify/channels/{id}`

구현: `notifyUpdateChannel` · `server/notify_api.go` · [정확한 handler 근거](evidence:handler-patch-api-notify-channels-id)

### 요청

| 위치/field | wire type | 필수·null | 기본/검증 |
|---|---|---|---|
| path `id` | `string` | 필수 | handler의 parse/range validation; 미해석 helper는 P |
| auth credential | Bearer header / `artex_token` cookie / query `token` | 세 위치 중 하나에 유효 JWT 필수 | `extractToken` 우선순위: header→cookie→query; query fallback은 SSE로 제한되지 않음 |
| body `name` | `string` (`*string`) | 요구 여부 미확정(P); absent/null→nil; null→nil | `req.Name != nil` |
| body `kind` | `string` (`*string`) | 요구 여부 미확정(P); absent/null→nil; null→nil | `!notify.ValidKind(*req.Kind); req.Kind != nil` |
| body `enabled` | `boolean` (`*bool`) | 요구 여부 미확정(P); absent/null→nil; null→nil | `req.Enabled != nil` |
| body `mode` | `string` (`*string`) | 요구 여부 미확정(P); absent/null→nil; null→nil | `!db.ValidNotifyMode(*req.Mode); req.Mode != nil` |
| body `config` | `object/JSON` (`map[string]any`) | 요구 여부 미확정(P); absent/null→nil; null/absent 구별 안 됨 | `nil` |
| body `filter` | [notify.Filter](#type-notify-filter) (`*notify.Filter`) | 요구 여부 미확정(P); absent/null→nil; null→nil | `req.Filter != nil` |
| body `rate_per_min` | `integer` (`*int`) | 요구 여부 미확정(P); absent/null→nil; null→nil | `*req.RatePerMin < 0; req.RatePerMin != nil` |

요청 판정: **C/P** · unknown field 거부 미설정.
직접 validation: `!db.ValidNotifyMode(*req.Mode)` · `!notify.ValidKind(*req.Kind)` · `*req.RatePerMin < 0` · `req.Enabled != nil` · `req.Filter != nil` · `req.Kind != nil` · `req.Mode != nil` · `req.Name != nil` · `req.RatePerMin != nil`

### 응답·오류

| 종류 | status/body | 해석 상태 |
|---|---|---|
| success/variant | `200` · `id:derived` | P/U · `static composite` |
| error | `400` · `{error:string}` · `"渠道 id 无效"` / `"请求体不是合法 JSON: " + err.Error()` / `fmt.Sprintf("渠道类型无效，可选：%s", strings.Join(notify.Kinds(), " / "))` / `err.Error()` / `"渠道名称不能为空"` / `"推送模式无效，可选：realtime / digest"` / `"限流值不能为负"` | C for direct writeErr; error helper 내부는 P |
| error | `401` · `{error:string}` · `"未授权"` / `"token 无效或已过期"` | C for direct writeErr; error helper 내부는 P |
| error | `404` · `{error:string}` · `"通知渠道不存在"` | C for direct writeErr; error helper 내부는 P |
| error | `500` · `{error:string}` · `err.Error()` | C for direct writeErr; error helper 내부는 P |
| error | `503` · `{error:string}` · `"PostgreSQL 不可用"` | C for direct writeErr; error helper 내부는 P |

공통 helper 오류 분기: `notifyChannelLookupErr` (`server/notify_api.go::notifyChannelLookupErr`: 404/500). Status union은 helper control-flow를 정적으로 펼친 P이며 runtime DB 결과는 관찰하지 않았다.

응답 schema 판정: **P** · top-level key는 직접 추출했지만 derived/helper/opaque 또는 nested type이 남음.

### 목록·동시성·완료

- Pagination/filter/sort: 등록된 직접 query key 중 표준 paging/filter key 없음.
- Idempotency/concurrency: Idempotency-Key/If-Match 없음; DB upsert/trigger/side effect 때문에 반복 의미를 handler별 확인(P).
- Side effect/completion: handler의 직접 mutation/호출 후 응답; 외부 전달·agent 실행이 파생되면 별도 상태 확인(P).
- 직접 협력자/실행 단서: `channel.Validate`, `db.ValidNotifyMode`, `err.Error`, `json.NewDecoder`, `json.NewDecoder(r.Body).Decode`, `notify.Get`, `notify.Kinds`, `notify.PrepareConfigUpdate`, `notify.ValidKind`, `notifyChannelLookupErr`, `pathInt`, `pg.NotificationChannelByID`, `pg.SaveNotificationChannel`, `pg.SetNotificationChannelEnabled`, `r.Context`, `req.Filter.Validate`; direct `go` statement는 미발견. 이 목록은 helper 내부의 background launch나 transaction 경계를 펼치지 않으므로 그 부분은 P/U다.
- 종합 의미 판정: **P/U**; route/auth와 위에서 직접 확인한 branch·shape는 C지만, 모든 helper·derived response·중첩 DTO·외부 효과까지 완결됐다는 뜻은 아니다.

<a id="schema-delete-api-notify-channels-id"></a>
## `DELETE /api/notify/channels/{id}`

구현: `notifyDeleteChannel` · `server/notify_api.go` · [정확한 handler 근거](evidence:handler-delete-api-notify-channels-id)

### 요청

| 위치/field | wire type | 필수·null | 기본/검증 |
|---|---|---|---|
| path `id` | `string` | 필수 | handler의 parse/range validation; 미해석 helper는 P |
| auth credential | Bearer header / `artex_token` cookie / query `token` | 세 위치 중 하나에 유효 JWT 필수 | `extractToken` 우선순위: header→cookie→query; query fallback은 SSE로 제한되지 않음 |
| body | 없음(직접 decoder/form read 미발견) | 해당 없음 | 해당 없음 |

요청 판정: **C/P** · unknown field 거부 미설정.
직접 validation: `!ok` · `err != nil` · `pg == nil`

### 응답·오류

| 종류 | status/body | 해석 상태 |
|---|---|---|
| success/variant | `200` · `ok:boolean` | C · `static composite` |
| error | `400` · `{error:string}` · `"渠道 id 无效"` | C for direct writeErr; error helper 내부는 P |
| error | `401` · `{error:string}` · `"未授权"` / `"token 无效或已过期"` | C for direct writeErr; error helper 내부는 P |
| error | `404` · `{error:string}` · `"通知渠道不存在"` | C for direct writeErr; error helper 내부는 P |
| error | `500` · `{error:string}` · `err.Error()` | C for direct writeErr; error helper 내부는 P |
| error | `503` · `{error:string}` · `"PostgreSQL 不可用"` | C for direct writeErr; error helper 내부는 P |

공통 helper 오류 분기: `notifyChannelLookupErr` (`server/notify_api.go::notifyChannelLookupErr`: 404/500). Status union은 helper control-flow를 정적으로 펼친 P이며 runtime DB 결과는 관찰하지 않았다.

응답 schema 판정: **C** · 직접 scalar/static top-level key와 field type을 추출; nested 내부 schema는 별도 type 참조가 없으면 P.

### 목록·동시성·완료

- Pagination/filter/sort: 등록된 직접 query key 중 표준 paging/filter key 없음.
- Idempotency/concurrency: Idempotency-Key/If-Match 없음; 반복 시 응답/존재 검사가 달라질 수 있음.
- Side effect/completion: handler의 직접 mutation/호출 후 응답; 외부 전달·agent 실행이 파생되면 별도 상태 확인(P).
- 직접 협력자/실행 단서: `notifyChannelLookupErr`, `pathInt`, `pg.DeleteNotificationChannel`, `r.Context`; direct `go` statement는 미발견. 이 목록은 helper 내부의 background launch나 transaction 경계를 펼치지 않으므로 그 부분은 P/U다.
- 종합 의미 판정: **P/U**; route/auth와 위에서 직접 확인한 branch·shape는 C지만, 모든 helper·derived response·중첩 DTO·외부 효과까지 완결됐다는 뜻은 아니다.

<a id="schema-post-api-notify-channels-id-test"></a>
## `POST /api/notify/channels/{id}/test`

구현: `notifyTestChannel` · `server/notify_api.go` · [정확한 handler 근거](evidence:handler-post-api-notify-channels-id-test)

### 요청

| 위치/field | wire type | 필수·null | 기본/검증 |
|---|---|---|---|
| path `id` | `string` | 필수 | handler의 parse/range validation; 미해석 helper는 P |
| auth credential | Bearer header / `artex_token` cookie / query `token` | 세 위치 중 하나에 유효 JWT 필수 | `extractToken` 우선순위: header→cookie→query; query fallback은 SSE로 제한되지 않음 |
| body | 없음(직접 decoder/form read 미발견) | 해당 없음 | 해당 없음 |

요청 판정: **C/P** · unknown field 거부 미설정.
직접 validation: `!ok` · `err != nil` · `len(ch.Config) > 0` · `pg == nil`

### 응답·오류

| 종류 | status/body | 해석 상태 |
|---|---|---|
| success/variant | `200` · `ok:boolean`, `latency_ms:helper result` | P/U · `static composite` |
| error | `400` · `{error:string}` · `"渠道 id 无效"` / `fmt.Sprintf("渠道类型 %q 未注册", ch.Kind)` / `err.Error()` | C for direct writeErr; error helper 내부는 P |
| error | `401` · `{error:string}` · `"未授权"` / `"token 无效或已过期"` | C for direct writeErr; error helper 내부는 P |
| error | `404` · `{error:string}` · `"通知渠道不存在"` | C for direct writeErr; error helper 내부는 P |
| error | `500` · `{error:string}` · `err.Error()` | C for direct writeErr; error helper 내부는 P |
| error | `502` · `{error:string}` · `err.Error()` | C for direct writeErr; error helper 내부는 P |
| error | `503` · `{error:string}` · `"PostgreSQL 不可用"` | C for direct writeErr; error helper 내부는 P |

공통 helper 오류 분기: `notifyChannelLookupErr` (`server/notify_api.go::notifyChannelLookupErr`: 404/500). Status union은 helper control-flow를 정적으로 펼친 P이며 runtime DB 결과는 관찰하지 않았다.

응답 schema 판정: **P** · top-level key는 직접 추출했지만 derived/helper/opaque 또는 nested type이 남음.

### 목록·동시성·완료

- Pagination/filter/sort: 등록된 직접 query key 중 표준 paging/filter key 없음.
- Idempotency/concurrency: Idempotency-Key 없음; unique constraint 또는 상태 check가 있는 경우만 중복을 제한(P).
- Side effect/completion: delivery/test effect는 remote channel 결과와 DB delivery state를 함께 확인.
- 직접 협력자/실행 단서: `channel.Send`, `channel.Validate`, `err.Error`, `notify.Get`, `notifyChannelLookupErr`, `notifyTestMessage`, `pathInt`, `pg.NotificationChannelByID`, `r.Context`; direct `go` statement는 미발견. 이 목록은 helper 내부의 background launch나 transaction 경계를 펼치지 않으므로 그 부분은 P/U다.
- 종합 의미 판정: **P/U**; route/auth와 위에서 직접 확인한 branch·shape는 C지만, 모든 helper·derived response·중첩 DTO·외부 효과까지 완결됐다는 뜻은 아니다.

<a id="schema-get-api-notify-deliveries"></a>
## `GET /api/notify/deliveries`

구현: `notifyListDeliveries` · `server/notify_api.go` · [정확한 handler 근거](evidence:handler-get-api-notify-deliveries)

### 요청

| 위치/field | wire type | 필수·null | 기본/검증 |
|---|---|---|---|
| query `channel_id` | `string` wire | 선택 | handler/helper 변환·default 미전개(P) |
| query `event_kind` | `string` wire | 선택 | handler/helper 변환·default 미전개(P) |
| query `page` | `string` wire | 선택 | positive decimal integer; invalid/zero/negative→1 |
| query `page_size` | `string` wire | 선택 | positive decimal integer; invalid/zero/negative→50 |
| query `state` | `string` wire | 선택 | handler/helper 변환·default 미전개(P) |
| auth credential | Bearer header / `artex_token` cookie / query `token` | 세 위치 중 하나에 유효 JWT 필수 | `extractToken` 우선순위: header→cookie→query; query fallback은 SSE로 제한되지 않음 |
| body | 없음(직접 decoder/form read 미발견) | 해당 없음 | 해당 없음 |

요청 판정: **C/P** · unknown field 거부 미설정.
직접 validation: `err != nil` · `pg == nil` · `v != ""`
직접 parse/default: `atoiDefault(v, 0)` · `f.ChannelID = int64(atoiDefault(v, 0))`

### 응답·오류

| 종류 | status/body | 해석 상태 |
|---|---|---|
| success/variant | `200` · `deliveries:derived`, `total:derived`, `page:derived`, `page_size:derived` | P/U · `static composite` |
| error | `401` · `{error:string}` · `"未授权"` / `"token 无效或已过期"` | C for direct writeErr; error helper 내부는 P |
| error | `500` · `{error:string}` · `err.Error()` | C for direct writeErr; error helper 내부는 P |
| error | `503` · `{error:string}` · `"PostgreSQL 不可用"` | C for direct writeErr; error helper 내부는 P |

응답 schema 판정: **P** · top-level key는 직접 추출했지만 derived/helper/opaque 또는 nested type이 남음.

### 목록·동시성·완료

- Pagination/filter/sort: `page`.
- Idempotency/concurrency: read/stream 요청; stream 재연결은 cursor 계약에 따름.
- Side effect/completion: 직접 조회/파일 전송 완료; 외부 조회가 있으면 응답 전 handler 결과만 보장.
- 직접 협력자/실행 단서: `atoiDefault`, `err.Error`, `pg.ListNotificationDeliveries`, `queryInt`, `r.Context`, `r.URL.Query`, `r.URL.Query().Get`, `toNotifyDeliveryDTO`; direct `go` statement는 미발견. 이 목록은 helper 내부의 background launch나 transaction 경계를 펼치지 않으므로 그 부분은 P/U다.
- 종합 의미 판정: **P/U**; route/auth와 위에서 직접 확인한 branch·shape는 C지만, 모든 helper·derived response·중첩 DTO·외부 효과까지 완결됐다는 뜻은 아니다.

<a id="schema-post-api-notify-deliveries-id-retry"></a>
## `POST /api/notify/deliveries/{id}/retry`

구현: `notifyRetryDelivery` · `server/notify_api.go` · [정확한 handler 근거](evidence:handler-post-api-notify-deliveries-id-retry)

### 요청

| 위치/field | wire type | 필수·null | 기본/검증 |
|---|---|---|---|
| path `id` | `string` | 필수 | handler의 parse/range validation; 미해석 helper는 P |
| auth credential | Bearer header / `artex_token` cookie / query `token` | 세 위치 중 하나에 유효 JWT 필수 | `extractToken` 우선순위: header→cookie→query; query fallback은 SSE로 제한되지 않음 |
| body | 없음(직접 decoder/form read 미발견) | 해당 없음 | 해당 없음 |

요청 판정: **C/P** · unknown field 거부 미설정.
직접 validation: `!ok` · `err != nil` · `pg == nil`

### 응답·오류

| 종류 | status/body | 해석 상태 |
|---|---|---|
| success/variant | `200` · `ok:boolean` | C · `static composite` |
| error | `400` · `{error:string}` · `"投递 id 无效"` / `err.Error()` | C for direct writeErr; error helper 내부는 P |
| error | `401` · `{error:string}` · `"未授权"` / `"token 无效或已过期"` | C for direct writeErr; error helper 내부는 P |
| error | `503` · `{error:string}` · `"PostgreSQL 不可用"` | C for direct writeErr; error helper 내부는 P |

응답 schema 판정: **C** · 직접 scalar/static top-level key와 field type을 추출; nested 내부 schema는 별도 type 참조가 없으면 P.

### 목록·동시성·완료

- Pagination/filter/sort: 등록된 직접 query key 중 표준 paging/filter key 없음.
- Idempotency/concurrency: Idempotency-Key 없음; unique constraint 또는 상태 check가 있는 경우만 중복을 제한(P).
- Side effect/completion: delivery/test effect는 remote channel 결과와 DB delivery state를 함께 확인.
- 직접 협력자/실행 단서: `err.Error`, `pathInt`, `pg.RetryNotificationDelivery`, `r.Context`; direct `go` statement는 미발견. 이 목록은 helper 내부의 background launch나 transaction 경계를 펼치지 않으므로 그 부분은 P/U다.
- 종합 의미 판정: **P/U**; route/auth와 위에서 직접 확인한 branch·shape는 C지만, 모든 helper·derived response·중첩 DTO·외부 효과까지 완결됐다는 뜻은 아니다.

<a id="schema-post-api-settings-web-search-test"></a>
## `POST /api/settings/web-search/test`

구현: `testWebSearch` · `server/server.go` · [정확한 handler 근거](evidence:handler-post-api-settings-web-search-test)

### 요청

| 위치/field | wire type | 필수·null | 기본/검증 |
|---|---|---|---|
| auth credential | Bearer header / `artex_token` cookie / query `token` | 세 위치 중 하나에 유효 JWT 필수 | `extractToken` 우선순위: header→cookie→query; query fallback은 SSE로 제한되지 않음 |
| body `web_search_backend` | `string` (`string`) | 요구 여부 미확정(P); null/absent 구별 안 됨 | `strings.TrimSpace(req.Backend) != ""` |
| body `web_search_proxy` | `string` (`string`) | 요구 여부 미확정(P); null/absent 구별 안 됨 | `Go zero value; 별도 기본은 handler/helper 확인` |
| body `brave_search_api_key` | `string` (`string`) | 요구 여부 미확정(P); null/absent 구별 안 됨 | `strings.TrimSpace(req.BraveKey) != ""` |
| body `tavily_search_api_key` | `string` (`string`) | 요구 여부 미확정(P); null/absent 구별 안 됨 | `strings.TrimSpace(req.TavilyKey) != ""` |

요청 판정: **C/P** · unknown field 거부 미설정.
직접 validation: `strings.TrimSpace(req.Backend) != ""` · `strings.TrimSpace(req.BraveKey) != ""` · `strings.TrimSpace(req.TavilyKey) != ""`

### 응답·오류

| 종류 | status/body | 해석 상태 |
|---|---|---|
| success/variant | `200` · `ok:boolean`, `error:helper result`, `backend:derived` | P/U · `static composite` |
| success/variant | `200` · `ok:boolean`, `error:string`, `backend:derived` | P/U · `static composite` |
| success/variant | `200` · `ok:boolean`, `count:helper result`, `backend:derived` | P/U · `static composite` |
| error | `400` · `{error:string}` · `err.Error()` | C for direct writeErr; error helper 내부는 P |
| error | `401` · `{error:string}` · `"未授权"` / `"token 无效或已过期"` | C for direct writeErr; error helper 내부는 P |

응답 schema 판정: **P** · top-level key는 직접 추출했지만 derived/helper/opaque 또는 nested type이 남음.

### 목록·동시성·완료

- Pagination/filter/sort: 등록된 직접 query key 중 표준 paging/filter key 없음.
- Idempotency/concurrency: Idempotency-Key 없음; unique constraint 또는 상태 check가 있는 경우만 중복을 제한(P).
- Side effect/completion: handler의 직접 mutation/호출 후 응답; 외부 전달·agent 실행이 파생되면 별도 상태 확인(P).
- 직접 협력자/실행 단서: `actool.WebSearchProbe`, `cancel`, `context.WithTimeout`, `err.Error`, `json.NewDecoder`, `json.NewDecoder(r.Body).Decode`, `r.Context`, `s.m.WebSearch`, `s.m.deepSeekSearchCreds`; direct `go` statement는 미발견. 이 목록은 helper 내부의 background launch나 transaction 경계를 펼치지 않으므로 그 부분은 P/U다.
- 종합 의미 판정: **P/U**; route/auth와 위에서 직접 확인한 branch·shape는 C지만, 모든 helper·derived response·중첩 DTO·외부 효과까지 완결됐다는 뜻은 아니다.

<a id="schema-get-api-report"></a>
## `GET /api/report`

구현: `getReport` · `server/server.go` · [정확한 handler 근거](evidence:handler-get-api-report)

### 요청

| 위치/field | wire type | 필수·null | 기본/검증 |
|---|---|---|---|
| query `task` | `string` wire | 선택 | handler/helper 변환·default 미전개(P) |
| auth credential | Bearer header / `artex_token` cookie / query `token` | 세 위치 중 하나에 유효 JWT 필수 | `extractToken` 우선순위: header→cookie→query; query fallback은 SSE로 제한되지 않음 |
| body | 없음(직접 decoder/form read 미발견) | 해당 없음 | 해당 없음 |

요청 판정: **C/P** · unknown field 거부 미설정.
직접 validation: `len(ns) > 0` · `t == nil`

### 응답·오류

| 종류 | status/body | 해석 상태 |
|---|---|---|
| success/variant | `200` · `text/markdown; charset=utf-8; generated report bytes` | P/U · `raw document` |
| error | `401` · `{error:string}` · `"未授权"` / `"token 无效或已过期"` | C for direct writeErr; error helper 내부는 P |
| error | `404` · `{error:string}` · `"no active task"` | C for direct writeErr; error helper 내부는 P |

응답 schema 판정: **P** · top-level key는 직접 추출했지만 derived/helper/opaque 또는 nested type이 남음.

### 목록·동시성·완료

- Pagination/filter/sort: `task`.
- Idempotency/concurrency: read/stream 요청; stream 재연결은 cursor 계약에 따름.
- Side effect/completion: 직접 조회/파일 전송 완료; 외부 조회가 있으면 응답 전 handler 결과만 보장.
- 직접 협력자/실행 단서: `[]byte`, `r.URL.Query`, `r.URL.Query().Get`, `report.Markdown`, `s.m.Assets`, `s.m.Assets().QueryByType`, `s.m.ResolveTask`, `t.Store.ListByKind`, `w.Header`, `w.Header().Set`, `w.Write`, `w.WriteHeader`; direct `go` statement는 미발견. 이 목록은 helper 내부의 background launch나 transaction 경계를 펼치지 않으므로 그 부분은 P/U다.
- 종합 의미 판정: **P/U**; route/auth와 위에서 직접 확인한 branch·shape는 C지만, 모든 helper·derived response·중첩 DTO·외부 효과까지 완결됐다는 뜻은 아니다.

<a id="schema-get-api-chat-mentions"></a>
## `GET /api/chat/mentions`

구현: `searchChatMentions` · `server/chat_mentions.go` · [정확한 handler 근거](evidence:handler-get-api-chat-mentions)

### 요청

| 위치/field | wire type | 필수·null | 기본/검증 |
|---|---|---|---|
| query `cursor` | `string` wire | 선택 | handler/helper 변환·default 미전개(P) |
| query `kind` | `string` wire | 선택 | handler/helper 변환·default 미전개(P) |
| query `q` | `string` wire | 선택 | handler/helper 변환·default 미전개(P) |
| auth credential | Bearer header / `artex_token` cookie / query `token` | 세 위치 중 하나에 유효 JWT 필수 | `extractToken` 우선순위: header→cookie→query; query fallback은 SSE로 제한되지 않음 |
| body | 없음(직접 decoder/form read 미발견) | 해당 없음 | 해당 없음 |

요청 판정: **C/P** · unknown field 거부 미설정.
직접 validation: `(kind != "" && !db.ValidChatMentionKind(kind)) \|\| utf8.RuneCountInString(query) > 200` · `err != nil` · `errors.Is(err, db.ErrInvalidChatMentionCursor)` · `pg == nil`

### 응답·오류

| 종류 | status/body | 해석 상태 |
|---|---|---|
| success/variant | `200` · `page` | P/U · `derived variable` |
| error | `400` · `{error:string}` · `"引用类型无效或搜索关键词超过 200 字"` / `err.Error()` | C for direct writeErr; error helper 내부는 P |
| error | `401` · `{error:string}` · `"未授权"` / `"token 无效或已过期"` | C for direct writeErr; error helper 내부는 P |
| error | `500` · `{error:string}` · `err.Error()` | C for direct writeErr; error helper 내부는 P |
| error | `503` · `{error:string}` · `"PostgreSQL 不可用"` | C for direct writeErr; error helper 내부는 P |

응답 schema 판정: **P** · top-level key는 직접 추출했지만 derived/helper/opaque 또는 nested type이 남음.

### 목록·동시성·완료

- Pagination/filter/sort: `q`.
- Idempotency/concurrency: read/stream 요청; stream 재연결은 cursor 계약에 따름.
- Side effect/completion: 직접 조회/파일 전송 완료; 외부 조회가 있으면 응답 전 handler 결과만 보장.
- 직접 협력자/실행 단서: `db.ValidChatMentionKind`, `err.Error`, `pg.SearchChatMentionsPage`, `r.Context`, `r.URL.Query`, `r.URL.Query().Get`, `utf8.RuneCountInString`; direct `go` statement는 미발견. 이 목록은 helper 내부의 background launch나 transaction 경계를 펼치지 않으므로 그 부분은 P/U다.
- 종합 의미 판정: **P/U**; route/auth와 위에서 직접 확인한 branch·shape는 C지만, 모든 helper·derived response·중첩 DTO·외부 효과까지 완결됐다는 뜻은 아니다.

<a id="schema-post-api-chat"></a>
## `POST /api/chat`

구현: `chat` · `server/server.go` · [정확한 handler 근거](evidence:handler-post-api-chat)

### 요청

| 위치/field | wire type | 필수·null | 기본/검증 |
|---|---|---|---|
| query `task` | `string` wire | 선택 | handler/helper 변환·default 미전개(P) |
| auth credential | Bearer header / `artex_token` cookie / query `token` | 세 위치 중 하나에 유효 JWT 필수 | `extractToken` 우선순위: header→cookie→query; query fallback은 SSE로 제한되지 않음 |
| body `message` | `string` (`string`) | 요구 여부 미확정(P); null/absent 구별 안 됨 | `Go zero value; 별도 기본은 handler/helper 확인` |
| body `attachments` | [array[chatAttachment]](#type-server-chatattachment) (`[]chatAttachment`) | 요구 여부 미확정(P); absent/null→nil; null/absent 구별 안 됨 | `func() { defer func() { s.finishTaskChat(t.ID, cancel) }() emit := func(rec db.Activity) { rec.MainSeg = segPtr s.engine.emitActivity(t, rec) } maTaskID, _ := strconv.ParseInt(t.ID, 10, 64) resume := func() { s.reviveTask(t) } taskDir := filepath.Join(s.m.dir, "tasks", t.ID) agentMsg := composeAgentMessage(agentMessage, req.Attachments, taskDir) s.engine.BeginLLMCall(t.ID) _, err := ma.Chat(ctx, maTaskID, mainSeg, s.m.Assets(), t.Store, t.Goal, agentMsg, emit, t.Notify, resume, t.NotifyGoal, t.NotifyHint) s.engine.EndLLMCall(t.ID) if err != nil && ctx.Err() == nil { s.engine.emitActivity(t, db.Activity{Worker: "mainagent", Kind: "text", IsError: true, Summary: "（主 Agent 出错：" + err.Error() + "）", MainSeg: segPtr}) } }()` |
| body `seg` | `integer` (`*int`) | 요구 여부 미확정(P); absent/null→nil; null→nil | `req.Seg != nil && *req.Seg >= 0` |

요청 판정: **C/P** · unknown field 거부 미설정.
직접 validation: `req.Seg != nil && *req.Seg >= 0`
직접 parse/default: `func() { defer func() { s.finishTaskChat(t.ID, cancel) }() emit := func(rec db.Activity) { rec.MainSeg = segPtr s.engine.emitActivity(t, rec) } maTaskID, _ := strconv.ParseInt(t.ID, 10, 64) resume := func() { s.reviveTask(t) } taskDir := filepath.Join(s.m.dir, "tasks", t.ID) agentMsg := composeAgentMessage(agentMessage, req.Attachments, taskDir) s.engine.BeginLLMCall(t.ID) _, err := ma.Chat(ctx, maTaskID, mainSeg, s.m.Assets(), t.Store, t.Goal, agentMsg, emit, t.Notify, resume, t.NotifyGoal, t.NotifyHint) s.engine.EndLLMCall(t.ID) if err != nil && ctx.Err() == nil { s.engine.emitActivity(t, db.Activity{Worker: "mainagent", Kind: "text", IsError: true, Summary: "（主 Agent 出错：" + err.Error() + "）", MainSeg: segPtr}) } }()` · `strconv.ParseInt(t.ID, 10, 64)`

### 응답·오류

| 종류 | status/body | 해석 상태 |
|---|---|---|
| success/variant | `202` · `status:string`, `mode:string` | C · `static composite` |
| success/variant | `200` · `reply:derived`, `mode:string` | P/U · `static composite` |
| error | `400` · `{error:string}` · `err.Error()` | C for direct writeErr; error helper 내부는 P |
| error | `401` · `{error:string}` · `"未授权"` / `"token 无效或已过期"` | C for direct writeErr; error helper 내부는 P |
| error | `404` · `{error:string}` · `"no active task"` | C for direct writeErr; error helper 내부는 P |
| error | `409` · `{error:string}` · `"任务正在删除，无法发送新消息"` / `"主 Agent 正在处理上一条消息，请稍候"` | C for direct writeErr; error helper 내부는 P |
| error | `500` · `{error:string}` · `err.Error()` | C for direct writeErr; error helper 내부는 P |

Helper 오류 분기: `prepareChatMentionMessage` · `server/chat_mentions.go::Server.prepareChatMentionMessage` · 위 `400, 500` status는 helper control-flow를 정적으로 펼친 P이며 호출 전/후 DB 효과는 operation별로 확인한다.

응답 schema 판정: **P** · top-level key는 직접 추출했지만 derived/helper/opaque 또는 nested type이 남음.

### 목록·동시성·완료

- Pagination/filter/sort: `task`.
- Idempotency/concurrency: Idempotency-Key 없음; unique constraint 또는 상태 check가 있는 경우만 중복을 제한(P).
- Side effect/completion: agent turn을 background context에서 실행할 수 있음; activity/stop/terminal event를 확인.
- 직접 협력자/실행 단서: `composeAgentMessage`, `context.WithCancelCause`, `ctx.Err`, `err.Error`, `filepath.Join`, `func() { defer func() { s.finishTaskChat(t.ID, cancel) }() emit := func(rec db.Activity) { rec.MainSeg = segPtr s.engine.emitActivity(t, rec) } maTaskID, _ := strconv.ParseInt(t.ID, 10, 64) resume := func() { s.reviveTask(t) } taskDir := filepath.Join(s.m.dir, "tasks", t.ID) agentMsg := composeAgentMessage(agentMessage, req.Attachments, taskDir) s.engine.BeginLLMCall(t.ID) _, err := ma.Chat(ctx, maTaskID, mainSeg, s.m.Assets(), t.Store, t.Goal, agentMsg, emit, t.Notify, resume, t.NotifyGoal, t.NotifyHint) s.engine.EndLLMCall(t.ID) if err != nil && ctx.Err() == nil { s.engine.emitActivity(t, db.Activity{Worker: "mainagent", Kind: "text", IsError: true, Summary: "（主 Agent 出错：" + err.Error() + "）", MainSeg: segPtr}) } }`, `func() { s.finishTaskChat(t.ID, cancel) }`, `intercept.WithReviewContext`, `json.NewDecoder`, `json.NewDecoder(r.Body).Decode`, `ma.Chat`, `r.URL.Query`, `r.URL.Query().Get`, `s.chatMu.Lock`, `s.chatMu.Unlock`, `s.engine.BeginLLMCall`, `s.engine.EndLLMCall`, `s.engine.IsDeleting` 외 5개; direct `go` statement는 발견. 이 목록은 helper 내부의 background launch나 transaction 경계를 펼치지 않으므로 그 부분은 P/U다.
- 종합 의미 판정: **P/U**; route/auth와 위에서 직접 확인한 branch·shape는 C지만, 모든 helper·derived response·중첩 DTO·외부 효과까지 완결됐다는 뜻은 아니다.

<a id="schema-post-api-chat-upload"></a>
## `POST /api/chat/upload`

구현: `chatUpload` · `server/chatupload.go` · [정확한 handler 근거](evidence:handler-post-api-chat-upload)

### 요청

| 위치/field | wire type | 필수·null | 기본/검증 |
|---|---|---|---|
| query `id` | `string` wire | 필수 | required `^[A-Za-z0-9_-]+$`; missing or invalid→400; task scope also requires an existing task |
| query `scope` | `string` wire | 필수 | required enum `task\|session\|staging`; missing or any other value→400; chooses tasks/sessions/drafts upload root |
| auth credential | Bearer header / `artex_token` cookie / query `token` | 세 위치 중 하나에 유효 JWT 필수 | `extractToken` 우선순위: header→cookie→query; query fallback은 SSE로 제한되지 않음 |
| multipart `file` (repeatable) | binary file | 1개 이상 필수 | request 128 MiB cap; filename 정규화·충돌 시 suffix |

요청 body 상한: **128 MiB** (`134217728` bytes) · source `server/chatupload.go::chatUpload` · 초과/decoder 경로 `400 "解析上传失败或超出大小限制: " + err.Error()`.

요청 판정: **C/P** · unknown field 거부 미설정.
직접 validation: `!s.engine.beginTaskOperation(id)` · `!safeChatID.MatchString(id)` · `err != nil` · `len(files) == 0` · `name == "" \|\| name == "." \|\| name == ".." \|\| strings.ContainsAny(name, \`/\\`)` · `s.m.ResolveTask(id) == nil` · `taskScoped`

### 응답·오류

| 종류 | status/body | 해석 상태 |
|---|---|---|
| success/variant | `200` · `attachments:derived` | P/U · `static composite` |
| error | `400` · `{error:string}` · `"scope 必须是 task / session / staging"` / `"非法 id"` / `"解析上传失败或超出大小限制: " + err.Error()` / `"缺少上传文件(表单字段 file)"` | C for direct writeErr; error helper 내부는 P |
| error | `401` · `{error:string}` · `"未授权"` / `"token 无效或已过期"` | C for direct writeErr; error helper 내부는 P |
| error | `404` · `{error:string}` · `"task not found"` | C for direct writeErr; error helper 내부는 P |
| error | `409` · `{error:string}` · `"任务正在删除，无法上传附件"` | C for direct writeErr; error helper 내부는 P |
| error | `500` · `{error:string}` · `"建目录失败: " + err.Error()` / `"保存失败: " + err.Error()` | C for direct writeErr; error helper 내부는 P |

응답 schema 판정: **P** · top-level key는 직접 추출했지만 derived/helper/opaque 또는 nested type이 남음.

### 목록·동시성·완료

- Pagination/filter/sort: 등록된 직접 query key 중 표준 paging/filter key 없음.
- Idempotency/concurrency: Idempotency-Key 없음; unique constraint 또는 상태 check가 있는 경우만 중복을 제한(P).
- Side effect/completion: handler의 직접 mutation/호출 후 응답; 외부 전달·agent 실행이 파생되면 별도 상태 확인(P).
- 직접 협력자/실행 단서: `err.Error`, `filepath.Base`, `filepath.Join`, `os.MkdirAll`, `r.ParseMultipartForm`, `r.URL.Query`, `r.URL.Query().Get`, `s.engine.beginTaskOperation`, `s.engine.decInflight`, `s.m.ResolveTask`, `safeChatID.MatchString`, `saveUpload`, `uniqueUploadPath`; direct `go` statement는 미발견. 이 목록은 helper 내부의 background launch나 transaction 경계를 펼치지 않으므로 그 부분은 P/U다.
- 종합 의미 판정: **P/U**; route/auth와 위에서 직접 확인한 branch·shape는 C지만, 모든 helper·derived response·중첩 DTO·외부 효과까지 완결됐다는 뜻은 아니다.

<a id="schema-get-api-tasks-id-chat-status"></a>
## `GET /api/tasks/{id}/chat/status`

구현: `taskChatStatus` · `server/server.go` · [정확한 handler 근거](evidence:handler-get-api-tasks-id-chat-status)

### 요청

| 위치/field | wire type | 필수·null | 기본/검증 |
|---|---|---|---|
| path `id` | `string` | 필수 | handler의 parse/range validation; 미해석 helper는 P |
| auth credential | Bearer header / `artex_token` cookie / query `token` | 세 위치 중 하나에 유효 JWT 필수 | `extractToken` 우선순위: header→cookie→query; query fallback은 SSE로 제한되지 않음 |
| body | 없음(직접 decoder/form read 미발견) | 해당 없음 | 해당 없음 |

요청 판정: **C/P** · unknown field 거부 미설정.
직접 validation: `t == nil`

### 응답·오류

| 종류 | status/body | 해석 상태 |
|---|---|---|
| success/variant | `200` · `running:derived` | P/U · `static composite` |
| error | `401` · `{error:string}` · `"未授权"` / `"token 无效或已过期"` | C for direct writeErr; error helper 내부는 P |
| error | `404` · `{error:string}` · `"task not found"` | C for direct writeErr; error helper 내부는 P |

응답 schema 판정: **P** · top-level key는 직접 추출했지만 derived/helper/opaque 또는 nested type이 남음.

### 목록·동시성·완료

- Pagination/filter/sort: 등록된 직접 query key 중 표준 paging/filter key 없음.
- Idempotency/concurrency: read/stream 요청; stream 재연결은 cursor 계약에 따름.
- Side effect/completion: 직접 조회/파일 전송 완료; 외부 조회가 있으면 응답 전 handler 결과만 보장.
- 직접 협력자/실행 단서: `r.PathValue`, `s.chatMu.Lock`, `s.chatMu.Unlock`, `s.m.ResolveTask`; direct `go` statement는 미발견. 이 목록은 helper 내부의 background launch나 transaction 경계를 펼치지 않으므로 그 부분은 P/U다.
- 종합 의미 판정: **P/U**; route/auth와 위에서 직접 확인한 branch·shape는 C지만, 모든 helper·derived response·중첩 DTO·외부 효과까지 완결됐다는 뜻은 아니다.

<a id="schema-post-api-tasks-id-chat-stop"></a>
## `POST /api/tasks/{id}/chat/stop`

구현: `stopChat` · `server/server.go` · [정확한 handler 근거](evidence:handler-post-api-tasks-id-chat-stop)

### 요청

| 위치/field | wire type | 필수·null | 기본/검증 |
|---|---|---|---|
| path `id` | `string` | 필수 | handler의 parse/range validation; 미해석 helper는 P |
| auth credential | Bearer header / `artex_token` cookie / query `token` | 세 위치 중 하나에 유효 JWT 필수 | `extractToken` 우선순위: header→cookie→query; query fallback은 SSE로 제한되지 않음 |
| body | 없음(직접 decoder/form read 미발견) | 해당 없음 | 해당 없음 |

요청 판정: **C/P** · unknown field 거부 미설정.
직접 validation: `!s.cancelTaskChat(t.ID, agent.AbortChatStoppedByUser)` · `t == nil`

### 응답·오류

| 종류 | status/body | 해석 상태 |
|---|---|---|
| success/variant | `200` · `status:string` | C · `static composite` |
| success/variant | `200` · `status:string` | C · `static composite` |
| error | `401` · `{error:string}` · `"未授权"` / `"token 无效或已过期"` | C for direct writeErr; error helper 내부는 P |
| error | `404` · `{error:string}` · `"task not found"` | C for direct writeErr; error helper 내부는 P |

응답 schema 판정: **C** · 직접 scalar/static top-level key와 field type을 추출; nested 내부 schema는 별도 type 참조가 없으면 P.

### 목록·동시성·완료

- Pagination/filter/sort: 등록된 직접 query key 중 표준 paging/filter key 없음.
- Idempotency/concurrency: Idempotency-Key 없음; unique constraint 또는 상태 check가 있는 경우만 중복을 제한(P).
- Side effect/completion: handler의 직접 mutation/호출 후 응답; 외부 전달·agent 실행이 파생되면 별도 상태 확인(P).
- 직접 협력자/실행 단서: `r.PathValue`, `s.m.ResolveTask`; direct `go` statement는 미발견. 이 목록은 helper 내부의 background launch나 transaction 경계를 펼치지 않으므로 그 부분은 P/U다.
- 종합 의미 판정: **P/U**; route/auth와 위에서 직접 확인한 branch·shape는 C지만, 모든 helper·derived response·중첩 DTO·외부 효과까지 완결됐다는 뜻은 아니다.

<a id="schema-delete-api-tasks-id"></a>
## `DELETE /api/tasks/{id}`

구현: `pgDeleteTask` · `server/server_mgmt.go` · [정확한 handler 근거](evidence:handler-delete-api-tasks-id)

### 요청

| 위치/field | wire type | 필수·null | 기본/검증 |
|---|---|---|---|
| path `id` | `string` | 필수 | handler의 parse/range validation; 미해석 helper는 P |
| auth credential | Bearer header / `artex_token` cookie / query `token` | 세 위치 중 하나에 유효 JWT 필수 | `extractToken` 우선순위: header→cookie→query; query fallback은 SSE로 제한되지 않음 |
| body `delete_assets` | `boolean` (`bool`) | 요구 여부 미확정(P); null/absent 구별 안 됨 | `Go zero value; 별도 기본은 handler/helper 확인` |
| body `delete_traffic` | `boolean` (`bool`) | 요구 여부 미확정(P); null/absent 구별 안 됨 | `Go zero value; 별도 기본은 handler/helper 확인` |
| body `delete_files` | `boolean` (`bool`) | 요구 여부 미확정(P); null/absent 구별 안 됨 | `Go zero value; 별도 기본은 handler/helper 확인` |
| body `delete_findings` | `boolean` (`bool`) | 요구 여부 미확정(P); null/absent 구별 안 됨 | `Go zero value; 별도 기본은 handler/helper 확인` |
| body `delete_llm_records` | `boolean` (`bool`) | 요구 여부 미확정(P); null/absent 구별 안 됨 | `Go zero value; 별도 기본은 handler/helper 확인` |

요청 판정: **C/P** · unknown field 거부 미설정.

### 응답·오류

| 종류 | status/body | 해석 상태 |
|---|---|---|
| success/variant | `200` · `result` | P/U · `derived variable` |
| error | `400` · `{error:string}` · `"任务 id 无效"` / `"invalid JSON: " + err.Error()` | C for direct writeErr; error helper 내부는 P |
| error | `401` · `{error:string}` · `"未授权"` / `"token 无效或已过期"` | C for direct writeErr; error helper 내부는 P |
| error | `409` · `{error:string}` · `"任务正在删除"` / `"任务仍有运行中的 Agent，删除已取消"` / `err.Error()` | C for direct writeErr; error helper 내부는 P |
| error | `500` · `{error:string}` · `err.Error()` | C for direct writeErr; error helper 내부는 P |

응답 schema 판정: **P** · top-level key는 직접 추출했지만 derived/helper/opaque 또는 nested type이 남음.

### 목록·동시성·완료

- Pagination/filter/sort: 등록된 직접 query key 중 표준 paging/filter key 없음.
- Idempotency/concurrency: Idempotency-Key/If-Match 없음; 반복 시 응답/존재 검사가 달라질 수 있음.
- Side effect/completion: handler의 직접 mutation/호출 후 응답; 외부 전달·agent 실행이 파생되면 별도 상태 확인(P).
- 직접 협력자/실행 단서: `cancelDrain`, `canonicalTaskID`, `context.WithTimeout`, `delete`, `err.Error`, `errors.As`, `func() { if !deleted { s.abortTaskDelete(id) } }`, `r.Context`, `r.PathValue`, `s.engine.StopTask`, `s.m.DeleteTask`, `s.taskAgentMu.Lock`, `s.taskAgentMu.Unlock`, `writeCommittedTaskDelete`; direct `go` statement는 미발견. 이 목록은 helper 내부의 background launch나 transaction 경계를 펼치지 않으므로 그 부분은 P/U다.
- 종합 의미 판정: **P/U**; route/auth와 위에서 직접 확인한 branch·shape는 C지만, 모든 helper·derived response·중첩 DTO·외부 효과까지 완결됐다는 뜻은 아니다.

<a id="schema-get-api-conversations"></a>
## `GET /api/conversations`

구현: `pgListConversations` · `server/conversations.go` · [정확한 handler 근거](evidence:handler-get-api-conversations)

### 요청

| 위치/field | wire type | 필수·null | 기본/검증 |
|---|---|---|---|
| auth credential | Bearer header / `artex_token` cookie / query `token` | 세 위치 중 하나에 유효 JWT 필수 | `extractToken` 우선순위: header→cookie→query; query fallback은 SSE로 제한되지 않음 |
| body | 없음(직접 decoder/form read 미발견) | 해당 없음 | 해당 없음 |

요청 판정: **C/P** · unknown field 거부 미설정.
직접 validation: `err != nil` · `pg == nil`

### 응답·오류

| 종류 | status/body | 해석 상태 |
|---|---|---|
| success/variant | `200` · `conversations:derived` | P/U · `static composite` |
| error | `401` · `{error:string}` · `"未授权"` / `"token 无效或已过期"` | C for direct writeErr; error helper 내부는 P |
| error | `500` · `{error:string}` · `err.Error()` | C for direct writeErr; error helper 내부는 P |
| error | `503` · `{error:string}` · `"PostgreSQL 不可用"` | C for direct writeErr; error helper 내부는 P |

응답 schema 판정: **P** · top-level key는 직접 추출했지만 derived/helper/opaque 또는 nested type이 남음.

### 목록·동시성·완료

- Pagination/filter/sort: 등록된 직접 query key 중 표준 paging/filter key 없음.
- Idempotency/concurrency: read/stream 요청; stream 재연결은 cursor 계약에 따름.
- Side effect/completion: 직접 조회/파일 전송 완료; 외부 조회가 있으면 응답 전 handler 결과만 보장.
- 직접 협력자/실행 단서: `err.Error`, `pg.ListConversations`, `s.chatMu.Lock`, `s.chatMu.Unlock`; direct `go` statement는 미발견. 이 목록은 helper 내부의 background launch나 transaction 경계를 펼치지 않으므로 그 부분은 P/U다.
- 종합 의미 판정: **P/U**; route/auth와 위에서 직접 확인한 branch·shape는 C지만, 모든 helper·derived response·중첩 DTO·외부 효과까지 완결됐다는 뜻은 아니다.

<a id="schema-post-api-conversations"></a>
## `POST /api/conversations`

구현: `pgCreateConversation` · `server/conversations.go` · [정확한 handler 근거](evidence:handler-post-api-conversations)

### 요청

| 위치/field | wire type | 필수·null | 기본/검증 |
|---|---|---|---|
| auth credential | Bearer header / `artex_token` cookie / query `token` | 세 위치 중 하나에 유효 JWT 필수 | `extractToken` 우선순위: header→cookie→query; query fallback은 SSE로 제한되지 않음 |
| body `agent_key` | `string` (`string`) | 필수(직접 거부 branch 확인); null/absent 구별 안 됨 | `trim 후 빈 문자열이면 400 후 return` |
| body `title` | `string` (`string`) | 요구 여부 미확정(P); null/absent 구별 안 됨 | `Go zero value; 별도 기본은 handler/helper 확인` |
| body `llm_profile_id` | `integer` (`*int64`) | 요구 여부 미확정(P); absent/null→nil; null→nil | `req.LLMProfileID != nil` |

요청 body 상한: **64 KiB** (`65536` bytes) · source `server/conversations.go::decodeConversationRequest` · 초과/decoder 경로 `413 "请求正文过大"`.

요청 판정: **C/P** · unknown field 거부 미설정.
직접 validation: `req.AgentKey == ""` · `req.LLMProfileID != nil` · `utf8.RuneCountInString(req.AgentKey) > maxConversationAgentKeyRunes`
공유 decoder/helper: `decodeConversationRequest`; helper 내부 size/presence validation은 같은 source evidence에서 확인하며 operation 단독 AST 판정은 P.

### 응답·오류

| 종류 | status/body | 해석 상태 |
|---|---|---|
| success/variant | `200` · `c` | P/U · `derived variable` |
| error | `400` · `{error:string}` · `"agent_key 不能为空"` / `fmt.Sprintf("agent_key 最多 %d 个字符", maxConversationAgentKeyRunes)` / `"指定的 LLM 配置不存在或未设置 API Key"` / `fmt.Sprintf("标题最多 %d 个字符", maxConversationTitleRunes)` | C for direct writeErr; error helper 내부는 P |
| error | `401` · `{error:string}` · `"未授权"` / `"token 无效或已过期"` | C for direct writeErr; error helper 내부는 P |
| error | `404` · `{error:string}` · `"agent 不存在"` | C for direct writeErr; error helper 내부는 P |
| error | `413` · `{error:string}` · `"请求正文过大"` | C for direct writeErr; error helper 내부는 P |
| error | `500` · `{error:string}` · `err.Error()` | C for direct writeErr; error helper 내부는 P |
| error | `503` · `{error:string}` · `"PostgreSQL 不可用"` | C for direct writeErr; error helper 내부는 P |

응답 schema 판정: **P** · top-level key는 직접 추출했지만 derived/helper/opaque 또는 nested type이 남음.

### 목록·동시성·완료

- Pagination/filter/sort: 등록된 직접 query key 중 표준 paging/filter key 없음.
- Idempotency/concurrency: Idempotency-Key 없음; unique constraint 또는 상태 check가 있는 경우만 중복을 제한(P).
- Side effect/completion: handler의 직접 mutation/호출 후 응답; 외부 전달·agent 실행이 파생되면 별도 상태 확인(P).
- 직접 협력자/실행 단서: `decodeConversationRequest`, `err.Error`, `pg.CreateConversation`, `pg.GetAgentByKey`, `utf8.RuneCountInString`; direct `go` statement는 미발견. 이 목록은 helper 내부의 background launch나 transaction 경계를 펼치지 않으므로 그 부분은 P/U다.
- 종합 의미 판정: **P/U**; route/auth와 위에서 직접 확인한 branch·shape는 C지만, 모든 helper·derived response·중첩 DTO·외부 효과까지 완결됐다는 뜻은 아니다.

<a id="schema-post-api-conversations-delete-batch"></a>
## `POST /api/conversations/delete/batch`

구현: `pgDeleteConversationsBatch` · `server/conversations.go` · [정확한 handler 근거](evidence:handler-post-api-conversations-delete-batch)

### 요청

| 위치/field | wire type | 필수·null | 기본/검증 |
|---|---|---|---|
| auth credential | Bearer header / `artex_token` cookie / query `token` | 세 위치 중 하나에 유효 JWT 필수 | `extractToken` 우선순위: header→cookie→query; query fallback은 SSE로 제한되지 않음 |
| body `ids` | `array[integer]` (`[]int64`) | 필수(직접 거부 branch 확인); null/absent 구별 안 됨 | `정규화 뒤 1..100개가 아니거나 non-positive id면 400` |

요청 body 상한: **64 KiB** (`65536` bytes) · source `server/conversations.go::decodeConversationRequest` · 초과/decoder 경로 `413 "请求正文过大"`.

요청 판정: **C/P** · unknown field 거부 미설정.
공유 decoder/helper: `decodeConversationRequest`; helper 내부 size/presence validation은 같은 source evidence에서 확인하며 operation 단독 AST 판정은 P.

### 응답·오류

| 종류 | status/body | 해석 상태 |
|---|---|---|
| success/variant | `200` · `items:derived` | P/U · `static composite` |
| error | `400` · `{error:string}` · `"对话 id 无效"` / `fmt.Sprintf("ids 数量必须为 1-%d", maxConversationDeleteBatch)` | C for direct writeErr; error helper 내부는 P |
| error | `401` · `{error:string}` · `"未授权"` / `"token 无效或已过期"` | C for direct writeErr; error helper 내부는 P |
| error | `413` · `{error:string}` · `"请求正文过大"` | C for direct writeErr; error helper 내부는 P |
| error | `500` · `{error:string}` · `err.Error()` | C for direct writeErr; error helper 내부는 P |
| error | `503` · `{error:string}` · `"PostgreSQL 不可用"` | C for direct writeErr; error helper 내부는 P |

응답 schema 판정: **P** · top-level key는 직접 추출했지만 derived/helper/opaque 또는 nested type이 남음.

### 목록·동시성·완료

- Pagination/filter/sort: 등록된 직접 query key 중 표준 paging/filter key 없음.
- Idempotency/concurrency: Idempotency-Key 없음; unique constraint 또는 상태 check가 있는 경우만 중복을 제한(P).
- Side effect/completion: handler의 직접 mutation/호출 후 응답; 외부 전달·agent 실행이 파생되면 별도 상태 확인(P).
- 직접 협력자/실행 단서: `decodeConversationRequest`, `err.Error`, `pg.DeleteConversations`; direct `go` statement는 미발견. 이 목록은 helper 내부의 background launch나 transaction 경계를 펼치지 않으므로 그 부분은 P/U다.
- 종합 의미 판정: **P/U**; route/auth와 위에서 직접 확인한 branch·shape는 C지만, 모든 helper·derived response·중첩 DTO·외부 효과까지 완결됐다는 뜻은 아니다.

<a id="schema-patch-api-conversations-id"></a>
## `PATCH /api/conversations/{id}`

구현: `pgRenameConversation` · `server/conversations.go` · [정확한 handler 근거](evidence:handler-patch-api-conversations-id)

### 요청

| 위치/field | wire type | 필수·null | 기본/검증 |
|---|---|---|---|
| path `id` | `string` | 필수 | handler의 parse/range validation; 미해석 helper는 P |
| auth credential | Bearer header / `artex_token` cookie / query `token` | 세 위치 중 하나에 유효 JWT 필수 | `extractToken` 우선순위: header→cookie→query; query fallback은 SSE로 제한되지 않음 |
| body `title` | `string` (`*string`) | 개별 선택(그룹 중 하나 필수); null→nil | `title/pinned 중 하나 필요` |
| body `pinned` | `boolean` (`*bool`) | 개별 선택(그룹 중 하나 필수); null→nil | `title/pinned 중 하나 필요` |

요청 body 상한: **64 KiB** (`65536` bytes) · source `server/conversations.go::decodeConversationRequest` · 초과/decoder 경로 `413 "请求正文过大"`.

요청 판정: **C/P** · unknown field 거부 미설정.
직접 validation: `req.Title != nil` · `req.Title == nil && req.Pinned == nil`
공유 decoder/helper: `decodeConversationRequest`; helper 내부 size/presence validation은 같은 source evidence에서 확인하며 operation 단독 AST 판정은 P.

### 응답·오류

| 종류 | status/body | 해석 상태 |
|---|---|---|
| success/variant | `200` · `updated` | P/U · `derived variable` |
| error | `400` · `{error:string}` · `"至少需要提供 title 或 pinned"` / `"标题不能为空"` / `fmt.Sprintf("标题最多 %d 个字符", maxConversationTitleRunes)` / `"bad conversation id"` | C for direct writeErr; error helper 내부는 P |
| error | `401` · `{error:string}` · `"未授权"` / `"token 无效或已过期"` | C for direct writeErr; error helper 내부는 P |
| error | `404` · `{error:string}` · `"conversation not found"` | C for direct writeErr; error helper 내부는 P |
| error | `413` · `{error:string}` · `"请求正文过大"` | C for direct writeErr; error helper 내부는 P |
| error | `500` · `{error:string}` · `err.Error()` | C for direct writeErr; error helper 내부는 P |
| error | `503` · `{error:string}` · `"PostgreSQL 不可用"` | C for direct writeErr; error helper 내부는 P |

공통 helper 오류 분기: `convByID` (`server/conversations.go::Server.convByID`: 503/400/500/404). Status union은 helper control-flow를 정적으로 펼친 P이며 runtime DB 결과는 관찰하지 않았다.

응답 schema 판정: **P** · top-level key는 직접 추출했지만 derived/helper/opaque 또는 nested type이 남음.

### 목록·동시성·완료

- Pagination/filter/sort: 등록된 직접 query key 중 표준 paging/filter key 없음.
- Idempotency/concurrency: Idempotency-Key/If-Match 없음; DB upsert/trigger/side effect 때문에 반복 의미를 handler별 확인(P).
- Side effect/completion: handler의 직접 mutation/호출 후 응답; 외부 전달·agent 실행이 파생되면 별도 상태 확인(P).
- 직접 협력자/실행 단서: `decodeConversationRequest`, `err.Error`, `pg.UpdateConversation`, `utf8.RuneCountInString`; direct `go` statement는 미발견. 이 목록은 helper 내부의 background launch나 transaction 경계를 펼치지 않으므로 그 부분은 P/U다.
- 종합 의미 판정: **P/U**; route/auth와 위에서 직접 확인한 branch·shape는 C지만, 모든 helper·derived response·중첩 DTO·외부 효과까지 완결됐다는 뜻은 아니다.

<a id="schema-patch-api-conversations-id-profile"></a>
## `PATCH /api/conversations/{id}/profile`

구현: `pgUpdateConversation` · `server/conversations.go` · [정확한 handler 근거](evidence:handler-patch-api-conversations-id-profile)

### 요청

| 위치/field | wire type | 필수·null | 기본/검증 |
|---|---|---|---|
| path `id` | `string` | 필수 | handler의 parse/range validation; 미해석 helper는 P |
| auth credential | Bearer header / `artex_token` cookie / query `token` | 세 위치 중 하나에 유효 JWT 필수 | `extractToken` 우선순위: header→cookie→query; query fallback은 SSE로 제한되지 않음 |
| body `llm_profile_id` | `integer` (`*int64`) | 요구 여부 미확정(P); absent/null→nil; null→nil | `req.LLMProfileID != nil` |

요청 body 상한: **64 KiB** (`65536` bytes) · source `server/conversations.go::decodeConversationRequest` · 초과/decoder 경로 `413 "请求正文过大"`.

요청 판정: **C/P** · unknown field 거부 미설정.
직접 validation: `req.LLMProfileID != nil`
공유 decoder/helper: `decodeConversationRequest`; helper 내부 size/presence validation은 같은 source evidence에서 확인하며 operation 단독 AST 판정은 P.

### 응답·오류

| 종류 | status/body | 해석 상태 |
|---|---|---|
| success/variant | `200` · `ok:boolean` | C · `static composite` |
| error | `400` · `{error:string}` · `"指定的 LLM 配置不存在或未设置 API Key"` / `"bad conversation id"` | C for direct writeErr; error helper 내부는 P |
| error | `401` · `{error:string}` · `"未授权"` / `"token 无效或已过期"` | C for direct writeErr; error helper 내부는 P |
| error | `404` · `{error:string}` · `"conversation not found"` | C for direct writeErr; error helper 내부는 P |
| error | `413` · `{error:string}` · `"请求正文过大"` | C for direct writeErr; error helper 내부는 P |
| error | `500` · `{error:string}` · `err.Error()` | C for direct writeErr; error helper 내부는 P |
| error | `503` · `{error:string}` · `"PostgreSQL 不可用"` | C for direct writeErr; error helper 내부는 P |

공통 helper 오류 분기: `convByID` (`server/conversations.go::Server.convByID`: 503/400/500/404). Status union은 helper control-flow를 정적으로 펼친 P이며 runtime DB 결과는 관찰하지 않았다.

응답 schema 판정: **C** · 직접 scalar/static top-level key와 field type을 추출; nested 내부 schema는 별도 type 참조가 없으면 P.

### 목록·동시성·완료

- Pagination/filter/sort: 등록된 직접 query key 중 표준 paging/filter key 없음.
- Idempotency/concurrency: Idempotency-Key/If-Match 없음; DB upsert/trigger/side effect 때문에 반복 의미를 handler별 확인(P).
- Side effect/completion: handler의 직접 mutation/호출 후 응답; 외부 전달·agent 실행이 파생되면 별도 상태 확인(P).
- 직접 협력자/실행 단서: `decodeConversationRequest`, `err.Error`, `pg.UpdateConversationProfile`; direct `go` statement는 미발견. 이 목록은 helper 내부의 background launch나 transaction 경계를 펼치지 않으므로 그 부분은 P/U다.
- 종합 의미 판정: **P/U**; route/auth와 위에서 직접 확인한 branch·shape는 C지만, 모든 helper·derived response·중첩 DTO·외부 효과까지 완결됐다는 뜻은 아니다.

<a id="schema-delete-api-conversations-id"></a>
## `DELETE /api/conversations/{id}`

구현: `pgDeleteConversation` · `server/conversations.go` · [정확한 handler 근거](evidence:handler-delete-api-conversations-id)

### 요청

| 위치/field | wire type | 필수·null | 기본/검증 |
|---|---|---|---|
| path `id` | `string` | 필수 | handler의 parse/range validation; 미해석 helper는 P |
| auth credential | Bearer header / `artex_token` cookie / query `token` | 세 위치 중 하나에 유효 JWT 필수 | `extractToken` 우선순위: header→cookie→query; query fallback은 SSE로 제한되지 않음 |
| body | 없음(직접 decoder/form read 미발견) | 해당 없음 | 해당 없음 |

요청 판정: **C/P** · unknown field 거부 미설정.
직접 validation: `!ok` · `err != nil`

### 응답·오류

| 종류 | status/body | 해석 상태 |
|---|---|---|
| success/variant | `200` · `deleted:expression` | P/U · `static composite` |
| error | `400` · `{error:string}` · `"bad conversation id"` | C for direct writeErr; error helper 내부는 P |
| error | `401` · `{error:string}` · `"未授权"` / `"token 无效或已过期"` | C for direct writeErr; error helper 내부는 P |
| error | `404` · `{error:string}` · `"conversation not found"` | C for direct writeErr; error helper 내부는 P |
| error | `500` · `{error:string}` · `err.Error()` | C for direct writeErr; error helper 내부는 P |
| error | `503` · `{error:string}` · `"PostgreSQL 不可用"` | C for direct writeErr; error helper 내부는 P |

공통 helper 오류 분기: `convByID` (`server/conversations.go::Server.convByID`: 503/400/500/404). Status union은 helper control-flow를 정적으로 펼친 P이며 runtime DB 결과는 관찰하지 않았다.

응답 schema 판정: **P** · top-level key는 직접 추출했지만 derived/helper/opaque 또는 nested type이 남음.

### 목록·동시성·완료

- Pagination/filter/sort: 등록된 직접 query key 중 표준 paging/filter key 없음.
- Idempotency/concurrency: Idempotency-Key/If-Match 없음; 반복 시 응답/존재 검사가 달라질 수 있음.
- Side effect/completion: handler의 직접 mutation/호출 후 응답; 외부 전달·agent 실행이 파생되면 별도 상태 확인(P).
- 직접 협력자/실행 단서: `err.Error`, `pg.DeleteConversation`; direct `go` statement는 미발견. 이 목록은 helper 내부의 background launch나 transaction 경계를 펼치지 않으므로 그 부분은 P/U다.
- 종합 의미 판정: **P/U**; route/auth와 위에서 직접 확인한 branch·shape는 C지만, 모든 helper·derived response·중첩 DTO·외부 효과까지 완결됐다는 뜻은 아니다.

<a id="schema-get-api-conversations-id-messages"></a>
## `GET /api/conversations/{id}/messages`

구현: `pgConversationMessages` · `server/conversations.go` · [정확한 handler 근거](evidence:handler-get-api-conversations-id-messages)

### 요청

| 위치/field | wire type | 필수·null | 기본/검증 |
|---|---|---|---|
| path `id` | `string` | 필수 | handler의 parse/range validation; 미해석 helper는 P |
| query `before` | `string` wire | 선택 | handler/helper 변환·default 미전개(P) |
| query `limit` | `string` wire | 선택 | handler/helper 변환·default 미전개(P) |
| query `since` | `string` wire | 선택 | handler/helper 변환·default 미전개(P) |
| auth credential | Bearer header / `artex_token` cookie / query `token` | 세 위치 중 하나에 유효 JWT 필수 | `extractToken` 우선순위: header→cookie→query; query fallback은 SSE로 제한되지 않음 |
| body | 없음(직접 decoder/form read 미발견) | 해당 없음 | 해당 없음 |

요청 판정: **C/P** · unknown field 거부 미설정.
직접 validation: `!ok` · `err != nil` · `n > 0` · `sv != ""`
직접 parse/default: `atoiDefault(q.Get("limit"), 200)` · `strconv.ParseInt(q.Get("before"), 10, 64)` · `strconv.ParseInt(sv, 10, 64)`

### 응답·오류

| 종류 | status/body | 해석 상태 |
|---|---|---|
| success/variant | `200` · `items:helper result`, `cursor:derived`, `running:derived`, `hasMore:boolean` | P/U · `static composite` |
| success/variant | `200` · `items:helper result`, `cursor:derived`, `running:derived`, `hasMore:derived` | P/U · `static composite` |
| error | `400` · `{error:string}` · `"bad conversation id"` | C for direct writeErr; error helper 내부는 P |
| error | `401` · `{error:string}` · `"未授权"` / `"token 无效或已过期"` | C for direct writeErr; error helper 내부는 P |
| error | `404` · `{error:string}` · `"conversation not found"` | C for direct writeErr; error helper 내부는 P |
| error | `500` · `{error:string}` · `err.Error()` | C for direct writeErr; error helper 내부는 P |
| error | `503` · `{error:string}` · `"PostgreSQL 不可用"` | C for direct writeErr; error helper 내부는 P |

공통 helper 오류 분기: `convByID` (`server/conversations.go::Server.convByID`: 503/400/500/404). Status union은 helper control-flow를 정적으로 펼친 P이며 runtime DB 결과는 관찰하지 않았다.

응답 schema 판정: **P** · top-level key는 직접 추출했지만 derived/helper/opaque 또는 nested type이 남음.

### 목록·동시성·완료

- Pagination/filter/sort: `before`, `limit`, `since`.
- Idempotency/concurrency: read/stream 요청; stream 재연결은 cursor 계약에 따름.
- Side effect/completion: agent turn을 background context에서 실행할 수 있음; activity/stop/terminal event를 확인.
- 직접 협력자/실행 단서: `activityDTOs`, `atoiDefault`, `err.Error`, `max`, `pg.ConvActivityList`, `pg.ConvActivityPage`, `q.Get`, `r.URL.Query`, `s.chatMu.Lock`, `s.chatMu.Unlock`; direct `go` statement는 미발견. 이 목록은 helper 내부의 background launch나 transaction 경계를 펼치지 않으므로 그 부분은 P/U다.
- 종합 의미 판정: **P/U**; route/auth와 위에서 직접 확인한 branch·shape는 C지만, 모든 helper·derived response·중첩 DTO·외부 효과까지 완결됐다는 뜻은 아니다.

<a id="schema-post-api-conversations-id-messages"></a>
## `POST /api/conversations/{id}/messages`

구현: `pgSendConversationMessage` · `server/conversations.go` · [정확한 handler 근거](evidence:handler-post-api-conversations-id-messages)

### 요청

| 위치/field | wire type | 필수·null | 기본/검증 |
|---|---|---|---|
| path `id` | `string` | 필수 | handler의 parse/range validation; 미해석 helper는 P |
| auth credential | Bearer header / `artex_token` cookie / query `token` | 세 위치 중 하나에 유효 JWT 필수 | `extractToken` 우선순위: header→cookie→query; query fallback은 SSE로 제한되지 않음 |
| body `message` | `string` (`string`) | 개별 선택(그룹 중 하나 필수); null/absent 구별 안 됨 | `message/attachments 중 하나 필요` |
| body `attachments` | [array[chatAttachment]](#type-server-chatattachment) (`[]chatAttachment`) | 개별 선택(그룹 중 하나 필수); null/absent 구별 안 됨 | `message/attachments 중 하나 필요` |

요청 판정: **C/P** · unknown field 거부 미설정.
직접 validation: `msg == "" && len(req.Attachments) == 0`

### 응답·오류

| 종류 | status/body | 해석 상태 |
|---|---|---|
| success/variant | `202` · `status:string` | C · `static composite` |
| error | `400` · `{error:string}` · `err.Error()` / `"消息不能为空"` / `"bad conversation id"` | C for direct writeErr; error helper 내부는 P |
| error | `401` · `{error:string}` · `"未授权"` / `"token 无效或已过期"` | C for direct writeErr; error helper 내부는 P |
| error | `404` · `{error:string}` · `"conversation not found"` | C for direct writeErr; error helper 내부는 P |
| error | `409` · `{error:string}` · `"该会话正在处理上一条消息，请稍候"` | C for direct writeErr; error helper 내부는 P |
| error | `500` · `{error:string}` · `err.Error()` | C for direct writeErr; error helper 내부는 P |
| error | `503` · `{error:string}` · `s.chatUnavailableReason()` / `"PostgreSQL 不可用"` | C for direct writeErr; error helper 내부는 P |

Helper 오류 분기: `prepareChatMentionMessage` · `server/chat_mentions.go::Server.prepareChatMentionMessage` · 위 `400, 500` status는 helper control-flow를 정적으로 펼친 P이며 호출 전/후 DB 효과는 operation별로 확인한다.

공통 helper 오류 분기: `convByID` (`server/conversations.go::Server.convByID`: 503/400/500/404). Status union은 helper control-flow를 정적으로 펼친 P이며 runtime DB 결과는 관찰하지 않았다.

응답 schema 판정: **C** · 직접 scalar/static top-level key와 field type을 추출; nested 내부 schema는 별도 type 참조가 없으면 P.

### 목록·동시성·완료

- Pagination/filter/sort: 등록된 직접 query key 중 표준 paging/filter key 없음.
- Idempotency/concurrency: Idempotency-Key 없음; unique constraint 또는 상태 check가 있는 경우만 중복을 제한(P).
- Side effect/completion: agent turn을 background context에서 실행할 수 있음; activity/stop/terminal event를 확인.
- 직접 협력자/실행 단서: `composeAgentMessage`, `err.Error`, `filepath.Join`, `firstLine`, `log.Printf`, `pg.AppendConvActivity`, `pg.RenameConversation`, `pg.TouchConversation`, `s.chatMu.Lock`, `s.chatMu.Unlock`, `userActivityWithAttachments`; direct `go` statement는 미발견. 이 목록은 helper 내부의 background launch나 transaction 경계를 펼치지 않으므로 그 부분은 P/U다.
- 종합 의미 판정: **P/U**; route/auth와 위에서 직접 확인한 branch·shape는 C지만, 모든 helper·derived response·중첩 DTO·외부 효과까지 완결됐다는 뜻은 아니다.

<a id="schema-post-api-conversations-id-stop"></a>
## `POST /api/conversations/{id}/stop`

구현: `pgStopConversation` · `server/conversations.go` · [정확한 handler 근거](evidence:handler-post-api-conversations-id-stop)

### 요청

| 위치/field | wire type | 필수·null | 기본/검증 |
|---|---|---|---|
| path `id` | `string` | 필수 | handler의 parse/range validation; 미해석 helper는 P |
| auth credential | Bearer header / `artex_token` cookie / query `token` | 세 위치 중 하나에 유효 JWT 필수 | `extractToken` 우선순위: header→cookie→query; query fallback은 SSE로 제한되지 않음 |
| body | 없음(직접 decoder/form read 미발견) | 해당 없음 | 해당 없음 |

요청 판정: **C/P** · unknown field 거부 미설정.
직접 validation: `!ok` · `cancel == nil`

### 응답·오류

| 종류 | status/body | 해석 상태 |
|---|---|---|
| success/variant | `200` · `status:string` | C · `static composite` |
| success/variant | `200` · `status:string` | C · `static composite` |
| error | `400` · `{error:string}` · `"bad conversation id"` | C for direct writeErr; error helper 내부는 P |
| error | `401` · `{error:string}` · `"未授权"` / `"token 无效或已过期"` | C for direct writeErr; error helper 내부는 P |
| error | `404` · `{error:string}` · `"conversation not found"` | C for direct writeErr; error helper 내부는 P |
| error | `500` · `{error:string}` · `err.Error()` | C for direct writeErr; error helper 내부는 P |
| error | `503` · `{error:string}` · `"PostgreSQL 不可用"` | C for direct writeErr; error helper 내부는 P |

공통 helper 오류 분기: `convByID` (`server/conversations.go::Server.convByID`: 503/400/500/404). Status union은 helper control-flow를 정적으로 펼친 P이며 runtime DB 결과는 관찰하지 않았다.

응답 schema 판정: **C** · 직접 scalar/static top-level key와 field type을 추출; nested 내부 schema는 별도 type 참조가 없으면 P.

### 목록·동시성·완료

- Pagination/filter/sort: 등록된 직접 query key 중 표준 paging/filter key 없음.
- Idempotency/concurrency: Idempotency-Key 없음; unique constraint 또는 상태 check가 있는 경우만 중복을 제한(P).
- Side effect/completion: handler의 직접 mutation/호출 후 응답; 외부 전달·agent 실행이 파생되면 별도 상태 확인(P).
- 직접 협력자/실행 단서: `cancel`, `s.chatMu.Lock`, `s.chatMu.Unlock`; direct `go` statement는 미발견. 이 목록은 helper 내부의 background launch나 transaction 경계를 펼치지 않으므로 그 부분은 P/U다.
- 종합 의미 판정: **P/U**; route/auth와 위에서 직접 확인한 branch·shape는 C지만, 모든 helper·derived response·중첩 DTO·외부 효과까지 완결됐다는 뜻은 아니다.

<a id="schema-get-api-conversations-id-messages-seq"></a>
## `GET /api/conversations/{id}/messages/{seq}`

구현: `pgConversationMsgDetail` · `server/conversations.go` · [정확한 handler 근거](evidence:handler-get-api-conversations-id-messages-seq)

### 요청

| 위치/field | wire type | 필수·null | 기본/검증 |
|---|---|---|---|
| path `id` | `string` | 필수 | handler의 parse/range validation; 미해석 helper는 P |
| path `seq` | `string` | 필수 | handler의 parse/range validation; 미해석 helper는 P |
| auth credential | Bearer header / `artex_token` cookie / query `token` | 세 위치 중 하나에 유효 JWT 필수 | `extractToken` 우선순위: header→cookie→query; query fallback은 SSE로 제한되지 않음 |
| body | 없음(직접 decoder/form read 미발견) | 해당 없음 | 해당 없음 |

요청 판정: **C/P** · unknown field 거부 미설정.
직접 validation: `!ok` · `err != nil`

### 응답·오류

| 종류 | status/body | 해석 상태 |
|---|---|---|
| success/variant | `200` · `detail:derived` | P/U · `static composite` |
| error | `400` · `{error:string}` · `"bad seq"` / `"bad conversation id"` | C for direct writeErr; error helper 내부는 P |
| error | `401` · `{error:string}` · `"未授权"` / `"token 无效或已过期"` | C for direct writeErr; error helper 내부는 P |
| error | `404` · `{error:string}` · `"conversation not found"` | C for direct writeErr; error helper 내부는 P |
| error | `500` · `{error:string}` · `err.Error()` | C for direct writeErr; error helper 내부는 P |
| error | `503` · `{error:string}` · `"PostgreSQL 不可用"` | C for direct writeErr; error helper 내부는 P |

공통 helper 오류 분기: `convByID` (`server/conversations.go::Server.convByID`: 503/400/500/404). Status union은 helper control-flow를 정적으로 펼친 P이며 runtime DB 결과는 관찰하지 않았다.

응답 schema 판정: **P** · top-level key는 직접 추출했지만 derived/helper/opaque 또는 nested type이 남음.

### 목록·동시성·완료

- Pagination/filter/sort: 등록된 직접 query key 중 표준 paging/filter key 없음.
- Idempotency/concurrency: read/stream 요청; stream 재연결은 cursor 계약에 따름.
- Side effect/completion: 직접 조회/파일 전송 완료; 외부 조회가 있으면 응답 전 handler 결과만 보장.
- 직접 협력자/실행 단서: `err.Error`, `pathInt`, `pg.ConvActivityDetail`; direct `go` statement는 미발견. 이 목록은 helper 내부의 background launch나 transaction 경계를 펼치지 않으므로 그 부분은 P/U다.
- 종합 의미 판정: **P/U**; route/auth와 위에서 직접 확인한 branch·shape는 C지만, 모든 helper·derived response·중첩 DTO·외부 효과까지 완결됐다는 뜻은 아니다.

<a id="schema-get-api-agents"></a>
## `GET /api/agents`

구현: `pgListAgents` · `server/server_mgmt.go` · [정확한 handler 근거](evidence:handler-get-api-agents)

### 요청

| 위치/field | wire type | 필수·null | 기본/검증 |
|---|---|---|---|
| auth credential | Bearer header / `artex_token` cookie / query `token` | 세 위치 중 하나에 유효 JWT 필수 | `extractToken` 우선순위: header→cookie→query; query fallback은 SSE로 제한되지 않음 |
| body | 없음(직접 decoder/form read 미발견) | 해당 없음 | 해당 없음 |

요청 판정: **C/P** · unknown field 거부 미설정.
직접 validation: `err != nil` · `err == nil` · `pg == nil`

### 응답·오류

| 종류 | status/body | 해석 상태 |
|---|---|---|
| success/variant | `200` · `agents:derived` | P/U · `static composite` |
| error | `401` · `{error:string}` · `"未授权"` / `"token 无效或已过期"` | C for direct writeErr; error helper 내부는 P |
| error | `500` · `{error:string}` · `err.Error()` | C for direct writeErr; error helper 내부는 P |
| error | `503` · `{error:string}` · `"PostgreSQL 不可用"` | C for direct writeErr; error helper 내부는 P |

응답 schema 판정: **P** · top-level key는 직접 추출했지만 derived/helper/opaque 또는 nested type이 남음.

### 목록·동시성·완료

- Pagination/filter/sort: 등록된 직접 query key 중 표준 paging/filter key 없음.
- Idempotency/concurrency: read/stream 요청; stream 재연결은 cursor 계약에 따름.
- Side effect/completion: 직접 조회/파일 전송 완료; 외부 조회가 있으면 응답 전 handler 결과만 보장.
- 직접 협력자/실행 단서: `agentDTOs`, `err.Error`, `pg.AgentBindingCounts`, `pg.ListAgents`; direct `go` statement는 미발견. 이 목록은 helper 내부의 background launch나 transaction 경계를 펼치지 않으므로 그 부분은 P/U다.
- 종합 의미 판정: **P/U**; route/auth와 위에서 직접 확인한 branch·shape는 C지만, 모든 helper·derived response·중첩 DTO·외부 효과까지 완결됐다는 뜻은 아니다.

<a id="schema-post-api-agents"></a>
## `POST /api/agents`

구현: `pgCreateAgent` · `server/server_mgmt.go` · [정확한 handler 근거](evidence:handler-post-api-agents)

### 요청

| 위치/field | wire type | 필수·null | 기본/검증 |
|---|---|---|---|
| auth credential | Bearer header / `artex_token` cookie / query `token` | 세 위치 중 하나에 유효 JWT 필수 | `extractToken` 우선순위: header→cookie→query; query fallback은 SSE로 제한되지 않음 |
| body `Key` | `string` (`string`) | 필수(직접 거부 branch 확인); null/absent 구별 안 됨 | `trim 뒤 key 정규식이 실패하면 400 후 return` |
| body `Name` | `string` (`string`) | 필수(직접 거부 branch 확인); null/absent 구별 안 됨 | `trim 후 빈 문자열이면 400 후 return` |
| body `Description` | `string` (`string`) | 요구 여부 미확정(P); null/absent 구별 안 됨 | `Go zero value; 별도 기본은 handler/helper 확인` |

요청 판정: **C/P** · unknown field 거부 미설정.
직접 validation: `!reAgentKey.MatchString(req.Key)` · `req.Name == ""`

### 응답·오류

| 종류 | status/body | 해석 상태 |
|---|---|---|
| success/variant | `200` · `agentDTO(a)` | P/U · `helper result` |
| error | `400` · `{error:string}` · `err.Error()` / `"key 需小写字母开头，仅含小写字母/数字/下划线"` / `"名称不能为空"` | C for direct writeErr; error helper 내부는 P |
| error | `401` · `{error:string}` · `"未授权"` / `"token 无效或已过期"` | C for direct writeErr; error helper 내부는 P |
| error | `409` · `{error:string}` · `"该 key 已存在"` | C for direct writeErr; error helper 내부는 P |
| error | `500` · `{error:string}` · `err.Error()` | C for direct writeErr; error helper 내부는 P |
| error | `503` · `{error:string}` · `"PostgreSQL 不可用"` | C for direct writeErr; error helper 내부는 P |

응답 schema 판정: **P** · top-level key는 직접 추출했지만 derived/helper/opaque 또는 nested type이 남음.

### 목록·동시성·완료

- Pagination/filter/sort: 등록된 직접 query key 중 표준 paging/filter key 없음.
- Idempotency/concurrency: Idempotency-Key 없음; unique constraint 또는 상태 check가 있는 경우만 중복을 제한(P).
- Side effect/completion: handler의 직접 mutation/호출 후 응답; 외부 전달·agent 실행이 파생되면 별도 상태 확인(P).
- 직접 협력자/실행 단서: `agentDTO`, `err.Error`, `log.Printf`, `pg.CreateAgent`, `pg.GetAgentByKey`, `pg.SeedPromptIfEmpty`, `reAgentKey.MatchString`; direct `go` statement는 미발견. 이 목록은 helper 내부의 background launch나 transaction 경계를 펼치지 않으므로 그 부분은 P/U다.
- 종합 의미 판정: **P/U**; route/auth와 위에서 직접 확인한 branch·shape는 C지만, 모든 helper·derived response·중첩 DTO·외부 효과까지 완결됐다는 뜻은 아니다.

<a id="schema-get-api-agents-key"></a>
## `GET /api/agents/{key}`

구현: `pgGetAgent` · `server/server_mgmt.go` · [정확한 handler 근거](evidence:handler-get-api-agents-key)

### 요청

| 위치/field | wire type | 필수·null | 기본/검증 |
|---|---|---|---|
| path `key` | `string` | 필수 | handler의 parse/range validation; 미해석 helper는 P |
| auth credential | Bearer header / `artex_token` cookie / query `token` | 세 위치 중 하나에 유효 JWT 필수 | `extractToken` 우선순위: header→cookie→query; query fallback은 SSE로 제한되지 않음 |
| body | 없음(직접 decoder/form read 미발견) | 해당 없음 | 해당 없음 |

요청 판정: **C/P** · unknown field 거부 미설정.
직접 validation: `!ok` · `sk == nil` · `vers == nil`

### 응답·오류

| 종류 | status/body | 해석 상태 |
|---|---|---|
| success/variant | `200` · `agent:helper result`, `prompt:derived`, `variables:derived`, `versions:derived`, `visibility:map[string]any`, `llm_profiles:derived`, `wrapup_prompt:expression`, `wrapup_default:helper result`, `wrapup_max_turns:expression`, `wrapup_max_turns_default:helper result`, `task_timeout_wrapup_supported:expression`, `task_timeout_wrapup_prompt:expression`, `task_timeout_wrapup_default:helper result`, `task_timeout_wrapup_max_turns:expression`, `task_timeout_wrapup_max_turns_default:helper result` | P/U · `static composite` |
| error | `401` · `{error:string}` · `"未授权"` / `"token 无效或已过期"` | C for direct writeErr; error helper 내부는 P |
| error | `404` · `{error:string}` · `"agent not found"` | C for direct writeErr; error helper 내부는 P |
| error | `500` · `{error:string}` · `err.Error()` | C for direct writeErr; error helper 내부는 P |
| error | `503` · `{error:string}` · `"PostgreSQL 不可用"` | C for direct writeErr; error helper 내부는 P |

공통 helper 오류 분기: `agentByKey` (`server/server_mgmt.go::Server.agentByKey`: 503/500/404). Status union은 helper control-flow를 정적으로 펼친 P이며 runtime DB 결과는 관찰하지 않았다.

응답 schema 판정: **P** · top-level key는 직접 추출했지만 derived/helper/opaque 또는 nested type이 남음.

### 목록·동시성·완료

- Pagination/filter/sort: 등록된 직접 query key 중 표준 paging/filter key 없음.
- Idempotency/concurrency: read/stream 요청; stream 재연결은 cursor 계약에 따름.
- Side effect/completion: 직접 조회/파일 전송 완료; 외부 조회가 있으면 응답 전 handler 결과만 보장.
- 직접 협력자/실행 단서: `agent.TaskTimeoutWrapupDefault`, `agent.WrapupDefault`, `agent.WrapupTurnsDefault`, `agentDTO`, `pg.AgentSkillNames`, `pg.AgentVisible`, `pg.CurrentPrompt`, `pg.ListProfiles`, `pg.ListPromptVersions`, `pg.PromptVars`, `withGlobalVars`; direct `go` statement는 미발견. 이 목록은 helper 내부의 background launch나 transaction 경계를 펼치지 않으므로 그 부분은 P/U다.
- 종합 의미 판정: **P/U**; route/auth와 위에서 직접 확인한 branch·shape는 C지만, 모든 helper·derived response·중첩 DTO·외부 효과까지 완결됐다는 뜻은 아니다.

<a id="schema-patch-api-agents-key"></a>
## `PATCH /api/agents/{key}`

구현: `pgUpdateAgent` · `server/server_mgmt.go` · [정확한 handler 근거](evidence:handler-patch-api-agents-key)

### 요청

| 위치/field | wire type | 필수·null | 기본/검증 |
|---|---|---|---|
| path `key` | `string` | 필수 | handler의 parse/range validation; 미해석 helper는 P |
| auth credential | Bearer header / `artex_token` cookie / query `token` | 세 위치 중 하나에 유효 JWT 필수 | `extractToken` 우선순위: header→cookie→query; query fallback은 SSE로 제한되지 않음 |
| body `Name` | `string` (`string`) | 필수(직접 거부 branch 확인); null/absent 구별 안 됨 | `trim 후 빈 문자열이면 400 후 return` |
| body `Description` | `string` (`string`) | 요구 여부 미확정(P); null/absent 구별 안 됨 | `Go zero value; 별도 기본은 handler/helper 확인` |

요청 판정: **C/P** · unknown field 거부 미설정.
직접 validation: `req.Name == ""`

### 응답·오류

| 종류 | status/body | 해석 상태 |
|---|---|---|
| success/variant | `200` · `ok:boolean` | C · `static composite` |
| error | `400` · `{error:string}` · `"内置 agent 不可修改名称/描述"` / `err.Error()` / `"名称不能为空"` | C for direct writeErr; error helper 내부는 P |
| error | `401` · `{error:string}` · `"未授权"` / `"token 无效或已过期"` | C for direct writeErr; error helper 내부는 P |
| error | `404` · `{error:string}` · `"agent not found"` | C for direct writeErr; error helper 내부는 P |
| error | `500` · `{error:string}` · `err.Error()` | C for direct writeErr; error helper 내부는 P |
| error | `503` · `{error:string}` · `"PostgreSQL 不可用"` | C for direct writeErr; error helper 내부는 P |

공통 helper 오류 분기: `agentByKey` (`server/server_mgmt.go::Server.agentByKey`: 503/500/404). Status union은 helper control-flow를 정적으로 펼친 P이며 runtime DB 결과는 관찰하지 않았다.

응답 schema 판정: **C** · 직접 scalar/static top-level key와 field type을 추출; nested 내부 schema는 별도 type 참조가 없으면 P.

### 목록·동시성·완료

- Pagination/filter/sort: 등록된 직접 query key 중 표준 paging/filter key 없음.
- Idempotency/concurrency: Idempotency-Key/If-Match 없음; DB upsert/trigger/side effect 때문에 반복 의미를 handler별 확인(P).
- Side effect/completion: handler의 직접 mutation/호출 후 응답; 외부 전달·agent 실행이 파생되면 별도 상태 확인(P).
- 직접 협력자/실행 단서: `err.Error`, `pg.UpdateAgentMeta`; direct `go` statement는 미발견. 이 목록은 helper 내부의 background launch나 transaction 경계를 펼치지 않으므로 그 부분은 P/U다.
- 종합 의미 판정: **P/U**; route/auth와 위에서 직접 확인한 branch·shape는 C지만, 모든 helper·derived response·중첩 DTO·외부 효과까지 완결됐다는 뜻은 아니다.

<a id="schema-delete-api-agents-key"></a>
## `DELETE /api/agents/{key}`

구현: `pgDeleteAgent` · `server/server_mgmt.go` · [정확한 handler 근거](evidence:handler-delete-api-agents-key)

### 요청

| 위치/field | wire type | 필수·null | 기본/검증 |
|---|---|---|---|
| path `key` | `string` | 필수 | handler의 parse/range validation; 미해석 helper는 P |
| auth credential | Bearer header / `artex_token` cookie / query `token` | 세 위치 중 하나에 유효 JWT 필수 | `extractToken` 우선순위: header→cookie→query; query fallback은 SSE로 제한되지 않음 |
| body | 없음(직접 decoder/form read 미발견) | 해당 없음 | 해당 없음 |

요청 판정: **C/P** · unknown field 거부 미설정.
직접 validation: `!ok` · `a.Builtin` · `err != nil`

### 응답·오류

| 종류 | status/body | 해석 상태 |
|---|---|---|
| success/variant | `200` · `deleted:expression` | P/U · `static composite` |
| error | `400` · `{error:string}` · `"内置 agent 不可删除"` | C for direct writeErr; error helper 내부는 P |
| error | `401` · `{error:string}` · `"未授权"` / `"token 无效或已过期"` | C for direct writeErr; error helper 내부는 P |
| error | `404` · `{error:string}` · `"agent not found"` | C for direct writeErr; error helper 내부는 P |
| error | `500` · `{error:string}` · `err.Error()` | C for direct writeErr; error helper 내부는 P |
| error | `503` · `{error:string}` · `"PostgreSQL 不可用"` | C for direct writeErr; error helper 내부는 P |

공통 helper 오류 분기: `agentByKey` (`server/server_mgmt.go::Server.agentByKey`: 503/500/404). Status union은 helper control-flow를 정적으로 펼친 P이며 runtime DB 결과는 관찰하지 않았다.

응답 schema 판정: **P** · top-level key는 직접 추출했지만 derived/helper/opaque 또는 nested type이 남음.

### 목록·동시성·완료

- Pagination/filter/sort: 등록된 직접 query key 중 표준 paging/filter key 없음.
- Idempotency/concurrency: Idempotency-Key/If-Match 없음; 반복 시 응답/존재 검사가 달라질 수 있음.
- Side effect/completion: handler의 직접 mutation/호출 후 응답; 외부 전달·agent 실행이 파생되면 별도 상태 확인(P).
- 직접 협력자/실행 단서: `err.Error`, `log.Printf`, `pg.DeleteAgent`, `pg.DeleteTriggersForAgent`, `pg.RemoveAgentFromToolBindings`; direct `go` statement는 미발견. 이 목록은 helper 내부의 background launch나 transaction 경계를 펼치지 않으므로 그 부분은 P/U다.
- 종합 의미 판정: **P/U**; route/auth와 위에서 직접 확인한 branch·shape는 C지만, 모든 helper·derived response·중첩 DTO·외부 효과까지 완결됐다는 뜻은 아니다.

<a id="schema-put-api-agents-key-config"></a>
## `PUT /api/agents/{key}/config`

구현: `pgSaveAgentConfig` · `server/server_mgmt.go` · [정확한 handler 근거](evidence:handler-put-api-agents-key-config)

### 요청

| 위치/field | wire type | 필수·null | 기본/검증 |
|---|---|---|---|
| path `key` | `string` | 필수 | handler의 parse/range validation; 미해석 helper는 P |
| auth credential | Bearer header / `artex_token` cookie / query `token` | 세 위치 중 하나에 유효 JWT 필수 | `extractToken` 우선순위: header→cookie→query; query fallback은 SSE로 제한되지 않음 |
| body `max_turns` | `integer` (`*int`) | 요구 여부 미확정(P); absent/null→nil; null→nil | `req.MaxTurns != nil` |
| body `run_seconds` | `integer` (`*int`) | 요구 여부 미확정(P); absent/null→nil; null→nil | `req.RunSeconds != nil` |
| body `web_search` | `boolean` (`*bool`) | 요구 여부 미확정(P); absent/null→nil; null→nil | `req.WebSearch != nil` |
| body `interactive_shell` | `boolean` (`*bool`) | 요구 여부 미확정(P); absent/null→nil; null→nil | `req.InteractiveShell != nil` |
| body `llm_profile_id` | `object/JSON` (`json.RawMessage`) | 요구 여부 미확정(P); null/absent 구별 안 됨 | `req.LLMProfileID != nil` |
| body `trigger_run_mode` | `string` (`*string`) | 요구 여부 미확정(P); absent/null→nil; null→nil | `req.TriggerRunMode != nil; req.TriggerRunMode != nil \|\| req.TriggerMergeMode != nil \|\| req.TriggerMaxParallel != nil` |
| body `trigger_merge_mode` | `string` (`*string`) | 요구 여부 미확정(P); absent/null→nil; null→nil | `req.TriggerMergeMode != nil; req.TriggerRunMode != nil \|\| req.TriggerMergeMode != nil \|\| req.TriggerMaxParallel != nil` |
| body `trigger_max_parallel` | `integer` (`*int`) | 요구 여부 미확정(P); absent/null→nil; null→nil | `req.TriggerMaxParallel != nil; req.TriggerRunMode != nil \|\| req.TriggerMergeMode != nil \|\| req.TriggerMaxParallel != nil` |

요청 판정: **C/P** · unknown field 거부 미설정.
직접 validation: `req.InteractiveShell != nil` · `req.LLMProfileID != nil` · `req.MaxTurns != nil` · `req.RunSeconds != nil` · `req.TriggerMaxParallel != nil` · `req.TriggerMergeMode != nil` · `req.TriggerRunMode != nil` · `req.TriggerRunMode != nil \|\| req.TriggerMergeMode != nil \|\| req.TriggerMaxParallel != nil` · `req.WebSearch != nil`

### 응답·오류

| 종류 | status/body | 해석 상태 |
|---|---|---|
| success/variant | `200` · `resp` | P/U · `derived variable` |
| error | `400` · `{error:string}` · `err.Error()` / `"llm_profile_id 格式错误"` / `"指定的 LLM 配置不存在或无效"` | C for direct writeErr; error helper 내부는 P |
| error | `401` · `{error:string}` · `"未授权"` / `"token 无效或已过期"` | C for direct writeErr; error helper 내부는 P |
| error | `404` · `{error:string}` · `"agent not found"` | C for direct writeErr; error helper 내부는 P |
| error | `500` · `{error:string}` · `err.Error()` | C for direct writeErr; error helper 내부는 P |
| error | `503` · `{error:string}` · `"PostgreSQL 不可用"` | C for direct writeErr; error helper 내부는 P |

공통 helper 오류 분기: `agentByKey` (`server/server_mgmt.go::Server.agentByKey`: 503/500/404). Status union은 helper control-flow를 정적으로 펼친 P이며 runtime DB 결과는 관찰하지 않았다.

응답 schema 판정: **P** · top-level key는 직접 추출했지만 derived/helper/opaque 또는 nested type이 남음.

### 목록·동시성·완료

- Pagination/filter/sort: 등록된 직접 query key 중 표준 paging/filter key 없음.
- Idempotency/concurrency: Idempotency-Key/If-Match 없음; DB upsert/trigger/side effect 때문에 반복 의미를 handler별 확인(P).
- Side effect/completion: handler의 직접 mutation/호출 후 응답; 외부 전달·agent 실행이 파생되면 별도 상태 확인(P).
- 직접 협력자/실행 단서: `err.Error`, `json.NewDecoder`, `json.NewDecoder(r.Body).Decode`, `pg.SetAgentInteractiveShell`, `pg.SetAgentLLMProfile`, `pg.SetAgentMaxTurns`, `pg.SetAgentRunSeconds`, `pg.SetAgentTriggerBehavior`, `pg.SetAgentWebSearch`, `s.cfgMu.Lock`, `s.cfgMu.Unlock`; direct `go` statement는 미발견. 이 목록은 helper 내부의 background launch나 transaction 경계를 펼치지 않으므로 그 부분은 P/U다.
- 종합 의미 판정: **P/U**; route/auth와 위에서 직접 확인한 branch·shape는 C지만, 모든 helper·derived response·중첩 DTO·외부 효과까지 완결됐다는 뜻은 아니다.

<a id="schema-put-api-agents-key-prompt"></a>
## `PUT /api/agents/{key}/prompt`

구현: `pgSavePrompt` · `server/server_mgmt.go` · [정확한 handler 근거](evidence:handler-put-api-agents-key-prompt)

### 요청

| 위치/field | wire type | 필수·null | 기본/검증 |
|---|---|---|---|
| path `key` | `string` | 필수 | handler의 parse/range validation; 미해석 helper는 P |
| auth credential | Bearer header / `artex_token` cookie / query `token` | 세 위치 중 하나에 유효 JWT 필수 | `extractToken` 우선순위: header→cookie→query; query fallback은 SSE로 제한되지 않음 |
| body `Template` | `string` (`string`) | 요구 여부 미확정(P); null/absent 구별 안 됨 | `Go zero value; 별도 기본은 handler/helper 확인` |
| body `Note` | `string` (`string`) | 요구 여부 미확정(P); null/absent 구별 안 됨 | `Go zero value; 별도 기본은 handler/helper 확인` |

요청 판정: **C/P** · unknown field 거부 미설정.

### 응답·오류

| 종류 | status/body | 해석 상태 |
|---|---|---|
| success/variant | `200` · `version:derived` | P/U · `static composite` |
| error | `400` · `{error:string}` · `err.Error()` / `bad` | C for direct writeErr; error helper 내부는 P |
| error | `401` · `{error:string}` · `"未授权"` / `"token 无效或已过期"` | C for direct writeErr; error helper 내부는 P |
| error | `404` · `{error:string}` · `"agent not found"` | C for direct writeErr; error helper 내부는 P |
| error | `500` · `{error:string}` · `err.Error()` | C for direct writeErr; error helper 내부는 P |
| error | `503` · `{error:string}` · `"PostgreSQL 不可用"` | C for direct writeErr; error helper 내부는 P |

공통 helper 오류 분기: `agentByKey` (`server/server_mgmt.go::Server.agentByKey`: 503/500/404). Status union은 helper control-flow를 정적으로 펼친 P이며 runtime DB 결과는 관찰하지 않았다.

응답 schema 판정: **P** · top-level key는 직접 추출했지만 derived/helper/opaque 또는 nested type이 남음.

### 목록·동시성·완료

- Pagination/filter/sort: 등록된 직접 query key 중 표준 paging/filter key 없음.
- Idempotency/concurrency: Idempotency-Key/If-Match 없음; DB upsert/trigger/side effect 때문에 반복 의미를 handler별 확인(P).
- Side effect/completion: handler의 직접 mutation/호출 후 응답; 외부 전달·agent 실행이 파생되면 별도 상태 확인(P).
- 직접 협력자/실행 단서: `err.Error`, `pg.PromptVars`, `pg.SavePrompt`, `validateTemplate`, `withGlobalVars`; direct `go` statement는 미발견. 이 목록은 helper 내부의 background launch나 transaction 경계를 펼치지 않으므로 그 부분은 P/U다.
- 종합 의미 판정: **P/U**; route/auth와 위에서 직접 확인한 branch·shape는 C지만, 모든 helper·derived response·중첩 DTO·외부 효과까지 완결됐다는 뜻은 아니다.

<a id="schema-post-api-agents-key-prompt-reset"></a>
## `POST /api/agents/{key}/prompt/reset`

구현: `pgResetPrompt` · `server/server_mgmt.go` · [정확한 handler 근거](evidence:handler-post-api-agents-key-prompt-reset)

### 요청

| 위치/field | wire type | 필수·null | 기본/검증 |
|---|---|---|---|
| path `key` | `string` | 필수 | handler의 parse/range validation; 미해석 helper는 P |
| auth credential | Bearer header / `artex_token` cookie / query `token` | 세 위치 중 하나에 유효 JWT 필수 | `extractToken` 우선순위: header→cookie→query; query fallback은 SSE로 제한되지 않음 |
| body | 없음(직접 decoder/form read 미발견) | 해당 없음 | 해당 없음 |

요청 판정: **C/P** · unknown field 거부 미설정.
직접 validation: `!has` · `!ok` · `err != nil`

### 응답·오류

| 종류 | status/body | 해석 상태 |
|---|---|---|
| success/variant | `200` · `version:derived` | P/U · `static composite` |
| error | `400` · `{error:string}` · `"该 agent 无内置默认提示词，无法恢复"` | C for direct writeErr; error helper 내부는 P |
| error | `401` · `{error:string}` · `"未授权"` / `"token 无效或已过期"` | C for direct writeErr; error helper 내부는 P |
| error | `404` · `{error:string}` · `"agent not found"` | C for direct writeErr; error helper 내부는 P |
| error | `500` · `{error:string}` · `err.Error()` | C for direct writeErr; error helper 내부는 P |
| error | `503` · `{error:string}` · `"PostgreSQL 不可用"` | C for direct writeErr; error helper 내부는 P |

공통 helper 오류 분기: `agentByKey` (`server/server_mgmt.go::Server.agentByKey`: 503/500/404). Status union은 helper control-flow를 정적으로 펼친 P이며 runtime DB 결과는 관찰하지 않았다.

응답 schema 판정: **P** · top-level key는 직접 추출했지만 derived/helper/opaque 또는 nested type이 남음.

### 목록·동시성·완료

- Pagination/filter/sort: 등록된 직접 query key 중 표준 paging/filter key 없음.
- Idempotency/concurrency: Idempotency-Key 없음; unique constraint 또는 상태 check가 있는 경우만 중복을 제한(P).
- Side effect/completion: handler의 직접 mutation/호출 후 응답; 외부 전달·agent 실행이 파생되면 별도 상태 확인(P).
- 직접 협력자/실행 단서: `agent.BuiltinPromptSeeds`, `err.Error`, `pg.ResetPromptToDefault`; direct `go` statement는 미발견. 이 목록은 helper 내부의 background launch나 transaction 경계를 펼치지 않으므로 그 부분은 P/U다.
- 종합 의미 판정: **P/U**; route/auth와 위에서 직접 확인한 branch·shape는 C지만, 모든 helper·derived response·중첩 DTO·외부 효과까지 완결됐다는 뜻은 아니다.

<a id="schema-put-api-agents-key-wrapup"></a>
## `PUT /api/agents/{key}/wrapup`

구현: `pgSaveWrapup` · `server/server_mgmt.go` · [정확한 handler 근거](evidence:handler-put-api-agents-key-wrapup)

### 요청

| 위치/field | wire type | 필수·null | 기본/검증 |
|---|---|---|---|
| path `key` | `string` | 필수 | handler의 parse/range validation; 미해석 helper는 P |
| auth credential | Bearer header / `artex_token` cookie / query `token` | 세 위치 중 하나에 유효 JWT 필수 | `extractToken` 우선순위: header→cookie→query; query fallback은 SSE로 제한되지 않음 |
| body `prompt` | `string` (`string`) | 요구 여부 미확정(P); null/absent 구별 안 됨 | `Go zero value; 별도 기본은 handler/helper 확인` |
| body `max_turns` | `integer` (`*int`) | 요구 여부 미확정(P); absent/null→nil; null→nil | `body.MaxTurns != nil` |

요청 판정: **C/P** · unknown field 거부 미설정.
직접 validation: `body.MaxTurns != nil`

### 응답·오류

| 종류 | status/body | 해석 상태 |
|---|---|---|
| success/variant | `200` · `ok:boolean` | C · `static composite` |
| error | `400` · `{error:string}` · `err.Error()` | C for direct writeErr; error helper 내부는 P |
| error | `401` · `{error:string}` · `"未授权"` / `"token 无效或已过期"` | C for direct writeErr; error helper 내부는 P |
| error | `404` · `{error:string}` · `"agent not found"` | C for direct writeErr; error helper 내부는 P |
| error | `500` · `{error:string}` · `err.Error()` | C for direct writeErr; error helper 내부는 P |
| error | `503` · `{error:string}` · `"PostgreSQL 不可用"` | C for direct writeErr; error helper 내부는 P |

공통 helper 오류 분기: `agentByKey` (`server/server_mgmt.go::Server.agentByKey`: 503/500/404). Status union은 helper control-flow를 정적으로 펼친 P이며 runtime DB 결과는 관찰하지 않았다.

응답 schema 판정: **C** · 직접 scalar/static top-level key와 field type을 추출; nested 내부 schema는 별도 type 참조가 없으면 P.

### 목록·동시성·완료

- Pagination/filter/sort: 등록된 직접 query key 중 표준 paging/filter key 없음.
- Idempotency/concurrency: Idempotency-Key/If-Match 없음; DB upsert/trigger/side effect 때문에 반복 의미를 handler별 확인(P).
- Side effect/completion: handler의 직접 mutation/호출 후 응답; 외부 전달·agent 실행이 파생되면 별도 상태 확인(P).
- 직접 협력자/실행 단서: `err.Error`, `pg.SetAgentWrapupMaxTurns`, `pg.SetAgentWrapupPrompt`; direct `go` statement는 미발견. 이 목록은 helper 내부의 background launch나 transaction 경계를 펼치지 않으므로 그 부분은 P/U다.
- 종합 의미 판정: **P/U**; route/auth와 위에서 직접 확인한 branch·shape는 C지만, 모든 helper·derived response·중첩 DTO·외부 효과까지 완결됐다는 뜻은 아니다.

<a id="schema-post-api-agents-key-wrapup-reset"></a>
## `POST /api/agents/{key}/wrapup/reset`

구현: `pgResetWrapup` · `server/server_mgmt.go` · [정확한 handler 근거](evidence:handler-post-api-agents-key-wrapup-reset)

### 요청

| 위치/field | wire type | 필수·null | 기본/검증 |
|---|---|---|---|
| path `key` | `string` | 필수 | handler의 parse/range validation; 미해석 helper는 P |
| auth credential | Bearer header / `artex_token` cookie / query `token` | 세 위치 중 하나에 유효 JWT 필수 | `extractToken` 우선순위: header→cookie→query; query fallback은 SSE로 제한되지 않음 |
| body | 없음(직접 decoder/form read 미발견) | 해당 없음 | 해당 없음 |

요청 판정: **C/P** · unknown field 거부 미설정.
직접 validation: `!ok` · `err != nil`

### 응답·오류

| 종류 | status/body | 해석 상태 |
|---|---|---|
| success/variant | `200` · `ok:boolean`, `wrapup_default:helper result`, `wrapup_max_turns_default:helper result` | P/U · `static composite` |
| error | `401` · `{error:string}` · `"未授权"` / `"token 无效或已过期"` | C for direct writeErr; error helper 내부는 P |
| error | `404` · `{error:string}` · `"agent not found"` | C for direct writeErr; error helper 내부는 P |
| error | `500` · `{error:string}` · `err.Error()` | C for direct writeErr; error helper 내부는 P |
| error | `503` · `{error:string}` · `"PostgreSQL 不可用"` | C for direct writeErr; error helper 내부는 P |

공통 helper 오류 분기: `agentByKey` (`server/server_mgmt.go::Server.agentByKey`: 503/500/404). Status union은 helper control-flow를 정적으로 펼친 P이며 runtime DB 결과는 관찰하지 않았다.

응답 schema 판정: **P** · top-level key는 직접 추출했지만 derived/helper/opaque 또는 nested type이 남음.

### 목록·동시성·완료

- Pagination/filter/sort: 등록된 직접 query key 중 표준 paging/filter key 없음.
- Idempotency/concurrency: Idempotency-Key 없음; unique constraint 또는 상태 check가 있는 경우만 중복을 제한(P).
- Side effect/completion: handler의 직접 mutation/호출 후 응답; 외부 전달·agent 실행이 파생되면 별도 상태 확인(P).
- 직접 협력자/실행 단서: `agent.WrapupDefault`, `agent.WrapupTurnsDefault`, `err.Error`, `pg.SetAgentWrapupMaxTurns`, `pg.SetAgentWrapupPrompt`; direct `go` statement는 미발견. 이 목록은 helper 내부의 background launch나 transaction 경계를 펼치지 않으므로 그 부분은 P/U다.
- 종합 의미 판정: **P/U**; route/auth와 위에서 직접 확인한 branch·shape는 C지만, 모든 helper·derived response·중첩 DTO·외부 효과까지 완결됐다는 뜻은 아니다.

<a id="schema-put-api-agents-key-wrapup-task-timeout"></a>
## `PUT /api/agents/{key}/wrapup/task-timeout`

구현: `pgSaveTaskTimeoutWrapup` · `server/server_mgmt.go` · [정확한 handler 근거](evidence:handler-put-api-agents-key-wrapup-task-timeout)

### 요청

| 위치/field | wire type | 필수·null | 기본/검증 |
|---|---|---|---|
| path `key` | `string` | 필수 | handler의 parse/range validation; 미해석 helper는 P |
| auth credential | Bearer header / `artex_token` cookie / query `token` | 세 위치 중 하나에 유효 JWT 필수 | `extractToken` 우선순위: header→cookie→query; query fallback은 SSE로 제한되지 않음 |
| body `prompt` | `string` (`string`) | 요구 여부 미확정(P); null/absent 구별 안 됨 | `Go zero value; 별도 기본은 handler/helper 확인` |
| body `max_turns` | `integer` (`*int`) | 요구 여부 미확정(P); absent/null→nil; null→nil | `body.MaxTurns != nil` |

요청 판정: **C/P** · unknown field 거부 미설정.
직접 validation: `body.MaxTurns != nil`

### 응답·오류

| 종류 | status/body | 해석 상태 |
|---|---|---|
| success/variant | `200` · `ok:boolean` | C · `static composite` |
| error | `400` · `{error:string}` · `err.Error()` | C for direct writeErr; error helper 내부는 P |
| error | `401` · `{error:string}` · `"未授权"` / `"token 无效或已过期"` | C for direct writeErr; error helper 내부는 P |
| error | `404` · `{error:string}` · `"agent not found"` | C for direct writeErr; error helper 내부는 P |
| error | `500` · `{error:string}` · `err.Error()` | C for direct writeErr; error helper 내부는 P |
| error | `503` · `{error:string}` · `"PostgreSQL 不可用"` | C for direct writeErr; error helper 내부는 P |

공통 helper 오류 분기: `agentByKey` (`server/server_mgmt.go::Server.agentByKey`: 503/500/404). Status union은 helper control-flow를 정적으로 펼친 P이며 runtime DB 결과는 관찰하지 않았다.

응답 schema 판정: **C** · 직접 scalar/static top-level key와 field type을 추출; nested 내부 schema는 별도 type 참조가 없으면 P.

### 목록·동시성·완료

- Pagination/filter/sort: 등록된 직접 query key 중 표준 paging/filter key 없음.
- Idempotency/concurrency: Idempotency-Key/If-Match 없음; DB upsert/trigger/side effect 때문에 반복 의미를 handler별 확인(P).
- Side effect/completion: handler의 직접 mutation/호출 후 응답; 외부 전달·agent 실행이 파생되면 별도 상태 확인(P).
- 직접 협력자/실행 단서: `err.Error`, `pg.SetAgentTaskTimeoutWrapup`; direct `go` statement는 미발견. 이 목록은 helper 내부의 background launch나 transaction 경계를 펼치지 않으므로 그 부분은 P/U다.
- 종합 의미 판정: **P/U**; route/auth와 위에서 직접 확인한 branch·shape는 C지만, 모든 helper·derived response·중첩 DTO·외부 효과까지 완결됐다는 뜻은 아니다.

<a id="schema-post-api-agents-key-wrapup-task-timeout-reset"></a>
## `POST /api/agents/{key}/wrapup/task-timeout/reset`

구현: `pgResetTaskTimeoutWrapup` · `server/server_mgmt.go` · [정확한 handler 근거](evidence:handler-post-api-agents-key-wrapup-task-timeout-reset)

### 요청

| 위치/field | wire type | 필수·null | 기본/검증 |
|---|---|---|---|
| path `key` | `string` | 필수 | handler의 parse/range validation; 미해석 helper는 P |
| auth credential | Bearer header / `artex_token` cookie / query `token` | 세 위치 중 하나에 유효 JWT 필수 | `extractToken` 우선순위: header→cookie→query; query fallback은 SSE로 제한되지 않음 |
| body | 없음(직접 decoder/form read 미발견) | 해당 없음 | 해당 없음 |

요청 판정: **C/P** · unknown field 거부 미설정.
직접 validation: `!ok` · `err != nil`

### 응답·오류

| 종류 | status/body | 해석 상태 |
|---|---|---|
| success/variant | `200` · `ok:boolean`, `task_timeout_wrapup_default:helper result`, `task_timeout_wrapup_max_turns_default:helper result` | P/U · `static composite` |
| error | `401` · `{error:string}` · `"未授权"` / `"token 无效或已过期"` | C for direct writeErr; error helper 내부는 P |
| error | `404` · `{error:string}` · `"agent not found"` | C for direct writeErr; error helper 내부는 P |
| error | `500` · `{error:string}` · `err.Error()` | C for direct writeErr; error helper 내부는 P |
| error | `503` · `{error:string}` · `"PostgreSQL 不可用"` | C for direct writeErr; error helper 내부는 P |

공통 helper 오류 분기: `agentByKey` (`server/server_mgmt.go::Server.agentByKey`: 503/500/404). Status union은 helper control-flow를 정적으로 펼친 P이며 runtime DB 결과는 관찰하지 않았다.

응답 schema 판정: **P** · top-level key는 직접 추출했지만 derived/helper/opaque 또는 nested type이 남음.

### 목록·동시성·완료

- Pagination/filter/sort: 등록된 직접 query key 중 표준 paging/filter key 없음.
- Idempotency/concurrency: Idempotency-Key 없음; unique constraint 또는 상태 check가 있는 경우만 중복을 제한(P).
- Side effect/completion: handler의 직접 mutation/호출 후 응답; 외부 전달·agent 실행이 파생되면 별도 상태 확인(P).
- 직접 협력자/실행 단서: `agent.TaskTimeoutWrapupDefault`, `agent.WrapupTurnsDefault`, `err.Error`, `pg.SetAgentTaskTimeoutWrapup`; direct `go` statement는 미발견. 이 목록은 helper 내부의 background launch나 transaction 경계를 펼치지 않으므로 그 부분은 P/U다.
- 종합 의미 판정: **P/U**; route/auth와 위에서 직접 확인한 branch·shape는 C지만, 모든 helper·derived response·중첩 DTO·외부 효과까지 완결됐다는 뜻은 아니다.

<a id="schema-get-api-agents-key-triggers"></a>
## `GET /api/agents/{key}/triggers`

구현: `pgListTriggers` · `server/triggers.go` · [정확한 handler 근거](evidence:handler-get-api-agents-key-triggers)

### 요청

| 위치/field | wire type | 필수·null | 기본/검증 |
|---|---|---|---|
| path `key` | `string` | 필수 | handler의 parse/range validation; 미해석 helper는 P |
| auth credential | Bearer header / `artex_token` cookie / query `token` | 세 위치 중 하나에 유효 JWT 필수 | `extractToken` 우선순위: header→cookie→query; query fallback은 SSE로 제한되지 않음 |
| body | 없음(직접 decoder/form read 미발견) | 해당 없음 | 해당 없음 |

요청 판정: **C/P** · unknown field 거부 미설정.
직접 validation: `!ok` · `err != nil`

### 응답·오류

| 종류 | status/body | 해석 상태 |
|---|---|---|
| success/variant | `200` · `triggers:derived` | P/U · `static composite` |
| error | `401` · `{error:string}` · `"未授权"` / `"token 无效或已过期"` | C for direct writeErr; error helper 내부는 P |
| error | `404` · `{error:string}` · `"agent not found"` | C for direct writeErr; error helper 내부는 P |
| error | `500` · `{error:string}` · `err.Error()` | C for direct writeErr; error helper 내부는 P |
| error | `503` · `{error:string}` · `"PostgreSQL 不可用"` | C for direct writeErr; error helper 내부는 P |

공통 helper 오류 분기: `agentByKey` (`server/server_mgmt.go::Server.agentByKey`: 503/500/404). Status union은 helper control-flow를 정적으로 펼친 P이며 runtime DB 결과는 관찰하지 않았다.

응답 schema 판정: **P** · top-level key는 직접 추출했지만 derived/helper/opaque 또는 nested type이 남음.

### 목록·동시성·완료

- Pagination/filter/sort: 등록된 직접 query key 중 표준 paging/filter key 없음.
- Idempotency/concurrency: read/stream 요청; stream 재연결은 cursor 계약에 따름.
- Side effect/completion: 직접 조회/파일 전송 완료; 외부 조회가 있으면 응답 전 handler 결과만 보장.
- 직접 협력자/실행 단서: `err.Error`, `pg.ListTriggersFor`; direct `go` statement는 미발견. 이 목록은 helper 내부의 background launch나 transaction 경계를 펼치지 않으므로 그 부분은 P/U다.
- 종합 의미 판정: **P/U**; route/auth와 위에서 직접 확인한 branch·shape는 C지만, 모든 helper·derived response·중첩 DTO·외부 효과까지 완결됐다는 뜻은 아니다.

<a id="schema-post-api-agents-key-triggers"></a>
## `POST /api/agents/{key}/triggers`

구현: `pgCreateTrigger` · `server/triggers.go` · [정확한 handler 근거](evidence:handler-post-api-agents-key-triggers)

### 요청

| 위치/field | wire type | 필수·null | 기본/검증 |
|---|---|---|---|
| path `key` | `string` | 필수 | handler의 parse/range validation; 미해석 helper는 P |
| auth credential | Bearer header / `artex_token` cookie / query `token` | 세 위치 중 하나에 유효 JWT 필수 | `extractToken` 우선순위: header→cookie→query; query fallback은 SSE로 제한되지 않음 |
| body `enabled` | `boolean` (`bool`) | 요구 여부 미확정(P); null/absent 구별 안 됨 | `Go zero value; 별도 기본은 handler/helper 확인` |
| body `interval_sec` | `integer` (`int`) | 요구 여부 미확정(P); null/absent 구별 안 됨 | `req.IntervalSec < 0` |
| body `on_finding` | `boolean` (`bool`) | 요구 여부 미확정(P); null/absent 구별 안 됨 | `Go zero value; 별도 기본은 handler/helper 확인` |
| body `on_goal_met` | `boolean` (`bool`) | 요구 여부 미확정(P); null/absent 구별 안 됨 | `Go zero value; 별도 기본은 handler/helper 확인` |
| body `on_task_timeout` | `boolean` (`bool`) | 요구 여부 미확정(P); null/absent 구별 안 됨 | `Go zero value; 별도 기본은 handler/helper 확인` |
| body `on_tool_call` | `boolean` (`bool`) | 요구 여부 미확정(P); null/absent 구별 안 됨 | `Go zero value; 별도 기본은 handler/helper 확인` |
| body `on_task_create` | `boolean` (`bool`) | 요구 여부 미확정(P); null/absent 구별 안 됨 | `Go zero value; 별도 기본은 handler/helper 확인` |
| body `interval_message` | `string` (`string`) | 요구 여부 미확정(P); null/absent 구별 안 됨 | `Go zero value; 별도 기본은 handler/helper 확인` |
| body `finding_message` | `string` (`string`) | 요구 여부 미확정(P); null/absent 구별 안 됨 | `Go zero value; 별도 기본은 handler/helper 확인` |
| body `goal_message` | `string` (`string`) | 요구 여부 미확정(P); null/absent 구별 안 됨 | `Go zero value; 별도 기본은 handler/helper 확인` |
| body `task_timeout_message` | `string` (`string`) | 요구 여부 미확정(P); null/absent 구별 안 됨 | `Go zero value; 별도 기본은 handler/helper 확인` |
| body `tool_call_message` | `string` (`string`) | 요구 여부 미확정(P); null/absent 구별 안 됨 | `Go zero value; 별도 기본은 handler/helper 확인` |
| body `task_create_message` | `string` (`string`) | 요구 여부 미확정(P); null/absent 구별 안 됨 | `Go zero value; 별도 기본은 handler/helper 확인` |
| body `tool_names` | `array[string]` (`[]string`) | 요구 여부 미확정(P); absent/null→nil; null/absent 구별 안 됨 | `nil` |

요청 판정: **C/P** · unknown field 거부 미설정.
직접 validation: `req.IntervalSec < 0`

### 응답·오류

| 종류 | status/body | 해석 상태 |
|---|---|---|
| success/variant | `200` · `tr` | P/U · `derived variable` |
| error | `400` · `{error:string}` · `"触发器仅支持自定义 agent"` / `err.Error()` / `msg` | C for direct writeErr; error helper 내부는 P |
| error | `401` · `{error:string}` · `"未授权"` / `"token 无效或已过期"` | C for direct writeErr; error helper 내부는 P |
| error | `404` · `{error:string}` · `"agent not found"` | C for direct writeErr; error helper 내부는 P |
| error | `500` · `{error:string}` · `err.Error()` | C for direct writeErr; error helper 내부는 P |
| error | `503` · `{error:string}` · `"PostgreSQL 不可用"` | C for direct writeErr; error helper 내부는 P |

공통 helper 오류 분기: `agentByKey` (`server/server_mgmt.go::Server.agentByKey`: 503/500/404). Status union은 helper control-flow를 정적으로 펼친 P이며 runtime DB 결과는 관찰하지 않았다.

응답 schema 판정: **P** · top-level key는 직접 추출했지만 derived/helper/opaque 또는 nested type이 남음.

### 목록·동시성·완료

- Pagination/filter/sort: 등록된 직접 query key 중 표준 paging/filter key 없음.
- Idempotency/concurrency: Idempotency-Key 없음; unique constraint 또는 상태 check가 있는 경우만 중복을 제한(P).
- Side effect/completion: handler의 직접 mutation/호출 후 응답; 외부 전달·agent 실행이 파생되면 별도 상태 확인(P).
- 직접 협력자/실행 단서: `err.Error`, `pg.CreateTrigger`, `validateTrigger`; direct `go` statement는 미발견. 이 목록은 helper 내부의 background launch나 transaction 경계를 펼치지 않으므로 그 부분은 P/U다.
- 종합 의미 판정: **P/U**; route/auth와 위에서 직접 확인한 branch·shape는 C지만, 모든 helper·derived response·중첩 DTO·외부 효과까지 완결됐다는 뜻은 아니다.

<a id="schema-patch-api-triggers-id"></a>
## `PATCH /api/triggers/{id}`

구현: `pgUpdateTrigger` · `server/triggers.go` · [정확한 handler 근거](evidence:handler-patch-api-triggers-id)

### 요청

| 위치/field | wire type | 필수·null | 기본/검증 |
|---|---|---|---|
| path `id` | `string` | 필수 | handler의 parse/range validation; 미해석 helper는 P |
| auth credential | Bearer header / `artex_token` cookie / query `token` | 세 위치 중 하나에 유효 JWT 필수 | `extractToken` 우선순위: header→cookie→query; query fallback은 SSE로 제한되지 않음 |
| body `enabled` | `boolean` (`bool`) | 요구 여부 미확정(P); null/absent 구별 안 됨 | `Go zero value; 별도 기본은 handler/helper 확인` |
| body `interval_sec` | `integer` (`int`) | 요구 여부 미확정(P); null/absent 구별 안 됨 | `req.IntervalSec < 0` |
| body `on_finding` | `boolean` (`bool`) | 요구 여부 미확정(P); null/absent 구별 안 됨 | `Go zero value; 별도 기본은 handler/helper 확인` |
| body `on_goal_met` | `boolean` (`bool`) | 요구 여부 미확정(P); null/absent 구별 안 됨 | `Go zero value; 별도 기본은 handler/helper 확인` |
| body `on_task_timeout` | `boolean` (`bool`) | 요구 여부 미확정(P); null/absent 구별 안 됨 | `Go zero value; 별도 기본은 handler/helper 확인` |
| body `on_tool_call` | `boolean` (`bool`) | 요구 여부 미확정(P); null/absent 구별 안 됨 | `Go zero value; 별도 기본은 handler/helper 확인` |
| body `on_task_create` | `boolean` (`bool`) | 요구 여부 미확정(P); null/absent 구별 안 됨 | `Go zero value; 별도 기본은 handler/helper 확인` |
| body `interval_message` | `string` (`string`) | 요구 여부 미확정(P); null/absent 구별 안 됨 | `Go zero value; 별도 기본은 handler/helper 확인` |
| body `finding_message` | `string` (`string`) | 요구 여부 미확정(P); null/absent 구별 안 됨 | `Go zero value; 별도 기본은 handler/helper 확인` |
| body `goal_message` | `string` (`string`) | 요구 여부 미확정(P); null/absent 구별 안 됨 | `Go zero value; 별도 기본은 handler/helper 확인` |
| body `task_timeout_message` | `string` (`string`) | 요구 여부 미확정(P); null/absent 구별 안 됨 | `Go zero value; 별도 기본은 handler/helper 확인` |
| body `tool_call_message` | `string` (`string`) | 요구 여부 미확정(P); null/absent 구별 안 됨 | `Go zero value; 별도 기본은 handler/helper 확인` |
| body `task_create_message` | `string` (`string`) | 요구 여부 미확정(P); null/absent 구별 안 됨 | `Go zero value; 별도 기본은 handler/helper 확인` |
| body `tool_names` | `array[string]` (`[]string`) | 요구 여부 미확정(P); absent/null→nil; null/absent 구별 안 됨 | `nil` |

요청 판정: **C/P** · unknown field 거부 미설정.
직접 validation: `req.IntervalSec < 0`

### 응답·오류

| 종류 | status/body | 해석 상태 |
|---|---|---|
| success/variant | `200` · `ok:boolean` | C · `static composite` |
| error | `400` · `{error:string}` · `"bad trigger id"` / `err.Error()` / `msg` | C for direct writeErr; error helper 내부는 P |
| error | `401` · `{error:string}` · `"未授权"` / `"token 无效或已过期"` | C for direct writeErr; error helper 내부는 P |
| error | `500` · `{error:string}` · `err.Error()` | C for direct writeErr; error helper 내부는 P |
| error | `503` · `{error:string}` · `"PostgreSQL 不可用"` | C for direct writeErr; error helper 내부는 P |

응답 schema 판정: **C** · 직접 scalar/static top-level key와 field type을 추출; nested 내부 schema는 별도 type 참조가 없으면 P.

### 목록·동시성·완료

- Pagination/filter/sort: 등록된 직접 query key 중 표준 paging/filter key 없음.
- Idempotency/concurrency: Idempotency-Key/If-Match 없음; DB upsert/trigger/side effect 때문에 반복 의미를 handler별 확인(P).
- Side effect/completion: handler의 직접 mutation/호출 후 응답; 외부 전달·agent 실행이 파생되면 별도 상태 확인(P).
- 직접 협력자/실행 단서: `err.Error`, `pathInt`, `pg.UpdateTrigger`, `validateTrigger`; direct `go` statement는 미발견. 이 목록은 helper 내부의 background launch나 transaction 경계를 펼치지 않으므로 그 부분은 P/U다.
- 종합 의미 판정: **P/U**; route/auth와 위에서 직접 확인한 branch·shape는 C지만, 모든 helper·derived response·중첩 DTO·외부 효과까지 완결됐다는 뜻은 아니다.

<a id="schema-delete-api-triggers-id"></a>
## `DELETE /api/triggers/{id}`

구현: `pgDeleteTrigger` · `server/triggers.go` · [정확한 handler 근거](evidence:handler-delete-api-triggers-id)

### 요청

| 위치/field | wire type | 필수·null | 기본/검증 |
|---|---|---|---|
| path `id` | `string` | 필수 | handler의 parse/range validation; 미해석 helper는 P |
| auth credential | Bearer header / `artex_token` cookie / query `token` | 세 위치 중 하나에 유효 JWT 필수 | `extractToken` 우선순위: header→cookie→query; query fallback은 SSE로 제한되지 않음 |
| body | 없음(직접 decoder/form read 미발견) | 해당 없음 | 해당 없음 |

요청 판정: **C/P** · unknown field 거부 미설정.
직접 validation: `!ok` · `err != nil` · `pg == nil`

### 응답·오류

| 종류 | status/body | 해석 상태 |
|---|---|---|
| success/variant | `200` · `deleted:derived` | P/U · `static composite` |
| error | `400` · `{error:string}` · `"bad trigger id"` | C for direct writeErr; error helper 내부는 P |
| error | `401` · `{error:string}` · `"未授权"` / `"token 无效或已过期"` | C for direct writeErr; error helper 내부는 P |
| error | `500` · `{error:string}` · `err.Error()` | C for direct writeErr; error helper 내부는 P |
| error | `503` · `{error:string}` · `"PostgreSQL 不可用"` | C for direct writeErr; error helper 내부는 P |

응답 schema 판정: **P** · top-level key는 직접 추출했지만 derived/helper/opaque 또는 nested type이 남음.

### 목록·동시성·완료

- Pagination/filter/sort: 등록된 직접 query key 중 표준 paging/filter key 없음.
- Idempotency/concurrency: Idempotency-Key/If-Match 없음; 반복 시 응답/존재 검사가 달라질 수 있음.
- Side effect/completion: handler의 직접 mutation/호출 후 응답; 외부 전달·agent 실행이 파생되면 별도 상태 확인(P).
- 직접 협력자/실행 단서: `err.Error`, `pathInt`, `pg.DeleteTrigger`; direct `go` statement는 미발견. 이 목록은 helper 내부의 background launch나 transaction 경계를 펼치지 않으므로 그 부분은 P/U다.
- 종합 의미 판정: **P/U**; route/auth와 위에서 직접 확인한 branch·shape는 C지만, 모든 helper·derived response·중첩 DTO·외부 효과까지 완결됐다는 뜻은 아니다.

<a id="schema-get-api-agents-key-prompts"></a>
## `GET /api/agents/{key}/prompts`

구현: `pgListPromptVersions` · `server/server_mgmt.go` · [정확한 handler 근거](evidence:handler-get-api-agents-key-prompts)

### 요청

| 위치/field | wire type | 필수·null | 기본/검증 |
|---|---|---|---|
| path `key` | `string` | 필수 | handler의 parse/range validation; 미해석 helper는 P |
| auth credential | Bearer header / `artex_token` cookie / query `token` | 세 위치 중 하나에 유효 JWT 필수 | `extractToken` 우선순위: header→cookie→query; query fallback은 SSE로 제한되지 않음 |
| body | 없음(직접 decoder/form read 미발견) | 해당 없음 | 해당 없음 |

요청 판정: **C/P** · unknown field 거부 미설정.
직접 validation: `!ok` · `err != nil`

### 응답·오류

| 종류 | status/body | 해석 상태 |
|---|---|---|
| success/variant | `200` · `versions:derived` | P/U · `static composite` |
| error | `401` · `{error:string}` · `"未授权"` / `"token 无效或已过期"` | C for direct writeErr; error helper 내부는 P |
| error | `404` · `{error:string}` · `"agent not found"` | C for direct writeErr; error helper 내부는 P |
| error | `500` · `{error:string}` · `err.Error()` | C for direct writeErr; error helper 내부는 P |
| error | `503` · `{error:string}` · `"PostgreSQL 不可用"` | C for direct writeErr; error helper 내부는 P |

공통 helper 오류 분기: `agentByKey` (`server/server_mgmt.go::Server.agentByKey`: 503/500/404). Status union은 helper control-flow를 정적으로 펼친 P이며 runtime DB 결과는 관찰하지 않았다.

응답 schema 판정: **P** · top-level key는 직접 추출했지만 derived/helper/opaque 또는 nested type이 남음.

### 목록·동시성·완료

- Pagination/filter/sort: 등록된 직접 query key 중 표준 paging/filter key 없음.
- Idempotency/concurrency: read/stream 요청; stream 재연결은 cursor 계약에 따름.
- Side effect/completion: 직접 조회/파일 전송 완료; 외부 조회가 있으면 응답 전 handler 결과만 보장.
- 직접 협력자/실행 단서: `err.Error`, `pg.ListPromptVersions`; direct `go` statement는 미발견. 이 목록은 helper 내부의 background launch나 transaction 경계를 펼치지 않으므로 그 부분은 P/U다.
- 종합 의미 판정: **P/U**; route/auth와 위에서 직접 확인한 branch·shape는 C지만, 모든 helper·derived response·중첩 DTO·외부 효과까지 완결됐다는 뜻은 아니다.

<a id="schema-get-api-agents-key-variables"></a>
## `GET /api/agents/{key}/variables`

구현: `pgPromptVars` · `server/server_mgmt.go` · [정확한 handler 근거](evidence:handler-get-api-agents-key-variables)

### 요청

| 위치/field | wire type | 필수·null | 기본/검증 |
|---|---|---|---|
| path `key` | `string` | 필수 | handler의 parse/range validation; 미해석 helper는 P |
| auth credential | Bearer header / `artex_token` cookie / query `token` | 세 위치 중 하나에 유효 JWT 필수 | `extractToken` 우선순위: header→cookie→query; query fallback은 SSE로 제한되지 않음 |
| body | 없음(직접 decoder/form read 미발견) | 해당 없음 | 해당 없음 |

요청 판정: **C/P** · unknown field 거부 미설정.
직접 validation: `!ok` · `err != nil`

### 응답·오류

| 종류 | status/body | 해석 상태 |
|---|---|---|
| success/variant | `200` · `variables:helper result` | P/U · `static composite` |
| error | `401` · `{error:string}` · `"未授权"` / `"token 无效或已过期"` | C for direct writeErr; error helper 내부는 P |
| error | `404` · `{error:string}` · `"agent not found"` | C for direct writeErr; error helper 내부는 P |
| error | `500` · `{error:string}` · `err.Error()` | C for direct writeErr; error helper 내부는 P |
| error | `503` · `{error:string}` · `"PostgreSQL 不可用"` | C for direct writeErr; error helper 내부는 P |

공통 helper 오류 분기: `agentByKey` (`server/server_mgmt.go::Server.agentByKey`: 503/500/404). Status union은 helper control-flow를 정적으로 펼친 P이며 runtime DB 결과는 관찰하지 않았다.

응답 schema 판정: **P** · top-level key는 직접 추출했지만 derived/helper/opaque 또는 nested type이 남음.

### 목록·동시성·완료

- Pagination/filter/sort: 등록된 직접 query key 중 표준 paging/filter key 없음.
- Idempotency/concurrency: read/stream 요청; stream 재연결은 cursor 계약에 따름.
- Side effect/completion: 직접 조회/파일 전송 완료; 외부 조회가 있으면 응답 전 handler 결과만 보장.
- 직접 협력자/실행 단서: `err.Error`, `pg.PromptVars`, `withGlobalVars`; direct `go` statement는 미발견. 이 목록은 helper 내부의 background launch나 transaction 경계를 펼치지 않으므로 그 부분은 P/U다.
- 종합 의미 판정: **P/U**; route/auth와 위에서 직접 확인한 branch·shape는 C지만, 모든 helper·derived response·중첩 DTO·외부 효과까지 완결됐다는 뜻은 아니다.

<a id="schema-post-api-agents-key-prompt-preview"></a>
## `POST /api/agents/{key}/prompt/preview`

구현: `pgPreviewPrompt` · `server/server_mgmt.go` · [정확한 handler 근거](evidence:handler-post-api-agents-key-prompt-preview)

### 요청

| 위치/field | wire type | 필수·null | 기본/검증 |
|---|---|---|---|
| path `key` | `string` | 필수 | handler의 parse/range validation; 미해석 helper는 P |
| auth credential | Bearer header / `artex_token` cookie / query `token` | 세 위치 중 하나에 유효 JWT 필수 | `extractToken` 우선순위: header→cookie→query; query fallback은 SSE로 제한되지 않음 |
| body `template` | `string` (`string`) | 선택; null/absent 구별 안 됨 | `빈 값이면 현재 prompt를 읽어 fallback` |
| body `sample` | `object/JSON` (`map[string]string`) | 요구 여부 미확정(P); absent/null→nil; null/absent 구별 안 됨 | `nil` |

요청 판정: **C/P** · unknown field 거부 미설정.
직접 validation: `body.Template == ""`

### 응답·오류

| 종류 | status/body | 해석 상태 |
|---|---|---|
| success/variant | `200` · `rendered:string`, `error:helper result` | P/U · `static composite` |
| success/variant | `200` · `rendered:derived` | P/U · `static composite` |
| error | `401` · `{error:string}` · `"未授权"` / `"token 无效或已过期"` | C for direct writeErr; error helper 내부는 P |
| error | `404` · `{error:string}` · `"agent not found"` | C for direct writeErr; error helper 내부는 P |
| error | `500` · `{error:string}` · `err.Error()` | C for direct writeErr; error helper 내부는 P |
| error | `503` · `{error:string}` · `"PostgreSQL 不可用"` | C for direct writeErr; error helper 내부는 P |

공통 helper 오류 분기: `agentByKey` (`server/server_mgmt.go::Server.agentByKey`: 503/500/404). Status union은 helper control-flow를 정적으로 펼친 P이며 runtime DB 결과는 관찰하지 않았다.

응답 schema 판정: **P** · top-level key는 직접 추출했지만 derived/helper/opaque 또는 nested type이 남음.

### 목록·동시성·완료

- Pagination/filter/sort: 등록된 직접 query key 중 표준 paging/filter key 없음.
- Idempotency/concurrency: Idempotency-Key 없음; unique constraint 또는 상태 check가 있는 경우만 중복을 제한(P).
- Side effect/completion: handler의 직접 mutation/호출 후 응답; 외부 전달·agent 실행이 파생되면 별도 상태 확인(P).
- 직접 협력자/실행 단서: `err.Error`, `pg.CurrentPrompt`, `pg.PromptVars`, `renderPrompt`, `withGlobalVars`; direct `go` statement는 미발견. 이 목록은 helper 내부의 background launch나 transaction 경계를 펼치지 않으므로 그 부분은 P/U다.
- 종합 의미 판정: **P/U**; route/auth와 위에서 직접 확인한 branch·shape는 C지만, 모든 helper·derived response·중첩 DTO·외부 효과까지 완결됐다는 뜻은 아니다.

<a id="schema-get-api-agents-key-visibility"></a>
## `GET /api/agents/{key}/visibility`

구현: `pgGetAgentVisibility` · `server/server_mgmt.go` · [정확한 handler 근거](evidence:handler-get-api-agents-key-visibility)

### 요청

| 위치/field | wire type | 필수·null | 기본/검증 |
|---|---|---|---|
| path `key` | `string` | 필수 | handler의 parse/range validation; 미해석 helper는 P |
| auth credential | Bearer header / `artex_token` cookie / query `token` | 세 위치 중 하나에 유효 JWT 필수 | `extractToken` 우선순위: header→cookie→query; query fallback은 SSE로 제한되지 않음 |
| body | 없음(직접 decoder/form read 미발견) | 해당 없음 | 해당 없음 |

요청 판정: **C/P** · unknown field 거부 미설정.
직접 validation: `!ok` · `sk == nil`

### 응답·오류

| 종류 | status/body | 해석 상태 |
|---|---|---|
| success/variant | `200` · `mcp:derived`, `skill:derived` | P/U · `static composite` |
| error | `401` · `{error:string}` · `"未授权"` / `"token 无效或已过期"` | C for direct writeErr; error helper 내부는 P |
| error | `404` · `{error:string}` · `"agent not found"` | C for direct writeErr; error helper 내부는 P |
| error | `500` · `{error:string}` · `err.Error()` | C for direct writeErr; error helper 내부는 P |
| error | `503` · `{error:string}` · `"PostgreSQL 不可用"` | C for direct writeErr; error helper 내부는 P |

공통 helper 오류 분기: `agentByKey` (`server/server_mgmt.go::Server.agentByKey`: 503/500/404). Status union은 helper control-flow를 정적으로 펼친 P이며 runtime DB 결과는 관찰하지 않았다.

응답 schema 판정: **P** · top-level key는 직접 추출했지만 derived/helper/opaque 또는 nested type이 남음.

### 목록·동시성·완료

- Pagination/filter/sort: 등록된 직접 query key 중 표준 paging/filter key 없음.
- Idempotency/concurrency: read/stream 요청; stream 재연결은 cursor 계약에 따름.
- Side effect/completion: 직접 조회/파일 전송 완료; 외부 조회가 있으면 응답 전 handler 결과만 보장.
- 직접 협력자/실행 단서: `pg.AgentSkillNames`, `pg.AgentVisible`; direct `go` statement는 미발견. 이 목록은 helper 내부의 background launch나 transaction 경계를 펼치지 않으므로 그 부분은 P/U다.
- 종합 의미 판정: **P/U**; route/auth와 위에서 직접 확인한 branch·shape는 C지만, 모든 helper·derived response·중첩 DTO·외부 효과까지 완결됐다는 뜻은 아니다.

<a id="schema-put-api-agents-key-visibility"></a>
## `PUT /api/agents/{key}/visibility`

구현: `pgSetAgentVisibility` · `server/server_mgmt.go` · [정확한 handler 근거](evidence:handler-put-api-agents-key-visibility)

### 요청

| 위치/field | wire type | 필수·null | 기본/검증 |
|---|---|---|---|
| path `key` | `string` | 필수 | handler의 parse/range validation; 미해석 helper는 P |
| auth credential | Bearer header / `artex_token` cookie / query `token` | 세 위치 중 하나에 유효 JWT 필수 | `extractToken` 우선순위: header→cookie→query; query fallback은 SSE로 제한되지 않음 |
| body `mcp` | `array[integer]` (`[]int64`) | 요구 여부 미확정(P); absent/null→nil; null/absent 구별 안 됨 | `nil` |
| body `skill` | `array[string]` (`[]string`) | 요구 여부 미확정(P); absent/null→nil; null/absent 구별 안 됨 | `nil` |

요청 판정: **C/P** · unknown field 거부 미설정.

### 응답·오류

| 종류 | status/body | 해석 상태 |
|---|---|---|
| success/variant | `200` · `ok:boolean` | C · `static composite` |
| error | `400` · `{error:string}` · `err.Error()` | C for direct writeErr; error helper 내부는 P |
| error | `401` · `{error:string}` · `"未授权"` / `"token 无效或已过期"` | C for direct writeErr; error helper 내부는 P |
| error | `404` · `{error:string}` · `"agent not found"` | C for direct writeErr; error helper 내부는 P |
| error | `500` · `{error:string}` · `err.Error()` | C for direct writeErr; error helper 내부는 P |
| error | `503` · `{error:string}` · `"PostgreSQL 不可用"` | C for direct writeErr; error helper 내부는 P |

공통 helper 오류 분기: `agentByKey` (`server/server_mgmt.go::Server.agentByKey`: 503/500/404). Status union은 helper control-flow를 정적으로 펼친 P이며 runtime DB 결과는 관찰하지 않았다.

응답 schema 판정: **C** · 직접 scalar/static top-level key와 field type을 추출; nested 내부 schema는 별도 type 참조가 없으면 P.

### 목록·동시성·완료

- Pagination/filter/sort: 등록된 직접 query key 중 표준 paging/filter key 없음.
- Idempotency/concurrency: Idempotency-Key/If-Match 없음; DB upsert/trigger/side effect 때문에 반복 의미를 handler별 확인(P).
- Side effect/completion: handler의 직접 mutation/호출 후 응답; 외부 전달·agent 실행이 파생되면 별도 상태 확인(P).
- 직접 협력자/실행 단서: `err.Error`, `pg.SetAgentSkillVisibility`, `pg.SetAgentVisibilityKind`; direct `go` statement는 미발견. 이 목록은 helper 내부의 background launch나 transaction 경계를 펼치지 않으므로 그 부분은 P/U다.
- 종합 의미 판정: **P/U**; route/auth와 위에서 직접 확인한 branch·shape는 C지만, 모든 helper·derived response·중첩 DTO·외부 효과까지 완결됐다는 뜻은 아니다.

<a id="schema-get-api-tools"></a>
## `GET /api/tools`

구현: `pgListTools` · `server/server_mgmt.go` · [정확한 handler 근거](evidence:handler-get-api-tools)

### 요청

| 위치/field | wire type | 필수·null | 기본/검증 |
|---|---|---|---|
| auth credential | Bearer header / `artex_token` cookie / query `token` | 세 위치 중 하나에 유효 JWT 필수 | `extractToken` 우선순위: header→cookie→query; query fallback은 SSE로 제한되지 않음 |
| body | 없음(직접 decoder/form read 미발견) | 해당 없음 | 해당 없음 |

요청 판정: **C/P** · unknown field 거부 미설정.
직접 validation: `countErr != nil` · `err != nil` · `pg == nil` · `ts == nil`

### 응답·오류

| 종류 | status/body | 해석 상태 |
|---|---|---|
| success/variant | `200` · `tools:derived` | P/U · `static composite` |
| error | `401` · `{error:string}` · `"未授权"` / `"token 无效或已过期"` | C for direct writeErr; error helper 내부는 P |
| error | `500` · `{error:string}` · `err.Error()` | C for direct writeErr; error helper 내부는 P |
| error | `503` · `{error:string}` · `"PostgreSQL 不可用"` | C for direct writeErr; error helper 내부는 P |

응답 schema 판정: **P** · top-level key는 직접 추출했지만 derived/helper/opaque 또는 nested type이 남음.

### 목록·동시성·완료

- Pagination/filter/sort: 등록된 직접 query key 중 표준 paging/filter key 없음.
- Idempotency/concurrency: read/stream 요청; stream 재연결은 cursor 계약에 따름.
- Side effect/completion: 직접 조회/파일 전송 완료; 외부 조회가 있으면 응답 전 handler 결과만 보장.
- 직접 협력자/실행 단서: `err.Error`, `log.Printf`, `pg.ListTools`, `pg.ToolUsageCounts`; direct `go` statement는 미발견. 이 목록은 helper 내부의 background launch나 transaction 경계를 펼치지 않으므로 그 부분은 P/U다.
- 종합 의미 판정: **P/U**; route/auth와 위에서 직접 확인한 branch·shape는 C지만, 모든 helper·derived response·중첩 DTO·외부 효과까지 완결됐다는 뜻은 아니다.

<a id="schema-put-api-tools-key"></a>
## `PUT /api/tools/{key}`

구현: `pgUpdateTool` · `server/server_mgmt.go` · [정확한 handler 근거](evidence:handler-put-api-tools-key)

### 요청

| 위치/field | wire type | 필수·null | 기본/검증 |
|---|---|---|---|
| path `key` | `string` | 필수 | handler의 parse/range validation; 미해석 helper는 P |
| auth credential | Bearer header / `artex_token` cookie / query `token` | 세 위치 중 하나에 유효 JWT 필수 | `extractToken` 우선순위: header→cookie→query; query fallback은 SSE로 제한되지 않음 |
| body `description` | `string` (`string`) | 요구 여부 미확정(P); null/absent 구별 안 됨 | `Go zero value; 별도 기본은 handler/helper 확인` |
| body `schema` | `object/JSON` (`json.RawMessage`) | 요구 여부 미확정(P); null/absent 구별 안 됨 | `Go zero value; 별도 기본은 handler/helper 확인` |
| body `agents` | `array[string]` (`[]string`) | 요구 여부 미확정(P); absent/null→nil; null/absent 구별 안 됨 | `nil` |
| body `enabled` | `boolean` (`bool`) | 요구 여부 미확정(P); null/absent 구별 안 됨 | `Go zero value; 별도 기본은 handler/helper 확인` |

요청 판정: **C/P** · unknown field 거부 미설정.

### 응답·오류

| 종류 | status/body | 해석 상태 |
|---|---|---|
| success/variant | `200` · `ok:boolean` | C · `static composite` |
| error | `400` · `{error:string}` · `err.Error()` | C for direct writeErr; error helper 내부는 P |
| error | `401` · `{error:string}` · `"未授权"` / `"token 无效或已过期"` | C for direct writeErr; error helper 내부는 P |
| error | `404` · `{error:string}` · `"工具不存在: " + key` | C for direct writeErr; error helper 내부는 P |
| error | `500` · `{error:string}` · `err.Error()` | C for direct writeErr; error helper 내부는 P |
| error | `503` · `{error:string}` · `"PostgreSQL 不可用"` | C for direct writeErr; error helper 내부는 P |

응답 schema 판정: **C** · 직접 scalar/static top-level key와 field type을 추출; nested 내부 schema는 별도 type 참조가 없으면 P.

### 목록·동시성·완료

- Pagination/filter/sort: 등록된 직접 query key 중 표준 paging/filter key 없음.
- Idempotency/concurrency: Idempotency-Key/If-Match 없음; DB upsert/trigger/side effect 때문에 반복 의미를 handler별 확인(P).
- Side effect/completion: handler의 직접 mutation/호출 후 응답; 외부 전달·agent 실행이 파생되면 별도 상태 확인(P).
- 직접 협력자/실행 단서: `err.Error`, `pg.GetTool`, `pg.UpdateTool`, `r.PathValue`; direct `go` statement는 미발견. 이 목록은 helper 내부의 background launch나 transaction 경계를 펼치지 않으므로 그 부분은 P/U다.
- 종합 의미 판정: **P/U**; route/auth와 위에서 직접 확인한 branch·shape는 C지만, 모든 helper·derived response·중첩 DTO·외부 효과까지 완결됐다는 뜻은 아니다.

<a id="schema-post-api-tools-custom"></a>
## `POST /api/tools/custom`

구현: `pgCreateCustomTool` · `server/customtool.go` · [정확한 handler 근거](evidence:handler-post-api-tools-custom)

### 요청

| 위치/field | wire type | 필수·null | 기본/검증 |
|---|---|---|---|
| auth credential | Bearer header / `artex_token` cookie / query `token` | 세 위치 중 하나에 유효 JWT 필수 | `extractToken` 우선순위: header→cookie→query; query fallback은 SSE로 제한되지 않음 |
| body `key` | `string` (`string`) | 요구 여부 미확정(P); null/absent 구별 안 됨 | `!reToolKey.MatchString(req.Key)` |
| body `description` | `string` (`string`) | 요구 여부 미확정(P); null/absent 구별 안 됨 | `Go zero value; 별도 기본은 handler/helper 확인` |
| body `schema` | `object/JSON` (`json.RawMessage`) | 요구 여부 미확정(P); null/absent 구별 안 됨 | `req.Kind == "http" && !hasSchemaProps(req.Schema)` |
| body `agents` | `array[string]` (`[]string`) | 요구 여부 미확정(P); absent/null→nil; null/absent 구별 안 됨 | `nil` |
| body `enabled` | `boolean` (`bool`) | 요구 여부 미확정(P); null/absent 구별 안 됨 | `Go zero value; 별도 기본은 handler/helper 확인` |
| body `kind` | `string` (`string`) | 요구 여부 미확정(P); null/absent 구별 안 됨 | `req.Kind != "command" && req.Kind != "script" && req.Kind != "http" && req.Kind != "shell"; req.Kind == "http" && !hasSchemaProps(req.Schema)` |
| body `exec` | `object/JSON` (`json.RawMessage`) | 요구 여부 미확정(P); null/absent 구별 안 됨 | `Go zero value; 별도 기본은 handler/helper 확인` |
| body `deferred` | `boolean` (`bool`) | 요구 여부 미확정(P); null/absent 구별 안 됨 | `Go zero value; 별도 기본은 handler/helper 확인` |

요청 판정: **C/P** · unknown field 거부 미설정.
직접 validation: `!reToolKey.MatchString(req.Key)` · `req.Kind != "command" && req.Kind != "script" && req.Kind != "http" && req.Kind != "shell"` · `req.Kind == "http" && !hasSchemaProps(req.Schema)`

### 응답·오류

| 종류 | status/body | 해석 상태 |
|---|---|---|
| success/variant | `200` · `key:expression` | P/U · `static composite` |
| error | `400` · `{error:string}` · `err.Error()` / `"key 需小写字母开头，仅含小写字母/数字/下划线"` / `"kind 需为 command / script / http / shell"` / `"http 工具必须提供参数 JSON Schema(不能留空)"` | C for direct writeErr; error helper 내부는 P |
| error | `401` · `{error:string}` · `"未授权"` / `"token 无效或已过期"` | C for direct writeErr; error helper 내부는 P |
| error | `409` · `{error:string}` · `"该 key 已存在(内置或自定义工具)"` | C for direct writeErr; error helper 내부는 P |
| error | `500` · `{error:string}` · `err.Error()` | C for direct writeErr; error helper 내부는 P |
| error | `503` · `{error:string}` · `"PostgreSQL 不可用"` | C for direct writeErr; error helper 내부는 P |

응답 schema 판정: **P** · top-level key는 직접 추출했지만 derived/helper/opaque 또는 nested type이 남음.

### 목록·동시성·완료

- Pagination/filter/sort: 등록된 직접 query key 중 표준 paging/filter key 없음.
- Idempotency/concurrency: Idempotency-Key 없음; unique constraint 또는 상태 check가 있는 경우만 중복을 제한(P).
- Side effect/completion: handler의 직접 mutation/호출 후 응답; 외부 전달·agent 실행이 파생되면 별도 상태 확인(P).
- 직접 협력자/실행 단서: `err.Error`, `hasSchemaProps`, `pg.CreateCustomTool`, `pg.GetTool`, `reToolKey.MatchString`; direct `go` statement는 미발견. 이 목록은 helper 내부의 background launch나 transaction 경계를 펼치지 않으므로 그 부분은 P/U다.
- 종합 의미 판정: **P/U**; route/auth와 위에서 직접 확인한 branch·shape는 C지만, 모든 helper·derived response·중첩 DTO·외부 효과까지 완결됐다는 뜻은 아니다.

<a id="schema-post-api-tools-custom-test"></a>
## `POST /api/tools/custom/test`

구현: `pgTestCustomTool` · `server/customtool.go` · [정확한 handler 근거](evidence:handler-post-api-tools-custom-test)

### 요청

| 위치/field | wire type | 필수·null | 기본/검증 |
|---|---|---|---|
| auth credential | Bearer header / `artex_token` cookie / query `token` | 세 위치 중 하나에 유효 JWT 필수 | `extractToken` 우선순위: header→cookie→query; query fallback은 SSE로 제한되지 않음 |
| body `kind` | `string` (`string`) | 요구 여부 미확정(P); null/absent 구별 안 됨 | `Go zero value; 별도 기본은 handler/helper 확인` |
| body `exec` | `object/JSON` (`json.RawMessage`) | 요구 여부 미확정(P); null/absent 구별 안 됨 | `Go zero value; 별도 기본은 handler/helper 확인` |
| body `params` | `object/JSON` (`map[string]any`) | 요구 여부 미확정(P); absent/null→nil; null/absent 구별 안 됨 | `nil` |

요청 판정: **C/P** · unknown field 거부 미설정.

### 응답·오류

| 종류 | status/body | 해석 상태 |
|---|---|---|
| success/variant | `200` · `output:helper result`, `is_error:expression` | P/U · `static composite` |
| error | `400` · `{error:string}` · `"无效的请求体"` / `"shell 类型工具是 bash 环境声明，无可执行内容"` / `"未知工具类型: " + req.Kind` | C for direct writeErr; error helper 내부는 P |
| error | `401` · `{error:string}` · `"未授权"` / `"token 无效或已过期"` | C for direct writeErr; error helper 내부는 P |
| error | `503` · `{error:string}` · `"PostgreSQL 不可用"` | C for direct writeErr; error helper 내부는 P |

응답 schema 판정: **P** · top-level key는 직접 추출했지만 derived/helper/opaque 또는 nested type이 남음.

### 목록·동시성·완료

- Pagination/filter/sort: 등록된 직접 query key 중 표준 paging/filter key 없음.
- Idempotency/concurrency: Idempotency-Key 없음; unique constraint 또는 상태 check가 있는 경우만 중복을 제한(P).
- Side effect/completion: handler의 직접 mutation/호출 후 응답; 외부 전달·agent 실행이 파생되면 별도 상태 확인(P).
- 직접 협력자/실행 단서: `cancel`, `context.WithTimeout`, `json.NewDecoder`, `json.NewDecoder(r.Body).Decode`, `r.Context`, `res.Flatten`; direct `go` statement는 미발견. 이 목록은 helper 내부의 background launch나 transaction 경계를 펼치지 않으므로 그 부분은 P/U다.
- 종합 의미 판정: **P/U**; route/auth와 위에서 직접 확인한 branch·shape는 C지만, 모든 helper·derived response·중첩 DTO·외부 효과까지 완결됐다는 뜻은 아니다.

<a id="schema-put-api-tools-custom-key"></a>
## `PUT /api/tools/custom/{key}`

구현: `pgUpdateCustomTool` · `server/customtool.go` · [정확한 handler 근거](evidence:handler-put-api-tools-custom-key)

### 요청

| 위치/field | wire type | 필수·null | 기본/검증 |
|---|---|---|---|
| path `key` | `string` | 필수 | handler의 parse/range validation; 미해석 helper는 P |
| auth credential | Bearer header / `artex_token` cookie / query `token` | 세 위치 중 하나에 유효 JWT 필수 | `extractToken` 우선순위: header→cookie→query; query fallback은 SSE로 제한되지 않음 |
| body `key` | `string` (`string`) | 요구 여부 미확정(P); null/absent 구별 안 됨 | `Go zero value; 별도 기본은 handler/helper 확인` |
| body `description` | `string` (`string`) | 요구 여부 미확정(P); null/absent 구별 안 됨 | `Go zero value; 별도 기본은 handler/helper 확인` |
| body `schema` | `object/JSON` (`json.RawMessage`) | 요구 여부 미확정(P); null/absent 구별 안 됨 | `req.Kind == "http" && !hasSchemaProps(req.Schema)` |
| body `agents` | `array[string]` (`[]string`) | 요구 여부 미확정(P); absent/null→nil; null/absent 구별 안 됨 | `nil` |
| body `enabled` | `boolean` (`bool`) | 요구 여부 미확정(P); null/absent 구별 안 됨 | `Go zero value; 별도 기본은 handler/helper 확인` |
| body `kind` | `string` (`string`) | 요구 여부 미확정(P); null/absent 구별 안 됨 | `req.Kind != "command" && req.Kind != "script" && req.Kind != "http" && req.Kind != "shell"; req.Kind == "http" && !hasSchemaProps(req.Schema)` |
| body `exec` | `object/JSON` (`json.RawMessage`) | 요구 여부 미확정(P); null/absent 구별 안 됨 | `Go zero value; 별도 기본은 handler/helper 확인` |
| body `deferred` | `boolean` (`bool`) | 요구 여부 미확정(P); null/absent 구별 안 됨 | `Go zero value; 별도 기본은 handler/helper 확인` |

요청 판정: **C/P** · unknown field 거부 미설정.
직접 validation: `req.Kind != "command" && req.Kind != "script" && req.Kind != "http" && req.Kind != "shell"` · `req.Kind == "http" && !hasSchemaProps(req.Schema)`

### 응답·오류

| 종류 | status/body | 해석 상태 |
|---|---|---|
| success/variant | `200` · `ok:boolean` | C · `static composite` |
| error | `400` · `{error:string}` · `"只能编辑自定义工具"` / `err.Error()` / `"kind 需为 command / script / http / shell"` / `"http 工具必须提供参数 JSON Schema(不能留空)"` | C for direct writeErr; error helper 내부는 P |
| error | `401` · `{error:string}` · `"未授权"` / `"token 无效或已过期"` | C for direct writeErr; error helper 내부는 P |
| error | `500` · `{error:string}` · `err.Error()` | C for direct writeErr; error helper 내부는 P |
| error | `503` · `{error:string}` · `"PostgreSQL 不可用"` | C for direct writeErr; error helper 내부는 P |

응답 schema 판정: **C** · 직접 scalar/static top-level key와 field type을 추출; nested 내부 schema는 별도 type 참조가 없으면 P.

### 목록·동시성·완료

- Pagination/filter/sort: 등록된 직접 query key 중 표준 paging/filter key 없음.
- Idempotency/concurrency: Idempotency-Key/If-Match 없음; DB upsert/trigger/side effect 때문에 반복 의미를 handler별 확인(P).
- Side effect/completion: handler의 직접 mutation/호출 후 응답; 외부 전달·agent 실행이 파생되면 별도 상태 확인(P).
- 직접 협력자/실행 단서: `err.Error`, `hasSchemaProps`, `pg.GetTool`, `pg.UpdateCustomTool`, `r.PathValue`; direct `go` statement는 미발견. 이 목록은 helper 내부의 background launch나 transaction 경계를 펼치지 않으므로 그 부분은 P/U다.
- 종합 의미 판정: **P/U**; route/auth와 위에서 직접 확인한 branch·shape는 C지만, 모든 helper·derived response·중첩 DTO·외부 효과까지 완결됐다는 뜻은 아니다.

<a id="schema-delete-api-tools-custom-key"></a>
## `DELETE /api/tools/custom/{key}`

구현: `pgDeleteCustomTool` · `server/customtool.go` · [정확한 handler 근거](evidence:handler-delete-api-tools-custom-key)

### 요청

| 위치/field | wire type | 필수·null | 기본/검증 |
|---|---|---|---|
| path `key` | `string` | 필수 | handler의 parse/range validation; 미해석 helper는 P |
| auth credential | Bearer header / `artex_token` cookie / query `token` | 세 위치 중 하나에 유효 JWT 필수 | `extractToken` 우선순위: header→cookie→query; query fallback은 SSE로 제한되지 않음 |
| body | 없음(직접 decoder/form read 미발견) | 해당 없음 | 해당 없음 |

요청 판정: **C/P** · unknown field 거부 미설정.
직접 validation: `err != nil` · `pg == nil`

### 응답·오류

| 종류 | status/body | 해석 상태 |
|---|---|---|
| success/variant | `200` · `deleted:derived` | P/U · `static composite` |
| error | `401` · `{error:string}` · `"未授权"` / `"token 无效或已过期"` | C for direct writeErr; error helper 내부는 P |
| error | `500` · `{error:string}` · `err.Error()` | C for direct writeErr; error helper 내부는 P |
| error | `503` · `{error:string}` · `"PostgreSQL 不可用"` | C for direct writeErr; error helper 내부는 P |

응답 schema 판정: **P** · top-level key는 직접 추출했지만 derived/helper/opaque 또는 nested type이 남음.

### 목록·동시성·완료

- Pagination/filter/sort: 등록된 직접 query key 중 표준 paging/filter key 없음.
- Idempotency/concurrency: Idempotency-Key/If-Match 없음; 반복 시 응답/존재 검사가 달라질 수 있음.
- Side effect/completion: handler의 직접 mutation/호출 후 응답; 외부 전달·agent 실행이 파생되면 별도 상태 확인(P).
- 직접 협력자/실행 단서: `err.Error`, `pg.DeleteCustomTool`, `r.PathValue`; direct `go` statement는 미발견. 이 목록은 helper 내부의 background launch나 transaction 경계를 펼치지 않으므로 그 부분은 P/U다.
- 종합 의미 판정: **P/U**; route/auth와 위에서 직접 확인한 branch·shape는 C지만, 모든 helper·derived response·중첩 DTO·외부 효과까지 완결됐다는 뜻은 아니다.

<a id="schema-post-api-settings-python-detect"></a>
## `POST /api/settings/python/detect`

구현: `pgDetectPython` · `server/server.go` · [정확한 handler 근거](evidence:handler-post-api-settings-python-detect)

### 요청

| 위치/field | wire type | 필수·null | 기본/검증 |
|---|---|---|---|
| auth credential | Bearer header / `artex_token` cookie / query `token` | 세 위치 중 하나에 유효 JWT 필수 | `extractToken` 우선순위: header→cookie→query; query fallback은 SSE로 제한되지 않음 |
| body | 없음(직접 decoder/form read 미발견) | 해당 없음 | 해당 없음 |

요청 판정: **C/P** · unknown field 거부 미설정.
직접 validation: `err != nil` · `p == ""`

### 응답·오류

| 종류 | status/body | 해석 상태 |
|---|---|---|
| success/variant | `200` · `python_interpreter:derived` | P/U · `static composite` |
| error | `401` · `{error:string}` · `"未授权"` / `"token 无效或已过期"` | C for direct writeErr; error helper 내부는 P |
| error | `404` · `{error:string}` · `"未检测到 python(python3/python 均不在 PATH)"` | C for direct writeErr; error helper 내부는 P |
| error | `500` · `{error:string}` · `err.Error()` | C for direct writeErr; error helper 내부는 P |

응답 schema 판정: **P** · top-level key는 직접 추출했지만 derived/helper/opaque 또는 nested type이 남음.

### 목록·동시성·완료

- Pagination/filter/sort: 등록된 직접 query key 중 표준 paging/filter key 없음.
- Idempotency/concurrency: Idempotency-Key 없음; unique constraint 또는 상태 check가 있는 경우만 중복을 제한(P).
- Side effect/completion: handler의 직접 mutation/호출 후 응답; 외부 전달·agent 실행이 파생되면 별도 상태 확인(P).
- 직접 협력자/실행 단서: `detectPython`, `err.Error`, `s.m.pg.SetSetting`; direct `go` statement는 미발견. 이 목록은 helper 내부의 background launch나 transaction 경계를 펼치지 않으므로 그 부분은 P/U다.
- 종합 의미 판정: **P/U**; route/auth와 위에서 직접 확인한 branch·shape는 C지만, 모든 helper·derived response·중첩 DTO·외부 효과까지 완결됐다는 뜻은 아니다.

<a id="schema-post-api-tools-key-reset"></a>
## `POST /api/tools/{key}/reset`

구현: `pgResetTool` · `server/server_mgmt.go` · [정확한 handler 근거](evidence:handler-post-api-tools-key-reset)

### 요청

| 위치/field | wire type | 필수·null | 기본/검증 |
|---|---|---|---|
| path `key` | `string` | 필수 | handler의 parse/range validation; 미해석 helper는 P |
| auth credential | Bearer header / `artex_token` cookie / query `token` | 세 위치 중 하나에 유효 JWT 필수 | `extractToken` 우선순위: header→cookie→query; query fallback은 SSE로 제한되지 않음 |
| body | 없음(직접 decoder/form read 미발견) | 해당 없음 | 해당 없음 |

요청 판정: **C/P** · unknown field 거부 미설정.
직접 validation: `err != nil` · `pg == nil` · `sd.Key != key` · `t.Name() != key`

### 응답·오류

| 종류 | status/body | 해석 상태 |
|---|---|---|
| success/variant | `200` · `ok:boolean` | C · `static composite` |
| error | `401` · `{error:string}` · `"未授权"` / `"token 无效或已过期"` | C for direct writeErr; error helper 내부는 P |
| error | `404` · `{error:string}` · `"非内置工具或不存在: " + key` | C for direct writeErr; error helper 내부는 P |
| error | `500` · `{error:string}` · `err.Error()` | C for direct writeErr; error helper 내부는 P |
| error | `503` · `{error:string}` · `"PostgreSQL 不可用"` | C for direct writeErr; error helper 내부는 P |

응답 schema 판정: **C** · 직접 scalar/static top-level key와 field type을 추출; nested 내부 schema는 별도 type 참조가 없으면 P.

### 목록·동시성·완료

- Pagination/filter/sort: 등록된 직접 query key 중 표준 paging/filter key 없음.
- Idempotency/concurrency: Idempotency-Key 없음; unique constraint 또는 상태 check가 있는 경우만 중복을 제한(P).
- Side effect/completion: handler의 직접 mutation/호출 후 응답; 외부 전달·agent 실행이 파생되면 별도 상태 확인(P).
- 직접 협력자/실행 단서: `agent.BuiltinToolSeeds`, `err.Error`, `pg.UpsertToolForce`, `r.PathValue`, `t.Description`, `t.InputSchema`, `t.Name`; direct `go` statement는 미발견. 이 목록은 helper 내부의 background launch나 transaction 경계를 펼치지 않으므로 그 부분은 P/U다.
- 종합 의미 판정: **P/U**; route/auth와 위에서 직접 확인한 branch·shape는 C지만, 모든 helper·derived response·중첩 DTO·외부 효과까지 완결됐다는 뜻은 아니다.

<a id="schema-get-api-mcp"></a>
## `GET /api/mcp`

구현: `pgListMCP` · `server/server_mgmt.go` · [정확한 handler 근거](evidence:handler-get-api-mcp)

### 요청

| 위치/field | wire type | 필수·null | 기본/검증 |
|---|---|---|---|
| auth credential | Bearer header / `artex_token` cookie / query `token` | 세 위치 중 하나에 유효 JWT 필수 | `extractToken` 우선순위: header→cookie→query; query fallback은 SSE로 제한되지 않음 |
| body | 없음(직접 decoder/form read 미발견) | 해당 없음 | 해당 없음 |

요청 판정: **C/P** · unknown field 거부 미설정.
직접 validation: `err != nil` · `ms == nil` · `pg == nil`

### 응답·오류

| 종류 | status/body | 해석 상태 |
|---|---|---|
| success/variant | `200` · `servers:derived` | P/U · `static composite` |
| error | `401` · `{error:string}` · `"未授权"` / `"token 无效或已过期"` | C for direct writeErr; error helper 내부는 P |
| error | `500` · `{error:string}` · `err.Error()` | C for direct writeErr; error helper 내부는 P |
| error | `503` · `{error:string}` · `"PostgreSQL 不可用"` | C for direct writeErr; error helper 내부는 P |

응답 schema 판정: **P** · top-level key는 직접 추출했지만 derived/helper/opaque 또는 nested type이 남음.

### 목록·동시성·완료

- Pagination/filter/sort: 등록된 직접 query key 중 표준 paging/filter key 없음.
- Idempotency/concurrency: read/stream 요청; stream 재연결은 cursor 계약에 따름.
- Side effect/completion: 직접 조회/파일 전송 완료; 외부 조회가 있으면 응답 전 handler 결과만 보장.
- 직접 협력자/실행 단서: `err.Error`, `pg.ListMCP`; direct `go` statement는 미발견. 이 목록은 helper 내부의 background launch나 transaction 경계를 펼치지 않으므로 그 부분은 P/U다.
- 종합 의미 판정: **P/U**; route/auth와 위에서 직접 확인한 branch·shape는 C지만, 모든 helper·derived response·중첩 DTO·외부 효과까지 완결됐다는 뜻은 아니다.

<a id="schema-post-api-mcp"></a>
## `POST /api/mcp`

구현: `pgSaveMCP` · `server/server_mgmt.go` · [정확한 handler 근거](evidence:handler-post-api-mcp)

### 요청

| 위치/field | wire type | 필수·null | 기본/검증 |
|---|---|---|---|
| auth credential | Bearer header / `artex_token` cookie / query `token` | 세 위치 중 하나에 유효 JWT 필수 | `extractToken` 우선순위: header→cookie→query; query fallback은 SSE로 제한되지 않음 |
| body `id` | `integer` (`int64`) | 요구 여부 미확정(P); null/absent 구별 안 됨 | `m.ID = id` |
| body `name` | `string` (`string`) | 요구 여부 미확정(P); null/absent 구별 안 됨 | `Go zero value; 별도 기본은 handler/helper 확인` |
| body `transport` | `string` (`string`) | 요구 여부 미확정(P); null/absent 구별 안 됨 | `Go zero value; 별도 기본은 handler/helper 확인` |
| body `command` | `string` (`string`) | 요구 여부 미확정(P); null/absent 구별 안 됨 | `Go zero value; 별도 기본은 handler/helper 확인` |
| body `args` | `object/JSON` (`json.RawMessage`) | 요구 여부 미확정(P); null/absent 구별 안 됨 | `Go zero value; 별도 기본은 handler/helper 확인` |
| body `env` | `object/JSON` (`json.RawMessage`) | 요구 여부 미확정(P); null/absent 구별 안 됨 | `Go zero value; 별도 기본은 handler/helper 확인` |
| body `url` | `string` (`string`) | 요구 여부 미확정(P); null/absent 구별 안 됨 | `Go zero value; 별도 기본은 handler/helper 확인` |
| body `enabled` | `boolean` (`bool`) | 요구 여부 미확정(P); null/absent 구별 안 됨 | `isNew && m.Enabled` |
| body `insecure` | `boolean` (`bool`) | 요구 여부 미확정(P); null/absent 구별 안 됨 | `Go zero value; 별도 기본은 handler/helper 확인` |
| body `tools` | `array[string]` (`[]string`) | 요구 여부 미확정(P); absent/null→nil; null/absent 구별 안 됨 | `nil` |

요청 판정: **C/P** · unknown field 거부 미설정.
직접 validation: `isNew && m.Enabled`

### 응답·오류

| 종류 | status/body | 해석 상태 |
|---|---|---|
| success/variant | `200` · `id:derived` | P/U · `static composite` |
| error | `400` · `{error:string}` · `err.Error()` | C for direct writeErr; error helper 내부는 P |
| error | `401` · `{error:string}` · `"未授权"` / `"token 无效或已过期"` | C for direct writeErr; error helper 내부는 P |
| error | `500` · `{error:string}` · `err.Error()` | C for direct writeErr; error helper 내부는 P |
| error | `503` · `{error:string}` · `"PostgreSQL 不可用"` | C for direct writeErr; error helper 내부는 P |

응답 schema 판정: **P** · top-level key는 직접 추출했지만 derived/helper/opaque 또는 nested type이 남음.

### 목록·동시성·완료

- Pagination/filter/sort: 등록된 직접 query key 중 표준 paging/filter key 없음.
- Idempotency/concurrency: Idempotency-Key 없음; unique constraint 또는 상태 check가 있는 경우만 중복을 제한(P).
- Side effect/completion: handler의 직접 mutation/호출 후 응답; 외부 전달·agent 실행이 파생되면 별도 상태 확인(P).
- 직접 협력자/실행 단서: `cancel`, `context.WithTimeout`, `err.Error`, `log.Printf`, `pg.SaveMCP`, `r.Context`; direct `go` statement는 미발견. 이 목록은 helper 내부의 background launch나 transaction 경계를 펼치지 않으므로 그 부분은 P/U다.
- 종합 의미 판정: **P/U**; route/auth와 위에서 직접 확인한 branch·shape는 C지만, 모든 helper·derived response·중첩 DTO·외부 효과까지 완결됐다는 뜻은 아니다.

<a id="schema-delete-api-mcp-id"></a>
## `DELETE /api/mcp/{id}`

구현: `pgDeleteMCP` · `server/server_mgmt.go` · [정확한 handler 근거](evidence:handler-delete-api-mcp-id)

### 요청

| 위치/field | wire type | 필수·null | 기본/검증 |
|---|---|---|---|
| path `id` | `string` | 필수 | handler의 parse/range validation; 미해석 helper는 P |
| auth credential | Bearer header / `artex_token` cookie / query `token` | 세 위치 중 하나에 유효 JWT 필수 | `extractToken` 우선순위: header→cookie→query; query fallback은 SSE로 제한되지 않음 |
| body | 없음(직접 decoder/form read 미발견) | 해당 없음 | 해당 없음 |

요청 판정: **C/P** · unknown field 거부 미설정.
직접 validation: `err != nil` · `pg == nil`

### 응답·오류

| 종류 | status/body | 해석 상태 |
|---|---|---|
| success/variant | `200` · `deleted:derived` | P/U · `static composite` |
| error | `401` · `{error:string}` · `"未授权"` / `"token 无效或已过期"` | C for direct writeErr; error helper 내부는 P |
| error | `500` · `{error:string}` · `err.Error()` | C for direct writeErr; error helper 내부는 P |
| error | `503` · `{error:string}` · `"PostgreSQL 不可用"` | C for direct writeErr; error helper 내부는 P |

응답 schema 판정: **P** · top-level key는 직접 추출했지만 derived/helper/opaque 또는 nested type이 남음.

### 목록·동시성·완료

- Pagination/filter/sort: 등록된 직접 query key 중 표준 paging/filter key 없음.
- Idempotency/concurrency: Idempotency-Key/If-Match 없음; 반복 시 응답/존재 검사가 달라질 수 있음.
- Side effect/completion: handler의 직접 mutation/호출 후 응답; 외부 전달·agent 실행이 파생되면 별도 상태 확인(P).
- 직접 협력자/실행 단서: `err.Error`, `pathInt`, `pg.DeleteMCP`; direct `go` statement는 미발견. 이 목록은 helper 내부의 background launch나 transaction 경계를 펼치지 않으므로 그 부분은 P/U다.
- 종합 의미 판정: **P/U**; route/auth와 위에서 직접 확인한 branch·shape는 C지만, 모든 helper·derived response·중첩 DTO·외부 효과까지 완결됐다는 뜻은 아니다.

<a id="schema-get-api-mcp-id-tools"></a>
## `GET /api/mcp/{id}/tools`

구현: `pgMCPTools` · `server/server_mgmt.go` · [정확한 handler 근거](evidence:handler-get-api-mcp-id-tools)

### 요청

| 위치/field | wire type | 필수·null | 기본/검증 |
|---|---|---|---|
| path `id` | `string` | 필수 | handler의 parse/range validation; 미해석 helper는 P |
| auth credential | Bearer header / `artex_token` cookie / query `token` | 세 위치 중 하나에 유효 JWT 필수 | `extractToken` 우선순위: header→cookie→query; query fallback은 SSE로 제한되지 않음 |
| body | 없음(직접 decoder/form read 미발견) | 해당 없음 | 해당 없음 |

요청 판정: **C/P** · unknown field 거부 미설정.
직접 validation: `err != nil` · `pg == nil` · `tools == nil`

### 응답·오류

| 종류 | status/body | 해석 상태 |
|---|---|---|
| success/variant | `200` · `tools:derived` | P/U · `static composite` |
| error | `401` · `{error:string}` · `"未授权"` / `"token 无效或已过期"` | C for direct writeErr; error helper 내부는 P |
| error | `500` · `{error:string}` · `err.Error()` | C for direct writeErr; error helper 내부는 P |
| error | `503` · `{error:string}` · `"PostgreSQL 不可用"` | C for direct writeErr; error helper 내부는 P |

응답 schema 판정: **P** · top-level key는 직접 추출했지만 derived/helper/opaque 또는 nested type이 남음.

### 목록·동시성·완료

- Pagination/filter/sort: 등록된 직접 query key 중 표준 paging/filter key 없음.
- Idempotency/concurrency: read/stream 요청; stream 재연결은 cursor 계약에 따름.
- Side effect/completion: 직접 조회/파일 전송 완료; 외부 조회가 있으면 응답 전 handler 결과만 보장.
- 직접 협력자/실행 단서: `err.Error`, `pathInt`, `pg.MCPToolsDetailed`; direct `go` statement는 미발견. 이 목록은 helper 내부의 background launch나 transaction 경계를 펼치지 않으므로 그 부분은 P/U다.
- 종합 의미 판정: **P/U**; route/auth와 위에서 직접 확인한 branch·shape는 C지만, 모든 helper·derived response·중첩 DTO·외부 효과까지 완결됐다는 뜻은 아니다.

<a id="schema-post-api-mcp-id-refresh"></a>
## `POST /api/mcp/{id}/refresh`

구현: `pgRefreshMCP` · `server/server_mgmt.go` · [정확한 handler 근거](evidence:handler-post-api-mcp-id-refresh)

### 요청

| 위치/field | wire type | 필수·null | 기본/검증 |
|---|---|---|---|
| path `id` | `string` | 필수 | handler의 parse/range validation; 미해석 helper는 P |
| auth credential | Bearer header / `artex_token` cookie / query `token` | 세 위치 중 하나에 유효 JWT 필수 | `extractToken` 우선순위: header→cookie→query; query fallback은 SSE로 제한되지 않음 |
| body | 없음(직접 decoder/form read 미발견) | 해당 없음 | 해당 없음 |

요청 판정: **C/P** · unknown field 거부 미설정.
직접 validation: `err != nil` · `m.ID == id` · `pg == nil` · `target == nil`

### 응답·오류

| 종류 | status/body | 해석 상태 |
|---|---|---|
| success/variant | `200` · `tools:derived` | P/U · `static composite` |
| error | `401` · `{error:string}` · `"未授权"` / `"token 无效或已过期"` | C for direct writeErr; error helper 내부는 P |
| error | `404` · `{error:string}` · `"MCP 不存在"` | C for direct writeErr; error helper 내부는 P |
| error | `500` · `{error:string}` · `err.Error()` | C for direct writeErr; error helper 내부는 P |
| error | `502` · `{error:string}` · `"工具发现失败：" + err.Error()` | C for direct writeErr; error helper 내부는 P |
| error | `503` · `{error:string}` · `"PostgreSQL 不可用"` | C for direct writeErr; error helper 내부는 P |

응답 schema 판정: **P** · top-level key는 직접 추출했지만 derived/helper/opaque 또는 nested type이 남음.

### 목록·동시성·완료

- Pagination/filter/sort: 등록된 직접 query key 중 표준 paging/filter key 없음.
- Idempotency/concurrency: Idempotency-Key 없음; unique constraint 또는 상태 check가 있는 경우만 중복을 제한(P).
- Side effect/completion: remote MCP discovery/refresh가 포함될 수 있으며 HTTP response가 해당 호출 완료 경계.
- 직접 협력자/실행 단서: `cancel`, `context.WithTimeout`, `err.Error`, `pathInt`, `pg.ListMCP`, `pg.MCPToolsDetailed`, `r.Context`; direct `go` statement는 미발견. 이 목록은 helper 내부의 background launch나 transaction 경계를 펼치지 않으므로 그 부분은 P/U다.
- 종합 의미 판정: **P/U**; route/auth와 위에서 직접 확인한 branch·shape는 C지만, 모든 helper·derived response·중첩 DTO·외부 효과까지 완결됐다는 뜻은 아니다.

<a id="schema-get-api-sync-scopesentry-status"></a>
## `GET /api/sync/scopesentry/status`

구현: `syncSSStatus` · `server/sync_scopesentry.go` · [정확한 handler 근거](evidence:handler-get-api-sync-scopesentry-status)

### 요청

| 위치/field | wire type | 필수·null | 기본/검증 |
|---|---|---|---|
| auth credential | Bearer header / `artex_token` cookie / query `token` | 세 위치 중 하나에 유효 JWT 필수 | `extractToken` 우선순위: header→cookie→query; query fallback은 SSE로 제한되지 않음 |
| body | 없음(직접 decoder/form read 미발견) | 해당 없음 | 해당 없음 |

요청 판정: **C/P** · unknown field 거부 미설정.
직접 validation: `cerr == nil` · `configured && m.Enabled` · `err != nil` · `m == nil` · `m.Tools != nil` · `s.pg(w) == nil` · `terr == nil`

### 응답·오류

| 종류 | status/body | 해석 상태 |
|---|---|---|
| success/variant | `200` · `resp` | P/U · `derived variable` |
| error | `401` · `{error:string}` · `"未授权"` / `"token 无效或已过期"` | C for direct writeErr; error helper 내부는 P |
| error | `500` · `{error:string}` · `err.Error()` | C for direct writeErr; error helper 내부는 P |
| error | `503` · `{error:string}` · `"PostgreSQL 不可用"` | C for direct writeErr; error helper 내부는 P |

응답 schema 판정: **P** · top-level key는 직접 추출했지만 derived/helper/opaque 또는 nested type이 남음.

### 목록·동시성·완료

- Pagination/filter/sort: 등록된 직접 query key 중 표준 paging/filter key 없음.
- Idempotency/concurrency: read/stream 요청; stream 재연결은 cursor 계약에 따름.
- Side effect/completion: 직접 조회/파일 전송 완료; 외부 조회가 있으면 응답 전 handler 결과만 보장.
- 직접 협력자/실행 단서: `cancel`, `cl.Close`, `cl.Tools`, `context.WithTimeout`, `envHasValue`, `err.Error`, `jsonStrMap`, `mcphttp.New`, `r.Context`; direct `go` statement는 미발견. 이 목록은 helper 내부의 background launch나 transaction 경계를 펼치지 않으므로 그 부분은 P/U다.
- 종합 의미 판정: **P/U**; route/auth와 위에서 직접 확인한 branch·shape는 C지만, 모든 helper·derived response·중첩 DTO·외부 효과까지 완결됐다는 뜻은 아니다.

<a id="schema-post-api-sync-scopesentry-datasource"></a>
## `POST /api/sync/scopesentry/datasource`

구현: `syncSSDatasource` · `server/sync_scopesentry.go` · [정확한 handler 근거](evidence:handler-post-api-sync-scopesentry-datasource)

### 요청

| 위치/field | wire type | 필수·null | 기본/검증 |
|---|---|---|---|
| auth credential | Bearer header / `artex_token` cookie / query `token` | 세 위치 중 하나에 유효 JWT 필수 | `extractToken` 우선순위: header→cookie→query; query fallback은 SSE로 제한되지 않음 |
| body `url` | `string` (`string`) | 요구 여부 미확정(P); null/absent 구별 안 됨 | `body.URL != ""` |
| body `api_key` | `string` (`string`) | 요구 여부 미확정(P); null/absent 구별 안 됨 | `body.APIKey != ""` |

요청 판정: **C/P** · unknown field 거부 미설정.
직접 validation: `body.APIKey != ""` · `body.URL != ""`

### 응답·오류

| 종류 | status/body | 해석 상태 |
|---|---|---|
| success/variant | `200` · `id:derived`, `enabled:expression` | P/U · `static composite` |
| error | `401` · `{error:string}` · `"未授权"` / `"token 无效或已过期"` | C for direct writeErr; error helper 내부는 P |
| error | `500` · `{error:string}` · `err.Error()` | C for direct writeErr; error helper 내부는 P |
| error | `503` · `{error:string}` · `"PostgreSQL 不可用"` | C for direct writeErr; error helper 내부는 P |

응답 schema 판정: **P** · top-level key는 직접 추출했지만 derived/helper/opaque 또는 nested type이 남음.

### 목록·동시성·완료

- Pagination/filter/sort: 등록된 직접 query key 중 표준 paging/filter key 없음.
- Idempotency/concurrency: Idempotency-Key 없음; unique constraint 또는 상태 check가 있는 경우만 중복을 제한(P).
- Side effect/completion: 외부 ScopeSentry 호출과 local upsert를 수행; HTTP response는 해당 handler run의 결과.
- 직접 협력자/실행 단서: `cancel`, `context.WithTimeout`, `envHasValue`, `err.Error`, `json.RawMessage`, `pg.SaveMCP`, `r.Context`; direct `go` statement는 미발견. 이 목록은 helper 내부의 background launch나 transaction 경계를 펼치지 않으므로 그 부분은 P/U다.
- 종합 의미 판정: **P/U**; route/auth와 위에서 직접 확인한 branch·shape는 C지만, 모든 helper·derived response·중첩 DTO·외부 효과까지 완결됐다는 뜻은 아니다.

<a id="schema-get-api-sync-scopesentry-projects"></a>
## `GET /api/sync/scopesentry/projects`

구현: `syncSSProjects` · `server/sync_scopesentry.go` · [정확한 handler 근거](evidence:handler-get-api-sync-scopesentry-projects)

### 요청

| 위치/field | wire type | 필수·null | 기본/검증 |
|---|---|---|---|
| query `page` | `string` wire | 선택 | positive decimal integer; invalid/zero/negative→1 |
| query `search` | `string` wire | 선택 | handler/helper 변환·default 미전개(P) |
| query `size` | `string` wire | 선택 | positive decimal integer; invalid/zero/negative→50 |
| auth credential | Bearer header / `artex_token` cookie / query `token` | 세 위치 중 하나에 유효 JWT 필수 | `extractToken` 우선순위: header→cookie→query; query fallback은 SSE로 제한되지 않음 |
| body | 없음(직접 decoder/form read 미발견) | 해당 없음 | 해당 없음 |

요청 판정: **C/P** · unknown field 거부 미설정.
직접 validation: `err != nil` · `ok && len(raw) > 0` · `q != ""` · `s.pg(w) == nil`

### 응답·오류

| 종류 | status/body | 해석 상태 |
|---|---|---|
| success/variant | `200` · `projects:derived`, `tag:expression` | P/U · `static composite` |
| error | `401` · `{error:string}` · `"未授权"` / `"token 无效或已过期"` | C for direct writeErr; error helper 내부는 P |
| error | `502` · `{error:string}` · `err.Error()` / `"list_projects_data 失败: " + err.Error()` / `"解析项目列表失败: " + err.Error()` | C for direct writeErr; error helper 내부는 P |
| error | `503` · `{error:string}` · `"PostgreSQL 不可用"` | C for direct writeErr; error helper 내부는 P |

응답 schema 판정: **P** · top-level key는 직접 추출했지만 derived/helper/opaque 또는 nested type이 남음.

### 목록·동시성·완료

- Pagination/filter/sort: `page`, `size`.
- Idempotency/concurrency: read/stream 요청; stream 재연결은 cursor 계약에 따름.
- Side effect/completion: 직접 조회/파일 전송 완료; 외부 조회가 있으면 응답 전 handler 결과만 보장.
- 직접 협력자/실행 단서: `[]byte`, `cancel`, `cl.Call`, `cl.Close`, `context.WithTimeout`, `err.Error`, `json.RawMessage`, `queryInt`, `r.Context`, `r.URL.Query`, `r.URL.Query().Get`; direct `go` statement는 미발견. 이 목록은 helper 내부의 background launch나 transaction 경계를 펼치지 않으므로 그 부분은 P/U다.
- 종합 의미 판정: **P/U**; route/auth와 위에서 직접 확인한 branch·shape는 C지만, 모든 helper·derived response·중첩 DTO·외부 효과까지 완결됐다는 뜻은 아니다.

<a id="schema-get-api-sync-scopesentry-tasks"></a>
## `GET /api/sync/scopesentry/tasks`

구현: `syncSSTasks` · `server/sync_scopesentry.go` · [정확한 handler 근거](evidence:handler-get-api-sync-scopesentry-tasks)

### 요청

| 위치/field | wire type | 필수·null | 기본/검증 |
|---|---|---|---|
| query `page` | `string` wire | 선택 | positive decimal integer; invalid/zero/negative→1 |
| query `search` | `string` wire | 선택 | handler/helper 변환·default 미전개(P) |
| query `size` | `string` wire | 선택 | positive decimal integer; invalid/zero/negative→50 |
| auth credential | Bearer header / `artex_token` cookie / query `token` | 세 위치 중 하나에 유효 JWT 필수 | `extractToken` 우선순위: header→cookie→query; query fallback은 SSE로 제한되지 않음 |
| body | 없음(직접 decoder/form read 미발견) | 해당 없음 | 해당 없음 |

요청 판정: **C/P** · unknown field 거부 미설정.
직접 validation: `err != nil` · `len(tasks) == 0` · `q != ""` · `s.pg(w) == nil`

### 응답·오류

| 종류 | status/body | 해석 상태 |
|---|---|---|
| success/variant | `200` · `tasks:derived` | P/U · `static composite` |
| error | `401` · `{error:string}` · `"未授权"` / `"token 无效或已过期"` | C for direct writeErr; error helper 내부는 P |
| error | `502` · `{error:string}` · `err.Error()` / `"list_tasks 失败: " + err.Error()` / `"解析任务列表失败: " + err.Error()` | C for direct writeErr; error helper 내부는 P |
| error | `503` · `{error:string}` · `"PostgreSQL 不可用"` | C for direct writeErr; error helper 내부는 P |

응답 schema 판정: **P** · top-level key는 직접 추출했지만 derived/helper/opaque 또는 nested type이 남음.

### 목록·동시성·완료

- Pagination/filter/sort: `page`, `size`.
- Idempotency/concurrency: read/stream 요청; stream 재연결은 cursor 계약에 따름.
- Side effect/completion: 직접 조회/파일 전송 완료; 외부 조회가 있으면 응답 전 handler 결과만 보장.
- 직접 협력자/실행 단서: `[]byte`, `cancel`, `cl.Call`, `cl.Close`, `context.WithTimeout`, `err.Error`, `json.RawMessage`, `queryInt`, `r.Context`, `r.URL.Query`, `r.URL.Query().Get`; direct `go` statement는 미발견. 이 목록은 helper 내부의 background launch나 transaction 경계를 펼치지 않으므로 그 부분은 P/U다.
- 종합 의미 판정: **P/U**; route/auth와 위에서 직접 확인한 branch·shape는 C지만, 모든 helper·derived response·중첩 DTO·외부 효과까지 완결됐다는 뜻은 아니다.

<a id="schema-post-api-sync-scopesentry-sync"></a>
## `POST /api/sync/scopesentry/sync`

구현: `syncSSRun` · `server/sync_scopesentry.go` · [정확한 handler 근거](evidence:handler-post-api-sync-scopesentry-sync)

### 요청

| 위치/field | wire type | 필수·null | 기본/검증 |
|---|---|---|---|
| auth credential | Bearer header / `artex_token` cookie / query `token` | 세 위치 중 하나에 유효 JWT 필수 | `extractToken` 우선순위: header→cookie→query; query fallback은 SSE로 제한되지 않음 |
| body `dimension` | `string` (`string`) | 필수(직접 거부 branch 확인); null/absent 구별 안 됨 | `project/task가 아니면 400 후 return` |
| body `targets` | `array[string]` (`[]string`) | 필수(직접 거부 branch 확인); null/absent 구별 안 됨 | `빈 배열이면 400 후 return` |
| body `asset_types` | `array[string]` (`[]string`) | 선택; null/absent 구별 안 됨 | `빈 배열이면 subdomain/service/app 기본값` |
| body `create_company` | `boolean` (`bool`) | 요구 여부 미확정(P); null/absent 구별 안 됨 | `req.CreateCompany` |
| body `page_size` | `integer` (`int`) | 요구 여부 미확정(P); null/absent 구별 안 됨 | `Go zero value; 별도 기본은 handler/helper 확인` |

요청 판정: **C/P** · unknown field 거부 미설정.
직접 validation: `len(req.AssetTypes) == 0` · `len(req.Targets) == 0` · `pageSize <= 0 \|\| pageSize > 500` · `req.CreateCompany` · `req.Dimension != "project" && req.Dimension != "task"`

### 응답·오류

| 종류 | status/body | 해석 상태 |
|---|---|---|
| success/variant | `200` · `synced:derived`, `companies:derived`, `warnings:derived`, `errors:derived` | P/U · `static composite` |
| error | `400` · `{error:string}` · `"invalid JSON: " + err.Error()` / `"dimension 必须是 project 或 task"` / `"targets 不能为空"` | C for direct writeErr; error helper 내부는 P |
| error | `401` · `{error:string}` · `"未授权"` / `"token 无效或已过期"` | C for direct writeErr; error helper 내부는 P |
| error | `502` · `{error:string}` · `err.Error()` | C for direct writeErr; error helper 내부는 P |
| error | `503` · `{error:string}` · `"database unavailable"` / `"PostgreSQL 不可用"` | C for direct writeErr; error helper 내부는 P |

응답 schema 판정: **P** · top-level key는 직접 추출했지만 derived/helper/opaque 또는 nested type이 남음.

### 목록·동시성·완료

- Pagination/filter/sort: 등록된 직접 query key 중 표준 paging/filter key 없음.
- Idempotency/concurrency: Idempotency-Key 없음; unique constraint 또는 상태 check가 있는 경우만 중복을 제한(P).
- Side effect/completion: 외부 ScopeSentry 호출과 local upsert를 수행; HTTP response는 해당 handler run의 결과.
- 직접 협력자/실행 단서: `cancel`, `cl.Close`, `context.WithTimeout`, `cs.AddScope`, `cs.RecomputeAttribution`, `cs.UpsertByName`, `err.Error`, `r.Context`; direct `go` statement는 미발견. 이 목록은 helper 내부의 background launch나 transaction 경계를 펼치지 않으므로 그 부분은 P/U다.
- 종합 의미 판정: **P/U**; route/auth와 위에서 직접 확인한 branch·shape는 C지만, 모든 helper·derived response·중첩 DTO·외부 효과까지 완결됐다는 뜻은 아니다.

<a id="schema-get-api-skills"></a>
## `GET /api/skills`

구현: `fsListSkills` · `server/server_mgmt.go` · [정확한 handler 근거](evidence:handler-get-api-skills)

### 요청

| 위치/field | wire type | 필수·null | 기본/검증 |
|---|---|---|---|
| auth credential | Bearer header / `artex_token` cookie / query `token` | 세 위치 중 하나에 유효 JWT 필수 | `extractToken` 우선순위: header→cookie→query; query fallback은 SSE로 제한되지 않음 |
| body | 없음(직접 decoder/form read 미발견) | 해당 없음 | 해당 없음 |

요청 판정: **C/P** · unknown field 거부 미설정.
직접 validation: `!e.IsDir()` · `allReg != nil` · `err != nil` · `node.Files == nil` · `ok` · `s.m.pg != nil` · `sk.Dir != ""`

### 응답·오류

| 종류 | status/body | 해석 상태 |
|---|---|---|
| success/variant | `200` · `skills:derived` | P/U · `static composite` |
| error | `401` · `{error:string}` · `"未授权"` / `"token 无效或已过期"` | C for direct writeErr; error helper 내부는 P |
| error | `500` · `{error:string}` · `err.Error()` | C for direct writeErr; error helper 내부는 P |

응답 schema 판정: **P** · top-level key는 직접 추출했지만 derived/helper/opaque 또는 nested type이 남음.

### 목록·동시성·완료

- Pagination/filter/sort: 등록된 직접 query key 중 표준 paging/filter key 없음.
- Idempotency/concurrency: read/stream 요청; stream 재연결은 cursor 계약에 따름.
- Side effect/completion: 직접 조회/파일 전송 완료; 외부 조회가 있으면 응답 전 handler 결과만 보장.
- 직접 협력자/실행 단서: `allReg.List`, `e.IsDir`, `e.Name`, `err.Error`, `filepath.Base`, `filepath.Join`, `log.Printf`, `os.MkdirAll`, `os.ReadDir`, `s.m.pg.SkillStats`, `skill.LoadDir`, `walkSkillFiles`; direct `go` statement는 미발견. 이 목록은 helper 내부의 background launch나 transaction 경계를 펼치지 않으므로 그 부분은 P/U다.
- 종합 의미 판정: **P/U**; route/auth와 위에서 직접 확인한 branch·shape는 C지만, 모든 helper·derived response·중첩 DTO·외부 효과까지 완결됐다는 뜻은 아니다.

<a id="schema-post-api-skills"></a>
## `POST /api/skills`

구현: `fsCreateSkill` · `server/server_mgmt.go` · [정확한 handler 근거](evidence:handler-post-api-skills)

### 요청

| 위치/field | wire type | 필수·null | 기본/검증 |
|---|---|---|---|
| auth credential | Bearer header / `artex_token` cookie / query `token` | 세 위치 중 하나에 유효 JWT 필수 | `extractToken` 우선순위: header→cookie→query; query fallback은 SSE로 제한되지 않음 |
| body `name` | `string` (`string`) | 필수(직접 거부 branch 확인); null/absent 구별 안 됨 | `validSkillName 실패면 400 후 return` |
| body `description` | `string` (`string`) | 필수(직접 거부 branch 확인); null/absent 구별 안 됨 | `trim 후 빈 문자열이면 400 후 return` |
| body `license` | `string` (`string`) | 요구 여부 미확정(P); null/absent 구별 안 됨 | `body.License != ""` |
| body `compatibility` | `string` (`string`) | 요구 여부 미확정(P); null/absent 구별 안 됨 | `body.Compatibility != ""` |
| body `mcps` | `array[string]` (`[]string`) | 요구 여부 미확정(P); absent/null→nil; null/absent 구별 안 됨 | `nil` |
| body `instructions` | `string` (`string`) | 요구 여부 미확정(P); null/absent 구별 안 됨 | `strings.TrimSpace(body.Instructions) != ""` |

요청 판정: **C/P** · unknown field 거부 미설정.
직접 validation: `!validSkillName(body.Name)` · `body.Compatibility != ""` · `body.License != ""` · `strings.TrimSpace(body.Description) == ""` · `strings.TrimSpace(body.Instructions) != ""`

### 응답·오류

| 종류 | status/body | 해석 상태 |
|---|---|---|
| success/variant | `201` · `name:expression` | P/U · `static composite` |
| error | `400` · `{error:string}` · `err.Error()` / `"skill name must be 1-64 lowercase alphanumeric/hyphen characters, not starting/ending/doubling hyphens"` / `"description is required"` | C for direct writeErr; error helper 내부는 P |
| error | `401` · `{error:string}` · `"未授权"` / `"token 无效或已过期"` | C for direct writeErr; error helper 내부는 P |
| error | `409` · `{error:string}` · `"skill already exists"` | C for direct writeErr; error helper 내부는 P |
| error | `500` · `{error:string}` · `err.Error()` | C for direct writeErr; error helper 내부는 P |

응답 schema 판정: **P** · top-level key는 직접 추출했지만 derived/helper/opaque 또는 nested type이 남음.

### 목록·동시성·완료

- Pagination/filter/sort: 등록된 직접 query key 중 표준 paging/filter key 없음.
- Idempotency/concurrency: Idempotency-Key 없음; unique constraint 또는 상태 check가 있는 경우만 중복을 제한(P).
- Side effect/completion: handler의 직접 mutation/호출 후 응답; 외부 전달·agent 실행이 파생되면 별도 상태 확인(P).
- 직접 협력자/실행 단서: `[]byte`, `cleanStrs`, `err.Error`, `filepath.Join`, `fmt.Fprintf`, `os.MkdirAll`, `os.RemoveAll`, `os.Stat`, `os.WriteFile`, `sb.String`, `sb.WriteString`, `validSkillName`; direct `go` statement는 미발견. 이 목록은 helper 내부의 background launch나 transaction 경계를 펼치지 않으므로 그 부분은 P/U다.
- 종합 의미 판정: **P/U**; route/auth와 위에서 직접 확인한 branch·shape는 C지만, 모든 helper·derived response·중첩 DTO·외부 효과까지 완결됐다는 뜻은 아니다.

<a id="schema-post-api-skills-upload"></a>
## `POST /api/skills/upload`

구현: `fsUploadSkill` · `server/server_mgmt.go` · [정확한 handler 근거](evidence:handler-post-api-skills-upload)

### 요청

| 위치/field | wire type | 필수·null | 기본/검증 |
|---|---|---|---|
| query `overwrite` | `string` wire | 선택 | handler/helper 변환·default 미전개(P) |
| auth credential | Bearer header / `artex_token` cookie / query `token` | 세 위치 중 하나에 유효 JWT 필수 | `extractToken` 우선순위: header→cookie→query; query fallback은 SSE로 제한되지 않음 |
| multipart `file` | ZIP binary | 필수 | size/method/path/SKILL.md/frontmatter 검증 |

요청 body 상한: **20 MiB** (`20971520` bytes) · source `server/server_mgmt.go::fsUploadSkill` · 초과/decoder 경로 `400 "缺少上传文件(表单字段 file)或超出大小限制"`.

요청 판정: **C/P** · unknown field 거부 미설정.
직접 validation: `!validSkillName(name)` · `entries > maxSkillEntries` · `err != nil` · `err == nil && !overwrite` · `f.UncompressedSize64 > maxSkillFileBytes` · `msg != ""` · `name == ""` · `name == "" && prefix != ""` · `overwrite` · `path.Base(e.name) != "SKILL.md"` · `prefix != "" && !strings.HasPrefix(e.name, prefix)` · `rel == ""` · `root != "."` · `skillMD == nil` · `skillMD == nil \|\| strings.Count(e.name, "/") < strings.Count(skillMD.name, "/")` · `total > maxSkillTotalBytes`

### 응답·오류

| 종류 | status/body | 해석 상태 |
|---|---|---|
| success/variant | `201` · `name:derived`, `files:derived` | P/U · `static composite` |
| error | `400` · `{error:string}` · `"缺少上传文件(表单字段 file)或超出大小限制"` / `err.Error()` / `"压缩包内未找到 SKILL.md"` / `"读取 SKILL.md 失败：" + err.Error()` / `"skill 名称无效（取自 SKILL.md 的 name 字段）：" + name + "（≤64 字符，字母开头，只能用小写字母/数字/连字符或中文等非 ASCII 字母，不能有空格、点、路径分隔符）"` / `"压缩包含非法路径 " + e.name + "：" + msg` / `"压缩包文件过多"` / `"文件过大：" + rel` / `"压缩包解压后过大"` / `"解压后缺少 SKILL.md"` | C for direct writeErr; error helper 내부는 P |
| error | `401` · `{error:string}` · `"未授权"` / `"token 无效或已过期"` | C for direct writeErr; error helper 내부는 P |
| error | `409` · `{error:string}` · `"skill 已存在：" + name + "（如需覆盖请确认后重试）"` | C for direct writeErr; error helper 내부는 P |
| error | `500` · `{error:string}` · `err.Error()` / `"安装失败：" + err.Error()` | C for direct writeErr; error helper 내부는 P |

응답 schema 판정: **P** · top-level key는 직접 추출했지만 derived/helper/opaque 또는 nested type이 남음.

### 목록·동시성·완료

- Pagination/filter/sort: 등록된 직접 query key 중 표준 paging/filter key 없음.
- Idempotency/concurrency: Idempotency-Key 없음; unique constraint 또는 상태 check가 있는 경우만 중복을 제한(P).
- Side effect/completion: handler의 직접 mutation/호출 후 응답; 외부 전달·agent 실행이 파생되면 별도 상태 확인(P).
- 직접 협력자/실행 단서: `checkSkillZipMethods`, `err.Error`, `f.Open`, `file.Close`, `filepath.Dir`, `filepath.Join`, `filepath.ToSlash`, `io.Copy`, `io.LimitReader`, `io.ReadAll`, `newSkillZipReader`, `os.Create`, `os.MkdirAll`, `os.MkdirTemp`, `os.RemoveAll`, `os.Rename`, `os.Stat`, `out.Close` 외 12개; direct `go` statement는 미발견. 이 목록은 helper 내부의 background launch나 transaction 경계를 펼치지 않으므로 그 부분은 P/U다.
- 종합 의미 판정: **P/U**; route/auth와 위에서 직접 확인한 branch·shape는 C지만, 모든 helper·derived response·중첩 DTO·외부 효과까지 완결됐다는 뜻은 아니다.

<a id="schema-delete-api-skills-name"></a>
## `DELETE /api/skills/{name}`

구현: `fsDeleteSkill` · `server/server_mgmt.go` · [정확한 handler 근거](evidence:handler-delete-api-skills-name)

### 요청

| 위치/field | wire type | 필수·null | 기본/검증 |
|---|---|---|---|
| path `name` | `string` | 필수 | handler의 parse/range validation; 미해석 helper는 P |
| auth credential | Bearer header / `artex_token` cookie / query `token` | 세 위치 중 하나에 유효 JWT 필수 | `extractToken` 우선순위: header→cookie→query; query fallback은 SSE로 제한되지 않음 |
| body | 없음(직접 decoder/form read 미발견) | 해당 없음 | 해당 없음 |

요청 판정: **C/P** · unknown field 거부 미설정.
직접 validation: `!validSkillName(name)` · `err != nil` · `pg == nil`

### 응답·오류

| 종류 | status/body | 해석 상태 |
|---|---|---|
| success/variant | `200` · `deleted:derived` | P/U · `static composite` |
| error | `400` · `{error:string}` · `"invalid skill name"` | C for direct writeErr; error helper 내부는 P |
| error | `401` · `{error:string}` · `"未授权"` / `"token 无效或已过期"` | C for direct writeErr; error helper 내부는 P |
| error | `500` · `{error:string}` · `err.Error()` | C for direct writeErr; error helper 내부는 P |
| error | `503` · `{error:string}` · `"PostgreSQL 不可用"` | C for direct writeErr; error helper 내부는 P |

응답 schema 판정: **P** · top-level key는 직접 추출했지만 derived/helper/opaque 또는 nested type이 남음.

### 목록·동시성·완료

- Pagination/filter/sort: 등록된 직접 query key 중 표준 paging/filter key 없음.
- Idempotency/concurrency: Idempotency-Key/If-Match 없음; 반복 시 응답/존재 검사가 달라질 수 있음.
- Side effect/completion: handler의 직접 mutation/호출 후 응답; 외부 전달·agent 실행이 파생되면 별도 상태 확인(P).
- 직접 협력자/실행 단서: `err.Error`, `filepath.Join`, `os.RemoveAll`, `pg.DeleteSkillVisibility`, `r.PathValue`, `validSkillName`; direct `go` statement는 미발견. 이 목록은 helper 내부의 background launch나 transaction 경계를 펼치지 않으므로 그 부분은 P/U다.
- 종합 의미 판정: **P/U**; route/auth와 위에서 직접 확인한 branch·shape는 C지만, 모든 helper·derived response·중첩 DTO·외부 효과까지 완결됐다는 뜻은 아니다.

<a id="schema-get-api-skills-missing"></a>
## `GET /api/skills/missing`

구현: `fsMissingSkills` · `server/server_mgmt.go` · [정확한 handler 근거](evidence:handler-get-api-skills-missing)

### 요청

| 위치/field | wire type | 필수·null | 기본/검증 |
|---|---|---|---|
| query `limit` | `string` wire | 선택 | handler/helper 변환·default 미전개(P) |
| auth credential | Bearer header / `artex_token` cookie / query `token` | 세 위치 중 하나에 유효 JWT 필수 | `extractToken` 우선순위: header→cookie→query; query fallback은 SSE로 제한되지 않음 |
| body | 없음(직접 decoder/form read 미발견) | 해당 없음 | 해당 없음 |

요청 판정: **C/P** · unknown field 거부 미설정.
직접 validation: `err != nil` · `pg == nil`

### 응답·오류

| 종류 | status/body | 해석 상태 |
|---|---|---|
| success/variant | `200` · `missing:derived` | P/U · `static composite` |
| error | `401` · `{error:string}` · `"未授权"` / `"token 无效或已过期"` | C for direct writeErr; error helper 내부는 P |
| error | `500` · `{error:string}` · `err.Error()` | C for direct writeErr; error helper 내부는 P |
| error | `503` · `{error:string}` · `"PostgreSQL 不可用"` | C for direct writeErr; error helper 내부는 P |

응답 schema 판정: **P** · top-level key는 직접 추출했지만 derived/helper/opaque 또는 nested type이 남음.

### 목록·동시성·완료

- Pagination/filter/sort: `limit`.
- Idempotency/concurrency: read/stream 요청; stream 재연결은 cursor 계약에 따름.
- Side effect/completion: 직접 조회/파일 전송 완료; 외부 조회가 있으면 응답 전 handler 결과만 보장.
- 직접 협력자/실행 단서: `err.Error`, `pg.MissingSkillStats`, `r.URL.Query`, `r.URL.Query().Get`; direct `go` statement는 미발견. 이 목록은 helper 내부의 background launch나 transaction 경계를 펼치지 않으므로 그 부분은 P/U다.
- 종합 의미 판정: **P/U**; route/auth와 위에서 직접 확인한 branch·shape는 C지만, 모든 helper·derived response·중첩 DTO·외부 효과까지 완결됐다는 뜻은 아니다.

<a id="schema-get-api-skills-name-usage"></a>
## `GET /api/skills/{name}/usage`

구현: `fsSkillUsage` · `server/server_mgmt.go` · [정확한 handler 근거](evidence:handler-get-api-skills-name-usage)

### 요청

| 위치/field | wire type | 필수·null | 기본/검증 |
|---|---|---|---|
| path `name` | `string` | 필수 | handler의 parse/range validation; 미해석 helper는 P |
| query `limit` | `string` wire | 선택 | handler/helper 변환·default 미전개(P) |
| auth credential | Bearer header / `artex_token` cookie / query `token` | 세 위치 중 하나에 유효 JWT 필수 | `extractToken` 우선순위: header→cookie→query; query fallback은 SSE로 제한되지 않음 |
| body | 없음(직접 decoder/form read 미발견) | 해당 없음 | 해당 없음 |

요청 판정: **C/P** · unknown field 거부 미설정.
직접 validation: `!validSkillName(name)` · `err != nil` · `pg == nil`

### 응답·오류

| 종류 | status/body | 해석 상태 |
|---|---|---|
| success/variant | `200` · `calls:derived` | P/U · `static composite` |
| error | `400` · `{error:string}` · `"非法 skill 名"` | C for direct writeErr; error helper 내부는 P |
| error | `401` · `{error:string}` · `"未授权"` / `"token 无效或已过期"` | C for direct writeErr; error helper 내부는 P |
| error | `500` · `{error:string}` · `err.Error()` | C for direct writeErr; error helper 내부는 P |
| error | `503` · `{error:string}` · `"PostgreSQL 不可用"` | C for direct writeErr; error helper 내부는 P |

응답 schema 판정: **P** · top-level key는 직접 추출했지만 derived/helper/opaque 또는 nested type이 남음.

### 목록·동시성·완료

- Pagination/filter/sort: `limit`.
- Idempotency/concurrency: read/stream 요청; stream 재연결은 cursor 계약에 따름.
- Side effect/completion: 직접 조회/파일 전송 완료; 외부 조회가 있으면 응답 전 handler 결과만 보장.
- 직접 협력자/실행 단서: `err.Error`, `pg.RecentSkillCalls`, `r.PathValue`, `r.URL.Query`, `r.URL.Query().Get`, `validSkillName`; direct `go` statement는 미발견. 이 목록은 helper 내부의 background launch나 transaction 경계를 펼치지 않으므로 그 부분은 P/U다.
- 종합 의미 판정: **P/U**; route/auth와 위에서 직접 확인한 branch·shape는 C지만, 모든 helper·derived response·중첩 DTO·외부 효과까지 완결됐다는 뜻은 아니다.

<a id="schema-put-api-skills-name-meta"></a>
## `PUT /api/skills/{name}/meta`

구현: `fsUpdateSkillMeta` · `server/server_mgmt.go` · [정확한 handler 근거](evidence:handler-put-api-skills-name-meta)

### 요청

| 위치/field | wire type | 필수·null | 기본/검증 |
|---|---|---|---|
| path `name` | `string` | 필수 | handler의 parse/range validation; 미해석 helper는 P |
| auth credential | Bearer header / `artex_token` cookie / query `token` | 세 위치 중 하나에 유효 JWT 필수 | `extractToken` 우선순위: header→cookie→query; query fallback은 SSE로 제한되지 않음 |
| body `mcps` | `array[string]` (`*[]string`) | 요구 여부 미확정(P); absent/null→nil; null→nil | `nil` |
| body `description` | `string` (`*string`) | 요구 여부 미확정(P); absent/null→nil; null→nil | `nil` |
| body `license` | `string` (`*string`) | 요구 여부 미확정(P); absent/null→nil; null→nil | `nil` |
| body `compatibility` | `string` (`*string`) | 요구 여부 미확정(P); absent/null→nil; null→nil | `nil` |

요청 판정: **C/P** · unknown field 거부 미설정.

### 응답·오류

| 종류 | status/body | 해석 상태 |
|---|---|---|
| success/variant | `200` · `ok:boolean` | C · `static composite` |
| error | `400` · `{error:string}` · `"invalid skill name"` / `err.Error()` | C for direct writeErr; error helper 내부는 P |
| error | `401` · `{error:string}` · `"未授权"` / `"token 无效或已过期"` | C for direct writeErr; error helper 내부는 P |
| error | `404` · `{error:string}` · `"skill not found"` | C for direct writeErr; error helper 내부는 P |
| error | `500` · `{error:string}` · `err.Error()` | C for direct writeErr; error helper 내부는 P |

응답 schema 판정: **C** · 직접 scalar/static top-level key와 field type을 추출; nested 내부 schema는 별도 type 참조가 없으면 P.

### 목록·동시성·완료

- Pagination/filter/sort: 등록된 직접 query key 중 표준 paging/filter key 없음.
- Idempotency/concurrency: Idempotency-Key/If-Match 없음; DB upsert/trigger/side effect 때문에 반복 의미를 handler별 확인(P).
- Side effect/completion: handler의 직접 mutation/호출 후 응답; 외부 전달·agent 실행이 파생되면 별도 상태 확인(P).
- 직접 협력자/실행 단서: `err.Error`, `filepath.Join`, `os.ReadFile`, `os.WriteFile`, `r.PathValue`, `rewriteSkillFrontmatter`, `validSkillName`; direct `go` statement는 미발견. 이 목록은 helper 내부의 background launch나 transaction 경계를 펼치지 않으므로 그 부분은 P/U다.
- 종합 의미 판정: **P/U**; route/auth와 위에서 직접 확인한 branch·shape는 C지만, 모든 helper·derived response·중첩 DTO·외부 효과까지 완결됐다는 뜻은 아니다.

<a id="schema-post-api-skills-name-dirs"></a>
## `POST /api/skills/{name}/dirs`

구현: `fsCreateDir` · `server/server_mgmt.go` · [정확한 handler 근거](evidence:handler-post-api-skills-name-dirs)

### 요청

| 위치/field | wire type | 필수·null | 기본/검증 |
|---|---|---|---|
| path `name` | `string` | 필수 | handler의 parse/range validation; 미해석 helper는 P |
| auth credential | Bearer header / `artex_token` cookie / query `token` | 세 위치 중 하나에 유효 JWT 필수 | `extractToken` 우선순위: header→cookie→query; query fallback은 SSE로 제한되지 않음 |
| body `path` | `string` (`string`) | 요구 여부 미확정(P); null/absent 구별 안 됨 | `Go zero value; 별도 기본은 handler/helper 확인` |

요청 판정: **C/P** · unknown field 거부 미설정.

### 응답·오류

| 종류 | status/body | 해석 상태 |
|---|---|---|
| success/variant | `201` · `dir:derived` | P/U · `static composite` |
| error | `400` · `{error:string}` · `"invalid skill name"` / `err.Error()` / `errMsg` | C for direct writeErr; error helper 내부는 P |
| error | `401` · `{error:string}` · `"未授权"` / `"token 无效或已过期"` | C for direct writeErr; error helper 내부는 P |
| error | `404` · `{error:string}` · `"skill not found"` | C for direct writeErr; error helper 내부는 P |
| error | `500` · `{error:string}` · `err.Error()` | C for direct writeErr; error helper 내부는 P |

응답 schema 판정: **P** · top-level key는 직접 추출했지만 derived/helper/opaque 또는 nested type이 남음.

### 목록·동시성·완료

- Pagination/filter/sort: 등록된 직접 query key 중 표준 paging/filter key 없음.
- Idempotency/concurrency: Idempotency-Key 없음; unique constraint 또는 상태 check가 있는 경우만 중복을 제한(P).
- Side effect/completion: handler의 직접 mutation/호출 후 응답; 외부 전달·agent 실행이 파생되면 별도 상태 확인(P).
- 직접 협력자/실행 단서: `err.Error`, `filepath.Join`, `os.IsNotExist`, `os.MkdirAll`, `os.Stat`, `r.PathValue`, `skillRelPath`, `validSkillName`; direct `go` statement는 미발견. 이 목록은 helper 내부의 background launch나 transaction 경계를 펼치지 않으므로 그 부분은 P/U다.
- 종합 의미 판정: **P/U**; route/auth와 위에서 직접 확인한 branch·shape는 C지만, 모든 helper·derived response·중첩 DTO·외부 효과까지 완결됐다는 뜻은 아니다.

<a id="schema-get-api-skills-name-files"></a>
## `GET /api/skills/{name}/files`

구현: `fsListFiles` · `server/server_mgmt.go` · [정확한 handler 근거](evidence:handler-get-api-skills-name-files)

### 요청

| 위치/field | wire type | 필수·null | 기본/검증 |
|---|---|---|---|
| path `name` | `string` | 필수 | handler의 parse/range validation; 미해석 helper는 P |
| auth credential | Bearer header / `artex_token` cookie / query `token` | 세 위치 중 하나에 유효 JWT 필수 | `extractToken` 우선순위: header→cookie→query; query fallback은 SSE로 제한되지 않음 |
| body | 없음(직접 decoder/form read 미발견) | 해당 없음 | 해당 없음 |

요청 판정: **C/P** · unknown field 거부 미설정.
직접 validation: `!validSkillName(name)` · `err != nil` · `files == nil` · `os.IsNotExist(err)`

### 응답·오류

| 종류 | status/body | 해석 상태 |
|---|---|---|
| success/variant | `200` · `files:derived` | P/U · `static composite` |
| error | `400` · `{error:string}` · `"invalid skill name"` | C for direct writeErr; error helper 내부는 P |
| error | `401` · `{error:string}` · `"未授权"` / `"token 无效或已过期"` | C for direct writeErr; error helper 내부는 P |
| error | `404` · `{error:string}` · `"skill not found"` | C for direct writeErr; error helper 내부는 P |
| error | `500` · `{error:string}` · `err.Error()` | C for direct writeErr; error helper 내부는 P |

응답 schema 판정: **P** · top-level key는 직접 추출했지만 derived/helper/opaque 또는 nested type이 남음.

### 목록·동시성·완료

- Pagination/filter/sort: 등록된 직접 query key 중 표준 paging/filter key 없음.
- Idempotency/concurrency: read/stream 요청; stream 재연결은 cursor 계약에 따름.
- Side effect/completion: 직접 조회/파일 전송 완료; 외부 조회가 있으면 응답 전 handler 결과만 보장.
- 직접 협력자/실행 단서: `err.Error`, `filepath.Join`, `os.IsNotExist`, `os.Stat`, `r.PathValue`, `validSkillName`, `walkSkillFiles`; direct `go` statement는 미발견. 이 목록은 helper 내부의 background launch나 transaction 경계를 펼치지 않으므로 그 부분은 P/U다.
- 종합 의미 판정: **P/U**; route/auth와 위에서 직접 확인한 branch·shape는 C지만, 모든 helper·derived response·중첩 DTO·외부 효과까지 완결됐다는 뜻은 아니다.

<a id="schema-get-api-skills-name-files-file"></a>
## `GET /api/skills/{name}/files/{file...}`

구현: `fsReadFile` · `server/server_mgmt.go` · [정확한 handler 근거](evidence:handler-get-api-skills-name-files-file)

### 요청

| 위치/field | wire type | 필수·null | 기본/검증 |
|---|---|---|---|
| path `file` | `string` | 필수 | handler의 parse/range validation; 미해석 helper는 P |
| path `name` | `string` | 필수 | handler의 parse/range validation; 미해석 helper는 P |
| auth credential | Bearer header / `artex_token` cookie / query `token` | 세 위치 중 하나에 유효 JWT 필수 | `extractToken` 우선순위: header→cookie→query; query fallback은 SSE로 제한되지 않음 |
| body | 없음(직접 decoder/form read 미발견) | 해당 없음 | 해당 없음 |

요청 판정: **C/P** · unknown field 거부 미설정.
직접 validation: `!validSkillName(name)` · `err != nil` · `errMsg != ""` · `os.IsNotExist(err)`

### 응답·오류

| 종류 | status/body | 해석 상태 |
|---|---|---|
| success/variant | `200` · `content:helper result`, `file:derived` | P/U · `static composite` |
| error | `400` · `{error:string}` · `"invalid skill name"` / `errMsg` | C for direct writeErr; error helper 내부는 P |
| error | `401` · `{error:string}` · `"未授权"` / `"token 无效或已过期"` | C for direct writeErr; error helper 내부는 P |
| error | `404` · `{error:string}` · `"file not found"` | C for direct writeErr; error helper 내부는 P |
| error | `500` · `{error:string}` · `err.Error()` | C for direct writeErr; error helper 내부는 P |

응답 schema 판정: **P** · top-level key는 직접 추출했지만 derived/helper/opaque 또는 nested type이 남음.

### 목록·동시성·완료

- Pagination/filter/sort: 등록된 직접 query key 중 표준 paging/filter key 없음.
- Idempotency/concurrency: read/stream 요청; stream 재연결은 cursor 계약에 따름.
- Side effect/completion: 직접 조회/파일 전송 완료; 외부 조회가 있으면 응답 전 handler 결과만 보장.
- 직접 협력자/실행 단서: `err.Error`, `filepath.Join`, `os.IsNotExist`, `os.ReadFile`, `r.PathValue`, `skillRelPath`, `validSkillName`; direct `go` statement는 미발견. 이 목록은 helper 내부의 background launch나 transaction 경계를 펼치지 않으므로 그 부분은 P/U다.
- 종합 의미 판정: **P/U**; route/auth와 위에서 직접 확인한 branch·shape는 C지만, 모든 helper·derived response·중첩 DTO·외부 효과까지 완결됐다는 뜻은 아니다.

<a id="schema-put-api-skills-name-files-file"></a>
## `PUT /api/skills/{name}/files/{file...}`

구현: `fsWriteFile` · `server/server_mgmt.go` · [정확한 handler 근거](evidence:handler-put-api-skills-name-files-file)

### 요청

| 위치/field | wire type | 필수·null | 기본/검증 |
|---|---|---|---|
| path `file` | `string` | 필수 | handler의 parse/range validation; 미해석 helper는 P |
| path `name` | `string` | 필수 | handler의 parse/range validation; 미해석 helper는 P |
| auth credential | Bearer header / `artex_token` cookie / query `token` | 세 위치 중 하나에 유효 JWT 필수 | `extractToken` 우선순위: header→cookie→query; query fallback은 SSE로 제한되지 않음 |
| body `content` | `string` (`string`) | 요구 여부 미확정(P); null/absent 구별 안 됨 | `Go zero value; 별도 기본은 handler/helper 확인` |

요청 판정: **C/P** · unknown field 거부 미설정.

### 응답·오류

| 종류 | status/body | 해석 상태 |
|---|---|---|
| success/variant | `200` · `ok:boolean` | C · `static composite` |
| error | `400` · `{error:string}` · `"invalid skill name"` / `errMsg` / `err.Error()` | C for direct writeErr; error helper 내부는 P |
| error | `401` · `{error:string}` · `"未授权"` / `"token 无效或已过期"` | C for direct writeErr; error helper 내부는 P |
| error | `404` · `{error:string}` · `"skill not found"` | C for direct writeErr; error helper 내부는 P |
| error | `500` · `{error:string}` · `err.Error()` | C for direct writeErr; error helper 내부는 P |

응답 schema 판정: **C** · 직접 scalar/static top-level key와 field type을 추출; nested 내부 schema는 별도 type 참조가 없으면 P.

### 목록·동시성·완료

- Pagination/filter/sort: 등록된 직접 query key 중 표준 paging/filter key 없음.
- Idempotency/concurrency: Idempotency-Key/If-Match 없음; DB upsert/trigger/side effect 때문에 반복 의미를 handler별 확인(P).
- Side effect/completion: handler의 직접 mutation/호출 후 응답; 외부 전달·agent 실행이 파생되면 별도 상태 확인(P).
- 직접 협력자/실행 단서: `[]byte`, `err.Error`, `filepath.Dir`, `filepath.Join`, `os.IsNotExist`, `os.MkdirAll`, `os.Stat`, `os.WriteFile`, `r.PathValue`, `skillRelPath`, `validSkillName`; direct `go` statement는 미발견. 이 목록은 helper 내부의 background launch나 transaction 경계를 펼치지 않으므로 그 부분은 P/U다.
- 종합 의미 판정: **P/U**; route/auth와 위에서 직접 확인한 branch·shape는 C지만, 모든 helper·derived response·중첩 DTO·외부 효과까지 완결됐다는 뜻은 아니다.

<a id="schema-delete-api-skills-name-files-file"></a>
## `DELETE /api/skills/{name}/files/{file...}`

구현: `fsDeletePath` · `server/server_mgmt.go` · [정확한 handler 근거](evidence:handler-delete-api-skills-name-files-file)

### 요청

| 위치/field | wire type | 필수·null | 기본/검증 |
|---|---|---|---|
| path `file` | `string` | 필수 | handler의 parse/range validation; 미해석 helper는 P |
| path `name` | `string` | 필수 | handler의 parse/range validation; 미해석 helper는 P |
| auth credential | Bearer header / `artex_token` cookie / query `token` | 세 위치 중 하나에 유효 JWT 필수 | `extractToken` 우선순위: header→cookie→query; query fallback은 SSE로 제한되지 않음 |
| body | 없음(직접 decoder/form read 미발견) | 해당 없음 | 해당 없음 |

요청 판정: **C/P** · unknown field 거부 미설정.
직접 validation: `!validSkillName(name)` · `err != nil` · `errMsg != ""` · `os.IsNotExist(err)`

### 응답·오류

| 종류 | status/body | 해석 상태 |
|---|---|---|
| success/variant | `200` · `deleted:derived` | P/U · `static composite` |
| error | `400` · `{error:string}` · `"invalid skill name"` / `errMsg` | C for direct writeErr; error helper 내부는 P |
| error | `401` · `{error:string}` · `"未授权"` / `"token 无效或已过期"` | C for direct writeErr; error helper 내부는 P |
| error | `404` · `{error:string}` · `"not found"` | C for direct writeErr; error helper 내부는 P |
| error | `500` · `{error:string}` · `err.Error()` | C for direct writeErr; error helper 내부는 P |

응답 schema 판정: **P** · top-level key는 직접 추출했지만 derived/helper/opaque 또는 nested type이 남음.

### 목록·동시성·완료

- Pagination/filter/sort: 등록된 직접 query key 중 표준 paging/filter key 없음.
- Idempotency/concurrency: Idempotency-Key/If-Match 없음; 반복 시 응답/존재 검사가 달라질 수 있음.
- Side effect/completion: handler의 직접 mutation/호출 후 응답; 외부 전달·agent 실행이 파생되면 별도 상태 확인(P).
- 직접 협력자/실행 단서: `err.Error`, `filepath.Join`, `os.IsNotExist`, `os.RemoveAll`, `os.Stat`, `r.PathValue`, `skillRelPath`, `validSkillName`; direct `go` statement는 미발견. 이 목록은 helper 내부의 background launch나 transaction 경계를 펼치지 않으므로 그 부분은 P/U다.
- 종합 의미 판정: **P/U**; route/auth와 위에서 직접 확인한 branch·shape는 C지만, 모든 helper·derived response·중첩 DTO·외부 효과까지 완결됐다는 뜻은 아니다.

<a id="schema-get-api-visibility-kind-id"></a>
## `GET /api/visibility/{kind}/{id}`

구현: `pgResourceVisibility` · `server/server_mgmt.go` · [정확한 handler 근거](evidence:handler-get-api-visibility-kind-id)

### 요청

| 위치/field | wire type | 필수·null | 기본/검증 |
|---|---|---|---|
| path `id` | `string` | 필수 | handler의 parse/range validation; 미해석 helper는 P |
| path `kind` | `string` | 필수 | handler의 parse/range validation; 미해석 helper는 P |
| auth credential | Bearer header / `artex_token` cookie / query `token` | 세 위치 중 하나에 유효 JWT 필수 | `extractToken` 우선순위: header→cookie→query; query fallback은 SSE로 제한되지 않음 |
| body | 없음(직접 decoder/form read 미발견) | 해당 없음 | 해당 없음 |

요청 판정: **C/P** · unknown field 거부 미설정.
직접 validation: `err != nil` · `pg == nil`

### 응답·오류

| 종류 | status/body | 해석 상태 |
|---|---|---|
| success/variant | `200` · `agents:helper result` | P/U · `static composite` |
| error | `401` · `{error:string}` · `"未授权"` / `"token 无效或已过期"` | C for direct writeErr; error helper 내부는 P |
| error | `500` · `{error:string}` · `err.Error()` | C for direct writeErr; error helper 내부는 P |
| error | `503` · `{error:string}` · `"PostgreSQL 不可用"` | C for direct writeErr; error helper 내부는 P |

응답 schema 판정: **P** · top-level key는 직접 추출했지만 derived/helper/opaque 또는 nested type이 남음.

### 목록·동시성·완료

- Pagination/filter/sort: 등록된 직접 query key 중 표준 paging/filter key 없음.
- Idempotency/concurrency: read/stream 요청; stream 재연결은 cursor 계약에 따름.
- Side effect/completion: 직접 조회/파일 전송 완료; 외부 조회가 있으면 응답 전 handler 결과만 보장.
- 직접 협력자/실행 단서: `err.Error`, `idStrings`, `pathInt`, `pg.ResourceAgents`, `r.PathValue`; direct `go` statement는 미발견. 이 목록은 helper 내부의 background launch나 transaction 경계를 펼치지 않으므로 그 부분은 P/U다.
- 종합 의미 판정: **P/U**; route/auth와 위에서 직접 확인한 branch·shape는 C지만, 모든 helper·derived response·중첩 DTO·외부 효과까지 완결됐다는 뜻은 아니다.

<a id="schema-post-api-visibility-toggle"></a>
## `POST /api/visibility/toggle`

구현: `pgToggleVisibility` · `server/server_mgmt.go` · [정확한 handler 근거](evidence:handler-post-api-visibility-toggle)

### 요청

| 위치/field | wire type | 필수·null | 기본/검증 |
|---|---|---|---|
| auth credential | Bearer header / `artex_token` cookie / query `token` | 세 위치 중 하나에 유효 JWT 필수 | `extractToken` 우선순위: header→cookie→query; query fallback은 SSE로 제한되지 않음 |
| body `agent_id` | `integer` (`int64`) | 요구 여부 미확정(P); null/absent 구별 안 됨 | `Go zero value; 별도 기본은 handler/helper 확인` |
| body `kind` | `string` (`string`) | 요구 여부 미확정(P); null/absent 구별 안 됨 | `Go zero value; 별도 기본은 handler/helper 확인` |
| body `resource_id` | `integer` (`int64`) | 요구 여부 미확정(P); null/absent 구별 안 됨 | `Go zero value; 별도 기본은 handler/helper 확인` |
| body `visible` | `boolean` (`bool`) | 요구 여부 미확정(P); null/absent 구별 안 됨 | `Go zero value; 별도 기본은 handler/helper 확인` |

요청 판정: **C/P** · unknown field 거부 미설정.

### 응답·오류

| 종류 | status/body | 해석 상태 |
|---|---|---|
| success/variant | `200` · `ok:boolean` | C · `static composite` |
| error | `400` · `{error:string}` · `err.Error()` | C for direct writeErr; error helper 내부는 P |
| error | `401` · `{error:string}` · `"未授权"` / `"token 无效或已过期"` | C for direct writeErr; error helper 내부는 P |
| error | `500` · `{error:string}` · `err.Error()` | C for direct writeErr; error helper 내부는 P |
| error | `503` · `{error:string}` · `"PostgreSQL 不可用"` | C for direct writeErr; error helper 내부는 P |

응답 schema 판정: **C** · 직접 scalar/static top-level key와 field type을 추출; nested 내부 schema는 별도 type 참조가 없으면 P.

### 목록·동시성·완료

- Pagination/filter/sort: 등록된 직접 query key 중 표준 paging/filter key 없음.
- Idempotency/concurrency: Idempotency-Key 없음; unique constraint 또는 상태 check가 있는 경우만 중복을 제한(P).
- Side effect/completion: handler의 직접 mutation/호출 후 응답; 외부 전달·agent 실행이 파생되면 별도 상태 확인(P).
- 직접 협력자/실행 단서: `err.Error`, `pg.ToggleVisibility`; direct `go` statement는 미발견. 이 목록은 helper 내부의 background launch나 transaction 경계를 펼치지 않으므로 그 부분은 P/U다.
- 종합 의미 판정: **P/U**; route/auth와 위에서 직접 확인한 branch·shape는 C지만, 모든 helper·derived response·중첩 DTO·외부 효과까지 완결됐다는 뜻은 아니다.

<a id="schema-get-api-visibility-skill-name"></a>
## `GET /api/visibility/skill/{name}`

구현: `pgSkillVisibility` · `server/server_mgmt.go` · [정확한 handler 근거](evidence:handler-get-api-visibility-skill-name)

### 요청

| 위치/field | wire type | 필수·null | 기본/검증 |
|---|---|---|---|
| path `name` | `string` | 필수 | handler의 parse/range validation; 미해석 helper는 P |
| auth credential | Bearer header / `artex_token` cookie / query `token` | 세 위치 중 하나에 유효 JWT 필수 | `extractToken` 우선순위: header→cookie→query; query fallback은 SSE로 제한되지 않음 |
| body | 없음(직접 decoder/form read 미발견) | 해당 없음 | 해당 없음 |

요청 판정: **C/P** · unknown field 거부 미설정.
직접 validation: `err != nil` · `pg == nil`

### 응답·오류

| 종류 | status/body | 해석 상태 |
|---|---|---|
| success/variant | `200` · `agents:helper result` | P/U · `static composite` |
| error | `401` · `{error:string}` · `"未授权"` / `"token 无效或已过期"` | C for direct writeErr; error helper 내부는 P |
| error | `500` · `{error:string}` · `err.Error()` | C for direct writeErr; error helper 내부는 P |
| error | `503` · `{error:string}` · `"PostgreSQL 不可用"` | C for direct writeErr; error helper 내부는 P |

응답 schema 판정: **P** · top-level key는 직접 추출했지만 derived/helper/opaque 또는 nested type이 남음.

### 목록·동시성·완료

- Pagination/filter/sort: 등록된 직접 query key 중 표준 paging/filter key 없음.
- Idempotency/concurrency: read/stream 요청; stream 재연결은 cursor 계약에 따름.
- Side effect/completion: 직접 조회/파일 전송 완료; 외부 조회가 있으면 응답 전 handler 결과만 보장.
- 직접 협력자/실행 단서: `err.Error`, `idStrings`, `pg.SkillAgents`, `r.PathValue`; direct `go` statement는 미발견. 이 목록은 helper 내부의 background launch나 transaction 경계를 펼치지 않으므로 그 부분은 P/U다.
- 종합 의미 판정: **P/U**; route/auth와 위에서 직접 확인한 branch·shape는 C지만, 모든 helper·derived response·중첩 DTO·외부 효과까지 완결됐다는 뜻은 아니다.

<a id="schema-post-api-visibility-skill-toggle"></a>
## `POST /api/visibility/skill/toggle`

구현: `pgToggleSkillVisibility` · `server/server_mgmt.go` · [정확한 handler 근거](evidence:handler-post-api-visibility-skill-toggle)

### 요청

| 위치/field | wire type | 필수·null | 기본/검증 |
|---|---|---|---|
| auth credential | Bearer header / `artex_token` cookie / query `token` | 세 위치 중 하나에 유효 JWT 필수 | `extractToken` 우선순위: header→cookie→query; query fallback은 SSE로 제한되지 않음 |
| body `agent_id` | `integer` (`int64`) | 요구 여부 미확정(P); null/absent 구별 안 됨 | `Go zero value; 별도 기본은 handler/helper 확인` |
| body `skill_name` | `string` (`string`) | 요구 여부 미확정(P); null/absent 구별 안 됨 | `Go zero value; 별도 기본은 handler/helper 확인` |
| body `visible` | `boolean` (`bool`) | 요구 여부 미확정(P); null/absent 구별 안 됨 | `Go zero value; 별도 기본은 handler/helper 확인` |

요청 판정: **C/P** · unknown field 거부 미설정.

### 응답·오류

| 종류 | status/body | 해석 상태 |
|---|---|---|
| success/variant | `200` · `ok:boolean` | C · `static composite` |
| error | `400` · `{error:string}` · `err.Error()` | C for direct writeErr; error helper 내부는 P |
| error | `401` · `{error:string}` · `"未授权"` / `"token 无效或已过期"` | C for direct writeErr; error helper 내부는 P |
| error | `500` · `{error:string}` · `err.Error()` | C for direct writeErr; error helper 내부는 P |
| error | `503` · `{error:string}` · `"PostgreSQL 不可用"` | C for direct writeErr; error helper 내부는 P |

응답 schema 판정: **C** · 직접 scalar/static top-level key와 field type을 추출; nested 내부 schema는 별도 type 참조가 없으면 P.

### 목록·동시성·완료

- Pagination/filter/sort: 등록된 직접 query key 중 표준 paging/filter key 없음.
- Idempotency/concurrency: Idempotency-Key 없음; unique constraint 또는 상태 check가 있는 경우만 중복을 제한(P).
- Side effect/completion: handler의 직접 mutation/호출 후 응답; 외부 전달·agent 실행이 파생되면 별도 상태 확인(P).
- 직접 협력자/실행 단서: `err.Error`, `pg.ToggleSkillVisibility`; direct `go` statement는 미발견. 이 목록은 helper 내부의 background launch나 transaction 경계를 펼치지 않으므로 그 부분은 P/U다.
- 종합 의미 판정: **P/U**; route/auth와 위에서 직접 확인한 branch·shape는 C지만, 모든 helper·derived response·중첩 DTO·외부 효과까지 완결됐다는 뜻은 아니다.

<a id="schema-get-api-llm-profiles"></a>
## `GET /api/llm/profiles`

구현: `pgListProfiles` · `server/server_mgmt.go` · [정확한 handler 근거](evidence:handler-get-api-llm-profiles)

### 요청

| 위치/field | wire type | 필수·null | 기본/검증 |
|---|---|---|---|
| auth credential | Bearer header / `artex_token` cookie / query `token` | 세 위치 중 하나에 유효 JWT 필수 | `extractToken` 우선순위: header→cookie→query; query fallback은 SSE로 제한되지 않음 |
| body | 없음(직접 decoder/form read 미발견) | 해당 없음 | 해당 없음 |

요청 판정: **C/P** · unknown field 거부 미설정.
직접 validation: `err != nil` · `pg == nil`

### 응답·오류

| 종류 | status/body | 해석 상태 |
|---|---|---|
| success/variant | `200` · `profiles:helper result` | P/U · `static composite` |
| error | `401` · `{error:string}` · `"未授权"` / `"token 无效或已过期"` | C for direct writeErr; error helper 내부는 P |
| error | `500` · `{error:string}` · `err.Error()` | C for direct writeErr; error helper 내부는 P |
| error | `503` · `{error:string}` · `"PostgreSQL 不可用"` | C for direct writeErr; error helper 내부는 P |

응답 schema 판정: **P** · top-level key는 직접 추출했지만 derived/helper/opaque 또는 nested type이 남음.

### 목록·동시성·완료

- Pagination/filter/sort: 등록된 직접 query key 중 표준 paging/filter key 없음.
- Idempotency/concurrency: read/stream 요청; stream 재연결은 cursor 계약에 따름.
- Side effect/completion: 직접 조회/파일 전송 완료; 외부 조회가 있으면 응답 전 handler 결과만 보장.
- 직접 협력자/실행 단서: `err.Error`, `llmProfileDTOs`, `pg.ListProfiles`; direct `go` statement는 미발견. 이 목록은 helper 내부의 background launch나 transaction 경계를 펼치지 않으므로 그 부분은 P/U다.
- 종합 의미 판정: **P/U**; route/auth와 위에서 직접 확인한 branch·shape는 C지만, 모든 helper·derived response·중첩 DTO·외부 효과까지 완결됐다는 뜻은 아니다.

<a id="schema-post-api-llm-profiles"></a>
## `POST /api/llm/profiles`

구현: `pgSaveProfile` · `server/server_mgmt.go` · [정확한 handler 근거](evidence:handler-post-api-llm-profiles)

### 요청

| 위치/field | wire type | 필수·null | 기본/검증 |
|---|---|---|---|
| auth credential | Bearer header / `artex_token` cookie / query `token` | 세 위치 중 하나에 유효 JWT 필수 | `extractToken` 우선순위: header→cookie→query; query fallback은 SSE로 제한되지 않음 |
| body `id` | `integer` (`int64`) | 요구 여부 미확정(P); null/absent 구별 안 됨 | `Go zero value; 별도 기본은 handler/helper 확인` |
| body `name` | `string` (`string`) | 요구 여부 미확정(P); null/absent 구별 안 됨 | `Go zero value; 별도 기본은 handler/helper 확인` |
| body `format` | `string` (`string`) | 요구 여부 미확정(P); null/absent 구별 안 됨 | `Go zero value; 별도 기본은 handler/helper 확인` |
| body `base_url` | `string` (`string`) | 요구 여부 미확정(P); null/absent 구별 안 됨 | `Go zero value; 별도 기본은 handler/helper 확인` |
| body `proxy` | `string` (`string`) | 요구 여부 미확정(P); null/absent 구별 안 됨 | `Go zero value; 별도 기본은 handler/helper 확인` |
| body `model` | `string` (`string`) | 요구 여부 미확정(P); null/absent 구별 안 됨 | `Go zero value; 별도 기본은 handler/helper 확인` |
| body `api_key_hint` | `string` (`string`) | 요구 여부 미확정(P); null/absent 구별 안 됨 | `Go zero value; 별도 기본은 handler/helper 확인` |
| body `rate_per_second` | `number` (`float64`) | 요구 여부 미확정(P); null/absent 구별 안 됨 | `Go zero value; 별도 기본은 handler/helper 확인` |
| body `rate_per_minute` | `number` (`float64`) | 요구 여부 미확정(P); null/absent 구별 안 됨 | `Go zero value; 별도 기본은 handler/helper 확인` |
| body `context_window_k` | `integer` (`int`) | 요구 여부 미확정(P); null/absent 구별 안 됨 | `Go zero value; 별도 기본은 handler/helper 확인` |
| body `thinking_type` | `string` (`string`) | 요구 여부 미확정(P); null/absent 구별 안 됨 | `Go zero value; 별도 기본은 handler/helper 확인` |
| body `reasoning_effort` | `string` (`string`) | 요구 여부 미확정(P); null/absent 구별 안 됨 | `Go zero value; 별도 기본은 handler/helper 확인` |
| body `is_default` | `boolean` (`bool`) | 요구 여부 미확정(P); null/absent 구별 안 됨 | `Go zero value; 별도 기본은 handler/helper 확인` |
| body `priority` | `integer` (`int`) | 요구 여부 미확정(P); null/absent 구별 안 됨 | `Go zero value; 별도 기본은 handler/helper 확인` |
| body `pool_exclude` | `boolean` (`bool`) | 요구 여부 미확정(P); null/absent 구별 안 됨 | `Go zero value; 별도 기본은 handler/helper 확인` |
| body `streaming` | `boolean` (`bool`) | 요구 여부 미확정(P); null/absent 구별 안 됨 | `p.Streaming = body.Streaming == nil \|\| *body.Streaming` |
| body `max_tokens` | `integer` (`int`) | 요구 여부 미확정(P); null/absent 구별 안 됨 | `Go zero value; 별도 기본은 handler/helper 확인` |
| body `max_tokens_field` | `string` (`string`) | 요구 여부 미확정(P); null/absent 구별 안 됨 | `Go zero value; 별도 기본은 handler/helper 확인` |
| body `session_header_key` | `string` (`string`) | 요구 여부 미확정(P); null/absent 구별 안 됨 | `Go zero value; 별도 기본은 handler/helper 확인` |
| body `retry` | `RetryOverride` (`RetryOverride`) | 요구 여부 미확정(P); null/absent 구별 안 됨 | `Go zero value; 별도 기본은 handler/helper 확인` |
| body `api_key` | `string` (`string`) | 요구 여부 미확정(P); null/absent 구별 안 됨 | `p.APIKey = body.APIKey` |
| body `streaming` | `boolean` (`*bool`) | 요구 여부 미확정(P); absent/null→nil; null→nil | `p.Streaming = body.Streaming == nil \|\| *body.Streaming` |

요청 판정: **C/P** · unknown field 거부 미설정.

### 응답·오류

| 종류 | status/body | 해석 상태 |
|---|---|---|
| success/variant | `200` · `id:derived` | P/U · `static composite` |
| error | `400` · `{error:string}` · `err.Error()` | C for direct writeErr; error helper 내부는 P |
| error | `401` · `{error:string}` · `"未授权"` / `"token 无效或已过期"` | C for direct writeErr; error helper 내부는 P |
| error | `500` · `{error:string}` · `err.Error()` | C for direct writeErr; error helper 내부는 P |
| error | `503` · `{error:string}` · `"PostgreSQL 不可用"` | C for direct writeErr; error helper 내부는 P |

응답 schema 판정: **P** · top-level key는 직접 추출했지만 derived/helper/opaque 또는 nested type이 남음.

### 목록·동시성·완료

- Pagination/filter/sort: 등록된 직접 query key 중 표준 paging/filter key 없음.
- Idempotency/concurrency: Idempotency-Key 없음; unique constraint 또는 상태 check가 있는 경우만 중복을 제한(P).
- Side effect/completion: handler의 직접 mutation/호출 후 응답; 외부 전달·agent 실행이 파생되면 별도 상태 확인(P).
- 직접 협력자/실행 단서: `err.Error`, `pg.SaveProfile`; direct `go` statement는 미발견. 이 목록은 helper 내부의 background launch나 transaction 경계를 펼치지 않으므로 그 부분은 P/U다.
- 종합 의미 판정: **P/U**; route/auth와 위에서 직접 확인한 branch·shape는 C지만, 모든 helper·derived response·중첩 DTO·외부 효과까지 완결됐다는 뜻은 아니다.

<a id="schema-delete-api-llm-profiles-id"></a>
## `DELETE /api/llm/profiles/{id}`

구현: `pgDeleteProfile` · `server/server_mgmt.go` · [정확한 handler 근거](evidence:handler-delete-api-llm-profiles-id)

### 요청

| 위치/field | wire type | 필수·null | 기본/검증 |
|---|---|---|---|
| path `id` | `string` | 필수 | handler의 parse/range validation; 미해석 helper는 P |
| auth credential | Bearer header / `artex_token` cookie / query `token` | 세 위치 중 하나에 유효 JWT 필수 | `extractToken` 우선순위: header→cookie→query; query fallback은 SSE로 제한되지 않음 |
| body | 없음(직접 decoder/form read 미발견) | 해당 없음 | 해당 없음 |

요청 판정: **C/P** · unknown field 거부 미설정.
직접 validation: `err != nil` · `pg == nil`

### 응답·오류

| 종류 | status/body | 해석 상태 |
|---|---|---|
| success/variant | `200` · `deleted:derived` | P/U · `static composite` |
| error | `401` · `{error:string}` · `"未授权"` / `"token 无效或已过期"` | C for direct writeErr; error helper 내부는 P |
| error | `404` · `{error:string}` · `err.Error()` | C for direct writeErr; error helper 내부는 P |
| error | `409` · `{error:string}` · `"当前激活的 LLM 配置不能删除，请先激活其他配置"` / `"LLM 配置正在被任务或会话修改，请重试"` / `"等待 LLM 配置引用释放超时，请重试"` | C for direct writeErr; error helper 내부는 P |
| error | `500` · `{error:string}` · `err.Error()` | C for direct writeErr; error helper 내부는 P |
| error | `503` · `{error:string}` · `"PostgreSQL 不可用"` | C for direct writeErr; error helper 내부는 P |

응답 schema 판정: **P** · top-level key는 직접 추출했지만 derived/helper/opaque 또는 nested type이 남음.

### 목록·동시성·완료

- Pagination/filter/sort: 등록된 직접 query key 중 표준 paging/filter key 없음.
- Idempotency/concurrency: Idempotency-Key/If-Match 없음; 반복 시 응답/존재 검사가 달라질 수 있음.
- Side effect/completion: handler의 직접 mutation/호출 후 응답; 외부 전달·agent 실행이 파생되면 별도 상태 확인(P).
- 직접 협력자/실행 단서: `err.Error`, `pathInt`, `pg.DeleteProfileContext`, `r.Context`, `s.llmHealth.Reset`; direct `go` statement는 미발견. 이 목록은 helper 내부의 background launch나 transaction 경계를 펼치지 않으므로 그 부분은 P/U다.
- 종합 의미 판정: **P/U**; route/auth와 위에서 직접 확인한 branch·shape는 C지만, 모든 helper·derived response·중첩 DTO·외부 효과까지 완결됐다는 뜻은 아니다.

<a id="schema-post-api-llm-profiles-active"></a>
## `POST /api/llm/profiles/active`

구현: `pgActivateProfile` · `server/server_mgmt.go` · [정확한 handler 근거](evidence:handler-post-api-llm-profiles-active)

### 요청

| 위치/field | wire type | 필수·null | 기본/검증 |
|---|---|---|---|
| auth credential | Bearer header / `artex_token` cookie / query `token` | 세 위치 중 하나에 유효 JWT 필수 | `extractToken` 우선순위: header→cookie→query; query fallback은 SSE로 제한되지 않음 |
| body `id` | `integer` (`int64`) | 요구 여부 미확정(P); null/absent 구별 안 됨 | `Go zero value; 별도 기본은 handler/helper 확인` |

요청 판정: **C/P** · unknown field 거부 미설정.

### 응답·오류

| 종류 | status/body | 해석 상태 |
|---|---|---|
| success/variant | `200` · `ok:boolean` | C · `static composite` |
| error | `400` · `{error:string}` · `err.Error()` | C for direct writeErr; error helper 내부는 P |
| error | `401` · `{error:string}` · `"未授权"` / `"token 无效或已过期"` | C for direct writeErr; error helper 내부는 P |
| error | `500` · `{error:string}` · `err.Error()` | C for direct writeErr; error helper 내부는 P |
| error | `503` · `{error:string}` · `"PostgreSQL 不可用"` | C for direct writeErr; error helper 내부는 P |

응답 schema 판정: **C** · 직접 scalar/static top-level key와 field type을 추출; nested 내부 schema는 별도 type 참조가 없으면 P.

### 목록·동시성·완료

- Pagination/filter/sort: 등록된 직접 query key 중 표준 paging/filter key 없음.
- Idempotency/concurrency: Idempotency-Key 없음; unique constraint 또는 상태 check가 있는 경우만 중복을 제한(P).
- Side effect/completion: handler의 직접 mutation/호출 후 응답; 외부 전달·agent 실행이 파생되면 별도 상태 확인(P).
- 직접 협력자/실행 단서: `err.Error`, `pg.SetActiveProfile`; direct `go` statement는 미발견. 이 목록은 helper 내부의 background launch나 transaction 경계를 펼치지 않으므로 그 부분은 P/U다.
- 종합 의미 판정: **P/U**; route/auth와 위에서 직접 확인한 branch·shape는 C지만, 모든 helper·derived response·중첩 DTO·외부 효과까지 완결됐다는 뜻은 아니다.

<a id="schema-get-api-llm-retry-policy"></a>
## `GET /api/llm/retry-policy`

구현: `pgGetLLMRetryPolicy` · `server/server_mgmt.go` · [정확한 handler 근거](evidence:handler-get-api-llm-retry-policy)

### 요청

| 위치/field | wire type | 필수·null | 기본/검증 |
|---|---|---|---|
| auth credential | Bearer header / `artex_token` cookie / query `token` | 세 위치 중 하나에 유효 JWT 필수 | `extractToken` 우선순위: header→cookie→query; query fallback은 SSE로 제한되지 않음 |
| body | 없음(직접 decoder/form read 미발견) | 해당 없음 | 해당 없음 |

요청 판정: **C/P** · unknown field 거부 미설정.
직접 validation: `pg == nil`

### 응답·오류

| 종류 | status/body | 해석 상태 |
|---|---|---|
| success/variant | `200` · `pg.LLMRetryPolicy()` | P/U · `helper result` |
| error | `401` · `{error:string}` · `"未授权"` / `"token 无效或已过期"` | C for direct writeErr; error helper 내부는 P |
| error | `503` · `{error:string}` · `"PostgreSQL 不可用"` | C for direct writeErr; error helper 내부는 P |

응답 schema 판정: **P** · top-level key는 직접 추출했지만 derived/helper/opaque 또는 nested type이 남음.

### 목록·동시성·완료

- Pagination/filter/sort: 등록된 직접 query key 중 표준 paging/filter key 없음.
- Idempotency/concurrency: read/stream 요청; stream 재연결은 cursor 계약에 따름.
- Side effect/completion: 직접 조회/파일 전송 완료; 외부 조회가 있으면 응답 전 handler 결과만 보장.
- 직접 협력자/실행 단서: `pg.LLMRetryPolicy`; direct `go` statement는 미발견. 이 목록은 helper 내부의 background launch나 transaction 경계를 펼치지 않으므로 그 부분은 P/U다.
- 종합 의미 판정: **P/U**; route/auth와 위에서 직접 확인한 branch·shape는 C지만, 모든 helper·derived response·중첩 DTO·외부 효과까지 완결됐다는 뜻은 아니다.

<a id="schema-post-api-llm-retry-policy"></a>
## `POST /api/llm/retry-policy`

구현: `pgSaveLLMRetryPolicy` · `server/server_mgmt.go` · [정확한 handler 근거](evidence:handler-post-api-llm-retry-policy)

### 요청

| 위치/field | wire type | 필수·null | 기본/검증 |
|---|---|---|---|
| auth credential | Bearer header / `artex_token` cookie / query `token` | 세 위치 중 하나에 유효 JWT 필수 | `extractToken` 우선순위: header→cookie→query; query fallback은 SSE로 제한되지 않음 |
| body `connect` | `RetryRule` (`RetryRule`) | 요구 여부 미확정(P); null/absent 구별 안 됨 | `Go zero value; 별도 기본은 handler/helper 확인` |
| body `empty` | `RetryRule` (`RetryRule`) | 요구 여부 미확정(P); null/absent 구별 안 됨 | `Go zero value; 별도 기본은 handler/helper 확인` |
| body `stream` | `RetryRule` (`RetryRule`) | 요구 여부 미확정(P); null/absent 구별 안 됨 | `Go zero value; 별도 기본은 handler/helper 확인` |
| body `breaker` | `RetryRule` (`RetryRule`) | 요구 여부 미확정(P); null/absent 구별 안 됨 | `Go zero value; 별도 기본은 handler/helper 확인` |
| body `intent` | `RetryRule` (`RetryRule`) | 요구 여부 미확정(P); null/absent 구별 안 됨 | `Go zero value; 별도 기본은 handler/helper 확인` |

요청 판정: **C/P** · unknown field 거부 미설정.

### 응답·오류

| 종류 | status/body | 해석 상태 |
|---|---|---|
| success/variant | `200` · `pg.LLMRetryPolicy()` | P/U · `helper result` |
| error | `400` · `{error:string}` · `err.Error()` | C for direct writeErr; error helper 내부는 P |
| error | `401` · `{error:string}` · `"未授权"` / `"token 无效或已过期"` | C for direct writeErr; error helper 내부는 P |
| error | `500` · `{error:string}` · `err.Error()` | C for direct writeErr; error helper 내부는 P |
| error | `503` · `{error:string}` · `"PostgreSQL 不可用"` | C for direct writeErr; error helper 내부는 P |

응답 schema 판정: **P** · top-level key는 직접 추출했지만 derived/helper/opaque 또는 nested type이 남음.

### 목록·동시성·완료

- Pagination/filter/sort: 등록된 직접 query key 중 표준 paging/filter key 없음.
- Idempotency/concurrency: Idempotency-Key 없음; unique constraint 또는 상태 check가 있는 경우만 중복을 제한(P).
- Side effect/completion: handler의 직접 mutation/호출 후 응답; 외부 전달·agent 실행이 파생되면 별도 상태 확인(P).
- 직접 협력자/실행 단서: `err.Error`, `pg.LLMRetryPolicy`, `pg.SetLLMRetryPolicy`; direct `go` statement는 미발견. 이 목록은 helper 내부의 background launch나 transaction 경계를 펼치지 않으므로 그 부분은 P/U다.
- 종합 의미 판정: **P/U**; route/auth와 위에서 직접 확인한 branch·shape는 C지만, 모든 helper·derived response·중첩 DTO·외부 효과까지 완결됐다는 뜻은 아니다.

<a id="schema-get-api-llm-pool"></a>
## `GET /api/llm/pool`

구현: `pgLLMPoolStatus` · `server/server_mgmt.go` · [정확한 handler 근거](evidence:handler-get-api-llm-pool)

### 요청

| 위치/field | wire type | 필수·null | 기본/검증 |
|---|---|---|---|
| auth credential | Bearer header / `artex_token` cookie / query `token` | 세 위치 중 하나에 유효 JWT 필수 | `extractToken` 우선순위: header→cookie→query; query fallback은 SSE로 제한되지 않음 |
| body | 없음(직접 decoder/form read 미발견) | 해당 없음 | 해당 없음 |

요청 판정: **C/P** · unknown field 거부 미설정.
직접 validation: `s.pg(w) == nil`

### 응답·오류

| 종류 | status/body | 해석 상태 |
|---|---|---|
| success/variant | `200` · `s.llmPoolStatus()` | P/U · `helper result` |
| error | `401` · `{error:string}` · `"未授权"` / `"token 无效或已过期"` | C for direct writeErr; error helper 내부는 P |
| error | `503` · `{error:string}` · `"PostgreSQL 不可用"` | C for direct writeErr; error helper 내부는 P |

응답 schema 판정: **P** · top-level key는 직접 추출했지만 derived/helper/opaque 또는 nested type이 남음.

### 목록·동시성·완료

- Pagination/filter/sort: 등록된 직접 query key 중 표준 paging/filter key 없음.
- Idempotency/concurrency: read/stream 요청; stream 재연결은 cursor 계약에 따름.
- Side effect/completion: 직접 조회/파일 전송 완료; 외부 조회가 있으면 응답 전 handler 결과만 보장.
- 직접 협력자/실행 단서: handler AST에서 별도 collaborator call 미발견; direct `go` statement는 미발견. 이 목록은 helper 내부의 background launch나 transaction 경계를 펼치지 않으므로 그 부분은 P/U다.
- 종합 의미 판정: **P/U**; route/auth와 위에서 직접 확인한 branch·shape는 C지만, 모든 helper·derived response·중첩 DTO·외부 효과까지 완결됐다는 뜻은 아니다.

<a id="schema-post-api-llm-pool-reset"></a>
## `POST /api/llm/pool/reset`

구현: `pgLLMPoolReset` · `server/server_mgmt.go` · [정확한 handler 근거](evidence:handler-post-api-llm-pool-reset)

### 요청

| 위치/field | wire type | 필수·null | 기본/검증 |
|---|---|---|---|
| auth credential | Bearer header / `artex_token` cookie / query `token` | 세 위치 중 하나에 유효 JWT 필수 | `extractToken` 우선순위: header→cookie→query; query fallback은 SSE로 제한되지 않음 |
| body `id` | `integer` (`int64`) | 요구 여부 미확정(P); null/absent 구별 안 됨 | `body.ID > 0` |

요청 판정: **C/P** · unknown field 거부 미설정.
직접 validation: `body.ID > 0`

### 응답·오류

| 종류 | status/body | 해석 상태 |
|---|---|---|
| success/variant | `200` · `s.llmPoolStatus()` | P/U · `helper result` |
| error | `400` · `{error:string}` · `err.Error()` | C for direct writeErr; error helper 내부는 P |
| error | `401` · `{error:string}` · `"未授权"` / `"token 无效或已过期"` | C for direct writeErr; error helper 내부는 P |
| error | `503` · `{error:string}` · `"PostgreSQL 不可用"` | C for direct writeErr; error helper 내부는 P |

응답 schema 판정: **P** · top-level key는 직접 추출했지만 derived/helper/opaque 또는 nested type이 남음.

### 목록·동시성·완료

- Pagination/filter/sort: 등록된 직접 query key 중 표준 paging/filter key 없음.
- Idempotency/concurrency: Idempotency-Key 없음; unique constraint 또는 상태 check가 있는 경우만 중복을 제한(P).
- Side effect/completion: handler의 직접 mutation/호출 후 응답; 외부 전달·agent 실행이 파생되면 별도 상태 확인(P).
- 직접 협력자/실행 단서: `err.Error`, `s.llmHealth.Reset`, `s.llmHealth.Snapshot`; direct `go` statement는 미발견. 이 목록은 helper 내부의 background launch나 transaction 경계를 펼치지 않으므로 그 부분은 P/U다.
- 종합 의미 판정: **P/U**; route/auth와 위에서 직접 확인한 branch·shape는 C지만, 모든 helper·derived response·중첩 DTO·외부 효과까지 완결됐다는 뜻은 아니다.

<a id="schema-post-api-llm-models"></a>
## `POST /api/llm/models`

구현: `pgListModels` · `server/server_mgmt.go` · [정확한 handler 근거](evidence:handler-post-api-llm-models)

### 요청

| 위치/field | wire type | 필수·null | 기본/검증 |
|---|---|---|---|
| auth credential | Bearer header / `artex_token` cookie / query `token` | 세 위치 중 하나에 유효 JWT 필수 | `extractToken` 우선순위: header→cookie→query; query fallback은 SSE로 제한되지 않음 |
| body `provider` | `string` (`string`) | 요구 여부 미확정(P); null/absent 구별 안 됨 | `Go zero value; 별도 기본은 handler/helper 확인` |
| body `base_url` | `string` (`string`) | 요구 여부 미확정(P); null/absent 구별 안 됨 | `Go zero value; 별도 기본은 handler/helper 확인` |
| body `api_key` | `string` (`string`) | 요구 여부 미확정(P); null/absent 구별 안 됨 | `Go zero value; 별도 기본은 handler/helper 확인` |
| body `proxy` | `string` (`string`) | 요구 여부 미확정(P); null/absent 구별 안 됨 | `Go zero value; 별도 기본은 handler/helper 확인` |
| body `profile_id` | `integer` (`*int64`) | 선택; null→nil | `직접 api_key가 없을 때 저장 key fallback에만 사용` |

요청 판정: **C/P** · unknown field 거부 미설정.
직접 validation: `apiKey == "" && req.ProfileID != nil`

### 응답·오류

| 종류 | status/body | 해석 상태 |
|---|---|---|
| success/variant | `200` · `ok:boolean`, `error:string` | C · `static composite` |
| success/variant | `200` · `ok:boolean`, `models:derived` | P/U · `static composite` |
| success/variant | `200` · `ok:boolean`, `models:[]string` | P/U · `static composite` |
| success/variant | `200` · `ok:boolean`, `error:derived` | P/U · `static composite` |
| error | `400` · `{error:string}` · `err.Error()` | C for direct writeErr; error helper 내부는 P |
| error | `401` · `{error:string}` · `"未授权"` / `"token 无效或已过期"` | C for direct writeErr; error helper 내부는 P |

응답 schema 판정: **P** · top-level key는 직접 추출했지만 derived/helper/opaque 또는 nested type이 남음.

### 목록·동시성·완료

- Pagination/filter/sort: 등록된 직접 query key 중 표준 paging/filter key 없음.
- Idempotency/concurrency: Idempotency-Key 없음; unique constraint 또는 상태 check가 있는 경우만 중복을 제한(P).
- Side effect/completion: handler의 직접 mutation/호출 후 응답; 외부 전달·agent 실행이 파생되면 별도 상태 확인(P).
- 직접 협력자/실행 단서: `anthropicHdr`, `bearerHdr`, `client.Do`, `err.Error`, `h.Set`, `io.LimitReader`, `io.ReadAll`, `min`, `r.Context`, `resp.Body.Close`, `s.m.pg.ProfileByID`; direct `go` statement는 미발견. 이 목록은 helper 내부의 background launch나 transaction 경계를 펼치지 않으므로 그 부분은 P/U다.
- 종합 의미 판정: **P/U**; route/auth와 위에서 직접 확인한 branch·shape는 C지만, 모든 helper·derived response·중첩 DTO·외부 효과까지 완결됐다는 뜻은 아니다.

<a id="schema-get-api-intercept-rules"></a>
## `GET /api/intercept/rules`

구현: `interceptListRules` · `server/intercept.go` · [정확한 handler 근거](evidence:handler-get-api-intercept-rules)

### 요청

| 위치/field | wire type | 필수·null | 기본/검증 |
|---|---|---|---|
| auth credential | Bearer header / `artex_token` cookie / query `token` | 세 위치 중 하나에 유효 JWT 필수 | `extractToken` 우선순위: header→cookie→query; query fallback은 SSE로 제한되지 않음 |
| body | 없음(직접 decoder/form read 미발견) | 해당 없음 | 해당 없음 |

요청 판정: **C/P** · unknown field 거부 미설정.
직접 validation: `err != nil` · `pg == nil` · `rules == nil`

### 응답·오류

| 종류 | status/body | 해석 상태 |
|---|---|---|
| success/variant | `200` · `rules:derived` | P/U · `static composite` |
| error | `401` · `{error:string}` · `"未授权"` / `"token 无效或已过期"` | C for direct writeErr; error helper 내부는 P |
| error | `500` · `{error:string}` · `err.Error()` | C for direct writeErr; error helper 내부는 P |
| error | `503` · `{error:string}` · `"PostgreSQL 不可用"` | C for direct writeErr; error helper 내부는 P |

응답 schema 판정: **P** · top-level key는 직접 추출했지만 derived/helper/opaque 또는 nested type이 남음.

### 목록·동시성·완료

- Pagination/filter/sort: 등록된 직접 query key 중 표준 paging/filter key 없음.
- Idempotency/concurrency: read/stream 요청; stream 재연결은 cursor 계약에 따름.
- Side effect/completion: 직접 조회/파일 전송 완료; 외부 조회가 있으면 응답 전 handler 결과만 보장.
- 직접 협력자/실행 단서: `err.Error`, `pg.ListInterceptRules`; direct `go` statement는 미발견. 이 목록은 helper 내부의 background launch나 transaction 경계를 펼치지 않으므로 그 부분은 P/U다.
- 종합 의미 판정: **P/U**; route/auth와 위에서 직접 확인한 branch·shape는 C지만, 모든 helper·derived response·중첩 DTO·외부 효과까지 완결됐다는 뜻은 아니다.

<a id="schema-post-api-intercept-rules"></a>
## `POST /api/intercept/rules`

구현: `interceptCreateRule` · `server/intercept.go` · [정확한 handler 근거](evidence:handler-post-api-intercept-rules)

### 요청

| 위치/field | wire type | 필수·null | 기본/검증 |
|---|---|---|---|
| auth credential | Bearer header / `artex_token` cookie / query `token` | 세 위치 중 하나에 유효 JWT 필수 | `extractToken` 우선순위: header→cookie→query; query fallback은 SSE로 제한되지 않음 |
| body `name` | `string` (`string`) | 요구 여부 미확정(P); null/absent 구별 안 됨 | `Go zero value; 별도 기본은 handler/helper 확인` |
| body `enabled` | `boolean` (`bool`) | 요구 여부 미확정(P); null/absent 구별 안 됨 | `Go zero value; 별도 기본은 handler/helper 확인` |
| body `priority` | `integer` (`int`) | 요구 여부 미확정(P); null/absent 구별 안 됨 | `Go zero value; 별도 기본은 handler/helper 확인` |
| body `match_target` | `string` (`string`) | 요구 여부 미확정(P); null/absent 구별 안 됨 | `Go zero value; 별도 기본은 handler/helper 확인` |
| body `match_type` | `string` (`string`) | 요구 여부 미확정(P); null/absent 구별 안 됨 | `Go zero value; 별도 기본은 handler/helper 확인` |
| body `pattern` | `string` (`string`) | 요구 여부 미확정(P); null/absent 구별 안 됨 | `Go zero value; 별도 기본은 handler/helper 확인` |
| body `action` | `string` (`string`) | 요구 여부 미확정(P); null/absent 구별 안 됨 | `Go zero value; 별도 기본은 handler/helper 확인` |
| body `message` | `string` (`string`) | 요구 여부 미확정(P); null/absent 구별 안 됨 | `Go zero value; 별도 기본은 handler/helper 확인` |
| body `timeout_enabled` | `boolean` (`bool`) | 요구 여부 미확정(P); null/absent 구별 안 됨 | `Go zero value; 별도 기본은 handler/helper 확인` |
| body `timeout_seconds` | `integer` (`int`) | 요구 여부 미확정(P); null/absent 구별 안 됨 | `Go zero value; 별도 기본은 handler/helper 확인` |
| body `timeout_action` | `string` (`string`) | 요구 여부 미확정(P); null/absent 구별 안 됨 | `Go zero value; 별도 기본은 handler/helper 확인` |

요청 판정: **C/P** · unknown field 거부 미설정.

### 응답·오류

| 종류 | status/body | 해석 상태 |
|---|---|---|
| success/variant | `200` · `rule` | P/U · `derived variable` |
| error | `400` · `{error:string}` · `err.Error()` | C for direct writeErr; error helper 내부는 P |
| error | `401` · `{error:string}` · `"未授权"` / `"token 无效或已过期"` | C for direct writeErr; error helper 내부는 P |
| error | `500` · `{error:string}` · `err.Error()` | C for direct writeErr; error helper 내부는 P |
| error | `503` · `{error:string}` · `"PostgreSQL 不可用"` | C for direct writeErr; error helper 내부는 P |

응답 schema 판정: **P** · top-level key는 직접 추출했지만 derived/helper/opaque 또는 nested type이 남음.

### 목록·동시성·완료

- Pagination/filter/sort: 등록된 직접 query key 중 표준 paging/filter key 없음.
- Idempotency/concurrency: Idempotency-Key 없음; unique constraint 또는 상태 check가 있는 경우만 중복을 제한(P).
- Side effect/completion: handler의 직접 mutation/호출 후 응답; 외부 전달·agent 실행이 파생되면 별도 상태 확인(P).
- 직접 협력자/실행 단서: `err.Error`, `pg.CreateInterceptRule`, `s.m.interceptor.Invalidate`, `validateInterceptRuleReq`; direct `go` statement는 미발견. 이 목록은 helper 내부의 background launch나 transaction 경계를 펼치지 않으므로 그 부분은 P/U다.
- 종합 의미 판정: **P/U**; route/auth와 위에서 직접 확인한 branch·shape는 C지만, 모든 helper·derived response·중첩 DTO·외부 효과까지 완결됐다는 뜻은 아니다.

<a id="schema-put-api-intercept-rules-id"></a>
## `PUT /api/intercept/rules/{id}`

구현: `interceptUpdateRule` · `server/intercept.go` · [정확한 handler 근거](evidence:handler-put-api-intercept-rules-id)

### 요청

| 위치/field | wire type | 필수·null | 기본/검증 |
|---|---|---|---|
| path `id` | `string` | 필수 | handler의 parse/range validation; 미해석 helper는 P |
| auth credential | Bearer header / `artex_token` cookie / query `token` | 세 위치 중 하나에 유효 JWT 필수 | `extractToken` 우선순위: header→cookie→query; query fallback은 SSE로 제한되지 않음 |
| body `name` | `string` (`string`) | 요구 여부 미확정(P); null/absent 구별 안 됨 | `Go zero value; 별도 기본은 handler/helper 확인` |
| body `enabled` | `boolean` (`bool`) | 요구 여부 미확정(P); null/absent 구별 안 됨 | `Go zero value; 별도 기본은 handler/helper 확인` |
| body `priority` | `integer` (`int`) | 요구 여부 미확정(P); null/absent 구별 안 됨 | `Go zero value; 별도 기본은 handler/helper 확인` |
| body `match_target` | `string` (`string`) | 요구 여부 미확정(P); null/absent 구별 안 됨 | `Go zero value; 별도 기본은 handler/helper 확인` |
| body `match_type` | `string` (`string`) | 요구 여부 미확정(P); null/absent 구별 안 됨 | `Go zero value; 별도 기본은 handler/helper 확인` |
| body `pattern` | `string` (`string`) | 요구 여부 미확정(P); null/absent 구별 안 됨 | `Go zero value; 별도 기본은 handler/helper 확인` |
| body `action` | `string` (`string`) | 요구 여부 미확정(P); null/absent 구별 안 됨 | `Go zero value; 별도 기본은 handler/helper 확인` |
| body `message` | `string` (`string`) | 요구 여부 미확정(P); null/absent 구별 안 됨 | `Go zero value; 별도 기본은 handler/helper 확인` |
| body `timeout_enabled` | `boolean` (`bool`) | 요구 여부 미확정(P); null/absent 구별 안 됨 | `Go zero value; 별도 기본은 handler/helper 확인` |
| body `timeout_seconds` | `integer` (`int`) | 요구 여부 미확정(P); null/absent 구별 안 됨 | `Go zero value; 별도 기본은 handler/helper 확인` |
| body `timeout_action` | `string` (`string`) | 요구 여부 미확정(P); null/absent 구별 안 됨 | `Go zero value; 별도 기본은 handler/helper 확인` |

요청 판정: **C/P** · unknown field 거부 미설정.

### 응답·오류

| 종류 | status/body | 해석 상태 |
|---|---|---|
| success/variant | `200` · `rule` | P/U · `derived variable` |
| error | `400` · `{error:string}` · `"bad rule id"` / `err.Error()` | C for direct writeErr; error helper 내부는 P |
| error | `401` · `{error:string}` · `"未授权"` / `"token 无效或已过期"` | C for direct writeErr; error helper 내부는 P |
| error | `500` · `{error:string}` · `err.Error()` | C for direct writeErr; error helper 내부는 P |
| error | `503` · `{error:string}` · `"PostgreSQL 不可用"` | C for direct writeErr; error helper 내부는 P |

응답 schema 판정: **P** · top-level key는 직접 추출했지만 derived/helper/opaque 또는 nested type이 남음.

### 목록·동시성·완료

- Pagination/filter/sort: 등록된 직접 query key 중 표준 paging/filter key 없음.
- Idempotency/concurrency: Idempotency-Key/If-Match 없음; DB upsert/trigger/side effect 때문에 반복 의미를 handler별 확인(P).
- Side effect/completion: handler의 직접 mutation/호출 후 응답; 외부 전달·agent 실행이 파생되면 별도 상태 확인(P).
- 직접 협력자/실행 단서: `err.Error`, `pathInt`, `pg.UpdateInterceptRule`, `s.m.interceptor.Invalidate`, `validateInterceptRuleReq`; direct `go` statement는 미발견. 이 목록은 helper 내부의 background launch나 transaction 경계를 펼치지 않으므로 그 부분은 P/U다.
- 종합 의미 판정: **P/U**; route/auth와 위에서 직접 확인한 branch·shape는 C지만, 모든 helper·derived response·중첩 DTO·외부 효과까지 완결됐다는 뜻은 아니다.

<a id="schema-delete-api-intercept-rules-id"></a>
## `DELETE /api/intercept/rules/{id}`

구현: `interceptDeleteRule` · `server/intercept.go` · [정확한 handler 근거](evidence:handler-delete-api-intercept-rules-id)

### 요청

| 위치/field | wire type | 필수·null | 기본/검증 |
|---|---|---|---|
| path `id` | `string` | 필수 | handler의 parse/range validation; 미해석 helper는 P |
| auth credential | Bearer header / `artex_token` cookie / query `token` | 세 위치 중 하나에 유효 JWT 필수 | `extractToken` 우선순위: header→cookie→query; query fallback은 SSE로 제한되지 않음 |
| body | 없음(직접 decoder/form read 미발견) | 해당 없음 | 해당 없음 |

요청 판정: **C/P** · unknown field 거부 미설정.
직접 validation: `!ok` · `err != nil` · `pg == nil`

### 응답·오류

| 종류 | status/body | 해석 상태 |
|---|---|---|
| success/variant | `200` · `deleted:derived` | P/U · `static composite` |
| error | `400` · `{error:string}` · `"bad rule id"` | C for direct writeErr; error helper 내부는 P |
| error | `401` · `{error:string}` · `"未授权"` / `"token 无效或已过期"` | C for direct writeErr; error helper 내부는 P |
| error | `500` · `{error:string}` · `err.Error()` | C for direct writeErr; error helper 내부는 P |
| error | `503` · `{error:string}` · `"PostgreSQL 不可用"` | C for direct writeErr; error helper 내부는 P |

응답 schema 판정: **P** · top-level key는 직접 추출했지만 derived/helper/opaque 또는 nested type이 남음.

### 목록·동시성·완료

- Pagination/filter/sort: 등록된 직접 query key 중 표준 paging/filter key 없음.
- Idempotency/concurrency: Idempotency-Key/If-Match 없음; 반복 시 응답/존재 검사가 달라질 수 있음.
- Side effect/completion: handler의 직접 mutation/호출 후 응답; 외부 전달·agent 실행이 파생되면 별도 상태 확인(P).
- 직접 협력자/실행 단서: `err.Error`, `pathInt`, `pg.DeleteInterceptRule`, `s.m.interceptor.Invalidate`; direct `go` statement는 미발견. 이 목록은 helper 내부의 background launch나 transaction 경계를 펼치지 않으므로 그 부분은 P/U다.
- 종합 의미 판정: **P/U**; route/auth와 위에서 직접 확인한 branch·shape는 C지만, 모든 helper·derived response·중첩 DTO·외부 효과까지 완결됐다는 뜻은 아니다.

<a id="schema-post-api-intercept-rules-id-toggle"></a>
## `POST /api/intercept/rules/{id}/toggle`

구현: `interceptToggleRule` · `server/intercept.go` · [정확한 handler 근거](evidence:handler-post-api-intercept-rules-id-toggle)

### 요청

| 위치/field | wire type | 필수·null | 기본/검증 |
|---|---|---|---|
| path `id` | `string` | 필수 | handler의 parse/range validation; 미해석 helper는 P |
| auth credential | Bearer header / `artex_token` cookie / query `token` | 세 위치 중 하나에 유효 JWT 필수 | `extractToken` 우선순위: header→cookie→query; query fallback은 SSE로 제한되지 않음 |
| body `enabled` | `boolean` (`bool`) | 요구 여부 미확정(P); null/absent 구별 안 됨 | `Go zero value; 별도 기본은 handler/helper 확인` |

요청 판정: **C/P** · unknown field 거부 미설정.

### 응답·오류

| 종류 | status/body | 해석 상태 |
|---|---|---|
| success/variant | `200` · `ok:boolean`, `enabled:expression` | P/U · `static composite` |
| error | `400` · `{error:string}` · `"bad rule id"` / `err.Error()` | C for direct writeErr; error helper 내부는 P |
| error | `401` · `{error:string}` · `"未授权"` / `"token 无效或已过期"` | C for direct writeErr; error helper 내부는 P |
| error | `500` · `{error:string}` · `err.Error()` | C for direct writeErr; error helper 내부는 P |
| error | `503` · `{error:string}` · `"PostgreSQL 不可用"` | C for direct writeErr; error helper 내부는 P |

응답 schema 판정: **P** · top-level key는 직접 추출했지만 derived/helper/opaque 또는 nested type이 남음.

### 목록·동시성·완료

- Pagination/filter/sort: 등록된 직접 query key 중 표준 paging/filter key 없음.
- Idempotency/concurrency: Idempotency-Key 없음; unique constraint 또는 상태 check가 있는 경우만 중복을 제한(P).
- Side effect/completion: handler의 직접 mutation/호출 후 응답; 외부 전달·agent 실행이 파생되면 별도 상태 확인(P).
- 직접 협력자/실행 단서: `err.Error`, `pathInt`, `pg.ToggleInterceptRule`, `s.m.interceptor.Invalidate`; direct `go` statement는 미발견. 이 목록은 helper 내부의 background launch나 transaction 경계를 펼치지 않으므로 그 부분은 P/U다.
- 종합 의미 판정: **P/U**; route/auth와 위에서 직접 확인한 branch·shape는 C지만, 모든 helper·derived response·중첩 DTO·외부 효과까지 완결됐다는 뜻은 아니다.

<a id="schema-get-api-asset-intercept-rules"></a>
## `GET /api/asset-intercept/rules`

구현: `assetInterceptListRules` · `server/asset_intercept.go` · [정확한 handler 근거](evidence:handler-get-api-asset-intercept-rules)

### 요청

| 위치/field | wire type | 필수·null | 기본/검증 |
|---|---|---|---|
| auth credential | Bearer header / `artex_token` cookie / query `token` | 세 위치 중 하나에 유효 JWT 필수 | `extractToken` 우선순위: header→cookie→query; query fallback은 SSE로 제한되지 않음 |
| body | 없음(직접 decoder/form read 미발견) | 해당 없음 | 해당 없음 |

요청 판정: **C/P** · unknown field 거부 미설정.
직접 validation: `err != nil` · `pg == nil` · `rules == nil`

### 응답·오류

| 종류 | status/body | 해석 상태 |
|---|---|---|
| success/variant | `200` · `rules:derived` | P/U · `static composite` |
| error | `401` · `{error:string}` · `"未授权"` / `"token 无效或已过期"` | C for direct writeErr; error helper 내부는 P |
| error | `500` · `{error:string}` · `err.Error()` | C for direct writeErr; error helper 내부는 P |
| error | `503` · `{error:string}` · `"PostgreSQL 不可用"` | C for direct writeErr; error helper 내부는 P |

응답 schema 판정: **P** · top-level key는 직접 추출했지만 derived/helper/opaque 또는 nested type이 남음.

### 목록·동시성·완료

- Pagination/filter/sort: 등록된 직접 query key 중 표준 paging/filter key 없음.
- Idempotency/concurrency: read/stream 요청; stream 재연결은 cursor 계약에 따름.
- Side effect/completion: 직접 조회/파일 전송 완료; 외부 조회가 있으면 응답 전 handler 결과만 보장.
- 직접 협력자/실행 단서: `err.Error`, `pg.ListAssetInterceptRules`; direct `go` statement는 미발견. 이 목록은 helper 내부의 background launch나 transaction 경계를 펼치지 않으므로 그 부분은 P/U다.
- 종합 의미 판정: **P/U**; route/auth와 위에서 직접 확인한 branch·shape는 C지만, 모든 helper·derived response·중첩 DTO·외부 효과까지 완결됐다는 뜻은 아니다.

<a id="schema-post-api-asset-intercept-rules"></a>
## `POST /api/asset-intercept/rules`

구현: `assetInterceptCreateRule` · `server/asset_intercept.go` · [정확한 handler 근거](evidence:handler-post-api-asset-intercept-rules)

### 요청

| 위치/field | wire type | 필수·null | 기본/검증 |
|---|---|---|---|
| auth credential | Bearer header / `artex_token` cookie / query `token` | 세 위치 중 하나에 유효 JWT 필수 | `extractToken` 우선순위: header→cookie→query; query fallback은 SSE로 제한되지 않음 |
| body `enabled` | `boolean` (`bool`) | 요구 여부 미확정(P); null/absent 구별 안 됨 | `Go zero value; 별도 기본은 handler/helper 확인` |
| body `kind` | `string` (`string`) | 요구 여부 미확정(P); null/absent 구별 안 됨 | `Go zero value; 별도 기본은 handler/helper 확인` |
| body `pattern` | `string` (`string`) | 요구 여부 미확정(P); null/absent 구별 안 됨 | `Go zero value; 별도 기본은 handler/helper 확인` |
| body `note` | `string` (`string`) | 요구 여부 미확정(P); null/absent 구별 안 됨 | `Go zero value; 별도 기본은 handler/helper 확인` |

요청 판정: **C/P** · unknown field 거부 미설정.

### 응답·오류

| 종류 | status/body | 해석 상태 |
|---|---|---|
| success/variant | `200` · `rule` | P/U · `derived variable` |
| error | `400` · `{error:string}` · `err.Error()` | C for direct writeErr; error helper 내부는 P |
| error | `401` · `{error:string}` · `"未授权"` / `"token 无效或已过期"` | C for direct writeErr; error helper 내부는 P |
| error | `500` · `{error:string}` · `err.Error()` | C for direct writeErr; error helper 내부는 P |
| error | `503` · `{error:string}` · `"PostgreSQL 不可用"` | C for direct writeErr; error helper 내부는 P |

응답 schema 판정: **P** · top-level key는 직접 추출했지만 derived/helper/opaque 또는 nested type이 남음.

### 목록·동시성·완료

- Pagination/filter/sort: 등록된 직접 query key 중 표준 paging/filter key 없음.
- Idempotency/concurrency: Idempotency-Key 없음; unique constraint 또는 상태 check가 있는 경우만 중복을 제한(P).
- Side effect/completion: handler의 직접 mutation/호출 후 응답; 외부 전달·agent 실행이 파생되면 별도 상태 확인(P).
- 직접 협력자/실행 단서: `err.Error`, `pg.CreateAssetInterceptRule`, `validateAssetInterceptRuleReq`; direct `go` statement는 미발견. 이 목록은 helper 내부의 background launch나 transaction 경계를 펼치지 않으므로 그 부분은 P/U다.
- 종합 의미 판정: **P/U**; route/auth와 위에서 직접 확인한 branch·shape는 C지만, 모든 helper·derived response·중첩 DTO·외부 효과까지 완결됐다는 뜻은 아니다.

<a id="schema-put-api-asset-intercept-rules-id"></a>
## `PUT /api/asset-intercept/rules/{id}`

구현: `assetInterceptUpdateRule` · `server/asset_intercept.go` · [정확한 handler 근거](evidence:handler-put-api-asset-intercept-rules-id)

### 요청

| 위치/field | wire type | 필수·null | 기본/검증 |
|---|---|---|---|
| path `id` | `string` | 필수 | handler의 parse/range validation; 미해석 helper는 P |
| auth credential | Bearer header / `artex_token` cookie / query `token` | 세 위치 중 하나에 유효 JWT 필수 | `extractToken` 우선순위: header→cookie→query; query fallback은 SSE로 제한되지 않음 |
| body `enabled` | `boolean` (`bool`) | 요구 여부 미확정(P); null/absent 구별 안 됨 | `Go zero value; 별도 기본은 handler/helper 확인` |
| body `kind` | `string` (`string`) | 요구 여부 미확정(P); null/absent 구별 안 됨 | `Go zero value; 별도 기본은 handler/helper 확인` |
| body `pattern` | `string` (`string`) | 요구 여부 미확정(P); null/absent 구별 안 됨 | `Go zero value; 별도 기본은 handler/helper 확인` |
| body `note` | `string` (`string`) | 요구 여부 미확정(P); null/absent 구별 안 됨 | `Go zero value; 별도 기본은 handler/helper 확인` |

요청 판정: **C/P** · unknown field 거부 미설정.

### 응답·오류

| 종류 | status/body | 해석 상태 |
|---|---|---|
| success/variant | `200` · `rule` | P/U · `derived variable` |
| error | `400` · `{error:string}` · `"bad rule id"` / `err.Error()` | C for direct writeErr; error helper 내부는 P |
| error | `401` · `{error:string}` · `"未授权"` / `"token 无效或已过期"` | C for direct writeErr; error helper 내부는 P |
| error | `500` · `{error:string}` · `err.Error()` | C for direct writeErr; error helper 내부는 P |
| error | `503` · `{error:string}` · `"PostgreSQL 不可用"` | C for direct writeErr; error helper 내부는 P |

응답 schema 판정: **P** · top-level key는 직접 추출했지만 derived/helper/opaque 또는 nested type이 남음.

### 목록·동시성·완료

- Pagination/filter/sort: 등록된 직접 query key 중 표준 paging/filter key 없음.
- Idempotency/concurrency: Idempotency-Key/If-Match 없음; DB upsert/trigger/side effect 때문에 반복 의미를 handler별 확인(P).
- Side effect/completion: handler의 직접 mutation/호출 후 응답; 외부 전달·agent 실행이 파생되면 별도 상태 확인(P).
- 직접 협력자/실행 단서: `err.Error`, `pathInt`, `pg.UpdateAssetInterceptRule`, `validateAssetInterceptRuleReq`; direct `go` statement는 미발견. 이 목록은 helper 내부의 background launch나 transaction 경계를 펼치지 않으므로 그 부분은 P/U다.
- 종합 의미 판정: **P/U**; route/auth와 위에서 직접 확인한 branch·shape는 C지만, 모든 helper·derived response·중첩 DTO·외부 효과까지 완결됐다는 뜻은 아니다.

<a id="schema-delete-api-asset-intercept-rules-id"></a>
## `DELETE /api/asset-intercept/rules/{id}`

구현: `assetInterceptDeleteRule` · `server/asset_intercept.go` · [정확한 handler 근거](evidence:handler-delete-api-asset-intercept-rules-id)

### 요청

| 위치/field | wire type | 필수·null | 기본/검증 |
|---|---|---|---|
| path `id` | `string` | 필수 | handler의 parse/range validation; 미해석 helper는 P |
| auth credential | Bearer header / `artex_token` cookie / query `token` | 세 위치 중 하나에 유효 JWT 필수 | `extractToken` 우선순위: header→cookie→query; query fallback은 SSE로 제한되지 않음 |
| body | 없음(직접 decoder/form read 미발견) | 해당 없음 | 해당 없음 |

요청 판정: **C/P** · unknown field 거부 미설정.
직접 validation: `!ok` · `err != nil` · `pg == nil`

### 응답·오류

| 종류 | status/body | 해석 상태 |
|---|---|---|
| success/variant | `200` · `deleted:derived` | P/U · `static composite` |
| error | `400` · `{error:string}` · `"bad rule id"` | C for direct writeErr; error helper 내부는 P |
| error | `401` · `{error:string}` · `"未授权"` / `"token 无效或已过期"` | C for direct writeErr; error helper 내부는 P |
| error | `500` · `{error:string}` · `err.Error()` | C for direct writeErr; error helper 내부는 P |
| error | `503` · `{error:string}` · `"PostgreSQL 不可用"` | C for direct writeErr; error helper 내부는 P |

응답 schema 판정: **P** · top-level key는 직접 추출했지만 derived/helper/opaque 또는 nested type이 남음.

### 목록·동시성·완료

- Pagination/filter/sort: 등록된 직접 query key 중 표준 paging/filter key 없음.
- Idempotency/concurrency: Idempotency-Key/If-Match 없음; 반복 시 응답/존재 검사가 달라질 수 있음.
- Side effect/completion: handler의 직접 mutation/호출 후 응답; 외부 전달·agent 실행이 파생되면 별도 상태 확인(P).
- 직접 협력자/실행 단서: `err.Error`, `pathInt`, `pg.DeleteAssetInterceptRule`; direct `go` statement는 미발견. 이 목록은 helper 내부의 background launch나 transaction 경계를 펼치지 않으므로 그 부분은 P/U다.
- 종합 의미 판정: **P/U**; route/auth와 위에서 직접 확인한 branch·shape는 C지만, 모든 helper·derived response·중첩 DTO·외부 효과까지 완결됐다는 뜻은 아니다.

<a id="schema-post-api-asset-intercept-rules-id-toggle"></a>
## `POST /api/asset-intercept/rules/{id}/toggle`

구현: `assetInterceptToggleRule` · `server/asset_intercept.go` · [정확한 handler 근거](evidence:handler-post-api-asset-intercept-rules-id-toggle)

### 요청

| 위치/field | wire type | 필수·null | 기본/검증 |
|---|---|---|---|
| path `id` | `string` | 필수 | handler의 parse/range validation; 미해석 helper는 P |
| auth credential | Bearer header / `artex_token` cookie / query `token` | 세 위치 중 하나에 유효 JWT 필수 | `extractToken` 우선순위: header→cookie→query; query fallback은 SSE로 제한되지 않음 |
| body `enabled` | `boolean` (`bool`) | 요구 여부 미확정(P); null/absent 구별 안 됨 | `Go zero value; 별도 기본은 handler/helper 확인` |

요청 판정: **C/P** · unknown field 거부 미설정.

### 응답·오류

| 종류 | status/body | 해석 상태 |
|---|---|---|
| success/variant | `200` · `ok:boolean`, `enabled:expression` | P/U · `static composite` |
| error | `400` · `{error:string}` · `"bad rule id"` / `err.Error()` | C for direct writeErr; error helper 내부는 P |
| error | `401` · `{error:string}` · `"未授权"` / `"token 无效或已过期"` | C for direct writeErr; error helper 내부는 P |
| error | `500` · `{error:string}` · `err.Error()` | C for direct writeErr; error helper 내부는 P |
| error | `503` · `{error:string}` · `"PostgreSQL 不可用"` | C for direct writeErr; error helper 내부는 P |

응답 schema 판정: **P** · top-level key는 직접 추출했지만 derived/helper/opaque 또는 nested type이 남음.

### 목록·동시성·완료

- Pagination/filter/sort: 등록된 직접 query key 중 표준 paging/filter key 없음.
- Idempotency/concurrency: Idempotency-Key 없음; unique constraint 또는 상태 check가 있는 경우만 중복을 제한(P).
- Side effect/completion: handler의 직접 mutation/호출 후 응답; 외부 전달·agent 실행이 파생되면 별도 상태 확인(P).
- 직접 협력자/실행 단서: `err.Error`, `pathInt`, `pg.ToggleAssetInterceptRule`; direct `go` statement는 미발견. 이 목록은 helper 내부의 background launch나 transaction 경계를 펼치지 않으므로 그 부분은 P/U다.
- 종합 의미 판정: **P/U**; route/auth와 위에서 직접 확인한 branch·shape는 C지만, 모든 helper·derived response·중첩 DTO·외부 효과까지 완결됐다는 뜻은 아니다.

<a id="schema-get-api-intercept-pending"></a>
## `GET /api/intercept/pending`

구현: `interceptListPending` · `server/intercept.go` · [정확한 handler 근거](evidence:handler-get-api-intercept-pending)

### 요청

| 위치/field | wire type | 필수·null | 기본/검증 |
|---|---|---|---|
| auth credential | Bearer header / `artex_token` cookie / query `token` | 세 위치 중 하나에 유효 JWT 필수 | `extractToken` 우선순위: header→cookie→query; query fallback은 SSE로 제한되지 않음 |
| body | 없음(직접 decoder/form read 미발견) | 해당 없음 | 해당 없음 |

요청 판정: **C/P** · unknown field 거부 미설정.
직접 validation: `err != nil` · `pending == nil` · `pg == nil`

### 응답·오류

| 종류 | status/body | 해석 상태 |
|---|---|---|
| success/variant | `200` · `pending:derived` | P/U · `static composite` |
| error | `401` · `{error:string}` · `"未授权"` / `"token 无效或已过期"` | C for direct writeErr; error helper 내부는 P |
| error | `500` · `{error:string}` · `err.Error()` | C for direct writeErr; error helper 내부는 P |
| error | `503` · `{error:string}` · `"PostgreSQL 不可用"` | C for direct writeErr; error helper 내부는 P |

응답 schema 판정: **P** · top-level key는 직접 추출했지만 derived/helper/opaque 또는 nested type이 남음.

### 목록·동시성·완료

- Pagination/filter/sort: 등록된 직접 query key 중 표준 paging/filter key 없음.
- Idempotency/concurrency: read/stream 요청; stream 재연결은 cursor 계약에 따름.
- Side effect/completion: 직접 조회/파일 전송 완료; 외부 조회가 있으면 응답 전 handler 결과만 보장.
- 직접 협력자/실행 단서: `err.Error`, `pg.ListPendingIntercepts`; direct `go` statement는 미발견. 이 목록은 helper 내부의 background launch나 transaction 경계를 펼치지 않으므로 그 부분은 P/U다.
- 종합 의미 판정: **P/U**; route/auth와 위에서 직접 확인한 branch·shape는 C지만, 모든 helper·derived response·중첩 DTO·외부 효과까지 완결됐다는 뜻은 아니다.

<a id="schema-get-api-intercept-pending-id"></a>
## `GET /api/intercept/pending/{id}`

구현: `interceptGetOne` · `server/intercept.go` · [정확한 handler 근거](evidence:handler-get-api-intercept-pending-id)

### 요청

| 위치/field | wire type | 필수·null | 기본/검증 |
|---|---|---|---|
| path `id` | `string` | 필수 | handler의 parse/range validation; 미해석 helper는 P |
| auth credential | Bearer header / `artex_token` cookie / query `token` | 세 위치 중 하나에 유효 JWT 필수 | `extractToken` 우선순위: header→cookie→query; query fallback은 SSE로 제한되지 않음 |
| body | 없음(직접 decoder/form read 미발견) | 해당 없음 | 해당 없음 |

요청 판정: **C/P** · unknown field 거부 미설정.
직접 validation: `!ok` · `err != nil` · `p == nil` · `pg == nil`

### 응답·오류

| 종류 | status/body | 해석 상태 |
|---|---|---|
| success/variant | `200` · `p` | P/U · `derived variable` |
| error | `400` · `{error:string}` · `"bad pending id"` | C for direct writeErr; error helper 내부는 P |
| error | `401` · `{error:string}` · `"未授权"` / `"token 无效或已过期"` | C for direct writeErr; error helper 내부는 P |
| error | `404` · `{error:string}` · `"not found"` | C for direct writeErr; error helper 내부는 P |
| error | `500` · `{error:string}` · `err.Error()` | C for direct writeErr; error helper 내부는 P |
| error | `503` · `{error:string}` · `"PostgreSQL 不可用"` | C for direct writeErr; error helper 내부는 P |

응답 schema 판정: **P** · top-level key는 직접 추출했지만 derived/helper/opaque 또는 nested type이 남음.

### 목록·동시성·완료

- Pagination/filter/sort: 등록된 직접 query key 중 표준 paging/filter key 없음.
- Idempotency/concurrency: read/stream 요청; stream 재연결은 cursor 계약에 따름.
- Side effect/completion: 직접 조회/파일 전송 완료; 외부 조회가 있으면 응답 전 handler 결과만 보장.
- 직접 협력자/실행 단서: `err.Error`, `pathInt`, `pg.GetInterceptPending`; direct `go` statement는 미발견. 이 목록은 helper 내부의 background launch나 transaction 경계를 펼치지 않으므로 그 부분은 P/U다.
- 종합 의미 판정: **P/U**; route/auth와 위에서 직접 확인한 branch·shape는 C지만, 모든 helper·derived response·중첩 DTO·외부 효과까지 완결됐다는 뜻은 아니다.

<a id="schema-post-api-intercept-pending-id-decide"></a>
## `POST /api/intercept/pending/{id}/decide`

구현: `interceptDecide` · `server/intercept.go` · [정확한 handler 근거](evidence:handler-post-api-intercept-pending-id-decide)

### 요청

| 위치/field | wire type | 필수·null | 기본/검증 |
|---|---|---|---|
| path `id` | `string` | 필수 | handler의 parse/range validation; 미해석 helper는 P |
| auth credential | Bearer header / `artex_token` cookie / query `token` | 세 위치 중 하나에 유효 JWT 필수 | `extractToken` 우선순위: header→cookie→query; query fallback은 SSE로 제한되지 않음 |
| body `decision` | `string` (`string`) | 요구 여부 미확정(P); null/absent 구별 안 됨 | `req.Decision != "allowed" && req.Decision != "denied"` |

요청 판정: **C/P** · unknown field 거부 미설정.
직접 validation: `req.Decision != "allowed" && req.Decision != "denied"`

### 응답·오류

| 종류 | status/body | 해석 상태 |
|---|---|---|
| success/variant | `200` · `ok:boolean` | C · `static composite` |
| error | `400` · `{error:string}` · `"bad pending id"` / `err.Error()` / `"decision 必须是 allowed 或 denied"` | C for direct writeErr; error helper 내부는 P |
| error | `401` · `{error:string}` · `"未授权"` / `"token 无效或已过期"` | C for direct writeErr; error helper 내부는 P |
| error | `409` · `{error:string}` · `err.Error()` | C for direct writeErr; error helper 내부는 P |
| error | `500` · `{error:string}` · `err.Error()` | C for direct writeErr; error helper 내부는 P |

응답 schema 판정: **C** · 직접 scalar/static top-level key와 field type을 추출; nested 내부 schema는 별도 type 참조가 없으면 P.

### 목록·동시성·완료

- Pagination/filter/sort: 등록된 직접 query key 중 표준 paging/filter key 없음.
- Idempotency/concurrency: Idempotency-Key 없음; unique constraint 또는 상태 check가 있는 경우만 중복을 제한(P).
- Side effect/completion: handler의 직접 mutation/호출 후 응답; 외부 전달·agent 실행이 파생되면 별도 상태 확인(P).
- 직접 협력자/실행 단서: `err.Error`, `pathInt`, `s.m.interceptor.Decide`; direct `go` statement는 미발견. 이 목록은 helper 내부의 background launch나 transaction 경계를 펼치지 않으므로 그 부분은 P/U다.
- 종합 의미 판정: **P/U**; route/auth와 위에서 직접 확인한 branch·shape는 C지만, 모든 helper·derived response·중첩 DTO·외부 효과까지 완결됐다는 뜻은 아니다.

<a id="schema-get-api-intercept-history"></a>
## `GET /api/intercept/history`

구현: `interceptHistory` · `server/intercept.go` · [정확한 handler 근거](evidence:handler-get-api-intercept-history)

### 요청

| 위치/field | wire type | 필수·null | 기본/검증 |
|---|---|---|---|
| query `decision_source` | `string` wire | 선택 | enum empty\|model\|rule\|unknown; invalid→400 |
| query `page` | `string` wire | 선택 | decimal integer; invalid/<1→1 |
| query `size` | `string` wire | 선택 | decimal integer; invalid/<1→20; >100→100 |
| query `status` | `string` wire | 선택 | enum empty\|pending\|allowed\|denied\|timeout; invalid→400 |
| auth credential | Bearer header / `artex_token` cookie / query `token` | 세 위치 중 하나에 유효 JWT 필수 | `extractToken` 우선순위: header→cookie→query; query fallback은 SSE로 제한되지 않음 |
| body | 없음(직접 decoder/form read 미발견) | 해당 없음 | 해당 없음 |

요청 판정: **C/P** · unknown field 거부 미설정.
직접 validation: `err != nil` · `items == nil` · `pg == nil` · `q.Get("page") == "" && q.Get("size") == "" && filter == (db.InterceptApprovalFilter{})`

### 응답·오류

| 종류 | status/body | 해석 상태 |
|---|---|---|
| success/variant | `200` · `items:derived`, `total:helper result` | P/U · `static composite` |
| success/variant | `200` · `items:derived`, `total:derived`, `page:derived`, `page_size:derived` | P/U · `static composite` |
| error | `400` · `{error:string}` · `err.Error()` | C for direct writeErr; error helper 내부는 P |
| error | `401` · `{error:string}` · `"未授权"` / `"token 无效或已过期"` | C for direct writeErr; error helper 내부는 P |
| error | `500` · `{error:string}` · `err.Error()` | C for direct writeErr; error helper 내부는 P |
| error | `503` · `{error:string}` · `"PostgreSQL 不可用"` | C for direct writeErr; error helper 내부는 P |

응답 schema 판정: **P** · top-level key는 직접 추출했지만 derived/helper/opaque 또는 nested type이 남음.

### 목록·동시성·완료

- Pagination/filter/sort: `page`, `size`, `status`.
- Idempotency/concurrency: read/stream 요청; stream 재연결은 cursor 계약에 따름.
- Side effect/completion: 직접 조회/파일 전송 완료; 외부 조회가 있으면 응답 전 handler 결과만 보장.
- 직접 협력자/실행 단서: `err.Error`, `interceptFilterParams`, `interceptPageParams`, `pg.ListAllIntercepts`, `pg.ListAllInterceptsPage`, `q.Get`, `r.URL.Query`; direct `go` statement는 미발견. 이 목록은 helper 내부의 background launch나 transaction 경계를 펼치지 않으므로 그 부분은 P/U다.
- 종합 의미 판정: **P/U**; route/auth와 위에서 직접 확인한 branch·shape는 C지만, 모든 helper·derived response·중첩 DTO·외부 효과까지 완결됐다는 뜻은 아니다.

<a id="schema-get-api-intercept-history-id"></a>
## `GET /api/intercept/history/{id}`

구현: `interceptDetail` · `server/intercept.go` · [정확한 handler 근거](evidence:handler-get-api-intercept-history-id)

### 요청

| 위치/field | wire type | 필수·null | 기본/검증 |
|---|---|---|---|
| path `id` | `string` | 필수 | handler의 parse/range validation; 미해석 helper는 P |
| auth credential | Bearer header / `artex_token` cookie / query `token` | 세 위치 중 하나에 유효 JWT 필수 | `extractToken` 우선순위: header→cookie→query; query fallback은 SSE로 제한되지 않음 |
| body | 없음(직접 decoder/form read 미발견) | 해당 없음 | 해당 없음 |

요청 판정: **C/P** · unknown field 거부 미설정.
직접 validation: `!ok \|\| id <= 0` · `detail == nil` · `err != nil` · `pg == nil`

### 응답·오류

| 종류 | status/body | 해석 상태 |
|---|---|---|
| success/variant | `200` · `detail` | P/U · `derived variable` |
| error | `400` · `{error:string}` · `"bad approval id"` | C for direct writeErr; error helper 내부는 P |
| error | `401` · `{error:string}` · `"未授权"` / `"token 无效或已过期"` | C for direct writeErr; error helper 내부는 P |
| error | `404` · `{error:string}` · `"not found"` | C for direct writeErr; error helper 내부는 P |
| error | `500` · `{error:string}` · `err.Error()` | C for direct writeErr; error helper 내부는 P |
| error | `503` · `{error:string}` · `"PostgreSQL 不可用"` | C for direct writeErr; error helper 내부는 P |

응답 schema 판정: **P** · top-level key는 직접 추출했지만 derived/helper/opaque 또는 nested type이 남음.

### 목록·동시성·완료

- Pagination/filter/sort: 등록된 직접 query key 중 표준 paging/filter key 없음.
- Idempotency/concurrency: read/stream 요청; stream 재연결은 cursor 계약에 따름.
- Side effect/completion: 직접 조회/파일 전송 완료; 외부 조회가 있으면 응답 전 handler 결과만 보장.
- 직접 협력자/실행 단서: `err.Error`, `pathInt`, `pg.GetInterceptDetail`; direct `go` statement는 미발견. 이 목록은 helper 내부의 background launch나 transaction 경계를 펼치지 않으므로 그 부분은 P/U다.
- 종합 의미 판정: **P/U**; route/auth와 위에서 직접 확인한 branch·shape는 C지만, 모든 helper·derived response·중첩 DTO·외부 효과까지 완결됐다는 뜻은 아니다.

<a id="schema-get-api-intercept-history-id-execution"></a>
## `GET /api/intercept/history/{id}/execution`

구현: `interceptExecution` · `server/intercept.go` · [정확한 handler 근거](evidence:handler-get-api-intercept-history-id-execution)

### 요청

| 위치/field | wire type | 필수·null | 기본/검증 |
|---|---|---|---|
| path `id` | `string` | 필수 | handler의 parse/range validation; 미해석 helper는 P |
| query `conversation` | `string` wire | 선택 | handler/helper 변환·default 미전개(P) |
| auth credential | Bearer header / `artex_token` cookie / query `token` | 세 위치 중 하나에 유효 JWT 필수 | `extractToken` 우선순위: header→cookie→query; query fallback은 SSE로 제한되지 않음 |
| body | 없음(직접 decoder/form read 미발견) | 해당 없음 | 해당 없음 |

요청 판정: **C/P** · unknown field 거부 미설정.
직접 validation: `!ok \|\| id <= 0` · `conv == nil` · `err != nil` · `errors.Is(err, db.ErrInterceptExecutionUnavailable)` · `errors.Is(err, db.ErrInterceptTaskDeleted) \|\| errors.Is(err, db.ErrInterceptSessionDeleted)` · `getErr != nil` · `parseErr == nil && convID > 0` · `pg == nil` · `target == nil`
직접 parse/default: `strconv.ParseInt(r.URL.Query().Get("conversation"), 10, 64)`

### 응답·오류

| 종류 | status/body | 해석 상태 |
|---|---|---|
| success/variant | `200` · `conversation_id:expression`, `task_id:expression`, `session:expression`, `seq:expression`, `items:helper result` | P/U · `static composite` |
| error | `400` · `{error:string}` · `"bad approval id"` | C for direct writeErr; error helper 내부는 P |
| error | `401` · `{error:string}` · `"未授权"` / `"token 无效或已过期"` | C for direct writeErr; error helper 내부는 P |
| error | `404` · `{error:string}` · `"审批记录已被删除或不存在"` | C for direct writeErr; error helper 내부는 P |
| error | `409` · `{error:string}` · `err.Error()` | C for direct writeErr; error helper 내부는 P |
| error | `410` · `{error:string}` · `err.Error()` / `"对话已被删除"` | C for direct writeErr; error helper 내부는 P |
| error | `500` · `{error:string}` · `err.Error()` / `getErr.Error()` | C for direct writeErr; error helper 내부는 P |
| error | `503` · `{error:string}` · `"PostgreSQL 不可用"` | C for direct writeErr; error helper 내부는 P |

응답 schema 판정: **P** · top-level key는 직접 추출했지만 derived/helper/opaque 또는 nested type이 남음.

### 목록·동시성·완료

- Pagination/filter/sort: 등록된 직접 query key 중 표준 paging/filter key 없음.
- Idempotency/concurrency: read/stream 요청; stream 재연결은 cursor 계약에 따름.
- Side effect/completion: 직접 조회/파일 전송 완료; 외부 조회가 있으면 응답 전 handler 결과만 보장.
- 직접 협력자/실행 단서: `activityDTOs`, `err.Error`, `getErr.Error`, `pathInt`, `pg.GetConversation`, `pg.GetInterceptExecution`, `r.URL.Query`, `r.URL.Query().Get`; direct `go` statement는 미발견. 이 목록은 helper 내부의 background launch나 transaction 경계를 펼치지 않으므로 그 부분은 P/U다.
- 종합 의미 판정: **P/U**; route/auth와 위에서 직접 확인한 branch·shape는 C지만, 모든 helper·derived response·중첩 DTO·외부 효과까지 완결됐다는 뜻은 아니다.

<a id="schema-get-api-intercept-task-taskid"></a>
## `GET /api/intercept/task/{taskID}`

구현: `interceptListTaskItems` · `server/intercept.go` · [정확한 handler 근거](evidence:handler-get-api-intercept-task-taskid)

### 요청

| 위치/field | wire type | 필수·null | 기본/검증 |
|---|---|---|---|
| path `taskID` | `string` | 필수 | handler의 parse/range validation; 미해석 helper는 P |
| query `decision_source` | `string` wire | 선택 | enum empty\|model\|rule\|unknown; invalid→400 |
| query `page` | `string` wire | 선택 | decimal integer; invalid/<1→1 |
| query `size` | `string` wire | 선택 | decimal integer; invalid/<1→20; >100→100 |
| query `status` | `string` wire | 선택 | enum empty\|pending\|allowed\|denied\|timeout; invalid→400 |
| auth credential | Bearer header / `artex_token` cookie / query `token` | 세 위치 중 하나에 유효 JWT 필수 | `extractToken` 우선순위: header→cookie→query; query fallback은 SSE로 제한되지 않음 |
| body | 없음(직접 decoder/form read 미발견) | 해당 없음 | 해당 없음 |

요청 판정: **C/P** · unknown field 거부 미설정.
직접 validation: `err != nil` · `items == nil` · `pg == nil` · `q.Get("page") == "" && q.Get("size") == "" && filter == (db.InterceptApprovalFilter{})` · `taskID == ""`

### 응답·오류

| 종류 | status/body | 해석 상태 |
|---|---|---|
| success/variant | `200` · `items:derived`, `total:helper result` | P/U · `static composite` |
| success/variant | `200` · `items:derived`, `total:derived`, `page:derived`, `page_size:derived` | P/U · `static composite` |
| error | `400` · `{error:string}` · `"bad task id"` / `err.Error()` | C for direct writeErr; error helper 내부는 P |
| error | `401` · `{error:string}` · `"未授权"` / `"token 无效或已过期"` | C for direct writeErr; error helper 내부는 P |
| error | `500` · `{error:string}` · `err.Error()` | C for direct writeErr; error helper 내부는 P |
| error | `503` · `{error:string}` · `"PostgreSQL 不可用"` | C for direct writeErr; error helper 내부는 P |

응답 schema 판정: **P** · top-level key는 직접 추출했지만 derived/helper/opaque 또는 nested type이 남음.

### 목록·동시성·완료

- Pagination/filter/sort: `page`, `size`, `status`.
- Idempotency/concurrency: read/stream 요청; stream 재연결은 cursor 계약에 따름.
- Side effect/completion: 직접 조회/파일 전송 완료; 외부 조회가 있으면 응답 전 handler 결과만 보장.
- 직접 협력자/실행 단서: `err.Error`, `interceptFilterParams`, `interceptPageParams`, `pg.ListTaskIntercepts`, `pg.ListTaskInterceptsPage`, `q.Get`, `r.PathValue`, `r.URL.Query`; direct `go` statement는 미발견. 이 목록은 helper 내부의 background launch나 transaction 경계를 펼치지 않으므로 그 부분은 P/U다.
- 종합 의미 판정: **P/U**; route/auth와 위에서 직접 확인한 branch·shape는 C지만, 모든 helper·derived response·중첩 DTO·외부 효과까지 완결됐다는 뜻은 아니다.

<a id="schema-get-api-intercept-tool-config"></a>
## `GET /api/intercept/tool-config`

구현: `interceptGetToolConfig` · `server/intercept.go` · [정확한 handler 근거](evidence:handler-get-api-intercept-tool-config)

### 요청

| 위치/field | wire type | 필수·null | 기본/검증 |
|---|---|---|---|
| auth credential | Bearer header / `artex_token` cookie / query `token` | 세 위치 중 하나에 유효 JWT 필수 | `extractToken` 우선순위: header→cookie→query; query fallback은 SSE로 제한되지 않음 |
| body | 없음(직접 decoder/form read 미발견) | 해당 없음 | 해당 없음 |

요청 판정: **C/P** · unknown field 거부 미설정.
직접 validation: `err != nil`

### 응답·오류

| 종류 | status/body | 해석 상태 |
|---|---|---|
| success/variant | `200` · `enabled_tools:derived` | P/U · `static composite` |
| error | `401` · `{error:string}` · `"未授权"` / `"token 无效或已过期"` | C for direct writeErr; error helper 내부는 P |
| error | `500` · `{error:string}` · `err.Error()` | C for direct writeErr; error helper 내부는 P |

응답 schema 판정: **P** · top-level key는 직접 추출했지만 derived/helper/opaque 또는 nested type이 남음.

### 목록·동시성·완료

- Pagination/filter/sort: 등록된 직접 query key 중 표준 paging/filter key 없음.
- Idempotency/concurrency: read/stream 요청; stream 재연결은 cursor 계약에 따름.
- Side effect/completion: 직접 조회/파일 전송 완료; 외부 조회가 있으면 응답 전 handler 결과만 보장.
- 직접 협력자/실행 단서: `err.Error`, `s.m.interceptor.GetEnabledTools`; direct `go` statement는 미발견. 이 목록은 helper 내부의 background launch나 transaction 경계를 펼치지 않으므로 그 부분은 P/U다.
- 종합 의미 판정: **P/U**; route/auth와 위에서 직접 확인한 branch·shape는 C지만, 모든 helper·derived response·중첩 DTO·외부 효과까지 완결됐다는 뜻은 아니다.

<a id="schema-put-api-intercept-tool-config"></a>
## `PUT /api/intercept/tool-config`

구현: `interceptSetToolConfig` · `server/intercept.go` · [정확한 handler 근거](evidence:handler-put-api-intercept-tool-config)

### 요청

| 위치/field | wire type | 필수·null | 기본/검증 |
|---|---|---|---|
| auth credential | Bearer header / `artex_token` cookie / query `token` | 세 위치 중 하나에 유효 JWT 필수 | `extractToken` 우선순위: header→cookie→query; query fallback은 SSE로 제한되지 않음 |
| body `enabled_tools` | `array[string]` (`[]string`) | 선택; null/absent 구별 안 됨 | `nil이면 빈 배열로 정규화` |

요청 판정: **C/P** · unknown field 거부 미설정.
직접 validation: `req.EnabledTools == nil`

### 응답·오류

| 종류 | status/body | 해석 상태 |
|---|---|---|
| success/variant | `200` · `ok:boolean` | C · `static composite` |
| error | `400` · `{error:string}` · `err.Error()` | C for direct writeErr; error helper 내부는 P |
| error | `401` · `{error:string}` · `"未授权"` / `"token 无效或已过期"` | C for direct writeErr; error helper 내부는 P |
| error | `500` · `{error:string}` · `err.Error()` | C for direct writeErr; error helper 내부는 P |

응답 schema 판정: **C** · 직접 scalar/static top-level key와 field type을 추출; nested 내부 schema는 별도 type 참조가 없으면 P.

### 목록·동시성·완료

- Pagination/filter/sort: 등록된 직접 query key 중 표준 paging/filter key 없음.
- Idempotency/concurrency: Idempotency-Key/If-Match 없음; DB upsert/trigger/side effect 때문에 반복 의미를 handler별 확인(P).
- Side effect/completion: handler의 직접 mutation/호출 후 응답; 외부 전달·agent 실행이 파생되면 별도 상태 확인(P).
- 직접 협력자/실행 단서: `err.Error`, `s.m.interceptor.SetEnabledTools`; direct `go` statement는 미발견. 이 목록은 helper 내부의 background launch나 transaction 경계를 펼치지 않으므로 그 부분은 P/U다.
- 종합 의미 판정: **P/U**; route/auth와 위에서 직접 확인한 branch·shape는 C지만, 모든 helper·derived response·중첩 DTO·외부 효과까지 완결됐다는 뜻은 아니다.

<a id="schema-get-api-intercept-judge"></a>
## `GET /api/intercept/judge`

구현: `interceptGetJudgeConfig` · `server/intercept.go` · [정확한 handler 근거](evidence:handler-get-api-intercept-judge)

### 요청

| 위치/field | wire type | 필수·null | 기본/검증 |
|---|---|---|---|
| auth credential | Bearer header / `artex_token` cookie / query `token` | 세 위치 중 하나에 유효 JWT 필수 | `extractToken` 우선순위: header→cookie→query; query fallback은 SSE로 제한되지 않음 |
| body | 없음(직접 decoder/form read 미발견) | 해당 없음 | 해당 없음 |

요청 판정: **C/P** · unknown field 거부 미설정.

### 응답·오류

| 종류 | status/body | 해석 상태 |
|---|---|---|
| success/variant | `200` · `s.m.interceptor.GetJudgeConfig()` | P/U · `helper result` |
| error | `401` · `{error:string}` · `"未授权"` / `"token 无效或已过期"` | C for direct writeErr; error helper 내부는 P |

응답 schema 판정: **P** · top-level key는 직접 추출했지만 derived/helper/opaque 또는 nested type이 남음.

### 목록·동시성·완료

- Pagination/filter/sort: 등록된 직접 query key 중 표준 paging/filter key 없음.
- Idempotency/concurrency: read/stream 요청; stream 재연결은 cursor 계약에 따름.
- Side effect/completion: 직접 조회/파일 전송 완료; 외부 조회가 있으면 응답 전 handler 결과만 보장.
- 직접 협력자/실행 단서: `s.m.interceptor.GetJudgeConfig`; direct `go` statement는 미발견. 이 목록은 helper 내부의 background launch나 transaction 경계를 펼치지 않으므로 그 부분은 P/U다.
- 종합 의미 판정: **P/U**; route/auth와 위에서 직접 확인한 branch·shape는 C지만, 모든 helper·derived response·중첩 DTO·외부 효과까지 완결됐다는 뜻은 아니다.

<a id="schema-put-api-intercept-judge"></a>
## `PUT /api/intercept/judge`

구현: `interceptSetJudgeConfig` · `server/intercept.go` · [정확한 handler 근거](evidence:handler-put-api-intercept-judge)

### 요청

| 위치/field | wire type | 필수·null | 기본/검증 |
|---|---|---|---|
| auth credential | Bearer header / `artex_token` cookie / query `token` | 세 위치 중 하나에 유효 JWT 필수 | `extractToken` 우선순위: header→cookie→query; query fallback은 SSE로 제한되지 않음 |
| body `enabled` | `boolean` (`bool`) | 요구 여부 미확정(P); null/absent 구별 안 됨 | `Go zero value; 별도 기본은 handler/helper 확인` |
| body `profile_id` | `integer` (`int64`) | 요구 여부 미확정(P); null/absent 구별 안 됨 | `Go zero value; 별도 기본은 handler/helper 확인` |
| body `prompt` | `string` (`string`) | 요구 여부 미확정(P); null/absent 구별 안 됨 | `Go zero value; 별도 기본은 handler/helper 확인` |
| body `timeout_seconds` | `integer` (`int`) | 요구 여부 미확정(P); null/absent 구별 안 됨 | `Go zero value; 별도 기본은 handler/helper 확인` |
| body `fail_action` | `string` (`string`) | 요구 여부 미확정(P); null/absent 구별 안 됨 | `Go zero value; 별도 기본은 handler/helper 확인` |
| body `ask_timeout_seconds` | `integer` (`int`) | 요구 여부 미확정(P); null/absent 구별 안 됨 | `Go zero value; 별도 기본은 handler/helper 확인` |
| body `ask_timeout_action` | `string` (`string`) | 요구 여부 미확정(P); null/absent 구별 안 됨 | `Go zero value; 별도 기본은 handler/helper 확인` |

요청 판정: **C/P** · unknown field 거부 미설정.

### 응답·오류

| 종류 | status/body | 해석 상태 |
|---|---|---|
| success/variant | `200` · `ok:boolean` | C · `static composite` |
| error | `400` · `{error:string}` · `err.Error()` / `"fail_action 必须是 allow、ask 或 deny"` / `"ask_timeout_action 必须是 allow 或 deny"` | C for direct writeErr; error helper 내부는 P |
| error | `401` · `{error:string}` · `"未授权"` / `"token 无效或已过期"` | C for direct writeErr; error helper 내부는 P |
| error | `500` · `{error:string}` · `err.Error()` | C for direct writeErr; error helper 내부는 P |

응답 schema 판정: **C** · 직접 scalar/static top-level key와 field type을 추출; nested 내부 schema는 별도 type 참조가 없으면 P.

### 목록·동시성·완료

- Pagination/filter/sort: 등록된 직접 query key 중 표준 paging/filter key 없음.
- Idempotency/concurrency: Idempotency-Key/If-Match 없음; DB upsert/trigger/side effect 때문에 반복 의미를 handler별 확인(P).
- Side effect/completion: handler의 직접 mutation/호출 후 응답; 외부 전달·agent 실행이 파생되면 별도 상태 확인(P).
- 직접 협력자/실행 단서: `err.Error`, `s.m.interceptor.SetJudgeConfig`; direct `go` statement는 미발견. 이 목록은 helper 내부의 background launch나 transaction 경계를 펼치지 않으므로 그 부분은 P/U다.
- 종합 의미 판정: **P/U**; route/auth와 위에서 직접 확인한 branch·shape는 C지만, 모든 helper·derived response·중첩 DTO·외부 효과까지 완결됐다는 뜻은 아니다.

<a id="schema-get-api-intercept-judge-usage"></a>
## `GET /api/intercept/judge/usage`

구현: `interceptJudgeUsage` · `server/intercept.go` · [정확한 handler 근거](evidence:handler-get-api-intercept-judge-usage)

### 요청

| 위치/field | wire type | 필수·null | 기본/검증 |
|---|---|---|---|
| query `days` | `string` wire | 선택 | handler/helper 변환·default 미전개(P) |
| auth credential | Bearer header / `artex_token` cookie / query `token` | 세 위치 중 하나에 유효 JWT 필수 | `extractToken` 우선순위: header→cookie→query; query fallback은 SSE로 제한되지 않음 |
| body | 없음(직접 decoder/form read 미발견) | 해당 없음 | 해당 없음 |

요청 판정: **C/P** · unknown field 거부 미설정.
직접 validation: `err != nil` · `pg == nil`
직접 parse/default: `atoiDefault(r.URL.Query().Get("days"), 30)`

### 응답·오류

| 종류 | status/body | 해석 상태 |
|---|---|---|
| success/variant | `200` · `db.JudgeUsage{Daily: []db.JudgeDayUsage{}}` | P/U · `static composite` |
| success/variant | `200` · `usage` | P/U · `derived variable` |
| error | `401` · `{error:string}` · `"未授权"` / `"token 无效或已过期"` | C for direct writeErr; error helper 내부는 P |
| error | `500` · `{error:string}` · `err.Error()` | C for direct writeErr; error helper 내부는 P |

응답 schema 판정: **P** · top-level key는 직접 추출했지만 derived/helper/opaque 또는 nested type이 남음.

### 목록·동시성·완료

- Pagination/filter/sort: `days`.
- Idempotency/concurrency: read/stream 요청; stream 재연결은 cursor 계약에 따름.
- Side effect/completion: 직접 조회/파일 전송 완료; 외부 조회가 있으면 응답 전 handler 결과만 보장.
- 직접 협력자/실행 단서: `atoiDefault`, `err.Error`, `pg.JudgeUsageStats`, `r.URL.Query`, `r.URL.Query().Get`, `s.m.PG`; direct `go` statement는 미발견. 이 목록은 helper 내부의 background launch나 transaction 경계를 펼치지 않으므로 그 부분은 P/U다.
- 종합 의미 판정: **P/U**; route/auth와 위에서 직접 확인한 branch·shape는 C지만, 모든 helper·derived response·중첩 DTO·외부 효과까지 완결됐다는 뜻은 아니다.

<a id="schema-get-api-exploration-findings-id-traffic"></a>
## `GET /api/exploration/findings/{id}/traffic`

구현: `getFindingTraffic` · `server/finding_traffic.go` · [정확한 handler 근거](evidence:handler-get-api-exploration-findings-id-traffic)

### 요청

| 위치/field | wire type | 필수·null | 기본/검증 |
|---|---|---|---|
| path `id` | `string` | 필수 | handler의 parse/range validation; 미해석 helper는 P |
| query `context_task` | `string` wire | 선택 | optional task id; missing task/finding lineage mismatch→404; write operations reject inherited finding as read-only→403 |
| auth credential | Bearer header / `artex_token` cookie / query `token` | 세 위치 중 하나에 유효 JWT 필수 | `extractToken` 우선순위: header→cookie→query; query fallback은 SSE로 제한되지 않음 |
| body | 없음(직접 decoder/form read 미발견) | 해당 없음 | 해당 없음 |

요청 판정: **C/P** · unknown field 거부 미설정.
직접 validation: `!ok` · `err != nil`

### 응답·오류

| 종류 | status/body | 해석 상태 |
|---|---|---|
| success/variant | `200` · `trafficSummary(out)` | P/U · `helper result` |
| error | `400` · `{error:string}` · `"invalid finding id"` | C for direct writeErr; error helper 내부는 P |
| error | `401` · `{error:string}` · `"未授权"` / `"token 无效或已过期"` | C for direct writeErr; error helper 내부는 P |
| error | `404` · `{error:string}` · `err.Error()` / `"finding not found" / "context task not found" / "finding is not part of context task"` | C for direct writeErr; error helper 내부는 P |
| error | `409` · `{error:string}` · `err.Error()` | C for direct writeErr; error helper 내부는 P |
| error | `422` · `{error:string}` · `err.Error()` | C for direct writeErr; error helper 내부는 P |
| error | `500` · `{error:string}` · `err.Error()` | C for direct writeErr; error helper 내부는 P |

Helper 오류 분기: `evidenceError` · `server/finding_traffic.go::evidenceError` · 위 `404, 409, 422` status는 helper control-flow를 정적으로 펼친 P이며 호출 전/후 DB 효과는 operation별로 확인한다.

Finding traffic pre-handler: `findingTrafficAccess` · `server/finding_traffic.go`가 finding/context_task 존재·lineage를 검사하고 write operation의 inherited finding을 403으로 거부한다. 위 400/403/404/500은 helper control-flow C, DB/runtime 결과는 P다.

응답 schema 판정: **P** · top-level key는 직접 추출했지만 derived/helper/opaque 또는 nested type이 남음.

### 목록·동시성·완료

- Pagination/filter/sort: 등록된 직접 query key 중 표준 paging/filter key 없음.
- Idempotency/concurrency: read/stream 요청; stream 재연결은 cursor 계약에 따름.
- Side effect/completion: 직접 조회/파일 전송 완료; 외부 조회가 있으면 응답 전 handler 결과만 보장.
- 직접 협력자/실행 단서: `evidenceError`, `r.Context`, `s.m.pg.GetFindingTraffic`, `trafficSummary`; direct `go` statement는 미발견. 이 목록은 helper 내부의 background launch나 transaction 경계를 펼치지 않으므로 그 부분은 P/U다.
- 종합 의미 판정: **P/U**; route/auth와 위에서 직접 확인한 branch·shape는 C지만, 모든 helper·derived response·중첩 DTO·외부 효과까지 완결됐다는 뜻은 아니다.

<a id="schema-post-api-exploration-findings-id-traffic"></a>
## `POST /api/exploration/findings/{id}/traffic`

구현: `bindFindingTraffic` · `server/finding_traffic.go` · [정확한 handler 근거](evidence:handler-post-api-exploration-findings-id-traffic)

### 요청

| 위치/field | wire type | 필수·null | 기본/검증 |
|---|---|---|---|
| path `id` | `string` | 필수 | handler의 parse/range validation; 미해석 helper는 P |
| query `context_task` | `string` wire | 선택 | optional task id; missing task/finding lineage mismatch→404; write operations reject inherited finding as read-only→403 |
| auth credential | Bearer header / `artex_token` cookie / query `token` | 세 위치 중 하나에 유효 JWT 필수 | `extractToken` 우선순위: header→cookie→query; query fallback은 SSE로 제한되지 않음 |
| body `traffic_refs` | [array[db.TrafficRef]](#type-db-trafficref) (`[]db.TrafficRef`) | 필수(직접 거부 branch 확인); null/absent 구별 안 됨 | `빈 배열이면 400 후 return` |

요청 body 상한: **1 MiB** (`1048576` bytes) · source `server/finding_traffic.go::bind/editFindingTraffic` · 초과/decoder 경로 `400 "invalid body"`.

요청 판정: **C/P** · unknown field 거부 미설정.
직접 validation: `len(body.Refs) == 0`

### 응답·오류

| 종류 | status/body | 해석 상태 |
|---|---|---|
| success/variant | `200` · `trafficSummary(out)` | P/U · `helper result` |
| error | `400` · `{error:string}` · `"invalid body"` / `"请选择流量"` / `"invalid finding id"` | C for direct writeErr; error helper 내부는 P |
| error | `401` · `{error:string}` · `"未授权"` / `"token 无效或已过期"` | C for direct writeErr; error helper 내부는 P |
| error | `403` · `{error:string}` · `"inherited finding is read-only"` | C for direct writeErr; error helper 내부는 P |
| error | `404` · `{error:string}` · `err.Error()` / `"finding not found" / "context task not found" / "finding is not part of context task"` | C for direct writeErr; error helper 내부는 P |
| error | `409` · `{error:string}` · `err.Error()` | C for direct writeErr; error helper 내부는 P |
| error | `422` · `{error:string}` · `err.Error()` | C for direct writeErr; error helper 내부는 P |
| error | `500` · `{error:string}` · `err.Error()` | C for direct writeErr; error helper 내부는 P |

Helper 오류 분기: `evidenceError` · `server/finding_traffic.go::evidenceError` · 위 `404, 409, 422` status는 helper control-flow를 정적으로 펼친 P이며 호출 전/후 DB 효과는 operation별로 확인한다.

Finding traffic pre-handler: `findingTrafficAccess` · `server/finding_traffic.go`가 finding/context_task 존재·lineage를 검사하고 write operation의 inherited finding을 403으로 거부한다. 위 400/403/404/500은 helper control-flow C, DB/runtime 결과는 P다.

응답 schema 판정: **P** · top-level key는 직접 추출했지만 derived/helper/opaque 또는 nested type이 남음.

### 목록·동시성·완료

- Pagination/filter/sort: 등록된 직접 query key 중 표준 paging/filter key 없음.
- Idempotency/concurrency: Idempotency-Key 없음; unique constraint 또는 상태 check가 있는 경우만 중복을 제한(P).
- Side effect/completion: handler의 직접 mutation/호출 후 응답; 외부 전달·agent 실행이 파생되면 별도 상태 확인(P).
- 직접 협력자/실행 단서: `evidenceError`, `json.NewDecoder`, `json.NewDecoder(http.MaxBytesReader(w, r.Body, 1<<20)).Decode`, `r.Context`, `s.evidenceStore().Bind`, `trafficSummary`; direct `go` statement는 미발견. 이 목록은 helper 내부의 background launch나 transaction 경계를 펼치지 않으므로 그 부분은 P/U다.
- 종합 의미 판정: **P/U**; route/auth와 위에서 직접 확인한 branch·shape는 C지만, 모든 helper·derived response·중첩 DTO·외부 효과까지 완결됐다는 뜻은 아니다.

<a id="schema-patch-api-exploration-findings-id-traffic-binding-id"></a>
## `PATCH /api/exploration/findings/{id}/traffic/{binding_id}`

구현: `editFindingTraffic` · `server/finding_traffic.go` · [정확한 handler 근거](evidence:handler-patch-api-exploration-findings-id-traffic-binding-id)

### 요청

| 위치/field | wire type | 필수·null | 기본/검증 |
|---|---|---|---|
| path `binding_id` | `string` | 필수 | handler의 parse/range validation; 미해석 helper는 P |
| path `id` | `string` | 필수 | handler의 parse/range validation; 미해석 helper는 P |
| query `context_task` | `string` wire | 선택 | optional task id; missing task/finding lineage mismatch→404; write operations reject inherited finding as read-only→403 |
| auth credential | Bearer header / `artex_token` cookie / query `token` | 세 위치 중 하나에 유효 JWT 필수 | `extractToken` 우선순위: header→cookie→query; query fallback은 SSE로 제한되지 않음 |
| body `version` | `integer` (`*int64`) | 필수(직접 거부 branch 확인); null→nil | `nil 또는 body decode 실패면 400 후 return` |
| body `role` | `string` (`*string`) | 요구 여부 미확정(P); absent/null→nil; null→nil | `nil` |
| body `note` | `string` (`*string`) | 요구 여부 미확정(P); absent/null→nil; null→nil | `nil` |

공유 decoder가 `binding_ids`도 parse하지만 이 method branch는 값을 사용하지 않아 의미 입력에서 제외한다.

요청 body 상한: **1 MiB** (`1048576` bytes) · source `server/finding_traffic.go::bind/editFindingTraffic` · 초과/decoder 경로 `400 "version 和有效请求体必填"`.

요청 판정: **C/P** · unknown field 거부 미설정.
직접 validation: `body.Version == nil` · `binding_id must parse as positive int64`

### 응답·오류

| 종류 | status/body | 해석 상태 |
|---|---|---|
| success/variant | `200` · `s.getFindingTraffic(w, r)` | P/U · `helper result` |
| error | `400` · `{error:string}` · `"version 和有效请求体必填"` / `"invalid binding id"` / `"invalid finding id"` | C for direct writeErr; error helper 내부는 P |
| error | `401` · `{error:string}` · `"未授权"` / `"token 无效或已过期"` | C for direct writeErr; error helper 내부는 P |
| error | `403` · `{error:string}` · `"inherited finding is read-only"` | C for direct writeErr; error helper 내부는 P |
| error | `404` · `{error:string}` · `err.Error()` / `"finding not found" / "context task not found" / "finding is not part of context task"` | C for direct writeErr; error helper 내부는 P |
| error | `409` · `{error:string}` · `err.Error()` | C for direct writeErr; error helper 내부는 P |
| error | `422` · `{error:string}` · `err.Error()` | C for direct writeErr; error helper 내부는 P |
| error | `500` · `{error:string}` · `err.Error()` | C for direct writeErr; error helper 내부는 P |

Helper 오류 분기: `evidenceError` · `server/finding_traffic.go::evidenceError` · 위 `404, 409, 422` status는 helper control-flow를 정적으로 펼친 P이며 호출 전/후 DB 효과는 operation별로 확인한다.

Finding traffic pre-handler: `findingTrafficAccess` · `server/finding_traffic.go`가 finding/context_task 존재·lineage를 검사하고 write operation의 inherited finding을 403으로 거부한다. 위 400/403/404/500은 helper control-flow C, DB/runtime 결과는 P다.

응답 schema 판정: **P** · top-level key는 직접 추출했지만 derived/helper/opaque 또는 nested type이 남음.

### 목록·동시성·완료

- Pagination/filter/sort: 등록된 직접 query key 중 표준 paging/filter key 없음.
- Idempotency/concurrency: body version 기반 optimistic concurrency; stale은 handler의 `409`; HTTP idempotency key는 없음.
- Side effect/completion: handler의 직접 mutation/호출 후 응답; 외부 전달·agent 실행이 파생되면 별도 상태 확인(P).
- 직접 협력자/실행 단서: `json.NewDecoder`, `s.m.pg.EditFindingTraffic`, `evidenceError`; direct `go` statement는 미발견. 이 목록은 helper 내부의 background launch나 transaction 경계를 펼치지 않으므로 그 부분은 P/U다.
- 종합 의미 판정: **P/U**; route/auth와 위에서 직접 확인한 branch·shape는 C지만, 모든 helper·derived response·중첩 DTO·외부 효과까지 완결됐다는 뜻은 아니다.

<a id="schema-delete-api-exploration-findings-id-traffic-binding-id"></a>
## `DELETE /api/exploration/findings/{id}/traffic/{binding_id}`

구현: `editFindingTraffic` · `server/finding_traffic.go` · [정확한 handler 근거](evidence:handler-delete-api-exploration-findings-id-traffic-binding-id)

### 요청

| 위치/field | wire type | 필수·null | 기본/검증 |
|---|---|---|---|
| path `binding_id` | `string` | 필수 | handler의 parse/range validation; 미해석 helper는 P |
| path `id` | `string` | 필수 | handler의 parse/range validation; 미해석 helper는 P |
| query `context_task` | `string` wire | 선택 | optional task id; missing task/finding lineage mismatch→404; write operations reject inherited finding as read-only→403 |
| auth credential | Bearer header / `artex_token` cookie / query `token` | 세 위치 중 하나에 유효 JWT 필수 | `extractToken` 우선순위: header→cookie→query; query fallback은 SSE로 제한되지 않음 |
| body `version` | `integer` (`*int64`) | 필수(직접 거부 branch 확인); null→nil | `nil 또는 body decode 실패면 400 후 return` |

공유 decoder가 `role`, `note`, `binding_ids`도 parse하지만 이 method branch는 값을 사용하지 않아 의미 입력에서 제외한다.

요청 body 상한: **1 MiB** (`1048576` bytes) · source `server/finding_traffic.go::bind/editFindingTraffic` · 초과/decoder 경로 `400 "version 和有效请求体必填"`.

요청 판정: **C/P** · unknown field 거부 미설정.
직접 validation: `body.Version == nil` · `binding_id must parse as positive int64`

### 응답·오류

| 종류 | status/body | 해석 상태 |
|---|---|---|
| success/variant | `200` · `s.getFindingTraffic(w, r)` | P/U · `helper result` |
| error | `400` · `{error:string}` · `"version 和有效请求体必填"` / `"invalid binding id"` / `"invalid finding id"` | C for direct writeErr; error helper 내부는 P |
| error | `401` · `{error:string}` · `"未授权"` / `"token 无效或已过期"` | C for direct writeErr; error helper 내부는 P |
| error | `403` · `{error:string}` · `"inherited finding is read-only"` | C for direct writeErr; error helper 내부는 P |
| error | `404` · `{error:string}` · `err.Error()` / `"finding not found" / "context task not found" / "finding is not part of context task"` | C for direct writeErr; error helper 내부는 P |
| error | `409` · `{error:string}` · `err.Error()` | C for direct writeErr; error helper 내부는 P |
| error | `422` · `{error:string}` · `err.Error()` | C for direct writeErr; error helper 내부는 P |
| error | `500` · `{error:string}` · `err.Error()` | C for direct writeErr; error helper 내부는 P |

Helper 오류 분기: `evidenceError` · `server/finding_traffic.go::evidenceError` · 위 `404, 409, 422` status는 helper control-flow를 정적으로 펼친 P이며 호출 전/후 DB 효과는 operation별로 확인한다.

Finding traffic pre-handler: `findingTrafficAccess` · `server/finding_traffic.go`가 finding/context_task 존재·lineage를 검사하고 write operation의 inherited finding을 403으로 거부한다. 위 400/403/404/500은 helper control-flow C, DB/runtime 결과는 P다.

응답 schema 판정: **P** · top-level key는 직접 추출했지만 derived/helper/opaque 또는 nested type이 남음.

### 목록·동시성·완료

- Pagination/filter/sort: 등록된 직접 query key 중 표준 paging/filter key 없음.
- Idempotency/concurrency: body version 기반 optimistic concurrency; stale은 handler의 `409`; HTTP idempotency key는 없음.
- Side effect/completion: handler의 직접 mutation/호출 후 응답; 외부 전달·agent 실행이 파생되면 별도 상태 확인(P).
- 직접 협력자/실행 단서: `json.NewDecoder`, `s.m.pg.EditFindingTraffic`, `evidenceError`; direct `go` statement는 미발견. 이 목록은 helper 내부의 background launch나 transaction 경계를 펼치지 않으므로 그 부분은 P/U다.
- 종합 의미 판정: **P/U**; route/auth와 위에서 직접 확인한 branch·shape는 C지만, 모든 helper·derived response·중첩 DTO·외부 효과까지 완결됐다는 뜻은 아니다.

<a id="schema-put-api-exploration-findings-id-traffic-order"></a>
## `PUT /api/exploration/findings/{id}/traffic/order`

구현: `editFindingTraffic` · `server/finding_traffic.go` · [정확한 handler 근거](evidence:handler-put-api-exploration-findings-id-traffic-order)

### 요청

| 위치/field | wire type | 필수·null | 기본/검증 |
|---|---|---|---|
| path `id` | `string` | 필수 | handler의 parse/range validation; 미해석 helper는 P |
| query `context_task` | `string` wire | 선택 | optional task id; missing task/finding lineage mismatch→404; write operations reject inherited finding as read-only→403 |
| auth credential | Bearer header / `artex_token` cookie / query `token` | 세 위치 중 하나에 유효 JWT 필수 | `extractToken` 우선순위: header→cookie→query; query fallback은 SSE로 제한되지 않음 |
| body `version` | `integer` (`*int64`) | 필수(직접 거부 branch 확인); null→nil | `nil 또는 body decode 실패면 400 후 return` |
| body `binding_ids` | `array[string]` (`[]string`) | 필수(직접 거부 branch 확인); null/absent 구별 안 됨 | `nil이면 400 후 return; 빈 non-nil 배열은 허용` |

공유 decoder가 `role`, `note`도 parse하지만 이 method branch는 값을 사용하지 않아 의미 입력에서 제외한다.

요청 body 상한: **1 MiB** (`1048576` bytes) · source `server/finding_traffic.go::bind/editFindingTraffic` · 초과/decoder 경로 `400 "version 和有效请求体必填"`.

요청 판정: **C/P** · unknown field 거부 미설정.
직접 validation: `body.Version == nil` · `body.Order == nil` · `binding_ids element must parse as positive int64`

### 응답·오류

| 종류 | status/body | 해석 상태 |
|---|---|---|
| success/variant | `200` · `s.getFindingTraffic(w, r)` | P/U · `helper result` |
| error | `400` · `{error:string}` · `"version 和有效请求体必填"` / `"binding_ids 必填"` / `"invalid binding id"` / `"invalid finding id"` | C for direct writeErr; error helper 내부는 P |
| error | `401` · `{error:string}` · `"未授权"` / `"token 无效或已过期"` | C for direct writeErr; error helper 내부는 P |
| error | `403` · `{error:string}` · `"inherited finding is read-only"` | C for direct writeErr; error helper 내부는 P |
| error | `404` · `{error:string}` · `err.Error()` / `"finding not found" / "context task not found" / "finding is not part of context task"` | C for direct writeErr; error helper 내부는 P |
| error | `409` · `{error:string}` · `err.Error()` | C for direct writeErr; error helper 내부는 P |
| error | `422` · `{error:string}` · `err.Error()` | C for direct writeErr; error helper 내부는 P |
| error | `500` · `{error:string}` · `err.Error()` | C for direct writeErr; error helper 내부는 P |

Helper 오류 분기: `evidenceError` · `server/finding_traffic.go::evidenceError` · 위 `404, 409, 422` status는 helper control-flow를 정적으로 펼친 P이며 호출 전/후 DB 효과는 operation별로 확인한다.

Finding traffic pre-handler: `findingTrafficAccess` · `server/finding_traffic.go`가 finding/context_task 존재·lineage를 검사하고 write operation의 inherited finding을 403으로 거부한다. 위 400/403/404/500은 helper control-flow C, DB/runtime 결과는 P다.

응답 schema 판정: **P** · top-level key는 직접 추출했지만 derived/helper/opaque 또는 nested type이 남음.

### 목록·동시성·완료

- Pagination/filter/sort: 등록된 직접 query key 중 표준 paging/filter key 없음.
- Idempotency/concurrency: body version 기반 optimistic concurrency; stale은 handler의 `409`; HTTP idempotency key는 없음.
- Side effect/completion: handler의 직접 mutation/호출 후 응답; 외부 전달·agent 실행이 파생되면 별도 상태 확인(P).
- 직접 협력자/실행 단서: `json.NewDecoder`, `s.m.pg.EditFindingTraffic`, `evidenceError`; direct `go` statement는 미발견. 이 목록은 helper 내부의 background launch나 transaction 경계를 펼치지 않으므로 그 부분은 P/U다.
- 종합 의미 판정: **P/U**; route/auth와 위에서 직접 확인한 branch·shape는 C지만, 모든 helper·derived response·중첩 DTO·외부 효과까지 완결됐다는 뜻은 아니다.

<a id="schema-get-api-exploration-findings-id-traffic-binding-id"></a>
## `GET /api/exploration/findings/{id}/traffic/{binding_id}`

구현: `getFindingTrafficDetail` · `server/finding_traffic.go` · [정확한 handler 근거](evidence:handler-get-api-exploration-findings-id-traffic-binding-id)

### 요청

| 위치/field | wire type | 필수·null | 기본/검증 |
|---|---|---|---|
| path `binding_id` | `string` | 필수 | handler의 parse/range validation; 미해석 helper는 P |
| path `id` | `string` | 필수 | handler의 parse/range validation; 미해석 helper는 P |
| query `context_task` | `string` wire | 선택 | optional task id; missing task/finding lineage mismatch→404; write operations reject inherited finding as read-only→403 |
| auth credential | Bearer header / `artex_token` cookie / query `token` | 세 위치 중 하나에 유효 JWT 필수 | `extractToken` 우선순위: header→cookie→query; query fallback은 SSE로 제한되지 않음 |
| body | 없음(직접 decoder/form read 미발견) | 해당 없음 | 해당 없음 |

요청 판정: **C/P** · unknown field 거부 미설정.
직접 validation: `!ok` · `err != nil` · `err != nil \|\| bid <= 0`
직접 parse/default: `strconv.ParseInt(r.PathValue("binding_id"), 10, 64)`

### 응답·오류

| 종류 | status/body | 해석 상태 |
|---|---|---|
| success/variant | `200` · `result` | P/U · `derived variable` |
| error | `400` · `{error:string}` · `"invalid binding id"` / `"invalid finding id"` | C for direct writeErr; error helper 내부는 P |
| error | `401` · `{error:string}` · `"未授权"` / `"token 无效或已过期"` | C for direct writeErr; error helper 내부는 P |
| error | `404` · `{error:string}` · `err.Error()` / `"finding not found" / "context task not found" / "finding is not part of context task"` | C for direct writeErr; error helper 내부는 P |
| error | `409` · `{error:string}` · `err.Error()` | C for direct writeErr; error helper 내부는 P |
| error | `422` · `{error:string}` · `err.Error()` | C for direct writeErr; error helper 내부는 P |
| error | `500` · `{error:string}` · `err.Error()` | C for direct writeErr; error helper 내부는 P |

Helper 오류 분기: `evidenceError` · `server/finding_traffic.go::evidenceError` · 위 `404, 409, 422` status는 helper control-flow를 정적으로 펼친 P이며 호출 전/후 DB 효과는 operation별로 확인한다.

Finding traffic pre-handler: `findingTrafficAccess` · `server/finding_traffic.go`가 finding/context_task 존재·lineage를 검사하고 write operation의 inherited finding을 403으로 거부한다. 위 400/403/404/500은 helper control-flow C, DB/runtime 결과는 P다.

응답 schema 판정: **P** · top-level key는 직접 추출했지만 derived/helper/opaque 또는 nested type이 남음.

### 목록·동시성·완료

- Pagination/filter/sort: 등록된 직접 query key 중 표준 paging/filter key 없음.
- Idempotency/concurrency: read/stream 요청; stream 재연결은 cursor 계약에 따름.
- Side effect/completion: 직접 조회/파일 전송 완료; 외부 조회가 있으면 응답 전 handler 결과만 보장.
- 직접 협력자/실행 단서: `evidenceError`, `r.Context`, `r.PathValue`, `readEvidencePreview`, `store.WithBinding`; direct `go` statement는 미발견. 이 목록은 helper 내부의 background launch나 transaction 경계를 펼치지 않으므로 그 부분은 P/U다.
- 종합 의미 판정: **P/U**; route/auth와 위에서 직접 확인한 branch·shape는 C지만, 모든 helper·derived response·중첩 DTO·외부 효과까지 완결됐다는 뜻은 아니다.

<a id="schema-get-api-exploration-findings-id-traffic-binding-id-body"></a>
## `GET /api/exploration/findings/{id}/traffic/{binding_id}/body`

구현: `getFindingTrafficBody` · `server/finding_traffic.go` · [정확한 handler 근거](evidence:handler-get-api-exploration-findings-id-traffic-binding-id-body)

### 요청

| 위치/field | wire type | 필수·null | 기본/검증 |
|---|---|---|---|
| path `binding_id` | `string` | 필수 | handler의 parse/range validation; 미해석 helper는 P |
| path `id` | `string` | 필수 | handler의 parse/range validation; 미해석 helper는 P |
| query `context_task` | `string` wire | 선택 | optional task id; missing task/finding lineage mismatch→404; write operations reject inherited finding as read-only→403 |
| query `download` | `string` wire | 선택 | exact string `1` selects raw attachment; any other/missing value returns preview JSON |
| query `length` | `string` wire | 선택 | int64 byte count; omitted/0/>8192→8192; negative→422 helper path |
| query `offset` | `string` wire | 선택 | int64 byte offset; omitted→0; parse/negative/out-of-body→422 helper path |
| query `side` | `string` wire | 성공 응답에 필수 | required enum `request\|response`; missing/other reaches OpenBody error→422 |
| auth credential | Bearer header / `artex_token` cookie / query `token` | 세 위치 중 하나에 유효 JWT 필수 | `extractToken` 우선순위: header→cookie→query; query fallback은 SSE로 제한되지 않음 |
| body | 없음(직접 decoder/form read 미발견) | 해당 없음 | 해당 없음 |

요청 판정: **C/P** · unknown field 거부 미설정.
직접 validation: `!ok` · `err != nil` · `err != nil \|\| bid <= 0` · `r.URL.Query().Get("download") == "1"` · `raw != ""`
직접 parse/default: `strconv.ParseInt(r.PathValue("binding_id"), 10, 64)` · `strconv.ParseInt(raw, 10, 64)`

### 응답·오류

| 종류 | status/body | 해석 상태 |
|---|---|---|
| success/variant | `200` · `preview` | P/U · `derived variable` |
| success/variant | `200` · `\`download=1\`: application/octet-stream; X-Content-Type-Options nosniff; Content-Disposition \`evidence-<binding>-<side>.bin\`; Content-Length; raw bytes` | P/U · `binary stream` |
| error | `400` · `{error:string}` · `"invalid binding id"` / `"invalid finding id"` | C for direct writeErr; error helper 내부는 P |
| error | `401` · `{error:string}` · `"未授权"` / `"token 无效或已过期"` | C for direct writeErr; error helper 내부는 P |
| error | `404` · `{error:string}` · `err.Error()` / `"finding not found" / "context task not found" / "finding is not part of context task"` | C for direct writeErr; error helper 내부는 P |
| error | `409` · `{error:string}` · `err.Error()` | C for direct writeErr; error helper 내부는 P |
| error | `422` · `{error:string}` · `err.Error()` | C for direct writeErr; error helper 내부는 P |
| error | `500` · `{error:string}` · `err.Error()` | C for direct writeErr; error helper 내부는 P |

Helper 오류 분기: `evidenceError` · `server/finding_traffic.go::evidenceError` · 위 `404, 409, 422` status는 helper control-flow를 정적으로 펼친 P이며 호출 전/후 DB 효과는 operation별로 확인한다.

Finding traffic pre-handler: `findingTrafficAccess` · `server/finding_traffic.go`가 finding/context_task 존재·lineage를 검사하고 write operation의 inherited finding을 403으로 거부한다. 위 400/403/404/500은 helper control-flow C, DB/runtime 결과는 P다.

응답 schema 판정: **P** · top-level key는 직접 추출했지만 derived/helper/opaque 또는 nested type이 남음.

### 목록·동시성·완료

- Pagination/filter/sort: `offset`.
- Idempotency/concurrency: read/stream 요청; stream 재연결은 cursor 계약에 따름.
- Side effect/completion: 직접 조회/파일 전송 완료; 외부 조회가 있으면 응답 전 handler 결과만 보장.
- 직접 협력자/실행 단서: `evidenceError`, `f.Close`, `io.Copy`, `r.Context`, `r.PathValue`, `r.URL.Query`, `r.URL.Query().Get`, `readEvidencePreview`, `store.Binding`, `store.OpenBody`, `w.Header`, `w.Header().Set`; direct `go` statement는 미발견. 이 목록은 helper 내부의 background launch나 transaction 경계를 펼치지 않으므로 그 부분은 P/U다.
- 종합 의미 판정: **P/U**; route/auth와 위에서 직접 확인한 branch·shape는 C지만, 모든 helper·derived response·중첩 DTO·외부 효과까지 완결됐다는 뜻은 아니다.

<a id="schema-get-api-conversations-id-side-questions"></a>
## `GET /api/conversations/{id}/side-questions`

구현: `handleSideQuestions` · `server/side_questions.go` · [정확한 handler 근거](evidence:handler-get-api-conversations-id-side-questions)

### 요청

| 위치/field | wire type | 필수·null | 기본/검증 |
|---|---|---|---|
| path `id` | `string` | 필수 | handler의 parse/range validation; 미해석 helper는 P |
| query `before` | `string` wire | 선택 | handler/helper 변환·default 미전개(P) |
| auth credential | Bearer header / `artex_token` cookie / query `token` | 세 위치 중 하나에 유효 JWT 필수 | `extractToken` 우선순위: header→cookie→query; query fallback은 SSE로 제한되지 않음 |
| body | 없음(직접 decoder/form read 미발견) | 해당 없음 | 해당 없음 |

요청 판정: **C/P** · unknown field 거부 미설정.

### 응답·오류

| 종류 | status/body | 해석 상태 |
|---|---|---|
| success/variant | `200` · `items:array[sidequestion.Exchange]`, `current:sidequestion.Exchange\|null`, `next_cursor:integer`, `snapshot:object\|null` | P/U · `static composite` |
| error | `400` · `{error:string}` · `"bad id"` | C for direct writeErr; error helper 내부는 P |
| error | `401` · `{error:string}` · `"未授权"` / `"token 无效或已过期"` | C for direct writeErr; error helper 내부는 P |
| error | `404` · `{error:string}` · `"conversation not found"` | C for direct writeErr; error helper 내부는 P |
| error | `500` · `{error:string}` · `err.Error()` | C for direct writeErr; error helper 내부는 P |
| error | `503` · `{error:string}` · `"旁路服务不可用"` | C for direct writeErr; error helper 내부는 P |

응답 schema 판정: **P** · top-level key는 직접 추출했지만 derived/helper/opaque 또는 nested type이 남음.

### 목록·동시성·완료

- Pagination/filter/sort: `before`.
- Idempotency/concurrency: read/stream 요청; stream 재연결은 cursor 계약에 따름.
- Side effect/completion: history/current snapshot 조회가 응답 전에 끝남; `before`는 invalid/missing이면 0으로 처리.
- 직접 협력자/실행 단서: `p.Key`, `s.m.pg.SideHistory`, `s.m.pg.CurrentSideRequest`; direct `go` statement는 미발견. 이 목록은 helper 내부의 background launch나 transaction 경계를 펼치지 않으므로 그 부분은 P/U다.
- 종합 의미 판정: **P/U**; route/auth와 위에서 직접 확인한 branch·shape는 C지만, 모든 helper·derived response·중첩 DTO·외부 효과까지 완결됐다는 뜻은 아니다.

<a id="schema-post-api-conversations-id-side-questions"></a>
## `POST /api/conversations/{id}/side-questions`

구현: `handleSideQuestions` · `server/side_questions.go` · [정확한 handler 근거](evidence:handler-post-api-conversations-id-side-questions)

### 요청

| 위치/field | wire type | 필수·null | 기본/검증 |
|---|---|---|---|
| path `id` | `string` | 필수 | handler의 parse/range validation; 미해석 helper는 P |
| auth credential | Bearer header / `artex_token` cookie / query `token` | 세 위치 중 하나에 유효 JWT 필수 | `extractToken` 우선순위: header→cookie→query; query fallback은 SSE로 제한되지 않음 |
| body `question` | `string` (`string`) | 필수(직접 거부 branch 확인); null/absent 구별 안 됨 | `trim 후 1–4000자가 아니면 400 후 return` |
| body `client_request_id` | `string` (`string`) | 필수(직접 거부 branch 확인); null/absent 구별 안 됨 | `validWorkerMessageRequestID 실패면 400 후 return` |

요청 body 상한: **32 KiB** (`32768` bytes) · source `server/side_questions.go::handleSideQuestions POST branch` · 초과/decoder 경로 `400 "bad json"`.

요청 판정: **C/P** · unknown field 거부 미설정.
직접 validation: `in.Question == "" \|\| len([]rune(in.Question)) > 4000 \|\| !validWorkerMessageRequestID(in.ClientID)` · `existing.Question != in.Question`

### 응답·오류

| 종류 | status/body | 해석 상태 |
|---|---|---|
| success/variant | `200` · `existing` | P/U · `derived variable` |
| success/variant | `202` · `e` | P/U · `derived variable` |
| error | `400` · `{error:string}` · `"bad id"` / `"bad json"` / `"问题须为 1–4000 字符，并提供有效请求 ID"` / `err.Error()` | C for direct writeErr; error helper 내부는 P |
| error | `401` · `{error:string}` · `"未授权"` / `"token 无效或已过期"` | C for direct writeErr; error helper 내부는 P |
| error | `404` · `{error:string}` · `"conversation not found"` | C for direct writeErr; error helper 내부는 P |
| error | `409` · `{error:string}` · `"任务正在归档或删除"` / `"同一请求 ID 不能用于不同问题"` / `"尚无上下文快照，请先运行主 Agent"` / `db.ErrSideBusy.Error()` | C for direct writeErr; error helper 내부는 P |
| error | `429` · `{error:string}` · `"旁路请求已达并发上限，请稍后重试"` | C for direct writeErr; error helper 내부는 P |
| error | `500` · `{error:string}` · `err.Error()` | C for direct writeErr; error helper 내부는 P |
| error | `503` · `{error:string}` · `"旁路服务不可用"` | C for direct writeErr; error helper 내부는 P |

Helper 오류 분기: `prepareChatMentionMessage` · `server/chat_mentions.go::Server.prepareChatMentionMessage` · 위 `400, 500` status는 helper control-flow를 정적으로 펼친 P이며 호출 전/후 DB 효과는 operation별로 확인한다.

응답 schema 판정: **P** · top-level key는 직접 추출했지만 derived/helper/opaque 또는 nested type이 남음.

### 목록·동시성·완료

- Pagination/filter/sort: 등록된 직접 query key 중 표준 paging/filter key 없음.
- Idempotency/concurrency: 같은 client_request_id+question은 기존 request를 200으로 반환; 같은 ID의 다른 question은 409.
- Side effect/completion: side-question session/request 시작; events/terminal request state가 완료 기준.
- 직접 협력자/실행 단서: `p.Key`, `json.NewDecoder`, `validWorkerMessageRequestID`, `s.engine.IsDeleting`, `s.m.pg.ExistingSideRequest`, `s.m.pg.SaveSideSnapshot`, `s.m.pg.StartSideRequest`; direct `go` statement는 발견. 이 목록은 helper 내부의 background launch나 transaction 경계를 펼치지 않으므로 그 부분은 P/U다.
- 종합 의미 판정: **P/U**; route/auth와 위에서 직접 확인한 branch·shape는 C지만, 모든 helper·derived response·중첩 DTO·외부 효과까지 완결됐다는 뜻은 아니다.

<a id="schema-delete-api-conversations-id-side-questions"></a>
## `DELETE /api/conversations/{id}/side-questions`

구현: `handleSideQuestions` · `server/side_questions.go` · [정확한 handler 근거](evidence:handler-delete-api-conversations-id-side-questions)

### 요청

| 위치/field | wire type | 필수·null | 기본/검증 |
|---|---|---|---|
| path `id` | `string` | 필수 | handler의 parse/range validation; 미해석 helper는 P |
| auth credential | Bearer header / `artex_token` cookie / query `token` | 세 위치 중 하나에 유효 JWT 필수 | `extractToken` 우선순위: header→cookie→query; query fallback은 SSE로 제한되지 않음 |
| body | 없음(직접 decoder/form read 미발견) | 해당 없음 | 해당 없음 |

요청 판정: **C/P** · unknown field 거부 미설정.

### 응답·오류

| 종류 | status/body | 해석 상태 |
|---|---|---|
| success/variant | `200` · `cleared:boolean` | C · `static composite` |
| error | `400` · `{error:string}` · `"bad id"` | C for direct writeErr; error helper 내부는 P |
| error | `401` · `{error:string}` · `"未授权"` / `"token 无效或已过期"` | C for direct writeErr; error helper 내부는 P |
| error | `404` · `{error:string}` · `"conversation not found"` | C for direct writeErr; error helper 내부는 P |
| error | `500` · `{error:string}` · `err.Error()` | C for direct writeErr; error helper 내부는 P |
| error | `503` · `{error:string}` · `"旁路服务不可用"` | C for direct writeErr; error helper 내부는 P |

응답 schema 판정: **C** · 직접 scalar/static top-level key와 field type을 추출; nested 내부 schema는 별도 type 참조가 없으면 P.

### 목록·동시성·완료

- Pagination/filter/sort: 등록된 직접 query key 중 표준 paging/filter key 없음.
- Idempotency/concurrency: Idempotency-Key/If-Match 없음; 반복 시 응답/존재 검사가 달라질 수 있음.
- Side effect/completion: active run에 cancel을 요청하고 DB history clear 후 응답; run goroutine 종료까지 기다리지는 않음.
- 직접 협력자/실행 단서: `p.Key`, `run.cancel`, `s.m.pg.ClearSideHistory`; direct `go` statement는 미발견. 이 목록은 helper 내부의 background launch나 transaction 경계를 펼치지 않으므로 그 부분은 P/U다.
- 종합 의미 판정: **P/U**; route/auth와 위에서 직접 확인한 branch·shape는 C지만, 모든 helper·derived response·중첩 DTO·외부 효과까지 완결됐다는 뜻은 아니다.

<a id="schema-get-api-tasks-id-chat-side-questions"></a>
## `GET /api/tasks/{id}/chat/side-questions`

구현: `handleSideQuestions` · `server/side_questions.go` · [정확한 handler 근거](evidence:handler-get-api-tasks-id-chat-side-questions)

### 요청

| 위치/field | wire type | 필수·null | 기본/검증 |
|---|---|---|---|
| path `id` | `string` | 필수 | handler의 parse/range validation; 미해석 helper는 P |
| query `before` | `string` wire | 선택 | handler/helper 변환·default 미전개(P) |
| auth credential | Bearer header / `artex_token` cookie / query `token` | 세 위치 중 하나에 유효 JWT 필수 | `extractToken` 우선순위: header→cookie→query; query fallback은 SSE로 제한되지 않음 |
| body | 없음(직접 decoder/form read 미발견) | 해당 없음 | 해당 없음 |

요청 판정: **C/P** · unknown field 거부 미설정.

### 응답·오류

| 종류 | status/body | 해석 상태 |
|---|---|---|
| success/variant | `200` · `items:array[sidequestion.Exchange]`, `current:sidequestion.Exchange\|null`, `next_cursor:integer`, `snapshot:object\|null` | P/U · `static composite` |
| error | `400` · `{error:string}` · `"bad id"` | C for direct writeErr; error helper 내부는 P |
| error | `401` · `{error:string}` · `"未授权"` / `"token 无效或已过期"` | C for direct writeErr; error helper 내부는 P |
| error | `404` · `{error:string}` · `"task not found"` | C for direct writeErr; error helper 내부는 P |
| error | `409` · `{error:string}` · `"任务已归档或正在删除"` | C for direct writeErr; error helper 내부는 P |
| error | `500` · `{error:string}` · `err.Error()` | C for direct writeErr; error helper 내부는 P |
| error | `503` · `{error:string}` · `"旁路服务不可用"` | C for direct writeErr; error helper 내부는 P |

응답 schema 판정: **P** · top-level key는 직접 추출했지만 derived/helper/opaque 또는 nested type이 남음.

### 목록·동시성·완료

- Pagination/filter/sort: `before`.
- Idempotency/concurrency: read/stream 요청; stream 재연결은 cursor 계약에 따름.
- Side effect/completion: history/current snapshot 조회가 응답 전에 끝남; `before`는 invalid/missing이면 0으로 처리.
- 직접 협력자/실행 단서: `p.Key`, `s.m.pg.SideHistory`, `s.m.pg.CurrentSideRequest`; direct `go` statement는 미발견. 이 목록은 helper 내부의 background launch나 transaction 경계를 펼치지 않으므로 그 부분은 P/U다.
- 종합 의미 판정: **P/U**; route/auth와 위에서 직접 확인한 branch·shape는 C지만, 모든 helper·derived response·중첩 DTO·외부 효과까지 완결됐다는 뜻은 아니다.

<a id="schema-post-api-tasks-id-chat-side-questions"></a>
## `POST /api/tasks/{id}/chat/side-questions`

구현: `handleSideQuestions` · `server/side_questions.go` · [정확한 handler 근거](evidence:handler-post-api-tasks-id-chat-side-questions)

### 요청

| 위치/field | wire type | 필수·null | 기본/검증 |
|---|---|---|---|
| path `id` | `string` | 필수 | handler의 parse/range validation; 미해석 helper는 P |
| auth credential | Bearer header / `artex_token` cookie / query `token` | 세 위치 중 하나에 유효 JWT 필수 | `extractToken` 우선순위: header→cookie→query; query fallback은 SSE로 제한되지 않음 |
| body `question` | `string` (`string`) | 필수(직접 거부 branch 확인); null/absent 구별 안 됨 | `trim 후 1–4000자가 아니면 400 후 return` |
| body `client_request_id` | `string` (`string`) | 필수(직접 거부 branch 확인); null/absent 구별 안 됨 | `validWorkerMessageRequestID 실패면 400 후 return` |

요청 body 상한: **32 KiB** (`32768` bytes) · source `server/side_questions.go::handleSideQuestions POST branch` · 초과/decoder 경로 `400 "bad json"`.

요청 판정: **C/P** · unknown field 거부 미설정.
직접 validation: `in.Question == "" \|\| len([]rune(in.Question)) > 4000 \|\| !validWorkerMessageRequestID(in.ClientID)` · `existing.Question != in.Question`

### 응답·오류

| 종류 | status/body | 해석 상태 |
|---|---|---|
| success/variant | `200` · `existing` | P/U · `derived variable` |
| success/variant | `202` · `e` | P/U · `derived variable` |
| error | `400` · `{error:string}` · `"bad id"` / `"bad json"` / `"问题须为 1–4000 字符，并提供有效请求 ID"` / `err.Error()` | C for direct writeErr; error helper 내부는 P |
| error | `401` · `{error:string}` · `"未授权"` / `"token 无效或已过期"` | C for direct writeErr; error helper 내부는 P |
| error | `404` · `{error:string}` · `"task not found"` | C for direct writeErr; error helper 내부는 P |
| error | `409` · `{error:string}` · `"任务已归档或正在删除"` / `"任务正在归档或删除"` / `"同一请求 ID 不能用于不同问题"` / `"尚无上下文快照，请先运行主 Agent"` / `db.ErrSideBusy.Error()` | C for direct writeErr; error helper 내부는 P |
| error | `429` · `{error:string}` · `"旁路请求已达并发上限，请稍后重试"` | C for direct writeErr; error helper 내부는 P |
| error | `500` · `{error:string}` · `err.Error()` | C for direct writeErr; error helper 내부는 P |
| error | `503` · `{error:string}` · `"旁路服务不可用"` | C for direct writeErr; error helper 내부는 P |

Helper 오류 분기: `prepareChatMentionMessage` · `server/chat_mentions.go::Server.prepareChatMentionMessage` · 위 `400, 500` status는 helper control-flow를 정적으로 펼친 P이며 호출 전/후 DB 효과는 operation별로 확인한다.

응답 schema 판정: **P** · top-level key는 직접 추출했지만 derived/helper/opaque 또는 nested type이 남음.

### 목록·동시성·완료

- Pagination/filter/sort: 등록된 직접 query key 중 표준 paging/filter key 없음.
- Idempotency/concurrency: 같은 client_request_id+question은 기존 request를 200으로 반환; 같은 ID의 다른 question은 409.
- Side effect/completion: side-question session/request 시작; events/terminal request state가 완료 기준.
- 직접 협력자/실행 단서: `p.Key`, `json.NewDecoder`, `validWorkerMessageRequestID`, `s.engine.IsDeleting`, `s.m.pg.ExistingSideRequest`, `s.m.pg.SaveSideSnapshot`, `s.m.pg.StartSideRequest`; direct `go` statement는 발견. 이 목록은 helper 내부의 background launch나 transaction 경계를 펼치지 않으므로 그 부분은 P/U다.
- 종합 의미 판정: **P/U**; route/auth와 위에서 직접 확인한 branch·shape는 C지만, 모든 helper·derived response·중첩 DTO·외부 효과까지 완결됐다는 뜻은 아니다.

<a id="schema-delete-api-tasks-id-chat-side-questions"></a>
## `DELETE /api/tasks/{id}/chat/side-questions`

구현: `handleSideQuestions` · `server/side_questions.go` · [정확한 handler 근거](evidence:handler-delete-api-tasks-id-chat-side-questions)

### 요청

| 위치/field | wire type | 필수·null | 기본/검증 |
|---|---|---|---|
| path `id` | `string` | 필수 | handler의 parse/range validation; 미해석 helper는 P |
| auth credential | Bearer header / `artex_token` cookie / query `token` | 세 위치 중 하나에 유효 JWT 필수 | `extractToken` 우선순위: header→cookie→query; query fallback은 SSE로 제한되지 않음 |
| body | 없음(직접 decoder/form read 미발견) | 해당 없음 | 해당 없음 |

요청 판정: **C/P** · unknown field 거부 미설정.

### 응답·오류

| 종류 | status/body | 해석 상태 |
|---|---|---|
| success/variant | `200` · `cleared:boolean` | C · `static composite` |
| error | `400` · `{error:string}` · `"bad id"` | C for direct writeErr; error helper 내부는 P |
| error | `401` · `{error:string}` · `"未授权"` / `"token 无效或已过期"` | C for direct writeErr; error helper 내부는 P |
| error | `404` · `{error:string}` · `"task not found"` | C for direct writeErr; error helper 내부는 P |
| error | `409` · `{error:string}` · `"任务已归档或正在删除"` | C for direct writeErr; error helper 내부는 P |
| error | `500` · `{error:string}` · `err.Error()` | C for direct writeErr; error helper 내부는 P |
| error | `503` · `{error:string}` · `"旁路服务不可用"` | C for direct writeErr; error helper 내부는 P |

응답 schema 판정: **C** · 직접 scalar/static top-level key와 field type을 추출; nested 내부 schema는 별도 type 참조가 없으면 P.

### 목록·동시성·완료

- Pagination/filter/sort: 등록된 직접 query key 중 표준 paging/filter key 없음.
- Idempotency/concurrency: Idempotency-Key/If-Match 없음; 반복 시 응답/존재 검사가 달라질 수 있음.
- Side effect/completion: active run에 cancel을 요청하고 DB history clear 후 응답; run goroutine 종료까지 기다리지는 않음.
- 직접 협력자/실행 단서: `p.Key`, `run.cancel`, `s.m.pg.ClearSideHistory`; direct `go` statement는 미발견. 이 목록은 helper 내부의 background launch나 transaction 경계를 펼치지 않으므로 그 부분은 P/U다.
- 종합 의미 판정: **P/U**; route/auth와 위에서 직접 확인한 branch·shape는 C지만, 모든 helper·derived response·중첩 DTO·외부 효과까지 완결됐다는 뜻은 아니다.

<a id="schema-get-api-tasks-id-intents-iid-side-questions"></a>
## `GET /api/tasks/{id}/intents/{iid}/side-questions`

구현: `handleSideQuestions` · `server/side_questions.go` · [정확한 handler 근거](evidence:handler-get-api-tasks-id-intents-iid-side-questions)

### 요청

| 위치/field | wire type | 필수·null | 기본/검증 |
|---|---|---|---|
| path `id` | `string` | 필수 | handler의 parse/range validation; 미해석 helper는 P |
| path `iid` | `string` | 필수 | handler의 parse/range validation; 미해석 helper는 P |
| query `before` | `string` wire | 선택 | handler/helper 변환·default 미전개(P) |
| auth credential | Bearer header / `artex_token` cookie / query `token` | 세 위치 중 하나에 유효 JWT 필수 | `extractToken` 우선순위: header→cookie→query; query fallback은 SSE로 제한되지 않음 |
| body | 없음(직접 decoder/form read 미발견) | 해당 없음 | 해당 없음 |

요청 판정: **C/P** · unknown field 거부 미설정.

### 응답·오류

| 종류 | status/body | 해석 상태 |
|---|---|---|
| success/variant | `200` · `items:array[sidequestion.Exchange]`, `current:sidequestion.Exchange\|null`, `next_cursor:integer`, `snapshot:object\|null` | P/U · `static composite` |
| error | `400` · `{error:string}` · `"bad id"` / `"bad intent id"` | C for direct writeErr; error helper 내부는 P |
| error | `401` · `{error:string}` · `"未授权"` / `"token 无效或已过期"` | C for direct writeErr; error helper 내부는 P |
| error | `404` · `{error:string}` · `"task not found"` / `"intent not found"` | C for direct writeErr; error helper 내부는 P |
| error | `409` · `{error:string}` · `"任务已归档或正在删除"` / `"Worker 已删除"` | C for direct writeErr; error helper 내부는 P |
| error | `500` · `{error:string}` · `err.Error()` | C for direct writeErr; error helper 내부는 P |
| error | `503` · `{error:string}` · `"旁路服务不可用"` | C for direct writeErr; error helper 내부는 P |

응답 schema 판정: **P** · top-level key는 직접 추출했지만 derived/helper/opaque 또는 nested type이 남음.

### 목록·동시성·완료

- Pagination/filter/sort: `before`.
- Idempotency/concurrency: read/stream 요청; stream 재연결은 cursor 계약에 따름.
- Side effect/completion: history/current snapshot 조회가 응답 전에 끝남; `before`는 invalid/missing이면 0으로 처리.
- 직접 협력자/실행 단서: `p.Key`, `s.m.pg.SideHistory`, `s.m.pg.CurrentSideRequest`; direct `go` statement는 미발견. 이 목록은 helper 내부의 background launch나 transaction 경계를 펼치지 않으므로 그 부분은 P/U다.
- 종합 의미 판정: **P/U**; route/auth와 위에서 직접 확인한 branch·shape는 C지만, 모든 helper·derived response·중첩 DTO·외부 효과까지 완결됐다는 뜻은 아니다.

<a id="schema-post-api-tasks-id-intents-iid-side-questions"></a>
## `POST /api/tasks/{id}/intents/{iid}/side-questions`

구현: `handleSideQuestions` · `server/side_questions.go` · [정확한 handler 근거](evidence:handler-post-api-tasks-id-intents-iid-side-questions)

### 요청

| 위치/field | wire type | 필수·null | 기본/검증 |
|---|---|---|---|
| path `id` | `string` | 필수 | handler의 parse/range validation; 미해석 helper는 P |
| path `iid` | `string` | 필수 | handler의 parse/range validation; 미해석 helper는 P |
| auth credential | Bearer header / `artex_token` cookie / query `token` | 세 위치 중 하나에 유효 JWT 필수 | `extractToken` 우선순위: header→cookie→query; query fallback은 SSE로 제한되지 않음 |
| body `question` | `string` (`string`) | 필수(직접 거부 branch 확인); null/absent 구별 안 됨 | `trim 후 1–4000자가 아니면 400 후 return` |
| body `client_request_id` | `string` (`string`) | 필수(직접 거부 branch 확인); null/absent 구별 안 됨 | `validWorkerMessageRequestID 실패면 400 후 return` |

요청 body 상한: **32 KiB** (`32768` bytes) · source `server/side_questions.go::handleSideQuestions POST branch` · 초과/decoder 경로 `400 "bad json"`.

요청 판정: **C/P** · unknown field 거부 미설정.
직접 validation: `in.Question == "" \|\| len([]rune(in.Question)) > 4000 \|\| !validWorkerMessageRequestID(in.ClientID)` · `existing.Question != in.Question`

### 응답·오류

| 종류 | status/body | 해석 상태 |
|---|---|---|
| success/variant | `200` · `existing` | P/U · `derived variable` |
| success/variant | `202` · `e` | P/U · `derived variable` |
| error | `400` · `{error:string}` · `"bad id"` / `"bad intent id"` / `"bad json"` / `"问题须为 1–4000 字符，并提供有效请求 ID"` / `err.Error()` | C for direct writeErr; error helper 내부는 P |
| error | `401` · `{error:string}` · `"未授权"` / `"token 无效或已过期"` | C for direct writeErr; error helper 내부는 P |
| error | `404` · `{error:string}` · `"task not found"` / `"intent not found"` | C for direct writeErr; error helper 내부는 P |
| error | `409` · `{error:string}` · `"任务已归档或正在删除"` / `"Worker 已删除"` / `"任务正在归档或删除"` / `"同一请求 ID 不能用于不同问题"` / `"尚无上下文快照，请先运行主 Agent"` / `db.ErrSideBusy.Error()` | C for direct writeErr; error helper 내부는 P |
| error | `429` · `{error:string}` · `"旁路请求已达并发上限，请稍后重试"` | C for direct writeErr; error helper 내부는 P |
| error | `500` · `{error:string}` · `err.Error()` | C for direct writeErr; error helper 내부는 P |
| error | `503` · `{error:string}` · `"旁路服务不可用"` | C for direct writeErr; error helper 내부는 P |

Helper 오류 분기: `prepareChatMentionMessage` · `server/chat_mentions.go::Server.prepareChatMentionMessage` · 위 `400, 500` status는 helper control-flow를 정적으로 펼친 P이며 호출 전/후 DB 효과는 operation별로 확인한다.

응답 schema 판정: **P** · top-level key는 직접 추출했지만 derived/helper/opaque 또는 nested type이 남음.

### 목록·동시성·완료

- Pagination/filter/sort: 등록된 직접 query key 중 표준 paging/filter key 없음.
- Idempotency/concurrency: 같은 client_request_id+question은 기존 request를 200으로 반환; 같은 ID의 다른 question은 409.
- Side effect/completion: side-question session/request 시작; events/terminal request state가 완료 기준.
- 직접 협력자/실행 단서: `p.Key`, `json.NewDecoder`, `validWorkerMessageRequestID`, `s.engine.IsDeleting`, `s.m.pg.ExistingSideRequest`, `s.m.pg.SaveSideSnapshot`, `s.m.pg.StartSideRequest`; direct `go` statement는 발견. 이 목록은 helper 내부의 background launch나 transaction 경계를 펼치지 않으므로 그 부분은 P/U다.
- 종합 의미 판정: **P/U**; route/auth와 위에서 직접 확인한 branch·shape는 C지만, 모든 helper·derived response·중첩 DTO·외부 효과까지 완결됐다는 뜻은 아니다.

<a id="schema-delete-api-tasks-id-intents-iid-side-questions"></a>
## `DELETE /api/tasks/{id}/intents/{iid}/side-questions`

구현: `handleSideQuestions` · `server/side_questions.go` · [정확한 handler 근거](evidence:handler-delete-api-tasks-id-intents-iid-side-questions)

### 요청

| 위치/field | wire type | 필수·null | 기본/검증 |
|---|---|---|---|
| path `id` | `string` | 필수 | handler의 parse/range validation; 미해석 helper는 P |
| path `iid` | `string` | 필수 | handler의 parse/range validation; 미해석 helper는 P |
| auth credential | Bearer header / `artex_token` cookie / query `token` | 세 위치 중 하나에 유효 JWT 필수 | `extractToken` 우선순위: header→cookie→query; query fallback은 SSE로 제한되지 않음 |
| body | 없음(직접 decoder/form read 미발견) | 해당 없음 | 해당 없음 |

요청 판정: **C/P** · unknown field 거부 미설정.

### 응답·오류

| 종류 | status/body | 해석 상태 |
|---|---|---|
| success/variant | `200` · `cleared:boolean` | C · `static composite` |
| error | `400` · `{error:string}` · `"bad id"` / `"bad intent id"` | C for direct writeErr; error helper 내부는 P |
| error | `401` · `{error:string}` · `"未授权"` / `"token 无效或已过期"` | C for direct writeErr; error helper 내부는 P |
| error | `404` · `{error:string}` · `"task not found"` / `"intent not found"` | C for direct writeErr; error helper 내부는 P |
| error | `409` · `{error:string}` · `"任务已归档或正在删除"` / `"Worker 已删除"` | C for direct writeErr; error helper 내부는 P |
| error | `500` · `{error:string}` · `err.Error()` | C for direct writeErr; error helper 내부는 P |
| error | `503` · `{error:string}` · `"旁路服务不可用"` | C for direct writeErr; error helper 내부는 P |

응답 schema 판정: **C** · 직접 scalar/static top-level key와 field type을 추출; nested 내부 schema는 별도 type 참조가 없으면 P.

### 목록·동시성·완료

- Pagination/filter/sort: 등록된 직접 query key 중 표준 paging/filter key 없음.
- Idempotency/concurrency: Idempotency-Key/If-Match 없음; 반복 시 응답/존재 검사가 달라질 수 있음.
- Side effect/completion: active run에 cancel을 요청하고 DB history clear 후 응답; run goroutine 종료까지 기다리지는 않음.
- 직접 협력자/실행 단서: `p.Key`, `run.cancel`, `s.m.pg.ClearSideHistory`; direct `go` statement는 미발견. 이 목록은 helper 내부의 background launch나 transaction 경계를 펼치지 않으므로 그 부분은 P/U다.
- 종합 의미 판정: **P/U**; route/auth와 위에서 직접 확인한 branch·shape는 C지만, 모든 helper·derived response·중첩 DTO·외부 효과까지 완결됐다는 뜻은 아니다.

<a id="schema-get-api-side-questions-requestid-events"></a>
## `GET /api/side-questions/{requestID}/events`

구현: `sideEvents` · `server/side_questions.go` · [정확한 handler 근거](evidence:handler-get-api-side-questions-requestid-events)

### 요청

| 위치/field | wire type | 필수·null | 기본/검증 |
|---|---|---|---|
| path `requestID` | `string` | 필수 | handler의 parse/range validation; 미해석 helper는 P |
| auth credential | Bearer header / `artex_token` cookie / query `token` | 세 위치 중 하나에 유효 JWT 필수 | `extractToken` 우선순위: header→cookie→query; query fallback은 SSE로 제한되지 않음 |
| body | 없음(직접 decoder/form read 미발견) | 해당 없음 | 해당 없음 |

요청 판정: **C/P** · unknown field 거부 미설정.
직접 validation: `!e.Running()` · `!ok` · `e == nil` · `e.Sequence > seq` · `err != nil` · `first == nil`

### 응답·오류

| 종류 | status/body | 해석 상태 |
|---|---|---|
| success/variant | `200` · `text/event-stream; Cache-Control:no-cache; X-Accel-Buffering:no; no explicit Connection header; \`event: snapshot\` + sequence ID + SideRequest JSON; cleared event; 15 s keepalive` | P/U · `SSE stream` |
| error | `401` · `{error:string}` · `"未授权"` / `"token 无效或已过期"` | C for direct writeErr; error helper 내부는 P |
| error | `404` · `{error:string}` · `"side question not found"` | C for direct writeErr; error helper 내부는 P |
| error | `500` · `{error:string}` · `"streaming unavailable"` / `err.Error()` | C for direct writeErr; error helper 내부는 P |

응답 schema 판정: **P** · top-level key는 직접 추출했지만 derived/helper/opaque 또는 nested type이 남음.

### 목록·동시성·완료

- Pagination/filter/sort: 등록된 직접 query key 중 표준 paging/filter key 없음.
- Idempotency/concurrency: read/stream 요청; stream 재연결은 cursor 계약에 따름.
- Side effect/completion: SSE 연결 수립; event lifecycle은 stream별 cursor/메모리 상태를 확인.
- 직접 협력자/실행 단서: `e.Running`, `err.Error`, `flusher.Flush`, `fmt.Fprint`, `fmt.Fprintf`, `heartbeat.Stop`, `r.Context`, `r.Context().Done`, `r.PathValue`, `s.ctx.Done`, `s.m.pg.SideRequest`, `tick.Stop`, `w.Header`, `w.Header().Set`; direct `go` statement는 미발견. 이 목록은 helper 내부의 background launch나 transaction 경계를 펼치지 않으므로 그 부분은 P/U다.
- 종합 의미 판정: **P/U**; route/auth와 위에서 직접 확인한 branch·shape는 C지만, 모든 helper·derived response·중첩 DTO·외부 효과까지 완결됐다는 뜻은 아니다.

<a id="schema-post-api-side-questions-requestid-cancel"></a>
## `POST /api/side-questions/{requestID}/cancel`

구현: `cancelSideRequest` · `server/side_questions.go` · [정확한 handler 근거](evidence:handler-post-api-side-questions-requestid-cancel)

### 요청

| 위치/field | wire type | 필수·null | 기본/검증 |
|---|---|---|---|
| path `requestID` | `string` | 필수 | handler의 parse/range validation; 미해석 helper는 P |
| auth credential | Bearer header / `artex_token` cookie / query `token` | 세 위치 중 하나에 유효 JWT 필수 | `extractToken` 우선순위: header→cookie→query; query fallback은 SSE로 제한되지 않음 |
| body | 없음(직접 decoder/form read 미발견) | 해당 없음 | 해당 없음 |

요청 판정: **C/P** · unknown field 거부 미설정.
직접 validation: `e == nil` · `err != nil` · `ok`

### 응답·오류

| 종류 | status/body | 해석 상태 |
|---|---|---|
| success/variant | `200` · `cancelled:boolean` | C · `static composite` |
| error | `401` · `{error:string}` · `"未授权"` / `"token 无效或已过期"` | C for direct writeErr; error helper 내부는 P |
| error | `404` · `{error:string}` · `"side question not found"` | C for direct writeErr; error helper 내부는 P |
| error | `500` · `{error:string}` · `err.Error()` | C for direct writeErr; error helper 내부는 P |

응답 schema 판정: **C** · 직접 scalar/static top-level key와 field type을 추출; nested 내부 schema는 별도 type 참조가 없으면 P.

### 목록·동시성·완료

- Pagination/filter/sort: 등록된 직접 query key 중 표준 paging/filter key 없음.
- Idempotency/concurrency: Idempotency-Key 없음; unique constraint 또는 상태 check가 있는 경우만 중복을 제한(P).
- Side effect/completion: side-question session/request 시작; events/terminal request state가 완료 기준.
- 직접 협력자/실행 단서: `err.Error`, `r.Context`, `r.PathValue`, `run.cancel`, `s.m.pg.SideRequest`, `s.side.mu.Lock`, `s.side.mu.Unlock`; direct `go` statement는 미발견. 이 목록은 helper 내부의 background launch나 transaction 경계를 펼치지 않으므로 그 부분은 P/U다.
- 종합 의미 판정: **P/U**; route/auth와 위에서 직접 확인한 branch·shape는 C지만, 모든 helper·derived response·중첩 DTO·외부 효과까지 완결됐다는 뜻은 아니다.

