<a id="db-semantic-catalog"></a>
# PostgreSQL 필드·접근 의미 카탈로그

51개 PostgreSQL 객체의 물리 필드와 정적 SQL 소비를 한곳에서 대조한다. Core 49개는 `db/schema.sql`, `llm_records`·`llm_usage`는 별도 `Ensure*Table` DDL에서 왔다. 이 페이지의 **C/A/P/U**는 각각 source로 확인(C), 알려진 shape까지 확인(A), production shape는 알지만 DB가 더 넓거나 한계가 남음(P), 의미를 확인하지 못함(U)을 뜻한다. 실제 운영 DB의 row·role·query plan·retention은 관찰하지 않았다. [core schema](evidence:schema-core) [runtime ledgers](evidence:schema-ledgers)

## 판정과 분모

| 축 | 결과 | 한계 |
|---|---:|---|
| Core 물리 schema | 49 table / 520 column / 97 명시 index, function 4 / trigger 22 (C) | 고정 source DDL 기준; 운영 drift 미관찰 |
| 보조 runtime ledger | 2 table / 31 column / 5 명시 index (C 선언, A 가용성) | 두 `Ensure*Table` 실패는 log 후 계속 |
| 전체 PostgreSQL 선언 | 51 table / 551 column / 102 명시 index | PK/UNIQUE 내부 index는 명시 index 분모 밖 |
| field type/null/default/FK | 551/551 정적 추출(C) | 동적 SQL·운영 migration 적용은 미확인 |
| generated/default 선언 | explicit generated column 0; `BIGSERIAL` 37개는 implicit sequence-backed default+not-null | generated expression과 sequence default를 혼동하지 않음; upgraded catalog drift는 U |
| business/encoding 의미 | 444 C / 93 A / 14 P / 0 U = 551 | production allowlist보다 DB writer/column이 넓은 10개 등은 P |
| reader/writer | production Go의 static SQL, 정적으로 평가한 string expression, runtime template, 닫힌 identifier allowlist를 위치·symbol 단위로 색인(A) | 임의 동적 identifier·실제 call reachability·완성 runtime SQL은 U |
| role/retention | U | DDL에 GRANT/RLS/TTL/partition 없음; deployment role과 운영 보존 기간 미관찰 |

## 공통 해석 규칙

- `nullable=예`는 SQL `NULL` 허용만 뜻한다. JSON의 누락/null/zero-value 구별은 각 request/scan 코드가 정하며 DB가 보장하지 않는다.
- `TIMESTAMPTZ`는 절대 시각을 보존하지만 표시 timezone은 client/session 설정에 달렸다. `BIGINT[]`·`TEXT[]`·`JSONB[]`는 원소 수나 내부 원소 schema를 DB가 제한하지 않는 경우가 많다.
- credential·request/response·evidence·prompt·snapshot을 민감 가능 또는 비밀/원문으로 표시했다. 이는 정적 분류이며 실제 값의 redaction/encryption을 보장하지 않는다.
- schema에서 `GRANT`, `REVOKE`, RLS policy를 찾지 못했다. application은 configured PostgreSQL credential을 공유하므로 최소권한과 운영 role 분리는 배포 설정에서 검증해야 한다(U).
- 아래 reader/writer는 production Go의 literal SQL, 정적으로 평가한 string expression, 명시적으로 해석한 닫힌 dynamic table allowlist를 exact access matrix에서 집계한 파일이다. 임의 runtime identifier와 실제 call reachability는 U이며, trigger/stored-function 효과는 별도 function/trigger 계약과 함께 본다.
- Field 표의 `column-level 제약 없음`은 해당 column 선언 자체에 제약이 없다는 뜻이다. Composite PK/UNIQUE와 cross-column CHECK는 각 affected field에서 [table-level constraint matrix](table-constraints.md#db-table-constraints)를 함께 연결한다.

<a id="semantic-companies"></a>
## `companies`

자산과 scope를 묶는 회사 식별자와 표시 정보를 저장한다.

**정적 접근** · reader: `db/chat_mentions.go`, `db/companies.go`, `db/finding_assets.go`, `db/task_archives_restore.go`, `db/task_assets_context.go`, `db/task_scope.go`, `db/tasks.go` · writer: `db/companies.go`

**transaction·retention** · FK delete 동작이 없거나 앱이 관계를 관리한다; schema에 TTL/partition 자동 보존 정책 없음; handler/cleanup/archive 삭제 경로와 운영 보존기간은 별도 확인(U)

**정확한 production SQL** · [symbol·verb·query access matrix](access-matrix.md#access-companies)

| 필드 | 타입 | null | default | 의미·encoding | 민감도 | 제약 | 판정 |
|---|---|---:|---|---|---|---|---|
| `id` | `BIGSERIAL` | 아니오 | `nextval(<implicit sequence>)` | row 식별자; BIGSERIAL | 일반(C) | PRIMARY KEY | C |
| `name` | `TEXT` | 아니오 | `—` | 표시 이름; UTF-8 text; 길이 제한 없음 | 미확정(U) | column-level 제약 없음 | C |
| `nkey` | `TEXT` | 아니오 | `—` | 정규화한 unique key; UTF-8 text; 길이 제한 없음 | 미확정(U) | UNIQUE | C |
| `logo` | `TEXT` | 예 | `—` | 회사 아바타에 쓰는 원격 이미지 URL이며, 비어 있으면 UI가 회사명 첫 글자로 대체한다. [의미 근거](evidence:db-field-companies-logo) | 미확정(U) | column-level 제약 없음 | C |
| `created_at` | `TIMESTAMPTZ` | 아니오 | `now()` | 생성 시각; 절대 시각; DB `now()` 또는 앱 입력 | 일반(C) | column-level 제약 없음 | C |
| `updated_at` | `TIMESTAMPTZ` | 아니오 | `now()` | 마지막 변경 시각; 절대 시각; DB `now()` 또는 앱 입력 | 일반(C) | column-level 제약 없음 | C |

**명시 index와 비용** · `idx_companies_nkey` — 해당 equality/join/filter/정렬의 planner candidate; 실제 query plan·효과 미관찰

<a id="semantic-assets"></a>
## `assets`

정규화한 도메인·IP·앱·서비스·endpoint와 관찰 속성, 회사·task provenance를 저장한다.

**정적 접근** · reader: `db/asset_dsl.go`, `db/assets.go`, `db/chat_mentions.go`, `db/companies.go`, `db/finding_assets.go`, `db/finding_retests.go`, `db/findings.go`, `db/notification.go`, `db/task_archives.go`, `db/task_archives_restore.go`, `db/task_assets.go`, `db/task_assets_context.go`, `db/task_scope.go`, `db/tasks.go` · writer: `db/assets.go`, `db/companies.go`, `db/task_archives_restore.go`, `db/task_assets.go`, `db/tasks.go`

**transaction·retention** · FK delete: SET NULL; schema에 TTL/partition 자동 보존 정책 없음; handler/cleanup/archive 삭제 경로와 운영 보존기간은 별도 확인(U)

**정확한 production SQL** · [symbol·verb·query access matrix](access-matrix.md#access-assets)

| 필드 | 타입 | null | default | 의미·encoding | 민감도 | 제약 | 판정 |
|---|---|---:|---|---|---|---|---|
| `id` | `BIGSERIAL` | 아니오 | `nextval(<implicit sequence>)` | row 식별자; BIGSERIAL | 일반(C) | PRIMARY KEY | C |
| `type` | `TEXT` | 아니오 | `—` | 객체 종류; UTF-8 text; 길이 제한 없음 | 미확정(U) | CHECK (type IN ( 'root_domain','ip','subdomain','app','service','endpoint' )) | C |
| `company_id` | `BIGINT` | 예 | `—` | company row 식별자 또는 논리 참조; BIGINT | 일반(C) | FK companies(id) ON DELETE SET NULL | A |
| `company_source` | `TEXT` | 아니오 | `'explicit'` | company_id 귀속의 출처/우선순위 표지: explicit은 직접 지정, scope는 회사 scope 자동 매칭이며 explicit 귀속을 upsert가 보존한다. [의미 근거](evidence:db-field-assets-company-source) | 미확정(U) | CHECK (company_source IN ('explicit','scope')) | C |
| `task_ids` | `BIGINT[]` | 아니오 | `'{}'` | 이 asset을 소유·참조하는 task registry id들의 비정규화 배열이다. task 생성/자산 upsert가 원소를 합치고 task 삭제가 해당 id를 제거한다. 한계: 원소에 FK가 없어 PostgreSQL은 존재하지 않는 task id도 허용한다. [의미 근거](evidence:db-field-assets-task-ids) | 일반(C) | column-level 제약 없음 | C |
| `domain` | `TEXT` | 예 | `—` | domain 값; UTF-8 text; 길이 제한 없음 | 민감 가능(A) | column-level 제약 없음 | C |
| `root_domain` | `TEXT` | 예 | `—` | 등록 가능한 root domain; UTF-8 text; 길이 제한 없음 | 민감 가능(A) | column-level 제약 없음 | C |
| `ip` | `TEXT` | 예 | `—` | IP text; UTF-8 text; 길이 제한 없음 | 민감 가능(A) | column-level 제약 없음 | C |
| `c_segment` | `CIDR` | 예 | `—` | C-segment CIDR; PostgreSQL network type | 민감 가능(A) | column-level 제약 없음 | C |
| `port` | `INTEGER` | 예 | `—` | network port; INTEGER | 일반(C) | CHECK (port BETWEEN 1 AND 65535) | C |
| `icp` | `TEXT` | 예 | `—` | root-domain/subdomain 자산에 관찰된 ICP 등록·备案 식별 문자열이다. [의미 근거](evidence:db-field-assets-icp) | 미확정(U) | column-level 제약 없음 | C |
| `bound_domains` | `TEXT[]` | 아니오 | `'{}'` | 해당 IP에 해석/연결된 도메인 집합이며 upsert·append 시 중복 제거해 합친다; PostgreSQL TEXT[] 배열; 원소 수 제한 없음 [의미 근거](evidence:db-field-assets-bound-domains) | 미확정(U) | column-level 제약 없음 | C |
| `open_ports` | `JSONB[]` | 아니오 | `'{}'` | open ports 값; 더 좁은 도메인 의미는 소비 코드에 분산됨; port/service 관찰 객체 배열; 원소의 중앙 SQL 제약은 없음 | 미확정(U) | column-level 제약 없음 | A |
| `record_type` | `TEXT` | 예 | `—` | subdomain DNS 레코드 종류이며 A/AAAA일 때 record_value를 IP 자산·바인딩으로 파생한다. [의미 근거](evidence:db-field-assets-record-type) | 미확정(U) | column-level 제약 없음 | C |
| `record_value` | `TEXT[]` | 예 | `—` | DNS 레코드 응답 값 목록이며 A/AAAA 값은 IP 후보로 파싱한다; PostgreSQL TEXT[] 배열; 원소 수 제한 없음 [의미 근거](evidence:db-field-assets-record-value) | 미확정(U) | column-level 제약 없음 | C |
| `bundle_id` | `TEXT` | 예 | `—` | 모바일 app package/bundle identifier이며 DB row id가 아니다.; UTF-8 text; 길이 제한 없음 [의미 근거](evidence:db-field-assets-bundle-id) | 일반(C) | column-level 제약 없음 | C |
| `app_name` | `TEXT` | 예 | `—` | 앱 표시 이름이며 bundle_id가 없을 때 app 자산 중복 제거 키가 된다. [의미 근거](evidence:db-field-assets-app-name) | 미확정(U) | column-level 제약 없음 | C |
| `category` | `TEXT` | 예 | `—` | 앱 분류/카테고리 문자열이다. [의미 근거](evidence:db-field-assets-category) | 미확정(U) | column-level 제약 없음 | C |
| `app_description` | `TEXT` | 예 | `—` | 앱 자산 설명 문자열이다. [의미 근거](evidence:db-field-assets-app-description) | 미확정(U) | column-level 제약 없음 | C |
| `app_icp` | `TEXT` | 예 | `—` | 앱에 연결해 저장하는 ICP 등록·备案 식별 문자열이다. [의미 근거](evidence:db-field-assets-app-icp) | 미확정(U) | column-level 제약 없음 | C |
| `url` | `TEXT` | 예 | `—` | 대상 또는 endpoint URL; UTF-8 text; 길이 제한 없음 | 민감 가능(A) | column-level 제약 없음 | C |
| `service_type` | `TEXT` | 예 | `—` | service 자산의 HTTP 계열/기타 구분자이며 현재 writer는 'http' 또는 'other'를 저장한다. [의미 근거](evidence:db-field-assets-service-type) | 미확정(U) | CHECK (service_type IN ('http','other')) | C |
| `service_name` | `TEXT` | 예 | `—` | 서비스 이름/프로토콜 표지다. HTTP URL에서는 scheme으로 유도하고 기타 서비스는 입력을 소문자 정규화한다. [의미 근거](evidence:db-field-assets-service-name) | 미확정(U) | column-level 제약 없음 | C |
| `favicon_mmh3` | `TEXT` | 예 | `—` | HTTP 서비스 favicon의 MurmurHash3 지문을 문자열로 저장한다. [의미 근거](evidence:db-field-assets-favicon-mmh3) | 미확정(U) | column-level 제약 없음 | C |
| `status_code` | `INTEGER` | 예 | `—` | 관찰 HTTP status; INTEGER | 일반(C) | column-level 제약 없음 | C |
| `content_length` | `BIGINT` | 예 | `—` | 관찰 content length; BIGINT | 일반(C) | column-level 제약 없음 | C |
| `page_title` | `TEXT` | 예 | `—` | HTTP 서비스 응답에서 관찰한 페이지 제목이다. [의미 근거](evidence:db-field-assets-page-title) | 미확정(U) | column-level 제약 없음 | C |
| `technologies` | `TEXT[]` | 아니오 | `'{}'` | HTTP 서비스에서 식별한 기술 스택 문자열 집합이며 upsert 시 중복 제거해 추가 병합한다; PostgreSQL TEXT[] 배열; 원소 수 제한 없음 [의미 근거](evidence:db-field-assets-technologies) | 미확정(U) | column-level 제약 없음 | C |
| `auth` | `JSONB[]` | 아니오 | `'{}'` | `AuthItem` 배열; type/username/password/token/description을 저장하며 실제 writer/test도 인증 원문을 지속화함 [근거](evidence:sensitivity-assets-auth) | 비밀/원문(C) | column-level 제약 없음 | C |
| `method` | `TEXT` | 예 | `—` | HTTP method; UTF-8 text; 길이 제한 없음 | 미확정(U) | column-level 제약 없음 | C |
| `params` | `JSONB[]` | 아니오 | `'{}'` | endpoint `ParamItem` 배열(location/name/value/type); request parameter value에 secret/PII가 포함될 수 있음 [근거](evidence:sensitivity-assets-params) | 민감 가능(A) | column-level 제약 없음 | A |
| `extra` | `JSONB` | 아니오 | `'{}'` | extra 값; 더 좁은 도메인 의미는 소비 코드에 분산됨; asset 종류별 확장 속성; 중앙 schema 없음 | 미확정(U) | column-level 제약 없음 | A |
| `created_at` | `TIMESTAMPTZ` | 아니오 | `now()` | 생성 시각; 절대 시각; DB `now()` 또는 앱 입력 | 일반(C) | column-level 제약 없음 | C |
| `last_seen` | `TIMESTAMPTZ` | 아니오 | `now()` | 마지막 관찰 시각; 절대 시각; DB `now()` 또는 앱 입력 | 일반(C) | column-level 제약 없음 | C |
| `updated_at` | `TIMESTAMPTZ` | 아니오 | `now()` | 마지막 변경 시각; 절대 시각; DB `now()` 또는 앱 입력 | 일반(C) | column-level 제약 없음 | C |

**명시 index와 비용** · `uq_av2_root_domain` — asset identity invariant; `Upsert*` writer가 `ON CONFLICT … DO UPDATE`로 관찰값·provenance를 merge; partial predicate 밖 row에는 미적용<br>`uq_av2_ip` — asset identity invariant; `Upsert*` writer가 `ON CONFLICT … DO UPDATE`로 관찰값·provenance를 merge; partial predicate 밖 row에는 미적용<br>`uq_av2_subdomain` — asset identity invariant; `Upsert*` writer가 `ON CONFLICT … DO UPDATE`로 관찰값·provenance를 merge; partial predicate 밖 row에는 미적용<br>`uq_av2_app_bundle` — asset identity invariant; `Upsert*` writer가 `ON CONFLICT … DO UPDATE`로 관찰값·provenance를 merge; partial predicate 밖 row에는 미적용<br>`uq_av2_app_name` — asset identity invariant; `Upsert*` writer가 `ON CONFLICT … DO UPDATE`로 관찰값·provenance를 merge; partial predicate 밖 row에는 미적용<br>`uq_av2_service_http` — asset identity invariant; `Upsert*` writer가 `ON CONFLICT … DO UPDATE`로 관찰값·provenance를 merge; partial predicate 밖 row에는 미적용<br>`uq_av2_service_other` — asset identity invariant; `Upsert*` writer가 `ON CONFLICT … DO UPDATE`로 관찰값·provenance를 merge; partial predicate 밖 row에는 미적용<br>`uq_av2_endpoint` — asset identity invariant; `Upsert*` writer가 `ON CONFLICT … DO UPDATE`로 관찰값·provenance를 merge; partial predicate 밖 row에는 미적용<br>`idx_av2_company` — 해당 equality/join/filter/정렬의 planner candidate; 실제 query plan·효과 미관찰; partial predicate 밖 row에는 미적용<br>`idx_av2_company_type` — 해당 equality/join/filter/정렬의 planner candidate; 실제 query plan·효과 미관찰; partial predicate 밖 row에는 미적용<br>`idx_av2_task_ids` — 배열/JSON 포함 조회의 planner candidate; 실제 query plan 미관찰; write amplification·index 크기 비용<br>`idx_av2_domain` — 해당 equality/join/filter/정렬의 planner candidate; 실제 query plan·효과 미관찰; partial predicate 밖 row에는 미적용<br>`idx_av2_root_domain` — 해당 equality/join/filter/정렬의 planner candidate; 실제 query plan·효과 미관찰; partial predicate 밖 row에는 미적용<br>`idx_av2_ip` — 해당 equality/join/filter/정렬의 planner candidate; 실제 query plan·효과 미관찰; partial predicate 밖 row에는 미적용<br>`idx_av2_c_segment` — CIDR containment/overlap 조회의 planner candidate; 실제 query plan 미관찰; GiST 유지 비용; partial predicate 밖 row에는 미적용<br>`idx_av2_technologies` — 배열/JSON 포함 조회의 planner candidate; 실제 query plan 미관찰; write amplification·index 크기 비용; partial predicate 밖 row에는 미적용<br>`idx_av2_bound_domains` — 배열/JSON 포함 조회의 planner candidate; 실제 query plan 미관찰; write amplification·index 크기 비용; partial predicate 밖 row에는 미적용<br>`idx_av2_open_ports` — 배열/JSON 포함 조회의 planner candidate; 실제 query plan 미관찰; write amplification·index 크기 비용; partial predicate 밖 row에는 미적용<br>`idx_av2_last_seen` — 최근 순서 조회의 planner candidate; 실제 query plan·효과 미관찰<br>`idx_av2_type_seen` — 최근 순서 조회의 planner candidate; 실제 query plan·효과 미관찰

<a id="semantic-company-scope"></a>
## `company_scope`

회사별 허용 domain/IP/CIDR/ICP/keyword scope 항목과 원문·판단 이유를 저장한다.

**정적 접근** · reader: `db/companies.go` · writer: `db/companies.go`

**transaction·retention** · FK delete: CASCADE; schema에 TTL/partition 자동 보존 정책 없음; handler/cleanup/archive 삭제 경로와 운영 보존기간은 별도 확인(U)

**정확한 production SQL** · [symbol·verb·query access matrix](access-matrix.md#access-company-scope)

| 필드 | 타입 | null | default | 의미·encoding | 민감도 | 제약 | 판정 |
|---|---|---:|---|---|---|---|---|
| `id` | `BIGSERIAL` | 아니오 | `nextval(<implicit sequence>)` | row 식별자; BIGSERIAL | 일반(C) | PRIMARY KEY | C |
| `company_id` | `BIGINT` | 아니오 | `—` | company row 식별자 또는 논리 참조; BIGINT | 일반(C) | FK companies(id) ON DELETE CASCADE <br>[table-level: UNIQUE(company_id,domain/net)](table-constraints.md#constraint-company-scope)| A |
| `kind` | `TEXT` | 아니오 | `—` | variant 또는 event 종류; UTF-8 text; 길이 제한 없음 | 미확정(U) | CHECK (kind IN ('domain','ip','cidr','icp','keyword')) <br>[table-level: kind↔payload CHECK](table-constraints.md#constraint-company-scope)| C |
| `domain` | `TEXT` | 예 | `—` | domain 값; UTF-8 text; 길이 제한 없음 | 민감 가능(A) | column-level 제약 없음 <br>[table-level: UNIQUE(company_id,domain)+payload CHECK](table-constraints.md#constraint-company-scope)| C |
| `net` | `CIDR` | 예 | `—` | IP/CIDR 값; PostgreSQL network type | 민감 가능(A) | column-level 제약 없음 <br>[table-level: UNIQUE(company_id,net)+payload CHECK](table-constraints.md#constraint-company-scope)| C |
| `value` | `TEXT` | 예 | `—` | key가 해석하는 값; UTF-8 text; 길이 제한 없음 | 민감 가능(A) | column-level 제약 없음 <br>[table-level: kind↔payload CHECK](table-constraints.md#constraint-company-scope)| C |
| `raw` | `TEXT` | 아니오 | `—` | 입력 원문; UTF-8 text; 길이 제한 없음 | 민감 가능(A) | column-level 제약 없음 | C |
| `reason` | `TEXT` | 예 | `—` | 판정 이유; UTF-8 text; 길이 제한 없음 | 미확정(U) | column-level 제약 없음 | C |
| `created_at` | `TIMESTAMPTZ` | 아니오 | `now()` | 생성 시각; 절대 시각; DB `now()` 또는 앱 입력 | 일반(C) | column-level 제약 없음 | C |

**명시 index와 비용** · `idx_sv2_domain` — 해당 equality/join/filter/정렬의 planner candidate; 실제 query plan·효과 미관찰; partial predicate 밖 row에는 미적용<br>`idx_sv2_net` — CIDR containment/overlap 조회의 planner candidate; 실제 query plan 미관찰; GiST 유지 비용; partial predicate 밖 row에는 미적용<br>`uq_sv2_value` — company scope value dedupe; insert writer의 `ON CONFLICT DO NOTHING`은 중복을 no-op 처리; partial predicate 밖 row에는 미적용<br>`idx_sv2_icp` — 해당 equality/join/filter/정렬의 planner candidate; 실제 query plan·효과 미관찰; partial predicate 밖 row에는 미적용<br>`idx_sv2_company` — 해당 equality/join/filter/정렬의 planner candidate; 실제 query plan·효과 미관찰

<a id="semantic-explorations"></a>
## `explorations`

task 탐색 graph의 root 상태, 목표와 현재 round를 저장한다.

**정적 접근** · reader: `db/digest.go`, `db/exploration.go`, `db/task_archives.go` · writer: `db/digest.go`, `db/exploration.go`, `db/task_archives_restore.go`, `db/tasks.go`

**transaction·retention** · FK delete 동작이 없거나 앱이 관계를 관리한다; schema에 TTL/partition 자동 보존 정책 없음; handler/cleanup/archive 삭제 경로와 운영 보존기간은 별도 확인(U)

**정확한 production SQL** · [symbol·verb·query access matrix](access-matrix.md#access-explorations)

| 필드 | 타입 | null | default | 의미·encoding | 민감도 | 제약 | 판정 |
|---|---|---:|---|---|---|---|---|
| `id` | `BIGSERIAL` | 아니오 | `nextval(<implicit sequence>)` | row 식별자; BIGSERIAL | 일반(C) | PRIMARY KEY | C |
| `description` | `TEXT` | 예 | `—` | 설명; UTF-8 text; 길이 제한 없음 | 미확정(U) | column-level 제약 없음 | C |
| `goal` | `TEXT` | 아니오 | `—` | 완료 목표; UTF-8 text; 길이 제한 없음 | 민감 가능(A) | column-level 제약 없음 | C |
| `status` | `TEXT` | 아니오 | `'open'` | lifecycle 상태; UTF-8 text; 길이 제한 없음 | 미확정(U) | CHECK (status IN ('open','achieved','failed')) | C |
| `created_at` | `TIMESTAMPTZ` | 아니오 | `now()` | 생성 시각; 절대 시각; DB `now()` 또는 앱 입력 | 일반(C) | column-level 제약 없음 | C |
| `updated_at` | `TIMESTAMPTZ` | 아니오 | `now()` | 마지막 변경 시각; 절대 시각; DB `now()` 또는 앱 입력 | 일반(C) | column-level 제약 없음 | C |
| `round_no` | `BIGINT` | 아니오 | `0` | exploration round 번호; BIGINT | 일반(C) | column-level 제약 없음 | C |

**명시 index와 비용** · 명시 index 없음; PK/UNIQUE가 만드는 내부 index는 102개 분모에 포함하지 않음.

<a id="semantic-exploration-nodes"></a>
## `exploration_nodes`

탐색 graph의 goal/intent/fact/finding/hint/digest 노드, 우선순위와 lifecycle을 저장한다.

**정적 접근** · reader: `db/assets.go`, `db/digest.go`, `db/exploration.go`, `db/finding_traffic.go`, `db/findings.go`, `db/side_questions.go`, `db/task_archives.go`, `db/task_archives_restore.go`, `db/task_assets.go`, `db/task_assets_context.go`, `db/task_scope.go`, `db/tasks.go`, `db/triggers.go` · writer: `db/digest.go`, `db/exploration.go`, `db/finding_traffic.go`, `db/findings.go`, `db/intent_admission.go`, `db/task_archives_restore.go`, `db/task_context.go`, `db/tasks.go`

**transaction·retention** · FK delete: CASCADE; schema에 TTL/partition 자동 보존 정책 없음; handler/cleanup/archive 삭제 경로와 운영 보존기간은 별도 확인(U)

**정확한 production SQL** · [symbol·verb·query access matrix](access-matrix.md#access-exploration-nodes)

| 필드 | 타입 | null | default | 의미·encoding | 민감도 | 제약 | 판정 |
|---|---|---:|---|---|---|---|---|
| `id` | `BIGSERIAL` | 아니오 | `nextval(<implicit sequence>)` | row 식별자; BIGSERIAL | 일반(C) | PRIMARY KEY | C |
| `exploration_id` | `BIGINT` | 아니오 | `—` | exploration row 식별자 또는 논리 참조; BIGINT | 일반(C) | FK explorations(id) ON DELETE CASCADE | A |
| `kind` | `TEXT` | 아니오 | `—` | variant 또는 event 종류; UTF-8 text; 길이 제한 없음 | 미확정(U) | column-level 제약 없음 <br>[table-level: ck_node_kind+kind/state CHECK](table-constraints.md#constraint-exploration-nodes)| C |
| `payload` | `JSONB` | 아니오 | `'{}'` | kind별 structured payload; kind별 graph payload; agent/db code가 부분적으로 해석하며 중앙 JSON Schema 없음 | 민감 가능(A) | column-level 제약 없음 | A |
| `priority` | `INT` | 아니오 | `0` | 우선순위; INT | 일반(C) | column-level 제약 없음 | C |
| `state` | `TEXT` | 아니오 | `'open'` | lifecycle 상태; UTF-8 text; 길이 제한 없음 | 미확정(U) | column-level 제약 없음 <br>[table-level: kind/state CHECK](table-constraints.md#constraint-exploration-nodes)| C |
| `origin` | `TEXT` | 예 | `—` | 노드를 만든 주체/경로 표지(예: planner, goals, human, worker, system)다. [의미 근거](evidence:db-field-exploration-nodes-origin) | 미확정(U) | column-level 제약 없음 | C |
| `owner` | `TEXT` | 예 | `—` | running intent를 원자적으로 claim한 실행 주체 표지이며 ClaimIntent가 설정한다. [의미 근거](evidence:db-field-exploration-nodes-owner) | 미확정(U) | column-level 제약 없음 | C |
| `blocked_reason` | `TEXT` | 예 | `—` | intent를 blocked로 전이시킨 이유이며 동일 이유로 재개할 intent를 찾는 데도 사용한다. [의미 근거](evidence:db-field-exploration-nodes-blocked-reason) | 미확정(U) | column-level 제약 없음 | C |
| `delete_reason` | `TEXT` | 예 | `—` | intent soft-delete(state='deleted') 때 보존하는 사용자 삭제 이유다. [의미 근거](evidence:db-field-exploration-nodes-delete-reason) | 미확정(U) | column-level 제약 없음 | C |
| `created_at` | `TIMESTAMPTZ` | 아니오 | `now()` | 생성 시각; 절대 시각; DB `now()` 또는 앱 입력 | 일반(C) | column-level 제약 없음 | C |
| `updated_at` | `TIMESTAMPTZ` | 아니오 | `now()` | 마지막 변경 시각; 절대 시각; DB `now()` 또는 앱 입력 | 일반(C) | column-level 제약 없음 | C |
| `completed_at` | `TIMESTAMPTZ` | 예 | `—` | 완료 시각; 절대 시각; DB `now()` 또는 앱 입력 | 일반(C) | column-level 제약 없음 | C |
| `content_version` | `INT` | 아니오 | `0` | optimistic content version; INT | 일반(C) | column-level 제약 없음 | C |
| `cold_since_round` | `BIGINT` | 예 | `—` | 노드가 live downstream branch를 잃어 cold가 된 가장 최근 planner round 번호이며 NULL은 hot이다. [의미 근거](evidence:db-field-exploration-nodes-cold-since-round) | 일반(C) | column-level 제약 없음 | C |

**명시 index와 비용** · `idx_expnodes_part` — 해당 equality/join/filter/정렬의 planner candidate; 실제 query plan·효과 미관찰<br>`idx_expnodes_frontier` — 최근 순서 조회의 planner candidate; 실제 query plan·효과 미관찰; partial predicate 밖 row에는 미적용

<a id="semantic-exploration-edges"></a>
## `exploration_edges`

한 exploration 안에서 source/destination 노드의 관계 종류를 저장한다.

**정적 접근** · reader: `db/digest.go`, `db/exploration.go`, `db/task_archives.go` · writer: `db/digest.go`, `db/exploration.go`, `db/finding_traffic.go`, `db/findings.go`, `db/task_archives_restore.go`

**transaction·retention** · FK delete: CASCADE; schema에 TTL/partition 자동 보존 정책 없음; handler/cleanup/archive 삭제 경로와 운영 보존기간은 별도 확인(U)

**정확한 production SQL** · [symbol·verb·query access matrix](access-matrix.md#access-exploration-edges)

| 필드 | 타입 | null | default | 의미·encoding | 민감도 | 제약 | 판정 |
|---|---|---:|---|---|---|---|---|
| `exploration_id` | `BIGINT` | 아니오 | `—` | exploration row 식별자 또는 논리 참조; BIGINT | 일반(C) | FK explorations(id) ON DELETE CASCADE <br>[table-level: composite PK](table-constraints.md#constraint-exploration-edges)| A |
| `src_id` | `BIGINT` | 아니오 | `—` | src row 식별자 또는 논리 참조; BIGINT | 일반(C) | FK exploration_nodes(id) ON DELETE CASCADE <br>[table-level: composite PK+no-self](table-constraints.md#constraint-exploration-edges)| A |
| `dst_id` | `BIGINT` | 아니오 | `—` | dst row 식별자 또는 논리 참조; BIGINT | 일반(C) | FK exploration_nodes(id) ON DELETE CASCADE <br>[table-level: composite PK+no-self](table-constraints.md#constraint-exploration-edges)| A |
| `rel` | `TEXT` | 아니오 | `—` | 방향성 탐색 그래프 edge 종류(spawns, derived_from, yields, proves, covers)이며 covers는 digest→member 관계다. [의미 근거](evidence:db-field-exploration-edges-rel) | 미확정(U) | column-level 제약 없음 <br>[table-level: composite PK+rel CHECK](table-constraints.md#constraint-exploration-edges)| C |
| `created_at` | `TIMESTAMPTZ` | 아니오 | `now()` | 생성 시각; 절대 시각; DB `now()` 또는 앱 입력 | 일반(C) | column-level 제약 없음 | C |

**명시 index와 비용** · `idx_expedges_src` — 해당 equality/join/filter/정렬의 planner candidate; 실제 query plan·효과 미관찰<br>`idx_expedges_dst` — 해당 equality/join/filter/정렬의 planner candidate; 실제 query plan·효과 미관찰

<a id="semantic-exploration-anchors"></a>
## `exploration_anchors`

탐색 노드를 실제 asset에 연결한다.

**정적 접근** · reader: `db/assets.go`, `db/digest.go`, `db/exploration.go`, `db/findings.go`, `db/task_archives.go`, `db/task_archives_restore.go`, `db/task_assets.go`, `db/task_assets_context.go`, `db/task_scope.go`, `db/tasks.go` · writer: `db/exploration.go`, `db/finding_traffic.go`, `db/findings.go`, `db/task_archives_restore.go`

**transaction·retention** · FK delete: CASCADE; schema에 TTL/partition 자동 보존 정책 없음; handler/cleanup/archive 삭제 경로와 운영 보존기간은 별도 확인(U)

**정확한 production SQL** · [symbol·verb·query access matrix](access-matrix.md#access-exploration-anchors)

| 필드 | 타입 | null | default | 의미·encoding | 민감도 | 제약 | 판정 |
|---|---|---:|---|---|---|---|---|
| `node_id` | `BIGINT` | 아니오 | `—` | node row 식별자 또는 논리 참조; BIGINT | 일반(C) | FK exploration_nodes(id) ON DELETE CASCADE <br>[table-level: composite PK](table-constraints.md#constraint-exploration-anchors)| A |
| `asset_id` | `BIGINT` | 아니오 | `—` | asset row 식별자 또는 논리 참조; BIGINT | 일반(C) | FK assets(id) ON DELETE CASCADE <br>[table-level: composite PK](table-constraints.md#constraint-exploration-anchors)| A |

**명시 index와 비용** · `idx_anchor_asset` — 해당 equality/join/filter/정렬의 planner candidate; 실제 query plan·효과 미관찰

<a id="semantic-task-constraints"></a>
## `task_constraints`

탐색별 scope·운영 제약과 그 출처를 저장한다.

**정적 접근** · reader: `db/constraints.go`, `db/finding_retests.go`, `db/task_archives.go` · writer: `db/constraints.go`, `db/task_archives_restore.go`

**transaction·retention** · FK delete: CASCADE; schema에 TTL/partition 자동 보존 정책 없음; handler/cleanup/archive 삭제 경로와 운영 보존기간은 별도 확인(U)

**정확한 production SQL** · [symbol·verb·query access matrix](access-matrix.md#access-task-constraints)

| 필드 | 타입 | null | default | 의미·encoding | 민감도 | 제약 | 판정 |
|---|---|---:|---|---|---|---|---|
| `id` | `BIGSERIAL` | 아니오 | `nextval(<implicit sequence>)` | row 식별자; BIGSERIAL | 일반(C) | PRIMARY KEY | C |
| `exploration_id` | `BIGINT` | 아니오 | `—` | exploration row 식별자 또는 논리 참조; BIGINT | 일반(C) | FK explorations(id) ON DELETE CASCADE | A |
| `kind` | `TEXT` | 아니오 | `—` | variant 또는 event 종류; UTF-8 text; 길이 제한 없음 | 미확정(U) | CHECK (kind IN ('allow','deny')) | C |
| `text` | `TEXT` | 아니오 | `—` | operator/agent가 작성해 planner·worker prompt에 주입하는 allow/deny 운영 제약의 본문이다. [의미 근거](evidence:db-field-task-constraints-text) | 민감 가능(A) | column-level 제약 없음 | C |
| `origin` | `TEXT` | 예 | `—` | 제약을 만든 주체 표지이며 현재 goals, human, system 계열 값이 사용된다. [의미 근거](evidence:db-field-task-constraints-origin) | 미확정(U) | column-level 제약 없음 | C |
| `created_at` | `TIMESTAMPTZ` | 아니오 | `now()` | 생성 시각; 절대 시각; DB `now()` 또는 앱 입력 | 일반(C) | column-level 제약 없음 | C |
| `updated_at` | `TIMESTAMPTZ` | 아니오 | `now()` | 마지막 변경 시각; 절대 시각; DB `now()` 또는 앱 입력 | 일반(C) | column-level 제약 없음 | C |

**명시 index와 비용** · `idx_task_constraints_exp` — 해당 equality/join/filter/정렬의 planner candidate; 실제 query plan·효과 미관찰

<a id="semantic-activity"></a>
## `activity`

worker/tool 실행과 token 사용, 오류·요약·detail을 탐색 timeline으로 기록한다.

**정적 접근** · reader: `db/commands.go`, `db/exploration.go`, `db/intercept_execution.go`, `db/task_archives.go`, `db/triggers.go` · writer: `db/exploration.go`, `db/findings.go`, `db/intent_admission.go`, `db/task_archives_restore.go`

**transaction·retention** · FK delete: CASCADE, SET NULL; schema에 TTL/partition 자동 보존 정책 없음; handler/cleanup/archive 삭제 경로와 운영 보존기간은 별도 확인(U)

**정확한 production SQL** · [symbol·verb·query access matrix](access-matrix.md#access-activity)

| 필드 | 타입 | null | default | 의미·encoding | 민감도 | 제약 | 판정 |
|---|---|---:|---|---|---|---|---|
| `id` | `BIGSERIAL` | 아니오 | `nextval(<implicit sequence>)` | row 식별자; BIGSERIAL | 일반(C) | PRIMARY KEY | C |
| `exploration_id` | `BIGINT` | 아니오 | `—` | exploration row 식별자 또는 논리 참조; BIGINT | 일반(C) | FK explorations(id) ON DELETE CASCADE | A |
| `node_id` | `BIGINT` | 예 | `—` | node row 식별자 또는 논리 참조; BIGINT | 일반(C) | FK exploration_nodes(id) ON DELETE SET NULL | A |
| `worker` | `TEXT` | 예 | `—` | worker 식별자; UTF-8 text; 길이 제한 없음 | 미확정(U) | column-level 제약 없음 | C |
| `kind` | `TEXT` | 예 | `—` | task exploration 실행 trace 행의 event 종류다. 현재 production writer는 text, thinking, tool_use, tool_result, usage, result, user, round, llm_switch, llm_failover, intercept_request, system을 기록한다. 한계: AppendActivity는 임의 kind를 받고 DB CHECK가 없으므로 닫힌 enum 보장은 아니다. [의미 근거](evidence:db-field-activity-kind) | 미확정(U) | column-level 제약 없음 | P |
| `tool` | `TEXT` | 예 | `—` | tool 이름; UTF-8 text; 길이 제한 없음 | 미확정(U) | column-level 제약 없음 | C |
| `tool_use_id` | `TEXT` | 예 | `—` | 모델 tool-call 식별자; UTF-8 text; 길이 제한 없음 | 일반(C) | column-level 제약 없음 | C |
| `is_error` | `BOOLEAN` | 아니오 | `false` | 실행 오류 여부; BOOLEAN | 일반(C) | column-level 제약 없음 | C |
| `summary` | `TEXT` | 예 | `—` | 요약; UTF-8 text; 길이 제한 없음 | 미확정(U) | column-level 제약 없음 | C |
| `detail` | `TEXT` | 예 | `—` | 상세 내용; UTF-8 text; 길이 제한 없음 | 민감 가능(A) | column-level 제약 없음 | C |
| `metadata` | `JSONB` | 아니오 | `'{}'` | event별 부가 metadata; activity 종류별 metadata; 생산자별 shape | 민감 가능(A) | column-level 제약 없음 | A |
| `input_tokens` | `INTEGER` | 예 | `—` | 입력 token 수; INTEGER | 일반(C) | column-level 제약 없음 | C |
| `output_tokens` | `INTEGER` | 예 | `—` | 출력 token 수; INTEGER | 일반(C) | column-level 제약 없음 | C |
| `cache_read_tokens` | `INTEGER` | 예 | `—` | cache read token 수; INTEGER | 일반(C) | column-level 제약 없음 | C |
| `cache_write_tokens` | `INTEGER` | 예 | `—` | cache write token 수; INTEGER | 일반(C) | column-level 제약 없음 | C |
| `created_at` | `TIMESTAMPTZ` | 아니오 | `now()` | 생성 시각; 절대 시각; DB `now()` 또는 앱 입력 | 일반(C) | column-level 제약 없음 | C |
| `main_seg` | `INTEGER` | 예 | `—` | resettable main-agent 대화 segment 번호다. NULL은 legacy segment 0이고 worker='mainagent' 행에만 사용된다. [의미 근거](evidence:db-field-activity-main-seg) | 일반(C) | column-level 제약 없음 | C |

**명시 index와 비용** · `idx_act_node` — 해당 equality/join/filter/정렬의 planner candidate; 실제 query plan·효과 미관찰<br>`idx_act_since` — 해당 equality/join/filter/정렬의 planner candidate; 실제 query plan·효과 미관찰<br>`idx_act_tool_call` — 해당 equality/join/filter/정렬의 planner candidate; 실제 query plan·효과 미관찰; partial predicate 밖 row에는 미적용<br>`idx_act_worker` — 해당 equality/join/filter/정렬의 planner candidate; 실제 query plan·효과 미관찰<br>`idx_act_main_seg` — 해당 equality/join/filter/정렬의 planner candidate; 실제 query plan·효과 미관찰; partial predicate 밖 row에는 미적용<br>`idx_act_result_usage` — 해당 equality/join/filter/정렬의 planner candidate; 실제 query plan·효과 미관찰; partial predicate 밖 row에는 미적용<br>`idx_act_latest` — 최근 순서 조회의 planner candidate; 실제 query plan·효과 미관찰

<a id="semantic-main-sessions"></a>
## `main_sessions`

exploration별 main-agent session 순번을 단조 증가시키는 cursor다.

**정적 접근** · reader: `db/exploration.go` · writer: `db/exploration.go`

**transaction·retention** · FK delete: CASCADE; schema에 TTL/partition 자동 보존 정책 없음; handler/cleanup/archive 삭제 경로와 운영 보존기간은 별도 확인(U)

**정확한 production SQL** · [symbol·verb·query access matrix](access-matrix.md#access-main-sessions)

| 필드 | 타입 | null | default | 의미·encoding | 민감도 | 제약 | 판정 |
|---|---|---:|---|---|---|---|---|
| `exploration_id` | `BIGINT` | 아니오 | `—` | exploration row 식별자 또는 논리 참조; BIGINT | 일반(C) | FK explorations(id) ON DELETE CASCADE <br>[table-level: composite PK](table-constraints.md#constraint-main-sessions)| A |
| `seq` | `INTEGER` | 아니오 | `—` | 단조 증가 session 순번; INTEGER | 일반(C) | column-level 제약 없음 <br>[table-level: composite PK](table-constraints.md#constraint-main-sessions)| C |
| `created_at` | `TIMESTAMPTZ` | 아니오 | `now()` | 생성 시각; 절대 시각; DB `now()` 또는 앱 입력 | 일반(C) | column-level 제약 없음 | C |

**명시 index와 비용** · 명시 index 없음; PK/UNIQUE가 만드는 내부 index는 102개 분모에 포함하지 않음.

<a id="semantic-settings"></a>
## `settings`

서버 전역 key/value 설정을 저장한다. value의 내부 형식은 key별 소비 코드가 정한다.

**정적 접근** · reader: `db/settings.go`, `server/finding_retests.go` · writer: `db/settings.go`, `server/finding_retests.go`

**transaction·retention** · FK delete 동작이 없거나 앱이 관계를 관리한다; schema에 TTL/partition 자동 보존 정책 없음; handler/cleanup/archive 삭제 경로와 운영 보존기간은 별도 확인(U)

**정확한 production SQL** · [symbol·verb·query access matrix](access-matrix.md#access-settings)

| 필드 | 타입 | null | default | 의미·encoding | 민감도 | 제약 | 판정 |
|---|---|---:|---|---|---|---|---|
| `key` | `TEXT` | 아니오 | `—` | 설정 또는 registry key; UTF-8 text; 길이 제한 없음 | 미확정(U) | PRIMARY KEY | C |
| `value` | `TEXT` | 아니오 | `—` | key가 해석하는 값; UTF-8 text; 길이 제한 없음 | key 의존 비밀/원문(C/A) | column-level 제약 없음 | C |
| `updated_at` | `TIMESTAMPTZ` | 아니오 | `now()` | 마지막 변경 시각; 절대 시각; DB `now()` 또는 앱 입력 | 일반(C) | column-level 제약 없음 | C |

**명시 index와 비용** · 명시 index 없음; PK/UNIQUE가 만드는 내부 index는 102개 분모에 포함하지 않음.

**`settings.value` key-dependent 민감도** · `auth.password_hash`는 bcrypt hash, provider 검색 설정의 Brave/Tavily key는 credential이다. boolean/URL/일반 정책 key도 같은 TEXT column을 공유하므로 row의 민감도와 encoding은 `key` consumer가 결정한다. DB column 수준 redaction/encryption은 없다. [auth setting](evidence:runtime-settings-auth) [runtime setting consumer](evidence:runtime-settings-server)

<a id="semantic-llm-profiles"></a>
## `llm_profiles`

LLM endpoint/model/credential, quota, reasoning, retry와 streaming 정책을 저장한다.

**정적 접근** · reader: `db/config.go`, `db/task_archives_restore.go` · writer: `db/config.go`

**transaction·retention** · FK delete 동작이 없거나 앱이 관계를 관리한다; schema에 TTL/partition 자동 보존 정책 없음; handler/cleanup/archive 삭제 경로와 운영 보존기간은 별도 확인(U)

**정확한 production SQL** · [symbol·verb·query access matrix](access-matrix.md#access-llm-profiles)

| 필드 | 타입 | null | default | 의미·encoding | 민감도 | 제약 | 판정 |
|---|---|---:|---|---|---|---|---|
| `id` | `BIGSERIAL` | 아니오 | `nextval(<implicit sequence>)` | row 식별자; BIGSERIAL | 일반(C) | PRIMARY KEY | C |
| `name` | `TEXT` | 아니오 | `—` | 표시 이름; UTF-8 text; 길이 제한 없음 | 미확정(U) | UNIQUE | C |
| `format` | `TEXT` | 아니오 | `—` | provider wire/API 형식 선택자: openai, anthropic, openai-responses다. [의미 근거](evidence:db-field-llm-profiles-format) | 미확정(U) | CHECK (format IN ('openai','anthropic','openai-responses')) | C |
| `base_url` | `TEXT` | 예 | `—` | LLM/MCP endpoint base URL; UTF-8 text; 길이 제한 없음 | 민감 가능(A) | column-level 제약 없음 | C |
| `proxy` | `TEXT` | 예 | `—` | outbound proxy URL; UTF-8 text; 길이 제한 없음 | 민감 가능(A) | column-level 제약 없음 | C |
| `model` | `TEXT` | 아니오 | `—` | 모델 식별자; UTF-8 text; 길이 제한 없음 | 미확정(U) | column-level 제약 없음 | C |
| `api_key` | `TEXT` | 예 | `—` | LLM provider 비밀 키; UTF-8 text; 길이 제한 없음 | 비밀/원문(C) | column-level 제약 없음 | C |
| `api_key_hint` | `TEXT` | 예 | `—` | API key가 4자 이상이면 ellipsis+마지막 4자로 생성하는 secret-derived UI hint [근거](evidence:sensitivity-api-key-hint) | 민감 파생(C) | column-level 제약 없음 | C |
| `rate_per_second` | `DOUBLE PRECISION` | 아니오 | `0` | 초당 LLM 호출 제한; DOUBLE PRECISION | 일반(C) | column-level 제약 없음 | C |
| `rate_per_minute` | `DOUBLE PRECISION` | 아니오 | `0` | 분당 LLM 호출 제한; DOUBLE PRECISION | 일반(C) | column-level 제약 없음 | C |
| `context_window_k` | `INTEGER` | 아니오 | `0` | 모델 context window를 천 token 단위로 저장해 local compaction 임계값을 정한다. 0은 200K 기본, 최대 1000(1M)으로 clamp한다. [의미 근거](evidence:db-field-llm-profiles-context-window-k) | 일반(C) | column-level 제약 없음 | C |
| `reasoning_effort` | `TEXT` | 아니오 | `''` | reasoning 강도 요청값이다. 빈 값은 미전송, 현재 low/medium/high/xhigh/max가 의미 있는 값이며 thinking_type과 독립이다. [의미 근거](evidence:db-field-llm-profiles-reasoning-effort) | 미확정(U) | column-level 제약 없음 | C |
| `thinking_type` | `TEXT` | 아니오 | `''` | thinking 기능 on/off 요청값이다. 빈 값은 미전송, disabled는 명시적 off, enabled는 on이며 reasoning_effort와 독립이다. [의미 근거](evidence:db-field-llm-profiles-thinking-type) | 미확정(U) | column-level 제약 없음 | C |
| `is_default` | `BOOLEAN` | 아니오 | `false` | 기본 LLM profile 여부; BOOLEAN | 일반(C) | column-level 제약 없음 | C |
| `priority` | `INTEGER` | 아니오 | `0` | 우선순위; INTEGER | 일반(C) | column-level 제약 없음 | C |
| `pool_exclude` | `BOOLEAN` | 아니오 | `false` | fallback pool 제외 여부; BOOLEAN | 일반(C) | column-level 제약 없음 | C |
| `streaming` | `BOOLEAN` | 아니오 | `true` | LLM streaming 사용 여부; BOOLEAN | 일반(C) | column-level 제약 없음 | C |
| `max_tokens` | `INTEGER` | 아니오 | `0` | 한 번의 모델 답변 output token 상한이다. 0이면 필드를 보내지 않고 endpoint 기본값을 쓴다. [의미 근거](evidence:db-field-llm-profiles-max-tokens) | 일반(C) | column-level 제약 없음 | C |
| `max_tokens_field` | `TEXT` | 아니오 | `''` | format=openai에서 output 상한을 보낼 key 선택자다. 빈 값은 max_tokens, 다른 허용값은 max_completion_tokens다. [의미 근거](evidence:db-field-llm-profiles-max-tokens-field) | 미확정(U) | column-level 제약 없음 | C |
| `session_header_key` | `TEXT` | 아니오 | `''` | 비어 있지 않으면 매 LLM 요청에 이 이름의 HTTP header를 넣고 현재 run session id를 값으로 보낸다. [의미 근거](evidence:db-field-llm-profiles-session-header-key) | 미확정(U) | column-level 제약 없음 | C |
| `retry_connect_attempts` | `INTEGER` | 아니오 | `0` | 연결 단계 retry 횟수 override: 0은 global 상속, -1은 비활성, 양수는 해당 횟수다. [의미 근거](evidence:db-field-llm-profiles-retry-connect-attempts) | 일반(C) | column-level 제약 없음 | C |
| `retry_connect_interval_ms` | `INTEGER` | 아니오 | `0` | retry connect interval 시간(ms); INTEGER | 일반(C) | column-level 제약 없음 | A |
| `retry_empty_attempts` | `INTEGER` | 아니오 | `0` | 빈 응답 retry 횟수 override: 0은 global 상속, -1은 비활성, 양수는 해당 횟수다. [의미 근거](evidence:db-field-llm-profiles-retry-empty-attempts) | 일반(C) | column-level 제약 없음 | C |
| `retry_empty_interval_ms` | `INTEGER` | 아니오 | `0` | retry empty interval 시간(ms); INTEGER | 일반(C) | column-level 제약 없음 | A |
| `retry_stream_attempts` | `INTEGER` | 아니오 | `0` | 출력을 아직 전달하지 않은 동일 provider stream 중단 retry 횟수 override: 0 상속, -1 비활성, 양수는 횟수다. [의미 근거](evidence:db-field-llm-profiles-retry-stream-attempts) | 일반(C) | column-level 제약 없음 | C |
| `retry_stream_interval_ms` | `INTEGER` | 아니오 | `0` | retry stream interval 시간(ms); INTEGER | 일반(C) | column-level 제약 없음 | A |
| `created_at` | `TIMESTAMPTZ` | 아니오 | `now()` | 생성 시각; 절대 시각; DB `now()` 또는 앱 입력 | 일반(C) | column-level 제약 없음 | C |
| `updated_at` | `TIMESTAMPTZ` | 아니오 | `now()` | 마지막 변경 시각; 절대 시각; DB `now()` 또는 앱 입력 | 일반(C) | column-level 제약 없음 | C |

**명시 index와 비용** · `uq_llm_one_default` — unique invariant; competing writer 결과는 해당 SQL의 conflict clause/transaction을 확인(U); partial predicate 밖 row에는 미적용

<a id="semantic-llm-profile-health"></a>
## `llm_profile_health`

profile별 circuit-breaker 실패 횟수와 냉각 종료 시각을 재시작 사이에 보존한다.

**정적 접근** · reader: `db/llmhealth.go` · writer: `db/llmhealth.go`

**transaction·retention** · FK delete: CASCADE; schema에 TTL/partition 자동 보존 정책 없음; handler/cleanup/archive 삭제 경로와 운영 보존기간은 별도 확인(U)

**정확한 production SQL** · [symbol·verb·query access matrix](access-matrix.md#access-llm-profile-health)

| 필드 | 타입 | null | default | 의미·encoding | 민감도 | 제약 | 판정 |
|---|---|---:|---|---|---|---|---|
| `profile_id` | `BIGINT` | 아니오 | `—` | profile row 식별자 또는 논리 참조; BIGINT | 일반(C) | FK llm_profiles(id) ON DELETE CASCADE; PRIMARY KEY | A |
| `fails` | `INTEGER` | 아니오 | `0` | 연속 실패 횟수; INTEGER | 일반(C) | column-level 제약 없음 | C |
| `trips` | `INTEGER` | 아니오 | `0` | 누적 circuit-breaker trip 횟수; INTEGER | 일반(C) | column-level 제약 없음 | C |
| `open_until` | `TIMESTAMPTZ` | 예 | `—` | circuit breaker 냉각 종료 시각; 절대 시각; DB `now()` 또는 앱 입력 | 일반(C) | column-level 제약 없음 | C |
| `last_error` | `TEXT` | 아니오 | `''` | 마지막 실패 내용; UTF-8 text; 길이 제한 없음 | 민감 가능(A) | column-level 제약 없음 | C |
| `last_at` | `TIMESTAMPTZ` | 아니오 | `now()` | 마지막 상태 변경 시각; 절대 시각; DB `now()` 또는 앱 입력 | 일반(C) | column-level 제약 없음 | C |

**명시 index와 비용** · 명시 index 없음; PK/UNIQUE가 만드는 내부 index는 102개 분모에 포함하지 않음.

<a id="semantic-task-categories"></a>
## `task_categories`

task를 묶는 사용자 정의 category와 정규화 key를 저장한다.

**정적 접근** · reader: `db/task_archives.go`, `db/task_archives_restore.go`, `db/task_categories.go`, `db/tasks.go` · writer: `db/task_categories.go`

**transaction·retention** · FK delete 동작이 없거나 앱이 관계를 관리한다; schema에 TTL/partition 자동 보존 정책 없음; handler/cleanup/archive 삭제 경로와 운영 보존기간은 별도 확인(U)

**정확한 production SQL** · [symbol·verb·query access matrix](access-matrix.md#access-task-categories)

| 필드 | 타입 | null | default | 의미·encoding | 민감도 | 제약 | 판정 |
|---|---|---:|---|---|---|---|---|
| `id` | `BIGSERIAL` | 아니오 | `nextval(<implicit sequence>)` | row 식별자; BIGSERIAL | 일반(C) | PRIMARY KEY | C |
| `name` | `TEXT` | 아니오 | `—` | 표시 이름; UTF-8 text; 길이 제한 없음 | 미확정(U) | column-level 제약 없음 | C |
| `nkey` | `TEXT` | 아니오 | `—` | 정규화한 unique key; UTF-8 text; 길이 제한 없음 | 미확정(U) | UNIQUE | C |
| `created_at` | `TIMESTAMPTZ` | 아니오 | `now()` | 생성 시각; 절대 시각; DB `now()` 또는 앱 입력 | 일반(C) | column-level 제약 없음 | C |
| `updated_at` | `TIMESTAMPTZ` | 아니오 | `now()` | 마지막 변경 시각; 절대 시각; DB `now()` 또는 앱 입력 | 일반(C) | column-level 제약 없음 | C |

**명시 index와 비용** · `idx_task_categories_name` — 해당 equality/join/filter/정렬의 planner candidate; 실제 query plan·효과 미관찰

<a id="semantic-tasks"></a>
## `tasks`

task 입력·실행/queue/archive lifecycle, timeout, 회사와 LLM chain 선택을 소유한다.

**정적 접근** · reader: `db/asset_dsl.go`, `db/assets.go`, `db/config.go`, `db/exploration.go`, `db/exploration_sources.go`, `db/finding_assets.go`, `db/finding_retests.go`, `db/finding_traffic.go`, `db/findings.go`, `db/intercept_execution.go`, `db/side_questions.go`, `db/task_archives.go`, `db/task_archives_restore.go`, `db/task_assets.go`, `db/task_assets_context.go`, `db/task_categories.go`, `db/task_context.go`, `db/task_scope.go`, `db/tasks.go`, `db/triggers.go` · writer: `db/config.go`, `db/task_archives_restore.go`, `db/task_categories.go`, `db/task_context.go`, `db/tasks.go`, `server/manager.go`

**transaction·retention** · FK delete: RESTRICT, SET NULL; schema에 TTL/partition 자동 보존 정책 없음; handler/cleanup/archive 삭제 경로와 운영 보존기간은 별도 확인(U)

**정확한 production SQL** · [symbol·verb·query access matrix](access-matrix.md#access-tasks)

| 필드 | 타입 | null | default | 의미·encoding | 민감도 | 제약 | 판정 |
|---|---|---:|---|---|---|---|---|
| `id` | `BIGSERIAL` | 아니오 | `nextval(<implicit sequence>)` | row 식별자; BIGSERIAL | 일반(C) | PRIMARY KEY | C |
| `name` | `TEXT` | 아니오 | `''` | 표시 이름; UTF-8 text; 길이 제한 없음 | 미확정(U) | column-level 제약 없음 | C |
| `category_id` | `BIGINT` | 예 | `—` | category row 식별자 또는 논리 참조; BIGINT | 일반(C) | FK task_categories(id) ON DELETE SET NULL | A |
| `description` | `TEXT` | 아니오 | `—` | 설명; UTF-8 text; 길이 제한 없음 | 미확정(U) | column-level 제약 없음 | C |
| `goal` | `TEXT` | 아니오 | `—` | 완료 목표; UTF-8 text; 길이 제한 없음 | 민감 가능(A) | column-level 제약 없음 | C |
| `exploration_id` | `BIGINT` | 아니오 | `—` | exploration row 식별자 또는 논리 참조; BIGINT | 일반(C) | FK explorations(id) ON DELETE RESTRICT; UNIQUE | A |
| `status` | `TEXT` | 아니오 | `'created'` | lifecycle 상태; UTF-8 text; 길이 제한 없음 | 미확정(U) | CHECK (status IN ('created','running','paused','done','failed','timeout')) | C |
| `paused` | `BOOLEAN` | 아니오 | `false` | task 일시정지 여부; BOOLEAN | 일반(C) | column-level 제약 없음 | C |
| `queued` | `BOOLEAN` | 아니오 | `false` | queue 대기 여부; BOOLEAN | 일반(C) | column-level 제약 없음 | C |
| `queued_at` | `TIMESTAMPTZ` | 예 | `—` | queue 등록 시각; 절대 시각; DB `now()` 또는 앱 입력 | 일반(C) | column-level 제약 없음 | C |
| `queue_mode` | `TEXT` | 아니오 | `''` | concurrency queue admission 종류다. bootstrap은 첫 실행, resume은 기존 graph/history가 있는 탐색 재개이며 빈 legacy 값은 graph로 추론한다. [의미 근거](evidence:db-field-tasks-queue-mode) | 미확정(U) | column-level 제약 없음 | C |
| `llm_profile_id` | `BIGINT` | 예 | `—` | llm profile row 식별자 또는 논리 참조; BIGINT | 일반(C) | FK llm_profiles(id) ON DELETE SET NULL | A |
| `active_llm_profile_id` | `BIGINT` | 예 | `—` | active llm profile row 식별자 또는 논리 참조; BIGINT | 일반(C) | FK llm_profiles(id) ON DELETE SET NULL | A |
| `llm_chain_revision` | `BIGINT` | 아니오 | `0` | task LLM failover chain의 optimistic revision/cursor로, chain 교체나 active profile 전진 때 증가해 stale 전환을 거부한다. [의미 근거](evidence:db-field-tasks-llm-chain-revision) | 일반(C) | column-level 제약 없음 | C |
| `company_id` | `BIGINT` | 예 | `—` | company row 식별자 또는 논리 참조; BIGINT | 일반(C) | FK companies(id) ON DELETE SET NULL | A |
| `parent_ref` | `TEXT` | 예 | `—` | orchestration spawn_task가 기록하는 부모 task id 문자열이다. 내용 상속(source_task_ids)과 별개인 부모/자식 포인터다. [의미 근거](evidence:db-field-tasks-parent-ref) | 미확정(U) | column-level 제약 없음 | C |
| `timeout_seconds` | `INTEGER` | 아니오 | `0` | timeout 길이(초); INTEGER | 일반(C) | column-level 제약 없음 | C |
| `plan_heartbeat_seconds` | `INTEGER` | 아니오 | `300` | planner heartbeat 간격(초); INTEGER | 일반(C) | column-level 제약 없음 | C |
| `coverage_enabled` | `BOOLEAN` | 아니오 | `true` | coverage loop 사용 여부; BOOLEAN | 일반(C) | column-level 제약 없음 | C |
| `pinned_at` | `TIMESTAMPTZ` | 예 | `—` | UI pin 시각; 절대 시각; DB `now()` 또는 앱 입력 | 일반(C) | column-level 제약 없음 | C |
| `first_run_at` | `TIMESTAMPTZ` | 예 | `—` | 첫 실행 시각; 절대 시각; DB `now()` 또는 앱 입력 | 일반(C) | column-level 제약 없음 | C |
| `deadline_at` | `TIMESTAMPTZ` | 예 | `—` | task 강제 종료 시각; 절대 시각; DB `now()` 또는 앱 입력 | 일반(C) | column-level 제약 없음 | C |
| `archived_at` | `TIMESTAMPTZ` | 예 | `—` | archive 시각; 절대 시각; DB `now()` 또는 앱 입력 | 일반(C) | column-level 제약 없음 | C |
| `deleted_at` | `TIMESTAMPTZ` | 예 | `—` | soft-delete 시각; 절대 시각; DB `now()` 또는 앱 입력 | 일반(C) | column-level 제약 없음 | C |
| `completed_at` | `TIMESTAMPTZ` | 예 | `—` | 완료 시각; 절대 시각; DB `now()` 또는 앱 입력 | 일반(C) | column-level 제약 없음 | C |
| `created_at` | `TIMESTAMPTZ` | 아니오 | `now()` | 생성 시각; 절대 시각; DB `now()` 또는 앱 입력 | 일반(C) | column-level 제약 없음 | C |
| `updated_at` | `TIMESTAMPTZ` | 아니오 | `now()` | 마지막 변경 시각; 절대 시각; DB `now()` 또는 앱 입력 | 일반(C) | column-level 제약 없음 | C |

**명시 index와 비용** · `idx_tasks_alive` — 최근 순서 조회의 planner candidate; 실제 query plan·효과 미관찰; partial predicate 밖 row에는 미적용<br>`idx_tasks_status` — 해당 equality/join/filter/정렬의 planner candidate; 실제 query plan·효과 미관찰; partial predicate 밖 row에는 미적용<br>`idx_tasks_category` — 최근 순서 조회의 planner candidate; 실제 query plan·효과 미관찰; partial predicate 밖 row에는 미적용<br>`idx_tasks_pinned` — 최근 순서 조회의 planner candidate; 실제 query plan·효과 미관찰; partial predicate 밖 row에는 미적용<br>`idx_tasks_archived` — 최근 순서 조회의 planner candidate; 실제 query plan·효과 미관찰; partial predicate 밖 row에는 미적용<br>`idx_tasks_llm_profile` — 해당 equality/join/filter/정렬의 planner candidate; 실제 query plan·효과 미관찰; partial predicate 밖 row에는 미적용<br>`idx_tasks_active_llm_profile` — 해당 equality/join/filter/정렬의 planner candidate; 실제 query plan·효과 미관찰; partial predicate 밖 row에는 미적용

<a id="semantic-task-archives"></a>
## `task_archives`

archive job 진행 상태와 생성 artifact 경로/hash, 복원용 snapshot 통계를 저장한다.

**정적 접근** · reader: `db/finding_traffic.go`, `db/task_archives.go`, `db/task_archives_restore.go` · writer: `db/task_archives.go`, `db/task_archives_restore.go`

**transaction·retention** · FK delete: CASCADE; schema에 TTL/partition 자동 보존 정책 없음; handler/cleanup/archive 삭제 경로와 운영 보존기간은 별도 확인(U)

**정확한 production SQL** · [symbol·verb·query access matrix](access-matrix.md#access-task-archives)

| 필드 | 타입 | null | default | 의미·encoding | 민감도 | 제약 | 판정 |
|---|---|---:|---|---|---|---|---|
| `id` | `BIGSERIAL` | 아니오 | `nextval(<implicit sequence>)` | row 식별자; BIGSERIAL | 일반(C) | PRIMARY KEY | C |
| `task_id` | `BIGINT` | 아니오 | `—` | task row 식별자 또는 논리 참조; BIGINT | 일반(C) | FK tasks(id) ON DELETE CASCADE; UNIQUE | A |
| `state` | `TEXT` | 아니오 | `'archive_queued'` | lifecycle 상태; UTF-8 text; 길이 제한 없음 | 미확정(U) | CHECK (state IN ( 'archive_queued','archiving','archive_failed','ready', 'restore_queued','restoring','restore_failed', 'delete_queued','deleting','delete_failed' )) | C |
| `phase` | `TEXT` | 아니오 | `'queued'` | 현재 archive/restore/delete job 내부 처리 단계 이름이다. state보다 세밀한 진행 위치를 UI/복구에 제공한다. [의미 근거](evidence:db-field-task-archives-phase) | 미확정(U) | column-level 제약 없음 | C |
| `progress` | `INTEGER` | 아니오 | `0` | archive job 진행률 0..100 정수다. [의미 근거](evidence:db-field-task-archives-progress) | 일반(C) | CHECK (progress BETWEEN 0 AND 100) | C |
| `error` | `TEXT` | 아니오 | `''` | 실패 내용; UTF-8 text; 길이 제한 없음 | 민감 가능(A) | column-level 제약 없음 | C |
| `warnings` | `JSONB` | 아니오 | `'[]'` | archive/restore 과정의 비치명 경고 문자열 배열을 JSON으로 저장한다. [의미 근거](evidence:db-field-task-archives-warnings) | 미확정(U) | column-level 제약 없음 | P |
| `format_version` | `INTEGER` | 아니오 | `2` | archive format version; INTEGER | 일반(C) | column-level 제약 없음 | C |
| `archive_path` | `TEXT` | 아니오 | `''` | archive artifact 경로; UTF-8 text; 길이 제한 없음 | 민감 가능(A) | column-level 제약 없음 | C |
| `sha256` | `TEXT` | 아니오 | `''` | archive artifact SHA-256; UTF-8 text; 길이 제한 없음 | 미확정(U) | column-level 제약 없음 | C |
| `original_size` | `BIGINT` | 아니오 | `0` | 원본 byte 크기; BIGINT | 일반(C) | column-level 제약 없음 | C |
| `compressed_size` | `BIGINT` | 아니오 | `0` | 압축 byte 크기; BIGINT | 일반(C) | column-level 제약 없음 | C |
| `task_name` | `TEXT` | 아니오 | `''` | cold archive 생성 시점 task 이름 snapshot이다. [의미 근거](evidence:db-field-task-archives-task-name) | 미확정(U) | column-level 제약 없음 | C |
| `task_description` | `TEXT` | 아니오 | `''` | cold archive 생성 시점 task description snapshot이다. [의미 근거](evidence:db-field-task-archives-task-description) | 미확정(U) | column-level 제약 없음 | C |
| `task_goal` | `TEXT` | 아니오 | `''` | cold archive 생성 시점 task goal snapshot이다. [의미 근거](evidence:db-field-task-archives-task-goal) | 미확정(U) | column-level 제약 없음 | C |
| `original_status` | `TEXT` | 아니오 | `''` | archive 직전 task status snapshot으로 restore 때 원 상태를 복원하는 값이다. [의미 근거](evidence:db-field-task-archives-original-status) | 미확정(U) | column-level 제약 없음 | C |
| `category_id_snapshot` | `BIGINT` | 예 | `—` | archive 시점 task category id snapshot이며 category가 사라질 수 있어 FK가 아니다. [의미 근거](evidence:db-field-task-archives-category-id-snapshot) | 일반(C) | column-level 제약 없음 | C |
| `category_name_snapshot` | `TEXT` | 아니오 | `''` | archive 시점 task category 표시 이름 snapshot이다. [의미 근거](evidence:db-field-task-archives-category-name-snapshot) | 미확정(U) | column-level 제약 없음 | C |
| `source_task_ids` | `BIGINT[]` | 아니오 | `'{}'` | archive provenance task 목록; PostgreSQL BIGINT[] 배열; 원소 수 제한 없음 | 일반(C) | column-level 제약 없음 | A |
| `remaining_timeout_seconds` | `BIGINT` | 아니오 | `0` | remaining timeout 시간(초); BIGINT | 일반(C) | column-level 제약 없음 | A |
| `data_counts` | `JSONB` | 아니오 | `'{}'` | archive 객체별 건수; archive 내 객체별 count map | 미확정(U) | column-level 제약 없음 | A |
| `aggregate_stats` | `JSONB` | 아니오 | `'{}'` | archive 집계 통계; archive 집계 map; format_version과 함께 해석 | 미확정(U) | column-level 제약 없음 | A |
| `archived_at` | `TIMESTAMPTZ` | 예 | `—` | archive 시각; 절대 시각; DB `now()` 또는 앱 입력 | 일반(C) | column-level 제약 없음 | C |
| `requested_at` | `TIMESTAMPTZ` | 아니오 | `now()` | archive 요청 시각; 절대 시각; DB `now()` 또는 앱 입력 | 일반(C) | column-level 제약 없음 | C |
| `created_at` | `TIMESTAMPTZ` | 아니오 | `now()` | 생성 시각; 절대 시각; DB `now()` 또는 앱 입력 | 일반(C) | column-level 제약 없음 | C |
| `updated_at` | `TIMESTAMPTZ` | 아니오 | `now()` | 마지막 변경 시각; 절대 시각; DB `now()` 또는 앱 입력 | 일반(C) | column-level 제약 없음 | C |

**명시 index와 비용** · `idx_task_archives_state` — 해당 equality/join/filter/정렬의 planner candidate; 실제 query plan·효과 미관찰<br>`idx_task_archives_archived` — 최근 순서 조회의 planner candidate; 실제 query plan·효과 미관찰<br>`idx_task_archives_sources` — 배열/JSON 포함 조회의 planner candidate; 실제 query plan 미관찰; write amplification·index 크기 비용

<a id="semantic-task-templates"></a>
## `task_templates`

재사용할 task 설명·goal·category와 intercept rule snapshot을 저장한다.

**정적 접근** · reader: `db/task_templates.go` · writer: `db/task_templates.go`

**transaction·retention** · FK delete: SET NULL; schema에 TTL/partition 자동 보존 정책 없음; handler/cleanup/archive 삭제 경로와 운영 보존기간은 별도 확인(U)

**정확한 production SQL** · [symbol·verb·query access matrix](access-matrix.md#access-task-templates)

| 필드 | 타입 | null | default | 의미·encoding | 민감도 | 제약 | 판정 |
|---|---|---:|---|---|---|---|---|
| `id` | `BIGSERIAL` | 아니오 | `nextval(<implicit sequence>)` | row 식별자; BIGSERIAL | 일반(C) | PRIMARY KEY | C |
| `name` | `TEXT` | 아니오 | `—` | 표시 이름; UTF-8 text; 길이 제한 없음 | 미확정(U) | column-level 제약 없음 | C |
| `nkey` | `TEXT` | 아니오 | `—` | 정규화한 unique key; UTF-8 text; 길이 제한 없음 | 미확정(U) | UNIQUE | C |
| `description` | `TEXT` | 아니오 | `—` | 설명; UTF-8 text; 길이 제한 없음 | 미확정(U) | column-level 제약 없음 | C |
| `goal` | `TEXT` | 아니오 | `—` | 완료 목표; UTF-8 text; 길이 제한 없음 | 민감 가능(A) | column-level 제약 없음 | C |
| `category_id` | `BIGINT` | 예 | `—` | category row 식별자 또는 논리 참조; BIGINT | 일반(C) | FK task_categories(id) ON DELETE SET NULL | A |
| `intercept_rules` | `JSONB` | 아니오 | `'[]'` | template의 intercept rule snapshot; template 적용 시 복원할 intercept rule 배열 | 미확정(U) | column-level 제약 없음 | A |
| `created_at` | `TIMESTAMPTZ` | 아니오 | `now()` | 생성 시각; 절대 시각; DB `now()` 또는 앱 입력 | 일반(C) | column-level 제약 없음 | C |
| `updated_at` | `TIMESTAMPTZ` | 아니오 | `now()` | 마지막 변경 시각; 절대 시각; DB `now()` 또는 앱 입력 | 일반(C) | column-level 제약 없음 | C |

**명시 index와 비용** · `idx_task_templates_updated` — 최근 순서 조회의 planner candidate; 실제 query plan·효과 미관찰

<a id="semantic-task-relations"></a>
## `task_relations`

파생 task와 source task 사이 provenance edge를 저장한다.

**정적 접근** · reader: `db/asset_dsl.go`, `db/exploration_sources.go`, `db/task_archives.go`, `db/task_archives_restore.go`, `db/task_assets.go`, `db/task_assets_context.go`, `db/task_context.go`, `db/task_scope.go` · writer: `db/task_archives_restore.go`, `db/tasks.go`

**transaction·retention** · FK delete: CASCADE; schema에 TTL/partition 자동 보존 정책 없음; handler/cleanup/archive 삭제 경로와 운영 보존기간은 별도 확인(U)

**정확한 production SQL** · [symbol·verb·query access matrix](access-matrix.md#access-task-relations)

| 필드 | 타입 | null | default | 의미·encoding | 민감도 | 제약 | 판정 |
|---|---|---:|---|---|---|---|---|
| `task_id` | `BIGINT` | 아니오 | `—` | task row 식별자 또는 논리 참조; BIGINT | 일반(C) | FK tasks(id) ON DELETE CASCADE <br>[table-level: composite PK+no-self](table-constraints.md#constraint-task-relations)| A |
| `source_task_id` | `BIGINT` | 아니오 | `—` | source task row 식별자 또는 논리 참조; BIGINT | 일반(C) | FK tasks(id) ON DELETE CASCADE <br>[table-level: composite PK+no-self](table-constraints.md#constraint-task-relations)| A |
| `created_at` | `TIMESTAMPTZ` | 아니오 | `now()` | 생성 시각; 절대 시각; DB `now()` 또는 앱 입력 | 일반(C) | column-level 제약 없음 | C |

**명시 index와 비용** · `idx_task_relations_source` — 해당 equality/join/filter/정렬의 planner candidate; 실제 query plan·효과 미관찰

<a id="semantic-task-asset-links"></a>
## `task_asset_links`

task와 asset 연결의 출처, 요약과 source exploration node를 저장한다.

**정적 접근** · reader: `db/task_archives.go`, `db/task_assets.go` · writer: `db/task_archives_restore.go`, `db/task_assets.go`, `db/tasks.go`

**transaction·retention** · FK delete: CASCADE, SET NULL; schema에 TTL/partition 자동 보존 정책 없음; handler/cleanup/archive 삭제 경로와 운영 보존기간은 별도 확인(U)

**정확한 production SQL** · [symbol·verb·query access matrix](access-matrix.md#access-task-asset-links)

| 필드 | 타입 | null | default | 의미·encoding | 민감도 | 제약 | 판정 |
|---|---|---:|---|---|---|---|---|
| `task_id` | `BIGINT` | 아니오 | `—` | task row 식별자 또는 논리 참조; BIGINT | 일반(C) | FK tasks(id) ON DELETE CASCADE <br>[table-level: composite PK](table-constraints.md#constraint-task-asset-links)| A |
| `asset_id` | `BIGINT` | 아니오 | `—` | asset row 식별자 또는 논리 참조; BIGINT | 일반(C) | FK assets(id) ON DELETE CASCADE <br>[table-level: composite PK](table-constraints.md#constraint-task-asset-links)| A |
| `source` | `TEXT` | 아니오 | `'system'` | task-asset 연결 provenance 종류다. 현재 system, legacy, auto/agent/manual 및 상세 writer가 쓰는 출처를 operator review에 노출한다; DB enum은 없다. [의미 근거](evidence:db-field-task-asset-links-source) | 미확정(U) | column-level 제약 없음 | P |
| `source_summary` | `TEXT` | 아니오 | `''` | task-asset 연결이 생긴 이유를 사람이 읽을 수 있게 저장한 provenance 설명이다. [의미 근거](evidence:db-field-task-asset-links-source-summary) | 미확정(U) | column-level 제약 없음 | C |
| `source_node_id` | `BIGINT` | 예 | `—` | source node row 식별자 또는 논리 참조; BIGINT | 일반(C) | FK exploration_nodes(id) ON DELETE SET NULL | A |
| `created_at` | `TIMESTAMPTZ` | 아니오 | `now()` | 생성 시각; 절대 시각; DB `now()` 또는 앱 입력 | 일반(C) | column-level 제약 없음 | C |
| `updated_at` | `TIMESTAMPTZ` | 아니오 | `now()` | 마지막 변경 시각; 절대 시각; DB `now()` 또는 앱 입력 | 일반(C) | column-level 제약 없음 | C |

**명시 index와 비용** · `idx_task_asset_links_asset` — 해당 equality/join/filter/정렬의 planner candidate; 실제 query plan·효과 미관찰<br>`idx_task_asset_links_node` — 해당 equality/join/filter/정렬의 planner candidate; 실제 query plan·효과 미관찰; partial predicate 밖 row에는 미적용

<a id="semantic-task-llm-profiles"></a>
## `task_llm_profiles`

task별 LLM fallback chain의 순서와 소진 상태를 저장한다.

**정적 접근** · reader: `db/config.go`, `db/task_archives.go`, `db/task_context.go` · writer: `db/config.go`, `db/task_archives_restore.go`, `db/task_context.go`, `db/tasks.go`

**transaction·retention** · FK delete: CASCADE; schema에 TTL/partition 자동 보존 정책 없음; handler/cleanup/archive 삭제 경로와 운영 보존기간은 별도 확인(U)

**정확한 production SQL** · [symbol·verb·query access matrix](access-matrix.md#access-task-llm-profiles)

| 필드 | 타입 | null | default | 의미·encoding | 민감도 | 제약 | 판정 |
|---|---|---:|---|---|---|---|---|
| `task_id` | `BIGINT` | 아니오 | `—` | task row 식별자 또는 논리 참조; BIGINT | 일반(C) | FK tasks(id) ON DELETE CASCADE <br>[table-level: composite PK+UNIQUE position](table-constraints.md#constraint-task-llm-profiles)| A |
| `profile_id` | `BIGINT` | 아니오 | `—` | profile row 식별자 또는 논리 참조; BIGINT | 일반(C) | FK llm_profiles(id) ON DELETE CASCADE <br>[table-level: composite PK](table-constraints.md#constraint-task-llm-profiles)| A |
| `position` | `INTEGER` | 아니오 | `—` | 정렬 순서; INTEGER | 일반(C) | CHECK (position >= 0) <br>[table-level: UNIQUE(task_id,position)](table-constraints.md#constraint-task-llm-profiles)| C |
| `status` | `TEXT` | 아니오 | `'ready'` | lifecycle 상태; UTF-8 text; 길이 제한 없음 | 미확정(U) | CHECK (status IN ('ready','quota_exhausted')) | C |
| `last_error` | `TEXT` | 예 | `—` | 마지막 실패 내용; UTF-8 text; 길이 제한 없음 | 민감 가능(A) | column-level 제약 없음 | C |
| `exhausted_at` | `TIMESTAMPTZ` | 예 | `—` | exhausted 시각; 절대 시각; DB `now()` 또는 앱 입력 | 일반(C) | column-level 제약 없음 | A |
| `created_at` | `TIMESTAMPTZ` | 아니오 | `now()` | 생성 시각; 절대 시각; DB `now()` 또는 앱 입력 | 일반(C) | column-level 제약 없음 | C |
| `updated_at` | `TIMESTAMPTZ` | 아니오 | `now()` | 마지막 변경 시각; 절대 시각; DB `now()` 또는 앱 입력 | 일반(C) | column-level 제약 없음 | C |

**명시 index와 비용** · `idx_task_llm_profiles_order` — 해당 equality/join/filter/정렬의 planner candidate; 실제 query plan·효과 미관찰<br>`idx_task_llm_profiles_profile` — 해당 equality/join/filter/정렬의 planner candidate; 실제 query plan·효과 미관찰

<a id="semantic-task-scope"></a>
## `task_scope`

task에 적용할 company/domain/IP/CIDR/ICP/keyword scope와 출처를 저장한다.

**정적 접근** · reader: `db/asset_dsl.go`, `db/task_archives.go`, `db/task_assets_context.go`, `db/task_context.go`, `db/task_scope.go` · writer: `db/task_archives_restore.go`, `db/task_scope.go`, `db/tasks.go`

**transaction·retention** · FK delete: CASCADE; schema에 TTL/partition 자동 보존 정책 없음; handler/cleanup/archive 삭제 경로와 운영 보존기간은 별도 확인(U)

**정확한 production SQL** · [symbol·verb·query access matrix](access-matrix.md#access-task-scope)

| 필드 | 타입 | null | default | 의미·encoding | 민감도 | 제약 | 판정 |
|---|---|---:|---|---|---|---|---|
| `id` | `BIGSERIAL` | 아니오 | `nextval(<implicit sequence>)` | row 식별자; BIGSERIAL | 일반(C) | PRIMARY KEY | C |
| `task_id` | `BIGINT` | 아니오 | `—` | task row 식별자 또는 논리 참조; BIGINT | 일반(C) | FK tasks(id) ON DELETE CASCADE | A |
| `kind` | `TEXT` | 아니오 | `—` | variant 또는 event 종류; UTF-8 text; 길이 제한 없음 | 미확정(U) | CHECK (kind IN ('company','root_domain','subdomain','ip','cidr','icp','keyword')) | C |
| `company_id` | `BIGINT` | 예 | `—` | company row 식별자 또는 논리 참조; BIGINT | 일반(C) | FK companies(id) ON DELETE CASCADE | A |
| `domain` | `TEXT` | 예 | `—` | domain 값; UTF-8 text; 길이 제한 없음 | 민감 가능(A) | column-level 제약 없음 | C |
| `net` | `CIDR` | 예 | `—` | IP/CIDR 값; PostgreSQL network type | 민감 가능(A) | column-level 제약 없음 | C |
| `value` | `TEXT` | 예 | `—` | key가 해석하는 값; UTF-8 text; 길이 제한 없음 | 민감 가능(A) | column-level 제약 없음 | C |
| `source` | `TEXT` | 아니오 | `'auto'` | scope 항목 생성 경로: auto는 자산 삽입 자동 범위, agent는 add_task_scope, manual은 UI/사용자 입력이다. [의미 근거](evidence:db-field-task-scope-source) | 미확정(U) | CHECK (source IN ('auto','agent','manual')) | C |
| `reason` | `TEXT` | 예 | `—` | 판정 이유; UTF-8 text; 길이 제한 없음 | 미확정(U) | column-level 제약 없음 | C |
| `created_at` | `TIMESTAMPTZ` | 아니오 | `now()` | 생성 시각; 절대 시각; DB `now()` 또는 앱 입력 | 일반(C) | column-level 제약 없음 | C |

**명시 index와 비용** · `uq_task_scope_v2` — task scope identity dedupe; insert writer의 `ON CONFLICT DO NOTHING`은 중복을 no-op 처리; same-name stale index definition은 startup이 수리하지 않음<br>`idx_ts_domain` — 해당 equality/join/filter/정렬의 planner candidate; 실제 query plan·효과 미관찰; partial predicate 밖 row에는 미적용<br>`idx_ts_net` — CIDR containment/overlap 조회의 planner candidate; 실제 query plan 미관찰; GiST 유지 비용; partial predicate 밖 row에는 미적용<br>`idx_ts_company` — 해당 equality/join/filter/정렬의 planner candidate; 실제 query plan·효과 미관찰; partial predicate 밖 row에는 미적용

<a id="semantic-agents"></a>
## `agents`

agent role, prompt/profile binding, turn/time limit, tool mode와 trigger 실행 정책을 저장한다.

**정적 접근** · reader: `db/config.go`, `db/db.go` · writer: `db/config.go`, `db/db.go`, `server/finding_retests.go`

**transaction·retention** · FK delete: SET NULL; schema에 TTL/partition 자동 보존 정책 없음; handler/cleanup/archive 삭제 경로와 운영 보존기간은 별도 확인(U)

**정확한 production SQL** · [symbol·verb·query access matrix](access-matrix.md#access-agents)

`trigger_run_mode`/`trigger_merge_mode` CHECK는 fresh table에는 있지만 legacy ADD COLUMN 경로에는 없다. 아래 제약 열은 fresh 최종 선언이며 upgraded DB 전역에 보편적으로 적용된다고 단정하지 않는다. [fresh/upgrade 경계](functions-triggers.md#schema-migrations)

| 필드 | 타입 | null | default | 의미·encoding | 민감도 | 제약 | 판정 |
|---|---|---:|---|---|---|---|---|
| `id` | `BIGSERIAL` | 아니오 | `nextval(<implicit sequence>)` | row 식별자; BIGSERIAL | 일반(C) | PRIMARY KEY | C |
| `key` | `TEXT` | 아니오 | `—` | 설정 또는 registry key; UTF-8 text; 길이 제한 없음 | 미확정(U) | UNIQUE; CHECK (key ~ '^[a-z][a-z0-9_]*$') | C |
| `name` | `TEXT` | 아니오 | `—` | 표시 이름; UTF-8 text; 길이 제한 없음 | 미확정(U) | column-level 제약 없음 | C |
| `description` | `TEXT` | 예 | `—` | 설명; UTF-8 text; 길이 제한 없음 | 미확정(U) | column-level 제약 없음 | C |
| `role` | `TEXT` | 아니오 | `—` | agent 실행 역할 allowlist(goals, main, planner, worker, assistant)이며 relation/evidence role이 아니다.; UTF-8 text; 길이 제한 없음 [의미 근거](evidence:db-field-agents-role) | 미확정(U) | column-level 제약 없음 <br>[table-level: agents_role_ck](table-constraints.md#constraint-agents)| C |
| `builtin` | `BOOLEAN` | 아니오 | `true` | built-in 객체 여부; BOOLEAN | 일반(C) | column-level 제약 없음 | C |
| `enabled` | `BOOLEAN` | 아니오 | `true` | 활성화 여부; BOOLEAN | 일반(C) | column-level 제약 없음 | C |
| `llm_profile_id` | `BIGINT` | 예 | `—` | llm profile row 식별자 또는 논리 참조; BIGINT | 일반(C) | FK llm_profiles(id) ON DELETE SET NULL | A |
| `current_prompt_id` | `BIGINT` | 예 | `—` | current prompt row 식별자 또는 논리 참조; BIGINT | 일반(C) | fixed-source/startup intent: FK agent_prompts(id) ON DELETE SET NULL; same-name wrong definition은 `duplicate_object`로 미수리 가능 | A |
| `max_turns` | `INTEGER` | 아니오 | `0` | agent 최대 turn 수; INTEGER | 일반(C) | column-level 제약 없음 | C |
| `run_seconds` | `INTEGER` | 아니오 | `1200` | agent 실행 제한(초); INTEGER | 일반(C) | column-level 제약 없음 | C |
| `web_search` | `BOOLEAN` | 아니오 | `false` | agent web search 허용 여부; BOOLEAN | 일반(C) | column-level 제약 없음 | C |
| `interactive_shell` | `BOOLEAN` | 아니오 | `false` | agent interactive shell 허용 여부; BOOLEAN | 일반(C) | column-level 제약 없음 | C |
| `wrapup_prompt` | `TEXT` | 아니오 | `''` | wrapup 시 사용할 prompt; UTF-8 text; 길이 제한 없음 | 미확정(U) | column-level 제약 없음 | A |
| `wrapup_max_turns` | `INTEGER` | 아니오 | `0` | wrapup prompt 최대 turn; INTEGER | 일반(C) | column-level 제약 없음 | A |
| `task_timeout_wrapup_prompt` | `TEXT` | 아니오 | `''` | task timeout wrapup 시 사용할 prompt; UTF-8 text; 길이 제한 없음 | 미확정(U) | column-level 제약 없음 | A |
| `task_timeout_wrapup_max_turns` | `INTEGER` | 아니오 | `0` | task timeout wrapup prompt 최대 turn; INTEGER | 일반(C) | column-level 제약 없음 | A |
| `trigger_run_mode` | `TEXT` | 아니오 | `'serial'` | agent trigger fire 실행 방식: serial은 agent별 1개씩, parallel은 각 fire를 별도 대화로 동시 실행한다. [의미 근거](evidence:db-field-agents-trigger-run-mode) | 미확정(U) | CHECK (trigger_run_mode IN ('serial','parallel')) | C |
| `trigger_merge_mode` | `TEXT` | 아니오 | `'all'` | serial trigger queue의 병합 정책: by_task, all, none이며 parallel에서는 무시된다. [의미 근거](evidence:db-field-agents-trigger-merge-mode) | 미확정(U) | CHECK (trigger_merge_mode IN ('by_task','all','none')) | C |
| `trigger_max_parallel` | `INTEGER` | 아니오 | `5` | parallel trigger mode의 agent별 동시 실행 상한이다. 0 이하이면 무제한으로 처리한다. [의미 근거](evidence:db-field-agents-trigger-max-parallel) | 일반(C) | column-level 제약 없음 | C |
| `created_at` | `TIMESTAMPTZ` | 아니오 | `now()` | 생성 시각; 절대 시각; DB `now()` 또는 앱 입력 | 일반(C) | column-level 제약 없음 | C |
| `updated_at` | `TIMESTAMPTZ` | 아니오 | `now()` | 마지막 변경 시각; 절대 시각; DB `now()` 또는 앱 입력 | 일반(C) | column-level 제약 없음 | C |

**명시 index와 비용** · `idx_agents_llm_profile` — 해당 equality/join/filter/정렬의 planner candidate; 실제 query plan·효과 미관찰; partial predicate 밖 row에는 미적용

<a id="semantic-agent-prompts"></a>
## `agent_prompts`

agent prompt template의 version history와 작성 메타데이터를 저장한다.

**정적 접근** · reader: `db/config.go` · writer: `db/config.go`, `server/finding_retests.go`

**transaction·retention** · FK delete: CASCADE; schema에 TTL/partition 자동 보존 정책 없음; handler/cleanup/archive 삭제 경로와 운영 보존기간은 별도 확인(U)

**정확한 production SQL** · [symbol·verb·query access matrix](access-matrix.md#access-agent-prompts)

| 필드 | 타입 | null | default | 의미·encoding | 민감도 | 제약 | 판정 |
|---|---|---:|---|---|---|---|---|
| `id` | `BIGSERIAL` | 아니오 | `nextval(<implicit sequence>)` | row 식별자; BIGSERIAL | 일반(C) | PRIMARY KEY | C |
| `agent_id` | `BIGINT` | 아니오 | `—` | agent row 식별자 또는 논리 참조; BIGINT | 일반(C) | FK agents(id) ON DELETE CASCADE <br>[table-level: UNIQUE(agent_id,version)](table-constraints.md#constraint-agent-prompts)| A |
| `version` | `INT` | 아니오 | `—` | version 번호; INT | 일반(C) | column-level 제약 없음 <br>[table-level: UNIQUE(agent_id,version)](table-constraints.md#constraint-agent-prompts)| C |
| `template_text` | `TEXT` | 아니오 | `—` | agent prompt template; UTF-8 text; 길이 제한 없음 | 비밀/원문(C) | column-level 제약 없음 | C |
| `note` | `TEXT` | 예 | `—` | 운영 메모; UTF-8 text; 길이 제한 없음 | 민감 가능(A) | column-level 제약 없음 | C |
| `updated_by` | `TEXT` | 예 | `—` | prompt version을 저장하거나 seed한 actor/source 표지(예: system 또는 요청자 값)다. [의미 근거](evidence:db-field-agent-prompts-updated-by) | 미확정(U) | column-level 제약 없음 | C |
| `created_at` | `TIMESTAMPTZ` | 아니오 | `now()` | 생성 시각; 절대 시각; DB `now()` 또는 앱 입력 | 일반(C) | column-level 제약 없음 | C |

**명시 index와 비용** · 명시 index 없음; PK/UNIQUE가 만드는 내부 index는 102개 분모에 포함하지 않음.

<a id="semantic-agent-prompt-vars"></a>
## `agent_prompt_vars`

agent prompt에서 치환할 변수의 설명·예시·source를 저장한다.

**정적 접근** · reader: `db/config.go` · writer: `db/db.go`

**transaction·retention** · FK delete: CASCADE; schema에 TTL/partition 자동 보존 정책 없음; handler/cleanup/archive 삭제 경로와 운영 보존기간은 별도 확인(U)

**정확한 production SQL** · [symbol·verb·query access matrix](access-matrix.md#access-agent-prompt-vars)

| 필드 | 타입 | null | default | 의미·encoding | 민감도 | 제약 | 판정 |
|---|---|---:|---|---|---|---|---|
| `id` | `BIGSERIAL` | 아니오 | `nextval(<implicit sequence>)` | row 식별자; BIGSERIAL | 일반(C) | PRIMARY KEY | C |
| `agent_id` | `BIGINT` | 아니오 | `—` | agent row 식별자 또는 논리 참조; BIGINT | 일반(C) | FK agents(id) ON DELETE CASCADE <br>[table-level: UNIQUE(agent_id,var_name)](table-constraints.md#constraint-agent-prompt-vars)| A |
| `var_name` | `TEXT` | 아니오 | `—` | Go text/template에서 {{.Name}} 형태로 참조하는 prompt 변수 이름이다. [의미 근거](evidence:db-field-agent-prompt-vars-var-name) | 미확정(U) | column-level 제약 없음 <br>[table-level: UNIQUE(agent_id,var_name)](table-constraints.md#constraint-agent-prompt-vars)| C |
| `description` | `TEXT` | 예 | `—` | 설명; UTF-8 text; 길이 제한 없음 | 미확정(U) | column-level 제약 없음 | C |
| `example` | `TEXT` | 예 | `—` | prompt preview에 쓰는 해당 변수의 예시 값이다. [의미 근거](evidence:db-field-agent-prompt-vars-example) | 미확정(U) | column-level 제약 없음 | C |
| `source` | `TEXT` | 아니오 | `—` | 변수 값의 공급 계층: exploration, runtime, distilled 중 하나다. [의미 근거](evidence:db-field-agent-prompt-vars-source) | 미확정(U) | CHECK (source IN ('exploration','runtime','distilled')) | C |

**명시 index와 비용** · 명시 index 없음; PK/UNIQUE가 만드는 내부 index는 102개 분모에 포함하지 않음.

<a id="semantic-mcp-servers"></a>
## `mcp_servers`

MCP transport/command/URL/environment와 활성화·TLS 완화 설정을 저장한다.

**정적 접근** · reader: `db/config.go` · writer: `db/config.go`, `db/db.go`

**transaction·retention** · FK delete 동작이 없거나 앱이 관계를 관리한다; schema에 TTL/partition 자동 보존 정책 없음; handler/cleanup/archive 삭제 경로와 운영 보존기간은 별도 확인(U)

**정확한 production SQL** · [symbol·verb·query access matrix](access-matrix.md#access-mcp-servers)

| 필드 | 타입 | null | default | 의미·encoding | 민감도 | 제약 | 판정 |
|---|---|---:|---|---|---|---|---|
| `id` | `BIGSERIAL` | 아니오 | `nextval(<implicit sequence>)` | row 식별자; BIGSERIAL | 일반(C) | PRIMARY KEY | C |
| `name` | `TEXT` | 아니오 | `—` | 표시 이름; UTF-8 text; 길이 제한 없음 | 미확정(U) | UNIQUE | C |
| `transport` | `TEXT` | 아니오 | `—` | MCP 연결 방식 선택자: stdio, http, sse다. [의미 근거](evidence:db-field-mcp-servers-transport) | 미확정(U) | CHECK (transport IN ('stdio','http','sse')) | C |
| `command` | `TEXT` | 예 | `—` | transport=stdio일 때 실행할 MCP server 프로그램/command이며 네트워크 transport에서는 비어 있을 수 있다. [의미 근거](evidence:db-field-mcp-servers-command) | 미확정(U) | column-level 제약 없음 | C |
| `args` | `JSONB` | 아니오 | `'[]'` | stdio MCP command 인자 JSON 배열; CLI flag/value에 credential이 들어갈 수 있음 [근거](evidence:sensitivity-mcp-args) | 민감 가능(A) | column-level 제약 없음 | A |
| `env` | `JSONB` | 아니오 | `'{}'` | MCP child process 환경 변수; 환경 변수 key/value object; 비밀 포함 가능 | 비밀/원문(C) | column-level 제약 없음 | A |
| `url` | `TEXT` | 예 | `—` | 대상 또는 endpoint URL; UTF-8 text; 길이 제한 없음 | 민감 가능(A) | column-level 제약 없음 | C |
| `enabled` | `BOOLEAN` | 아니오 | `true` | 활성화 여부; BOOLEAN | 일반(C) | column-level 제약 없음 | C |
| `insecure` | `BOOLEAN` | 아니오 | `false` | TLS 검증 완화 여부; BOOLEAN | 일반(C) | column-level 제약 없음 | C |
| `created_at` | `TIMESTAMPTZ` | 아니오 | `now()` | 생성 시각; 절대 시각; DB `now()` 또는 앱 입력 | 일반(C) | column-level 제약 없음 | C |
| `updated_at` | `TIMESTAMPTZ` | 아니오 | `now()` | 마지막 변경 시각; 절대 시각; DB `now()` 또는 앱 입력 | 일반(C) | column-level 제약 없음 | C |

**명시 index와 비용** · 명시 index 없음; PK/UNIQUE가 만드는 내부 index는 102개 분모에 포함하지 않음.

<a id="semantic-mcp-tools-cache"></a>
## `mcp_tools_cache`

MCP discovery 결과인 tool 이름·설명·input schema snapshot을 저장한다.

**정적 접근** · reader: `db/config.go` · writer: `db/config.go`

**transaction·retention** · FK delete: CASCADE; schema에 TTL/partition 자동 보존 정책 없음; handler/cleanup/archive 삭제 경로와 운영 보존기간은 별도 확인(U)

**정확한 production SQL** · [symbol·verb·query access matrix](access-matrix.md#access-mcp-tools-cache)

| 필드 | 타입 | null | default | 의미·encoding | 민감도 | 제약 | 판정 |
|---|---|---:|---|---|---|---|---|
| `id` | `BIGSERIAL` | 아니오 | `nextval(<implicit sequence>)` | row 식별자; BIGSERIAL | 일반(C) | PRIMARY KEY | C |
| `server_id` | `BIGINT` | 아니오 | `—` | server row 식별자 또는 논리 참조; BIGINT | 일반(C) | FK mcp_servers(id) ON DELETE CASCADE <br>[table-level: UNIQUE(server_id,tool_name)](table-constraints.md#constraint-mcp-tools-cache)| A |
| `tool_name` | `TEXT` | 아니오 | `—` | 원격 MCP tools/list에서 발견해 해당 server_id 아래 cache한 tool 이름이다. [의미 근거](evidence:db-field-mcp-tools-cache-tool-name) | 미확정(U) | column-level 제약 없음 <br>[table-level: UNIQUE(server_id,tool_name)](table-constraints.md#constraint-mcp-tools-cache)| C |
| `description` | `TEXT` | 예 | `—` | 설명; UTF-8 text; 길이 제한 없음 | 미확정(U) | column-level 제약 없음 | C |
| `schema` | `JSONB` | 예 | `—` | tool/MCP input JSON Schema; MCP tool input JSON Schema | 미확정(U) | column-level 제약 없음 | A |
| `discovered_at` | `TIMESTAMPTZ` | 아니오 | `now()` | MCP discovery 시각; 절대 시각; DB `now()` 또는 앱 입력 | 일반(C) | column-level 제약 없음 | C |

**명시 index와 비용** · 명시 index 없음; PK/UNIQUE가 만드는 내부 index는 102개 분모에 포함하지 않음.

<a id="semantic-agent-visibility"></a>
## `agent_visibility`

agent가 볼 MCP server/tool 또는 resource의 allow/deny override를 저장한다.

**정적 접근** · reader: `db/config.go` · writer: `db/config.go`

**transaction·retention** · FK delete: CASCADE; schema에 TTL/partition 자동 보존 정책 없음; handler/cleanup/archive 삭제 경로와 운영 보존기간은 별도 확인(U)

**정확한 production SQL** · [symbol·verb·query access matrix](access-matrix.md#access-agent-visibility)

| 필드 | 타입 | null | default | 의미·encoding | 민감도 | 제약 | 판정 |
|---|---|---:|---|---|---|---|---|
| `agent_id` | `BIGINT` | 아니오 | `—` | agent row 식별자 또는 논리 참조; BIGINT | 일반(C) | FK agents(id) ON DELETE CASCADE <br>[table-level: composite PK](table-constraints.md#constraint-agent-visibility)| A |
| `resource_kind` | `TEXT` | 아니오 | `—` | agent visibility 대상 종류이며 현재 schema allowlist는 mcp 하나다. [의미 근거](evidence:db-field-agent-visibility-resource-kind) | 미확정(U) | CHECK (resource_kind IN ('mcp')) <br>[table-level: composite PK](table-constraints.md#constraint-agent-visibility)| C |
| `resource_id` | `BIGINT` | 아니오 | `—` | resource_kind=mcp인 가시성 행에서 노출 대상 MCP server의 id다. agent별/자원별 조회와 삭제가 같은 (kind, resource_id)를 사용한다. 한계: mcp_servers(id) FK가 없어 삭제 정합성은 application 경로에 의존한다. [의미 근거](evidence:db-field-agent-visibility-resource-id) | 일반(C) | column-level 제약 없음 <br>[table-level: composite PK](table-constraints.md#constraint-agent-visibility)| C |
| `mcp_tool_name` | `TEXT` | 아니오 | `''` | 복합 PK의 MCP tool 세분화 자리지만 현재 production visibility writer/reader는 server 단위 resource_id만 사용하고 이 값은 기본 빈 문자열이다. tool-level 의미/consumer는 구현되지 않았다. [의미 근거](evidence:db-field-agent-visibility-mcp-tool-name) | 미확정(U) | column-level 제약 없음 <br>[table-level: composite PK](table-constraints.md#constraint-agent-visibility)| C |
| `enabled` | `BOOLEAN` | 아니오 | `true` | 활성화 여부; BOOLEAN | 일반(C) | column-level 제약 없음 | C |
| `created_at` | `TIMESTAMPTZ` | 아니오 | `now()` | 생성 시각; 절대 시각; DB `now()` 또는 앱 입력 | 일반(C) | column-level 제약 없음 | C |

**명시 index와 비용** · `idx_vis_resource` — 해당 equality/join/filter/정렬의 planner candidate; 실제 query plan·효과 미관찰

<a id="semantic-agent-skill-visibility"></a>
## `agent_skill_visibility`

agent별 skill 노출 override를 저장한다.

**정적 접근** · reader: `db/config.go` · writer: `db/config.go`, `db/db.go`

**transaction·retention** · FK delete: CASCADE; schema에 TTL/partition 자동 보존 정책 없음; handler/cleanup/archive 삭제 경로와 운영 보존기간은 별도 확인(U)

**정확한 production SQL** · [symbol·verb·query access matrix](access-matrix.md#access-agent-skill-visibility)

| 필드 | 타입 | null | default | 의미·encoding | 민감도 | 제약 | 판정 |
|---|---|---:|---|---|---|---|---|
| `agent_id` | `BIGINT` | 아니오 | `—` | agent row 식별자 또는 논리 참조; BIGINT | 일반(C) | FK agents(id) ON DELETE CASCADE <br>[table-level: composite PK](table-constraints.md#constraint-agent-skill-visibility)| A |
| `skill_name` | `TEXT` | 아니오 | `—` | agent에게 보이도록 허용한 filesystem skill의 이름이다. [의미 근거](evidence:db-field-agent-skill-visibility-skill-name) | 미확정(U) | column-level 제약 없음 <br>[table-level: composite PK](table-constraints.md#constraint-agent-skill-visibility)| C |
| `enabled` | `BOOLEAN` | 아니오 | `true` | 활성화 여부; BOOLEAN | 일반(C) | column-level 제약 없음 | C |
| `created_at` | `TIMESTAMPTZ` | 아니오 | `now()` | 생성 시각; 절대 시각; DB `now()` 또는 앱 입력 | 일반(C) | column-level 제약 없음 | C |

**명시 index와 비용** · `idx_askv_skill` — 해당 equality/join/filter/정렬의 planner candidate; 실제 query plan·효과 미관찰

<a id="semantic-skill-usage"></a>
## `skill_usage`

skill 탐색/호출의 task·intent·session attribution과 발견 여부를 기록한다.

**정적 접근** · reader: `db/skill_usage.go`, `db/task_archives.go` · writer: `db/skill_usage.go`, `db/task_archives_restore.go`

**transaction·retention** · FK delete 동작이 없거나 앱이 관계를 관리한다; schema에 TTL/partition 자동 보존 정책 없음; handler/cleanup/archive 삭제 경로와 운영 보존기간은 별도 확인(U)

**정확한 production SQL** · [symbol·verb·query access matrix](access-matrix.md#access-skill-usage)

| 필드 | 타입 | null | default | 의미·encoding | 민감도 | 제약 | 판정 |
|---|---|---:|---|---|---|---|---|
| `id` | `BIGSERIAL` | 아니오 | `nextval(<implicit sequence>)` | row 식별자; BIGSERIAL | 일반(C) | PRIMARY KEY | C |
| `ts` | `TIMESTAMPTZ` | 아니오 | `now()` | event 시각; 절대 시각; DB `now()` 또는 앱 입력 | 일반(C) | column-level 제약 없음 | C |
| `skill` | `TEXT` | 아니오 | `—` | Skill 호출에서 모델이 요청한 skill 이름이다. found=false면 존재하지 않은 이름도 gap 분석을 위해 남긴다. [의미 근거](evidence:db-field-skill-usage-skill) | 미확정(U) | column-level 제약 없음 | C |
| `agent_key` | `TEXT` | 예 | `—` | agent registry key; UTF-8 text; 길이 제한 없음 | 미확정(U) | column-level 제약 없음 | C |
| `task_id` | `BIGINT` | 예 | `—` | Skill 호출이 귀속된 task registry id다. chat/non-task 실행은 RunInfo의 0을 SQL NULL로 저장하며 task 삭제 뒤에도 통계를 보존한다. [의미 근거](evidence:db-field-skill-usage-task-id) | 일반(C) | column-level 제약 없음 | C |
| `exploration_id` | `BIGINT` | 예 | `—` | Skill 호출이 귀속된 exploration id다. RunInfo에서 알 수 없으면 0을 SQL NULL로 저장한다. [의미 근거](evidence:db-field-skill-usage-exploration-id) | 일반(C) | column-level 제약 없음 | C |
| `intent_id` | `BIGINT` | 예 | `—` | Skill 호출이 귀속된 worker intent node id다. planner/mainagent/chat 또는 알 수 없는 실행은 0을 SQL NULL로 저장한다. [의미 근거](evidence:db-field-skill-usage-intent-id) | 일반(C) | column-level 제약 없음 | C |
| `session_id` | `TEXT` | 예 | `—` | agent/LLM session 식별자; UTF-8 text; 길이 제한 없음 | 일반(C) | column-level 제약 없음 | C |
| `args_len` | `INTEGER` | 아니오 | `0` | Skill 호출 input args 문자열의 Go len 값, 즉 UTF-8 byte 길이다. 본문 자체는 저장하지 않는다. [의미 근거](evidence:db-field-skill-usage-args-len) | 일반(C) | column-level 제약 없음 | C |
| `found` | `BOOLEAN` | 아니오 | `true` | skill 발견 여부; BOOLEAN | 일반(C) | column-level 제약 없음 | C |

**명시 index와 비용** · `idx_skill_usage_skill` — 최근 순서 조회의 planner candidate; 실제 query plan·효과 미관찰<br>`idx_skill_usage_task` — 해당 equality/join/filter/정렬의 planner candidate; 실제 query plan·효과 미관찰

<a id="semantic-tool-usage"></a>
## `tool_usage`

tool 호출의 task·intent·session attribution을 기록한다.

**정적 접근** · reader: `db/task_archives.go`, `db/tool_usage.go` · writer: `db/task_archives_restore.go`, `db/tool_usage.go`

**transaction·retention** · FK delete 동작이 없거나 앱이 관계를 관리한다; schema에 TTL/partition 자동 보존 정책 없음; handler/cleanup/archive 삭제 경로와 운영 보존기간은 별도 확인(U)

**정확한 production SQL** · [symbol·verb·query access matrix](access-matrix.md#access-tool-usage)

| 필드 | 타입 | null | default | 의미·encoding | 민감도 | 제약 | 판정 |
|---|---|---:|---|---|---|---|---|
| `id` | `BIGSERIAL` | 아니오 | `nextval(<implicit sequence>)` | row 식별자; BIGSERIAL | 일반(C) | PRIMARY KEY | C |
| `ts` | `TIMESTAMPTZ` | 아니오 | `now()` | event 시각; 절대 시각; DB `now()` 또는 앱 입력 | 일반(C) | column-level 제약 없음 | C |
| `tool_key` | `TEXT` | 아니오 | `—` | tool registry key; UTF-8 text; 길이 제한 없음 | 미확정(U) | column-level 제약 없음 | C |
| `agent_key` | `TEXT` | 예 | `—` | agent registry key; UTF-8 text; 길이 제한 없음 | 미확정(U) | column-level 제약 없음 | C |
| `task_id` | `BIGINT` | 예 | `—` | catalog tool 호출이 귀속된 task registry id다. RunInfo의 0은 SQL NULL이며 task 삭제 뒤에도 통계를 보존한다. [의미 근거](evidence:db-field-tool-usage-task-id) | 일반(C) | column-level 제약 없음 | C |
| `exploration_id` | `BIGINT` | 예 | `—` | catalog tool 호출이 귀속된 exploration id다. RunInfo에서 알 수 없으면 0을 SQL NULL로 저장한다. [의미 근거](evidence:db-field-tool-usage-exploration-id) | 일반(C) | column-level 제약 없음 | C |
| `intent_id` | `BIGINT` | 예 | `—` | catalog tool 호출이 귀속된 worker intent node id다. planner/mainagent/chat 또는 알 수 없는 실행은 0을 SQL NULL로 저장한다. [의미 근거](evidence:db-field-tool-usage-intent-id) | 일반(C) | column-level 제약 없음 | C |
| `session_id` | `TEXT` | 예 | `—` | agent/LLM session 식별자; UTF-8 text; 길이 제한 없음 | 일반(C) | column-level 제약 없음 | C |

**명시 index와 비용** · `idx_tool_usage_tool` — 최근 순서 조회의 planner candidate; 실제 query plan·효과 미관찰<br>`idx_tool_usage_task` — 해당 equality/join/filter/정렬의 planner candidate; 실제 query plan·효과 미관찰

<a id="semantic-tools"></a>
## `tools`

built-in/custom tool의 schema, agent visibility, exec binding과 deferred flag를 저장한다.

**정적 접근** · reader: `db/config.go`, `db/tools.go` · writer: `db/tools.go`, `server/finding_traffic.go`, `server/finding_workflow.go`

**transaction·retention** · FK delete 동작이 없거나 앱이 관계를 관리한다; schema에 TTL/partition 자동 보존 정책 없음; handler/cleanup/archive 삭제 경로와 운영 보존기간은 별도 확인(U)

**정확한 production SQL** · [symbol·verb·query access matrix](access-matrix.md#access-tools)

| 필드 | 타입 | null | default | 의미·encoding | 민감도 | 제약 | 판정 |
|---|---|---:|---|---|---|---|---|
| `key` | `TEXT` | 아니오 | `—` | 설정 또는 registry key; UTF-8 text; 길이 제한 없음 | 미확정(U) | PRIMARY KEY | C |
| `system` | `BOOLEAN` | 아니오 | `true` | system tool 여부; BOOLEAN | 일반(C) | column-level 제약 없음 | C |
| `description` | `TEXT` | 아니오 | `''` | 설명; UTF-8 text; 길이 제한 없음 | 미확정(U) | column-level 제약 없음 | C |
| `schema` | `JSONB` | 아니오 | `'{}'` | tool/MCP input JSON Schema; tool input JSON Schema | 미확정(U) | column-level 제약 없음 | A |
| `agents` | `JSONB` | 아니오 | `'[]'` | agents 값; 더 좁은 도메인 의미는 소비 코드에 분산됨; 노출 agent key 배열 | 미확정(U) | column-level 제약 없음 | A |
| `enabled` | `BOOLEAN` | 아니오 | `true` | 활성화 여부; BOOLEAN | 일반(C) | column-level 제약 없음 | C |
| `kind` | `TEXT` | 아니오 | `'builtin'` | tool kind를 구분한다. builtin은 seeded platform tool, command/script/http는 실행 가능한 custom tool, shell은 Bash 환경 선언·hint이며 callable custom dispatch 대상이 아니다. 한계: DB CHECK가 없고 낮은 수준 DB writer는 임의 문자열을 저장할 수 있다. [의미 근거](evidence:db-field-tools-kind) | 미확정(U) | column-level 제약 없음 | P |
| `exec` | `JSONB` | 아니오 | `'{}'` | executable custom tool의 command template, Python script 또는 HTTP URL/header/body/proxy spec이다. shell row는 환경 metadata라 dispatcher가 exec를 쓰지 않지만 DB는 임의 JSON을 보존할 수 있다. Authorization/Bearer 같은 credential-bearing 구성을 저장할 수 있음 [근거](evidence:sensitivity-tool-exec) | 민감 가능(A) | JSON 구조의 DB CHECK 없음 | A |
| `deferred` | `BOOLEAN` | 아니오 | `false` | 지연 실행 tool 여부; BOOLEAN | 일반(C) | column-level 제약 없음 | C |
| `updated_at` | `TIMESTAMPTZ` | 아니오 | `now()` | 마지막 변경 시각; 절대 시각; DB `now()` 또는 앱 입력 | 일반(C) | column-level 제약 없음 | C |

**명시 index와 비용** · 명시 index 없음; PK/UNIQUE가 만드는 내부 index는 102개 분모에 포함하지 않음.

<a id="semantic-conversations"></a>
## `conversations`

대화형 agent session의 agent/profile binding, title과 pin 시각을 저장한다.

**정적 접근** · reader: `db/config.go`, `db/conversation.go`, `db/intercept.go`, `db/intercept_detail.go`, `db/side_questions.go` · writer: `db/conversation.go`, `db/finding_retests.go`

**transaction·retention** · FK delete: SET NULL; schema에 TTL/partition 자동 보존 정책 없음; handler/cleanup/archive 삭제 경로와 운영 보존기간은 별도 확인(U)

**정확한 production SQL** · [symbol·verb·query access matrix](access-matrix.md#access-conversations)

| 필드 | 타입 | null | default | 의미·encoding | 민감도 | 제약 | 판정 |
|---|---|---:|---|---|---|---|---|
| `id` | `BIGSERIAL` | 아니오 | `nextval(<implicit sequence>)` | row 식별자; BIGSERIAL | 일반(C) | PRIMARY KEY | C |
| `agent_key` | `TEXT` | 아니오 | `—` | agent registry key; UTF-8 text; 길이 제한 없음 | 미확정(U) | column-level 제약 없음 | C |
| `title` | `TEXT` | 아니오 | `''` | 대화 목록/UI에 표시하는 conversation 제목이다. [의미 근거](evidence:db-field-conversations-title) | 미확정(U) | column-level 제약 없음 | C |
| `llm_profile_id` | `BIGINT` | 예 | `—` | llm profile row 식별자 또는 논리 참조; BIGINT | 일반(C) | FK llm_profiles(id) ON DELETE SET NULL | A |
| `pinned_at` | `TIMESTAMPTZ` | 예 | `—` | UI pin 시각; 절대 시각; DB `now()` 또는 앱 입력 | 일반(C) | column-level 제약 없음 | C |
| `created_at` | `TIMESTAMPTZ` | 아니오 | `now()` | 생성 시각; 절대 시각; DB `now()` 또는 앱 입력 | 일반(C) | column-level 제약 없음 | C |
| `updated_at` | `TIMESTAMPTZ` | 아니오 | `now()` | 마지막 변경 시각; 절대 시각; DB `now()` 또는 앱 입력 | 일반(C) | column-level 제약 없음 | C |

**명시 index와 비용** · `idx_conversations_llm_profile` — 해당 equality/join/filter/정렬의 planner candidate; 실제 query plan·효과 미관찰; partial predicate 밖 row에는 미적용<br>`idx_conversations_pinned` — 최근 순서 조회의 planner candidate; 실제 query plan·효과 미관찰; partial predicate 밖 row에는 미적용

<a id="semantic-conversation-activities"></a>
## `conversation_activities`

conversation worker/tool timeline, 오류·token accounting을 저장한다.

**정적 접근** · reader: `db/conversation.go`, `db/intercept_execution.go` · writer: `db/conversation.go`, `db/finding_retests.go`

**transaction·retention** · FK delete: CASCADE; schema에 TTL/partition 자동 보존 정책 없음; handler/cleanup/archive 삭제 경로와 운영 보존기간은 별도 확인(U)

**정확한 production SQL** · [symbol·verb·query access matrix](access-matrix.md#access-conversation-activities)

| 필드 | 타입 | null | default | 의미·encoding | 민감도 | 제약 | 판정 |
|---|---|---:|---|---|---|---|---|
| `id` | `BIGSERIAL` | 아니오 | `nextval(<implicit sequence>)` | row 식별자; BIGSERIAL | 일반(C) | PRIMARY KEY | C |
| `conversation_id` | `BIGINT` | 아니오 | `—` | conversation row 식별자 또는 논리 참조; BIGINT | 일반(C) | FK conversations(id) ON DELETE CASCADE | A |
| `worker` | `TEXT` | 예 | `—` | worker 식별자; UTF-8 text; 길이 제한 없음 | 미확정(U) | column-level 제약 없음 | C |
| `kind` | `TEXT` | 예 | `—` | 대화 trace의 human/agent execution event 종류다. 현재 production writer는 user, text, thinking, tool_use, tool_result, usage, result, intercept_request를 기록한다. 한계: AppendConvActivity는 임의 kind를 받고 DB CHECK가 없으므로 닫힌 enum 보장은 아니다. [의미 근거](evidence:db-field-conversation-activities-kind) | 미확정(U) | column-level 제약 없음 | P |
| `tool` | `TEXT` | 예 | `—` | tool 이름; UTF-8 text; 길이 제한 없음 | 미확정(U) | column-level 제약 없음 | C |
| `tool_use_id` | `TEXT` | 예 | `—` | 모델 tool-call 식별자; UTF-8 text; 길이 제한 없음 | 일반(C) | column-level 제약 없음 | C |
| `is_error` | `BOOLEAN` | 아니오 | `false` | 실행 오류 여부; BOOLEAN | 일반(C) | column-level 제약 없음 | C |
| `summary` | `TEXT` | 예 | `—` | 요약; UTF-8 text; 길이 제한 없음 | 미확정(U) | column-level 제약 없음 | C |
| `detail` | `TEXT` | 예 | `—` | 상세 내용; UTF-8 text; 길이 제한 없음 | 민감 가능(A) | column-level 제약 없음 | C |
| `input_tokens` | `INTEGER` | 예 | `—` | 입력 token 수; INTEGER | 일반(C) | column-level 제약 없음 | C |
| `output_tokens` | `INTEGER` | 예 | `—` | 출력 token 수; INTEGER | 일반(C) | column-level 제약 없음 | C |
| `cache_read_tokens` | `INTEGER` | 예 | `—` | cache read token 수; INTEGER | 일반(C) | column-level 제약 없음 | C |
| `cache_write_tokens` | `INTEGER` | 예 | `—` | cache write token 수; INTEGER | 일반(C) | column-level 제약 없음 | C |
| `created_at` | `TIMESTAMPTZ` | 아니오 | `now()` | 생성 시각; 절대 시각; DB `now()` 또는 앱 입력 | 일반(C) | column-level 제약 없음 | C |

**명시 index와 비용** · `idx_conv_act` — 해당 equality/join/filter/정렬의 planner candidate; 실제 query plan·효과 미관찰<br>`idx_conv_act_tool_call` — 해당 equality/join/filter/정렬의 planner candidate; 실제 query plan·효과 미관찰; partial predicate 밖 row에는 미적용

<a id="semantic-agent-triggers"></a>
## `agent_triggers`

agent의 interval·event trigger 조건, message와 마지막 실행 시각을 저장한다.

**정적 접근** · reader: `db/triggers.go` · writer: `db/triggers.go`

**transaction·retention** · FK delete 동작이 없거나 앱이 관계를 관리한다; schema에 TTL/partition 자동 보존 정책 없음; handler/cleanup/archive 삭제 경로와 운영 보존기간은 별도 확인(U)

**정확한 production SQL** · [symbol·verb·query access matrix](access-matrix.md#access-agent-triggers)

| 필드 | 타입 | null | default | 의미·encoding | 민감도 | 제약 | 판정 |
|---|---|---:|---|---|---|---|---|
| `id` | `BIGSERIAL` | 아니오 | `nextval(<implicit sequence>)` | row 식별자; BIGSERIAL | 일반(C) | PRIMARY KEY | C |
| `agent_key` | `TEXT` | 아니오 | `—` | agent registry key; UTF-8 text; 길이 제한 없음 | 미확정(U) | column-level 제약 없음 | C |
| `enabled` | `BOOLEAN` | 아니오 | `true` | 활성화 여부; BOOLEAN | 일반(C) | column-level 제약 없음 | C |
| `interval_sec` | `INTEGER` | 아니오 | `0` | trigger interval(초); INTEGER | 일반(C) | column-level 제약 없음 | C |
| `on_finding` | `BOOLEAN` | 아니오 | `false` | 새 finding event가 persisted watermark를 넘을 때 이 agent trigger를 발화할지 정한다. [의미 근거](evidence:db-field-agent-triggers-on-finding) | 일반(C) | column-level 제약 없음 | C |
| `on_goal_met` | `BOOLEAN` | 아니오 | `false` | goal이 met로 바뀐 event에 이 agent trigger를 발화할지 정한다. [의미 근거](evidence:db-field-agent-triggers-on-goal-met) | 일반(C) | column-level 제약 없음 | C |
| `on_task_timeout` | `BOOLEAN` | 아니오 | `false` | task가 timeout terminal state에 새로 진입했을 때 trigger를 발화할지 정한다. [의미 근거](evidence:db-field-agent-triggers-on-task-timeout) | 일반(C) | column-level 제약 없음 | C |
| `on_tool_call` | `BOOLEAN` | 아니오 | `false` | 선택한 tool_names의 tool_result event에 trigger를 발화할지 정한다. [의미 근거](evidence:db-field-agent-triggers-on-tool-call) | 일반(C) | column-level 제약 없음 | C |
| `on_task_create` | `BOOLEAN` | 아니오 | `false` | 새 task id가 persisted watermark를 넘을 때 trigger를 발화할지 정한다. [의미 근거](evidence:db-field-agent-triggers-on-task-create) | 일반(C) | column-level 제약 없음 | C |
| `interval_message` | `TEXT` | 아니오 | `''` | interval trigger message; UTF-8 text; 길이 제한 없음 | 미확정(U) | column-level 제약 없음 | A |
| `finding_message` | `TEXT` | 아니오 | `''` | finding trigger message; UTF-8 text; 길이 제한 없음 | 미확정(U) | column-level 제약 없음 | A |
| `goal_message` | `TEXT` | 아니오 | `''` | goal trigger message; UTF-8 text; 길이 제한 없음 | 미확정(U) | column-level 제약 없음 | A |
| `task_timeout_message` | `TEXT` | 아니오 | `''` | task timeout trigger message; UTF-8 text; 길이 제한 없음 | 미확정(U) | column-level 제약 없음 | A |
| `tool_call_message` | `TEXT` | 아니오 | `''` | tool call trigger message; UTF-8 text; 길이 제한 없음 | 미확정(U) | column-level 제약 없음 | A |
| `task_create_message` | `TEXT` | 아니오 | `''` | task create trigger message; UTF-8 text; 길이 제한 없음 | 미확정(U) | column-level 제약 없음 | A |
| `tool_names` | `TEXT` | 아니오 | `''` | on_tool_call trigger가 감시할 tool key 목록을 JSON text로 encode한 값이며 empty on_tool_call은 API가 거부한다. [의미 근거](evidence:db-field-agent-triggers-tool-names) | 미확정(U) | column-level 제약 없음 | C |
| `last_fire` | `TIMESTAMPTZ` | 예 | `—` | trigger 마지막 실행 시각; 절대 시각; DB `now()` 또는 앱 입력 | 일반(C) | column-level 제약 없음 | C |
| `created_at` | `TIMESTAMPTZ` | 아니오 | `now()` | 생성 시각; 절대 시각; DB `now()` 또는 앱 입력 | 일반(C) | column-level 제약 없음 | C |
| `updated_at` | `TIMESTAMPTZ` | 아니오 | `now()` | 마지막 변경 시각; 절대 시각; DB `now()` 또는 앱 입력 | 일반(C) | column-level 제약 없음 | C |

**명시 index와 비용** · `idx_agent_triggers_agent` — 해당 equality/join/filter/정렬의 planner candidate; 실제 query plan·효과 미관찰

<a id="semantic-scheduler-state"></a>
## `scheduler_state`

scheduler cursor/watermark 같은 작은 key/value 상태를 저장한다.

**정적 접근** · reader: `db/triggers.go` · writer: `db/triggers.go`

**transaction·retention** · FK delete 동작이 없거나 앱이 관계를 관리한다; schema에 TTL/partition 자동 보존 정책 없음; handler/cleanup/archive 삭제 경로와 운영 보존기간은 별도 확인(U)

**정확한 production SQL** · [symbol·verb·query access matrix](access-matrix.md#access-scheduler-state)

| 필드 | 타입 | null | default | 의미·encoding | 민감도 | 제약 | 판정 |
|---|---|---:|---|---|---|---|---|
| `key` | `TEXT` | 아니오 | `—` | 설정 또는 registry key; UTF-8 text; 길이 제한 없음 | 미확정(U) | PRIMARY KEY | C |
| `value` | `TEXT` | 아니오 | `''` | key가 해석하는 값; UTF-8 text; 길이 제한 없음 | 민감 가능(A) | column-level 제약 없음 | C |

**명시 index와 비용** · 명시 index 없음; PK/UNIQUE가 만드는 내부 index는 102개 분모에 포함하지 않음.

<a id="semantic-intercept-rules"></a>
## `intercept_rules`

tool-call 승인/차단 규칙의 match 조건, action과 timeout 결정을 저장한다.

**정적 접근** · reader: `db/db.go`, `db/intercept.go`, `db/intercept_detail.go` · writer: `db/db.go`, `db/intercept.go`

**transaction·retention** · FK delete 동작이 없거나 앱이 관계를 관리한다; schema에 TTL/partition 자동 보존 정책 없음; handler/cleanup/archive 삭제 경로와 운영 보존기간은 별도 확인(U)

**정확한 production SQL** · [symbol·verb·query access matrix](access-matrix.md#access-intercept-rules)

| 필드 | 타입 | null | default | 의미·encoding | 민감도 | 제약 | 판정 |
|---|---|---:|---|---|---|---|---|
| `id` | `BIGSERIAL` | 아니오 | `nextval(<implicit sequence>)` | row 식별자; BIGSERIAL | 일반(C) | PRIMARY KEY | C |
| `name` | `TEXT` | 아니오 | `—` | 표시 이름; UTF-8 text; 길이 제한 없음 | 미확정(U) | column-level 제약 없음 | C |
| `enabled` | `BOOLEAN` | 아니오 | `true` | 활성화 여부; BOOLEAN | 일반(C) | column-level 제약 없음 | C |
| `priority` | `INTEGER` | 아니오 | `0` | 우선순위; INTEGER | 일반(C) | column-level 제약 없음 | C |
| `match_target` | `TEXT` | 아니오 | `—` | 규칙이 비교할 대상: tool_name 또는 직렬화된 tool_input이다. [의미 근거](evidence:db-field-intercept-rules-match-target) | 미확정(U) | CHECK (match_target IN ('tool_name', 'tool_input')) | C |
| `match_type` | `TEXT` | 아니오 | `—` | pattern 해석 방식: literal string 또는 regex다. [의미 근거](evidence:db-field-intercept-rules-match-type) | 미확정(U) | CHECK (match_type IN ('string', 'regex')) | C |
| `pattern` | `TEXT` | 아니오 | `—` | match pattern; UTF-8 text; 길이 제한 없음 | 미확정(U) | column-level 제약 없음 | C |
| `action` | `TEXT` | 아니오 | `—` | rule 결정; UTF-8 text; 길이 제한 없음 | 미확정(U) | CHECK (action IN ('allow', 'deny', 'ask')) | C |
| `message` | `TEXT` | 아니오 | `''` | rule 또는 trigger message; UTF-8 text; 길이 제한 없음 | 민감 가능(A) | column-level 제약 없음 | C |
| `timeout_enabled` | `BOOLEAN` | 아니오 | `true` | 승인 timeout 사용 여부; BOOLEAN | 일반(C) | column-level 제약 없음 | C |
| `timeout_seconds` | `INTEGER` | 아니오 | `60` | timeout 길이(초); INTEGER | 일반(C) | column-level 제약 없음 | C |
| `timeout_action` | `TEXT` | 아니오 | `'deny'` | 승인 timeout 시 결정; UTF-8 text; 길이 제한 없음 | 미확정(U) | column-level 제약 없음 | C |
| `created_at` | `TIMESTAMPTZ` | 아니오 | `NOW()` | 생성 시각; 절대 시각; DB `now()` 또는 앱 입력 | 일반(C) | column-level 제약 없음 | C |
| `updated_at` | `TIMESTAMPTZ` | 아니오 | `NOW()` | 마지막 변경 시각; 절대 시각; DB `now()` 또는 앱 입력 | 일반(C) | column-level 제약 없음 | C |

**명시 index와 비용** · 명시 index 없음; PK/UNIQUE가 만드는 내부 index는 102개 분모에 포함하지 않음.

<a id="semantic-intercept-pending"></a>
## `intercept_pending`

승인 대기 tool call의 원 입력·결정·감사 기록과 conversation/task 문맥을 저장한다.

**정적 접근** · reader: `db/intercept.go`, `db/intercept_detail.go`, `db/task_archives.go` · writer: `db/intercept.go`, `db/intercept_detail.go`, `db/task_archives_restore.go`

**transaction·retention** · FK delete: CASCADE, SET NULL; schema에 TTL/partition 자동 보존 정책 없음; handler/cleanup/archive 삭제 경로와 운영 보존기간은 별도 확인(U)

**정확한 production SQL** · [symbol·verb·query access matrix](access-matrix.md#access-intercept-pending)

`decision_source` 빈 값은 startup마다 rule/model/unknown으로 반복 backfill된다. Field default가 빈 문자열이라도 startup 후 기존 row의 실제 값은 이 CASE 조건에 달려 있다. [migration 경계](functions-triggers.md#schema-migrations)

| 필드 | 타입 | null | default | 의미·encoding | 민감도 | 제약 | 판정 |
|---|---|---:|---|---|---|---|---|
| `id` | `BIGSERIAL` | 아니오 | `nextval(<implicit sequence>)` | row 식별자; BIGSERIAL | 일반(C) | PRIMARY KEY | C |
| `rule_id` | `BIGINT` | 예 | `—` | rule row 식별자 또는 논리 참조; BIGINT | 일반(C) | FK intercept_rules(id) ON DELETE SET NULL | A |
| `conversation_id` | `BIGINT` | 예 | `—` | conversation row 식별자 또는 논리 참조; BIGINT | 일반(C) | FK conversations(id) ON DELETE CASCADE | A |
| `task_id` | `TEXT` | 예 | `—` | 승인 대기 tool call이 속한 task registry id를 십진 문자열로 저장한다. background/conversation-only 요청은 NULL이며 실행 재구성 시 양의 int64로 parse해 live task를 확인한다. 한계: TEXT이고 FK가 없어 형식·존재는 DB가 강제하지 않는다. [의미 근거](evidence:db-field-intercept-pending-task-id) | 일반(C) | column-level 제약 없음 | C |
| `agent_name` | `TEXT` | 아니오 | `''` | 승인 대기 중인 tool call을 요청한 agent 이름/key 표지다. [의미 근거](evidence:db-field-intercept-pending-agent-name) | 미확정(U) | column-level 제약 없음 | C |
| `tool_name` | `TEXT` | 아니오 | `—` | 승인 판단 대상 tool call의 tool 이름이다. [의미 근거](evidence:db-field-intercept-pending-tool-name) | 미확정(U) | column-level 제약 없음 | C |
| `tool_input` | `JSONB` | 아니오 | `'{}'` | 승인 대상 tool 입력; 원 tool-call 입력 object | 비밀/원문(C) | column-level 제약 없음 | A |
| `status` | `TEXT` | 아니오 | `'pending'` | lifecycle 상태; UTF-8 text; 길이 제한 없음 | 미확정(U) | CHECK (status IN ('pending', 'allowed', 'denied', 'timeout')) | C |
| `reason` | `TEXT` | 아니오 | `''` | 판정 이유; UTF-8 text; 길이 제한 없음 | 미확정(U) | column-level 제약 없음 | C |
| `decided_at` | `TIMESTAMPTZ` | 예 | `—` | 승인 결정 시각; 절대 시각; DB `now()` 또는 앱 입력 | 일반(C) | column-level 제약 없음 | C |
| `created_at` | `TIMESTAMPTZ` | 아니오 | `NOW()` | 생성 시각; 절대 시각; DB `now()` 또는 앱 입력 | 일반(C) | column-level 제약 없음 | C |
| `audit` | `JSONB` | 예 | `—` | 승인/차단 감사 정보; 결정 source/actor 등 감사 metadata; writer별 세부 shape | 민감 가능(A) | column-level 제약 없음 | A |
| `decision_source` | `TEXT` | 아니오 | `''` | 승인 결과 출처다. legacy backfill은 rule, model, unknown으로 분류한다. [의미 근거](evidence:db-field-intercept-pending-decision-source) | 미확정(U) | column-level 제약 없음 | C |

**명시 index와 비용** · `idx_intercept_pending_status` — 최근 순서 조회의 planner candidate; 실제 query plan·효과 미관찰<br>`idx_intercept_pending_task` — 최근 순서 조회의 planner candidate; 실제 query plan·효과 미관찰

<a id="semantic-findings"></a>
## `findings`

검증된 finding의 분류·severity·근거·보고서, asset 연결과 evidence version을 저장한다.

**정적 접근** · reader: `db/chat_mentions.go`, `db/exploration.go`, `db/finding_assets.go`, `db/finding_retests.go`, `db/finding_traffic.go`, `db/finding_traffic_archive.go`, `db/findings.go`, `db/notification.go`, `db/task_archives.go`, `evidence/store.go` · writer: `db/exploration.go`, `db/finding_traffic.go`, `db/findings.go`, `db/notification.go`, `db/task_archives_restore.go`, `db/tasks.go`

**transaction·retention** · FK delete: SET NULL; schema에 TTL/partition 자동 보존 정책 없음; handler/cleanup/archive 삭제 경로와 운영 보존기간은 별도 확인(U)

**정확한 production SQL** · [symbol·verb·query access matrix](access-matrix.md#access-findings)

| 필드 | 타입 | null | default | 의미·encoding | 민감도 | 제약 | 판정 |
|---|---|---:|---|---|---|---|---|
| `id` | `BIGSERIAL` | 아니오 | `nextval(<implicit sequence>)` | row 식별자; BIGSERIAL | 일반(C) | PRIMARY KEY | C |
| `task_id` | `BIGINT` | 예 | `—` | task row 식별자 또는 논리 참조; BIGINT | 일반(C) | FK tasks(id) ON DELETE SET NULL | A |
| `node_id` | `BIGINT` | 예 | `—` | node row 식별자 또는 논리 참조; BIGINT | 일반(C) | FK exploration_nodes(id) ON DELETE SET NULL | A |
| `vulnclass` | `TEXT` | 아니오 | `''` | 취약점 분류; UTF-8 text; 길이 제한 없음 | 미확정(U) | column-level 제약 없음 | C |
| `name` | `TEXT` | 아니오 | `''` | 표시 이름; UTF-8 text; 길이 제한 없음 | 미확정(U) | column-level 제약 없음 | C |
| `severity` | `TEXT` | 아니오 | `''` | finding severity; UTF-8 text; 길이 제한 없음 | 미확정(U) | column-level 제약 없음 | C |
| `summary` | `TEXT` | 아니오 | `''` | 요약; UTF-8 text; 길이 제한 없음 | 미확정(U) | column-level 제약 없음 | C |
| `evidence` | `TEXT` | 아니오 | `''` | finding 또는 retest 근거; UTF-8 text; 길이 제한 없음 | 민감 가능(A) | column-level 제약 없음 | C |
| `worker` | `TEXT` | 아니오 | `''` | worker 식별자; UTF-8 text; 길이 제한 없음 | 미확정(U) | column-level 제약 없음 | C |
| `asset_ids` | `JSONB` | 아니오 | `'[]'` | finding에 관련된 asset id 목록의 production JSON 배열이다. PostgreSQL은 임의 JSONB도 허용하므로 물리 shape는 강제되지 않는다. [의미 근거](evidence:db-field-findings-asset-ids) | 일반(C) | column-level 제약 없음 | P |
| `status` | `TEXT` | 아니오 | `'pending'` | finding triage 상태다. 현재 application allowlist는 pending, in_progress, confirmed, resolved, fixed, false_positive, ignored, duplicate, risk_accepted다. 한계: DB CHECK가 없으므로 물리적으로는 이 allowlist보다 넓다. [의미 근거](evidence:db-field-findings-status) | 미확정(U) | column-level 제약 없음 | P |
| `report` | `TEXT` | 아니오 | `''` | finding 보고서 본문; UTF-8 text; 길이 제한 없음 | 민감 가능(A) | column-level 제약 없음 | C |
| `created_at` | `TIMESTAMPTZ` | 아니오 | `now()` | 생성 시각; 절대 시각; DB `now()` 또는 앱 입력 | 일반(C) | column-level 제약 없음 | C |
| `evidence_version` | `BIGINT` | 아니오 | `0` | finding에 bind된 traffic evidence set의 변경 revision이며 binding 추가/삭제/순서 변경 때 증가한다. [의미 근거](evidence:db-field-findings-evidence-version) | 일반(C) | column-level 제약 없음 | C |
| `report_evidence_version` | `BIGINT` | 아니오 | `0` | 현재 report를 작성할 때 사용한 evidence_version snapshot이다. evidence_version과 다르면 report stale로 표시한다. [의미 근거](evidence:db-field-findings-report-evidence-version) | 일반(C) | column-level 제약 없음 | C |

**명시 index와 비용** · `idx_findings_task` — 최근 순서 조회의 planner candidate; 실제 query plan·효과 미관찰<br>`idx_findings_time` — 최근 순서 조회의 planner candidate; 실제 query plan·효과 미관찰<br>`idx_findings_status` — 최근 순서 조회의 planner candidate; 실제 query plan·효과 미관찰<br>`idx_findings_asset_ids` — 배열/JSON 포함 조회의 planner candidate; 실제 query plan 미관찰; write amplification·index 크기 비용

<a id="semantic-finding-retests"></a>
## `finding_retests`

finding 재검증 run의 conversation, verdict, snapshot·근거와 lifecycle을 저장한다.

**정적 접근** · reader: `db/finding_retests.go` · writer: `db/finding_retests.go`

**transaction·retention** · FK delete: CASCADE, SET NULL; schema에 TTL/partition 자동 보존 정책 없음; handler/cleanup/archive 삭제 경로와 운영 보존기간은 별도 확인(U)

**정확한 production SQL** · [symbol·verb·query access matrix](access-matrix.md#access-finding-retests)

| 필드 | 타입 | null | default | 의미·encoding | 민감도 | 제약 | 판정 |
|---|---|---:|---|---|---|---|---|
| `id` | `BIGSERIAL` | 아니오 | `nextval(<implicit sequence>)` | row 식별자; BIGSERIAL | 일반(C) | PRIMARY KEY | C |
| `finding_id` | `BIGINT` | 아니오 | `—` | finding row 식별자 또는 논리 참조; BIGINT | 일반(C) | FK findings(id) ON DELETE CASCADE | A |
| `conversation_id` | `BIGINT` | 예 | `—` | conversation row 식별자 또는 논리 참조; BIGINT | 일반(C) | FK conversations(id) ON DELETE SET NULL; UNIQUE | A |
| `status` | `TEXT` | 아니오 | `'pending'` | lifecycle 상태; UTF-8 text; 길이 제한 없음 | 미확정(U) | CHECK (status IN ('pending','running','completed','failed','stopped')) | C |
| `verdict` | `TEXT` | 아니오 | `''` | 재검증 판정; UTF-8 text; 길이 제한 없음 | 미확정(U) | CHECK (verdict IN ('','reproduced','fixed','inconclusive')) | C |
| `notes` | `TEXT` | 아니오 | `''` | 사용자가 retest를 시작할 때 넣는 최대 4000자 보충 지시/설명이며 retest agent 초기 메시지에 포함된다. [의미 근거](evidence:db-field-finding-retests-notes) | 미확정(U) | column-level 제약 없음 | C |
| `snapshot` | `JSONB` | 아니오 | `—` | 시점 고정 snapshot; retest 시작 시 finding/context snapshot | 비밀/원문(C) | column-level 제약 없음 | A |
| `summary` | `TEXT` | 아니오 | `''` | 요약; UTF-8 text; 길이 제한 없음 | 미확정(U) | column-level 제약 없음 | C |
| `evidence` | `TEXT` | 아니오 | `''` | finding 또는 retest 근거; UTF-8 text; 길이 제한 없음 | 민감 가능(A) | column-level 제약 없음 | C |
| `error` | `TEXT` | 아니오 | `''` | 실패 내용; UTF-8 text; 길이 제한 없음 | 민감 가능(A) | column-level 제약 없음 | C |
| `created_at` | `TIMESTAMPTZ` | 아니오 | `now()` | 생성 시각; 절대 시각; DB `now()` 또는 앱 입력 | 일반(C) | column-level 제약 없음 | C |
| `started_at` | `TIMESTAMPTZ` | 예 | `—` | 시작 시각; 절대 시각; DB `now()` 또는 앱 입력 | 일반(C) | column-level 제약 없음 | C |
| `finished_at` | `TIMESTAMPTZ` | 예 | `—` | 종료 시각; 절대 시각; DB `now()` 또는 앱 입력 | 일반(C) | column-level 제약 없음 | C |

**명시 index와 비용** · `idx_finding_retests_history` — 최근 순서 조회의 planner candidate; 실제 query plan·효과 미관찰<br>`idx_finding_retests_active` — finding별 active retest 하나의 partial unique backstop; writer의 row-lock/existing check 뒤 남은 race·schema drift는 DB error가 될 수 있음

<a id="semantic-traffic-evidence-snapshots"></a>
## `traffic_evidence_snapshots`

finding에 고정한 HTTP 교환의 request/response metadata·header/hash를 보존한다.

**정적 접근** · reader: `db/finding_traffic.go`, `db/task_archives.go`, `evidence/store.go` · writer: `db/finding_traffic.go`, `db/finding_traffic_archive.go`, `evidence/store.go`

**transaction·retention** · FK delete 동작이 없거나 앱이 관계를 관리한다; schema에 TTL/partition 자동 보존 정책 없음; handler/cleanup/archive 삭제 경로와 운영 보존기간은 별도 확인(U)

**정확한 production SQL** · [symbol·verb·query access matrix](access-matrix.md#access-traffic-evidence-snapshots)

| 필드 | 타입 | null | default | 의미·encoding | 민감도 | 제약 | 판정 |
|---|---|---:|---|---|---|---|---|
| `id` | `TEXT` | 아니오 | `—` | row 식별자; UTF-8 text; 길이 제한 없음 | 일반(C) | PRIMARY KEY | C |
| `source_traffic_id` | `TEXT` | 아니오 | `—` | 증거 snapshot을 만들 때 읽은 원본 Traffic SQLite exchange의 id다. CAS body와 정규화 metadata를 PostgreSQL snapshot으로 복제할 때 원본 참조를 보존한다. 한계: Traffic SQLite에 대한 cross-store FK는 없다. [의미 근거](evidence:db-field-traffic-evidence-snapshots-source-traffic-id) | 일반(C) | column-level 제약 없음 | C |
| `captured_at` | `BIGINT` | 아니오 | `—` | `ExchangeMeta.TS` (`time.Now().Unix()`) 복사본인 Unix seconds capture 시각 [근거](evidence:sensitivity-traffic-captured-at) | 일반(C) | column-level 제약 없음 | C |
| `url` | `TEXT` | 아니오 | `—` | 대상 또는 endpoint URL; UTF-8 text; 길이 제한 없음 | 민감 가능(A) | column-level 제약 없음 | C |
| `method` | `TEXT` | 아니오 | `—` | HTTP method; UTF-8 text; 길이 제한 없음 | 미확정(U) | column-level 제약 없음 | C |
| `status` | `INTEGER` | 아니오 | `—` | captured HTTP response status code다.; INTEGER [의미 근거](evidence:db-field-traffic-evidence-snapshots-status) | 일반(C) | column-level 제약 없음 | C |
| `content_type` | `TEXT` | 아니오 | `''` | HTTP content type; UTF-8 text; 길이 제한 없음 | 미확정(U) | column-level 제약 없음 | C |
| `req_head` | `TEXT` | 아니오 | `—` | HTTP request header; UTF-8 text; 길이 제한 없음 | 비밀/원문(C) | column-level 제약 없음 | C |
| `resp_head` | `TEXT` | 아니오 | `—` | HTTP response header; UTF-8 text; 길이 제한 없음 | 비밀/원문(C) | column-level 제약 없음 | C |
| `req_hash` | `TEXT` | 아니오 | `—` | request body hash; UTF-8 text; 길이 제한 없음 | 미확정(U) | column-level 제약 없음 | C |
| `resp_hash` | `TEXT` | 아니오 | `—` | response body hash; UTF-8 text; 길이 제한 없음 | 미확정(U) | column-level 제약 없음 | C |
| `req_len` | `BIGINT` | 아니오 | `—` | request body byte 길이; BIGINT | 일반(C) | column-level 제약 없음 | C |
| `resp_len` | `BIGINT` | 아니오 | `—` | response body byte 길이; BIGINT | 일반(C) | column-level 제약 없음 | C |
| `unreferenced_at` | `TIMESTAMPTZ` | 예 | `—` | finding 참조가 사라진 시각; 절대 시각; DB `now()` 또는 앱 입력 | 일반(C) | column-level 제약 없음 | C |
| `created_at` | `TIMESTAMPTZ` | 아니오 | `now()` | 생성 시각; 절대 시각; DB `now()` 또는 앱 입력 | 일반(C) | column-level 제약 없음 | C |

**명시 index와 비용** · 명시 index 없음; PK/UNIQUE가 만드는 내부 index는 102개 분모에 포함하지 않음.

<a id="semantic-finding-traffic-bindings"></a>
## `finding_traffic_bindings`

finding과 immutable traffic snapshot의 역할·순서 연결을 저장한다.

**정적 접근** · reader: `db/finding_traffic.go`, `db/finding_traffic_archive.go`, `db/findings.go`, `db/task_archives.go`, `evidence/store.go` · writer: `db/finding_traffic.go`, `db/finding_traffic_archive.go`

**transaction·retention** · FK delete: CASCADE; schema에 TTL/partition 자동 보존 정책 없음; handler/cleanup/archive 삭제 경로와 운영 보존기간은 별도 확인(U)

**정확한 production SQL** · [symbol·verb·query access matrix](access-matrix.md#access-finding-traffic-bindings)

| 필드 | 타입 | null | default | 의미·encoding | 민감도 | 제약 | 판정 |
|---|---|---:|---|---|---|---|---|
| `id` | `BIGSERIAL` | 아니오 | `nextval(<implicit sequence>)` | row 식별자; BIGSERIAL | 일반(C) | PRIMARY KEY | C |
| `finding_id` | `BIGINT` | 아니오 | `—` | finding row 식별자 또는 논리 참조; BIGINT | 일반(C) | FK findings(id) ON DELETE CASCADE <br>[table-level: UNIQUE(finding_id,snapshot_id)](table-constraints.md#constraint-finding-traffic-bindings)| A |
| `snapshot_id` | `TEXT` | 아니오 | `—` | snapshot row 식별자 또는 논리 참조; UTF-8 text; 길이 제한 없음 | 일반(C) | FK traffic_evidence_snapshots(id) <br>[table-level: UNIQUE(finding_id,snapshot_id)](table-constraints.md#constraint-finding-traffic-bindings)| A |
| `role` | `TEXT` | 아니오 | `'supporting'` | traffic snapshot이 finding 결론을 지지하는 용도다. 현재 application allowlist는 baseline, proof, verification, supporting이며 빈 입력은 supporting으로 정규화한다. 한계: DB CHECK가 없으므로 물리적으로는 이 allowlist보다 넓다. [의미 근거](evidence:db-field-finding-traffic-bindings-role) | 미확정(U) | column-level 제약 없음 | P |
| `note` | `TEXT` | 아니오 | `''` | 운영 메모; UTF-8 text; 길이 제한 없음 | 민감 가능(A) | column-level 제약 없음 | C |
| `position` | `INTEGER` | 아니오 | `—` | 정렬 순서; INTEGER | 일반(C) | column-level 제약 없음 | C |
| `created_at` | `TIMESTAMPTZ` | 아니오 | `now()` | 생성 시각; 절대 시각; DB `now()` 또는 앱 입력 | 일반(C) | column-level 제약 없음 | C |

**명시 index와 비용** · `idx_finding_traffic_order` — 해당 equality/join/filter/정렬의 planner candidate; 실제 query plan·효과 미관찰<br>`idx_finding_traffic_snapshot` — 해당 equality/join/filter/정렬의 planner candidate; 실제 query plan·효과 미관찰

<a id="semantic-server-logs"></a>
## `server_logs`

서버가 DB에 남긴 level/tag/text 로그를 시간순으로 저장한다.

**정적 접근** · reader: `db/logs.go` · writer: `db/logs.go`

**transaction·retention** · FK delete 동작이 없거나 앱이 관계를 관리한다; schema에 TTL/partition 자동 보존 정책 없음; handler/cleanup/archive 삭제 경로와 운영 보존기간은 별도 확인(U)

**정확한 production SQL** · [symbol·verb·query access matrix](access-matrix.md#access-server-logs)

| 필드 | 타입 | null | default | 의미·encoding | 민감도 | 제약 | 판정 |
|---|---|---:|---|---|---|---|---|
| `id` | `BIGSERIAL` | 아니오 | `nextval(<implicit sequence>)` | row 식별자; BIGSERIAL | 일반(C) | PRIMARY KEY | C |
| `created_at` | `TIMESTAMPTZ` | 아니오 | `now()` | 생성 시각; 절대 시각; DB `now()` 또는 앱 입력 | 일반(C) | column-level 제약 없음 | C |
| `level` | `TEXT` | 아니오 | `'info'` | persisted backend log의 severity/level 문자열이다. [의미 근거](evidence:db-field-server-logs-level) | 미확정(U) | column-level 제약 없음 | C |
| `tag` | `TEXT` | 아니오 | `''` | persisted backend log의 component/category tag다. [의미 근거](evidence:db-field-server-logs-tag) | 미확정(U) | column-level 제약 없음 | C |
| `text` | `TEXT` | 아니오 | `''` | persisted backend log message 본문이다. [의미 근거](evidence:db-field-server-logs-text) | 민감 가능(A) | column-level 제약 없음 | C |

**명시 index와 비용** · `idx_server_logs_id` — 최근 순서 조회의 planner candidate; 실제 query plan·효과 미관찰

<a id="semantic-side-question-sessions"></a>
## `side_question_sessions`

side-question의 session snapshot, generation/version과 memory를 저장한다.

**정적 접근** · reader: `db/side_questions.go`, `db/task_archives.go` · writer: `db/exploration.go`, `db/side_questions.go`, `db/task_archives_restore.go`

**transaction·retention** · FK delete: CASCADE; schema에 TTL/partition 자동 보존 정책 없음; handler/cleanup/archive 삭제 경로와 운영 보존기간은 별도 확인(U)

**정확한 production SQL** · [symbol·verb·query access matrix](access-matrix.md#access-side-question-sessions)

| 필드 | 타입 | null | default | 의미·encoding | 민감도 | 제약 | 판정 |
|---|---|---:|---|---|---|---|---|
| `session_key` | `TEXT` | 아니오 | `—` | parent context를 식별하는 합성 key: conv-ID, task-ID-exp-ID-main, 또는 task-ID-exp-ID-worker-iINTENT 형식이다. [의미 근거](evidence:db-field-side-question-sessions-session-key) | 미확정(U) | PRIMARY KEY | C |
| `conversation_id` | `BIGINT` | 예 | `—` | conversation row 식별자 또는 논리 참조; BIGINT | 일반(C) | FK conversations(id) ON DELETE CASCADE <br>[table-level: parent XOR CHECK](table-constraints.md#constraint-side-question-sessions)| A |
| `task_id` | `BIGINT` | 예 | `—` | task row 식별자 또는 논리 참조; BIGINT | 일반(C) | FK tasks(id) ON DELETE CASCADE <br>[table-level: parent XOR CHECK](table-constraints.md#constraint-side-question-sessions)| A |
| `exploration_id` | `BIGINT` | 예 | `—` | exploration row 식별자 또는 논리 참조; BIGINT | 일반(C) | FK explorations(id) ON DELETE CASCADE <br>[table-level: parent XOR CHECK](table-constraints.md#constraint-side-question-sessions)| A |
| `intent_id` | `BIGINT` | 예 | `—` | intent row 식별자 또는 논리 참조; BIGINT | 일반(C) | FK exploration_nodes(id) ON DELETE CASCADE <br>[table-level: parent XOR CHECK(intent optional)](table-constraints.md#constraint-side-question-sessions)| A |
| `run_id` | `BIGINT` | 아니오 | `—` | Capture attach마다 time.Now().UnixNano()로 만든 run freshness id이며 DB row 참조가 아니다.; BIGINT [의미 근거](evidence:db-field-side-question-sessions-run-id) | 일반(C) | column-level 제약 없음 | C |
| `version` | `BIGINT` | 아니오 | `—` | version 번호; BIGINT | 일반(C) | column-level 제약 없음 | C |
| `snapshot` | `JSONB` | 아니오 | `—` | 시점 고정 snapshot; side-question 실행 문맥 snapshot | 비밀/원문(C) | column-level 제약 없음 | A |
| `generation` | `BIGINT` | 아니오 | `0` | side-question generation; BIGINT | 일반(C) | column-level 제약 없음 | C |
| `memory` | `JSONB` | 아니오 | `'{}'` | side-question 압축 memory; 압축된 대화 memory object | 비밀/원문(C) | column-level 제약 없음 | A |

**명시 index와 비용** · `idx_side_sessions_conv` — 해당 equality/join/filter/정렬의 planner candidate; 실제 query plan·효과 미관찰<br>`idx_side_sessions_task` — 해당 equality/join/filter/정렬의 planner candidate; 실제 query plan·효과 미관찰<br>`idx_side_sessions_exp` — 해당 equality/join/filter/정렬의 planner candidate; 실제 query plan·효과 미관찰<br>`idx_side_sessions_intent` — 해당 equality/join/filter/정렬의 planner candidate; 실제 query plan·효과 미관찰

<a id="semantic-side-question-requests"></a>
## `side_question_requests`

side-question 요청/응답, 상태, model, usage와 순서를 저장한다.

**정적 접근** · reader: `db/side_questions.go`, `db/task_archives.go` · writer: `db/side_questions.go`, `db/task_archives_restore.go`

**transaction·retention** · FK delete: CASCADE; schema에 TTL/partition 자동 보존 정책 없음; handler/cleanup/archive 삭제 경로와 운영 보존기간은 별도 확인(U)

**정확한 production SQL** · [symbol·verb·query access matrix](access-matrix.md#access-side-question-requests)

| 필드 | 타입 | null | default | 의미·encoding | 민감도 | 제약 | 판정 |
|---|---|---:|---|---|---|---|---|
| `id` | `TEXT` | 아니오 | `—` | row 식별자; UTF-8 text; 길이 제한 없음 | 일반(C) | PRIMARY KEY | C |
| `ordinal` | `BIGSERIAL` | 아니오 | `nextval(<implicit sequence>)` | session 안 요청 순번; BIGSERIAL | 미확정(U) | UNIQUE | C |
| `session_key` | `TEXT` | 아니오 | `—` | 질문이 속한 side-question parent session의 합성 key이며 session row에 cascade FK로 연결된다. [의미 근거](evidence:db-field-side-question-requests-session-key) | 미확정(U) | FK side_question_sessions(session_key) ON DELETE CASCADE <br>[table-level: UNIQUE(session,generation,client)](table-constraints.md#constraint-side-question-requests)| C |
| `generation` | `BIGINT` | 아니오 | `—` | side-question generation; BIGINT | 일반(C) | column-level 제약 없음 <br>[table-level: UNIQUE(session,generation,client)](table-constraints.md#constraint-side-question-requests)| C |
| `client_id` | `TEXT` | 아니오 | `—` | caller가 보내는 client_request_id로, 같은 session generation 내 idempotency key다.; UTF-8 text; 길이 제한 없음 [의미 근거](evidence:db-field-side-question-requests-client-id) | 일반(C) | column-level 제약 없음 <br>[table-level: UNIQUE(session,generation,client)](table-constraints.md#constraint-side-question-requests)| C |
| `question` | `TEXT` | 아니오 | `—` | 사용자/agent 질문; UTF-8 text; 길이 제한 없음 | 민감 가능(A) | column-level 제약 없음 | C |
| `answer` | `TEXT` | 아니오 | `''` | 모델 답변; UTF-8 text; 길이 제한 없음 | 민감 가능(A) | column-level 제약 없음 | C |
| `status` | `TEXT` | 아니오 | `—` | lifecycle 상태; UTF-8 text; 길이 제한 없음 | 미확정(U) | CHECK(status IN ('running','completed','failed','cancelled','interrupted')) | C |
| `error` | `TEXT` | 아니오 | `''` | 실패 내용; UTF-8 text; 길이 제한 없음 | 민감 가능(A) | column-level 제약 없음 | C |
| `model` | `JSONB` | 아니오 | `—` | 질문에 사용한 credential-free model identity snapshot(profile id/name/format/model/identity/streaming/window tokens)의 JSON이다. [의미 근거](evidence:db-field-side-question-requests-model) | 미확정(U) | column-level 제약 없음 | P |
| `snapshot_at` | `TIMESTAMPTZ` | 아니오 | `—` | 문맥 snapshot 시각; 절대 시각; DB `now()` 또는 앱 입력 | 일반(C) | column-level 제약 없음 | C |
| `created_at` | `TIMESTAMPTZ` | 아니오 | `now()` | 생성 시각; 절대 시각; DB `now()` 또는 앱 입력 | 일반(C) | column-level 제약 없음 | C |
| `sequence` | `BIGINT` | 아니오 | `0` | event sequence; BIGINT | 일반(C) | column-level 제약 없음 | C |
| `usage` | `JSONB` | 아니오 | `'{}'` | usage 값; 더 좁은 도메인 의미는 소비 코드에 분산됨; provider token usage object | 미확정(U) | column-level 제약 없음 | A |
| `context_info` | `JSONB` | 아니오 | `'{}'` | context info 값; 더 좁은 도메인 의미는 소비 코드에 분산됨; 질문 문맥 provenance object | 민감 가능(A) | column-level 제약 없음 | A |

**명시 index와 비용** · `idx_side_request_running` — session별 running request 하나의 partial unique backstop; busy check 뒤 남은 race·schema drift는 DB error가 될 수 있음<br>`idx_side_requests_history` — 최근 순서 조회의 planner candidate; 실제 query plan·효과 미관찰

<a id="semantic-asset-intercept-rules"></a>
## `asset_intercept_rules`

asset 수준의 built-in/custom intercept match rule을 저장한다.

**정적 접근** · reader: `db/asset_intercept.go` · writer: `db/asset_intercept.go`, `db/db.go`

**transaction·retention** · FK delete 동작이 없거나 앱이 관계를 관리한다; schema에 TTL/partition 자동 보존 정책 없음; handler/cleanup/archive 삭제 경로와 운영 보존기간은 별도 확인(U)

**정확한 production SQL** · [symbol·verb·query access matrix](access-matrix.md#access-asset-intercept-rules)

| 필드 | 타입 | null | default | 의미·encoding | 민감도 | 제약 | 판정 |
|---|---|---:|---|---|---|---|---|
| `id` | `BIGSERIAL` | 아니오 | `nextval(<implicit sequence>)` | row 식별자; BIGSERIAL | 일반(C) | PRIMARY KEY | C |
| `enabled` | `BOOLEAN` | 아니오 | `true` | 활성화 여부; BOOLEAN | 일반(C) | column-level 제약 없음 | C |
| `kind` | `TEXT` | 아니오 | `—` | variant 또는 event 종류; UTF-8 text; 길이 제한 없음 | 미확정(U) | CHECK (kind IN ( 'exact_domain', 'exact_ip', 'exact_url', 'fuzzy_domain', 'fuzzy_ip', 'fuzzy_url', 'cidr')) | C |
| `pattern` | `TEXT` | 아니오 | `—` | match pattern; UTF-8 text; 길이 제한 없음 | 미확정(U) | column-level 제약 없음 | C |
| `note` | `TEXT` | 아니오 | `''` | 운영 메모; UTF-8 text; 길이 제한 없음 | 민감 가능(A) | column-level 제약 없음 | C |
| `builtin` | `BOOLEAN` | 아니오 | `false` | built-in 객체 여부; BOOLEAN | 일반(C) | column-level 제약 없음 | C |
| `created_at` | `TIMESTAMPTZ` | 아니오 | `NOW()` | 생성 시각; 절대 시각; DB `now()` 또는 앱 입력 | 일반(C) | column-level 제약 없음 | C |
| `updated_at` | `TIMESTAMPTZ` | 아니오 | `NOW()` | 마지막 변경 시각; 절대 시각; DB `now()` 또는 앱 입력 | 일반(C) | column-level 제약 없음 | C |

**명시 index와 비용** · `idx_asset_intercept_enabled` — 해당 equality/join/filter/정렬의 planner candidate; 실제 query plan·효과 미관찰

<a id="semantic-task-intercept-rules"></a>
## `task_intercept_rules`

task 수준의 intercept allow/deny/approval rule을 저장한다.

**정적 접근** · reader: `db/task_intercept.go` · writer: `db/task_intercept.go`

**transaction·retention** · FK delete: CASCADE; schema에 TTL/partition 자동 보존 정책 없음; handler/cleanup/archive 삭제 경로와 운영 보존기간은 별도 확인(U)

**정확한 production SQL** · [symbol·verb·query access matrix](access-matrix.md#access-task-intercept-rules)

`action` CHECK는 fresh table에는 있지만 legacy ADD COLUMN 경로에는 없다. 아래 제약 열은 fresh 선언을 표시하며 upgraded DB의 무결성은 backend 쓰기 검증을 함께 봐야 한다. [fresh/upgrade 경계](functions-triggers.md#schema-migrations)

| 필드 | 타입 | null | default | 의미·encoding | 민감도 | 제약 | 판정 |
|---|---|---:|---|---|---|---|---|
| `id` | `BIGSERIAL` | 아니오 | `nextval(<implicit sequence>)` | row 식별자; BIGSERIAL | 일반(C) | PRIMARY KEY | C |
| `task_id` | `BIGINT` | 아니오 | `—` | task row 식별자 또는 논리 참조; BIGINT | 일반(C) | FK tasks(id) ON DELETE CASCADE | A |
| `enabled` | `BOOLEAN` | 아니오 | `true` | 활성화 여부; BOOLEAN | 일반(C) | column-level 제약 없음 | C |
| `action` | `TEXT` | 아니오 | `'block'` | rule 결정; UTF-8 text; 길이 제한 없음 | 미확정(U) | CHECK (action IN ('block','allow')) | C |
| `kind` | `TEXT` | 아니오 | `—` | variant 또는 event 종류; UTF-8 text; 길이 제한 없음 | 미확정(U) | CHECK (kind IN ( 'exact_domain', 'exact_ip', 'exact_url', 'fuzzy_domain', 'fuzzy_ip', 'fuzzy_url', 'cidr')) | C |
| `pattern` | `TEXT` | 아니오 | `—` | match pattern; UTF-8 text; 길이 제한 없음 | 미확정(U) | column-level 제약 없음 | C |
| `note` | `TEXT` | 아니오 | `''` | 운영 메모; UTF-8 text; 길이 제한 없음 | 민감 가능(A) | column-level 제약 없음 | C |
| `created_at` | `TIMESTAMPTZ` | 아니오 | `NOW()` | 생성 시각; 절대 시각; DB `now()` 또는 앱 입력 | 일반(C) | column-level 제약 없음 | C |
| `updated_at` | `TIMESTAMPTZ` | 아니오 | `NOW()` | 마지막 변경 시각; 절대 시각; DB `now()` 또는 앱 입력 | 일반(C) | column-level 제약 없음 | C |

**명시 index와 비용** · `idx_task_intercept_task` — 해당 equality/join/filter/정렬의 planner candidate; 실제 query plan·효과 미관찰

<a id="semantic-notification-channels"></a>
## `notification_channels`

알림 채널 종류·credential 포함 설정, filter/mode와 rate limit을 저장한다.

**정적 접근** · reader: `db/notification.go`, `db/notification_delivery.go` · writer: `db/notification.go`

**transaction·retention** · FK delete 동작이 없거나 앱이 관계를 관리한다; schema에 TTL/partition 자동 보존 정책 없음; handler/cleanup/archive 삭제 경로와 운영 보존기간은 별도 확인(U)

**정확한 production SQL** · [symbol·verb·query access matrix](access-matrix.md#access-notification-channels)

| 필드 | 타입 | null | default | 의미·encoding | 민감도 | 제약 | 판정 |
|---|---|---:|---|---|---|---|---|
| `id` | `BIGSERIAL` | 아니오 | `nextval(<implicit sequence>)` | row 식별자; BIGSERIAL | 일반(C) | PRIMARY KEY | C |
| `name` | `TEXT` | 아니오 | `—` | 표시 이름; UTF-8 text; 길이 제한 없음 | 미확정(U) | column-level 제약 없음 | C |
| `kind` | `TEXT` | 아니오 | `—` | notification channel adapter 종류다. 현재 registry/server allowlist는 dingtalk, feishu, wecom, webhook, telegram, email이다. 한계: server가 검사하지만 DB CHECK는 없어 낮은 수준 writer는 더 넓은 값을 저장할 수 있다. [의미 근거](evidence:db-field-notification-channels-kind) | 미확정(U) | column-level 제약 없음 | P |
| `enabled` | `BOOLEAN` | 아니오 | `true` | 활성화 여부; BOOLEAN | 일반(C) | column-level 제약 없음 | C |
| `config` | `JSONB` | 아니오 | `'{}'` | 종류별 설정; credential을 포함할 수 있음; kind별: dingtalk/feishu {webhook,secret}; wecom {webhook}; webhook {url,method,content_type,headers,body_template}; telegram {bot_token,chat_id,base_url}; email {host,port,username,password,from,to,tls} | 비밀/원문(C) | column-level 제약 없음 | A |
| `mode` | `TEXT` | 아니오 | `'realtime'` | 알림 전달 시점: realtime은 event별 즉시, digest는 주기별 batch 집계다. [의미 근거](evidence:db-field-notification-channels-mode) | 미확정(U) | column-level 제약 없음 | C |
| `filter` | `JSONB` | 아니오 | `'{}'` | 알림 필터 조건; min_severity, task_ids, asset_ids, vulnclass_include/exclude, on_status_change | 미확정(U) | column-level 제약 없음 | A |
| `rate_per_min` | `INTEGER` | 아니오 | `20` | 분당 전송 제한; INTEGER | 일반(C) | column-level 제약 없음 | C |
| `created_at` | `TIMESTAMPTZ` | 아니오 | `now()` | 생성 시각; 절대 시각; DB `now()` 또는 앱 입력 | 일반(C) | column-level 제약 없음 | C |
| `updated_at` | `TIMESTAMPTZ` | 아니오 | `now()` | 마지막 변경 시각; 절대 시각; DB `now()` 또는 앱 입력 | 일반(C) | column-level 제약 없음 | C |

**명시 index와 비용** · 명시 index 없음; PK/UNIQUE가 만드는 내부 index는 102개 분모에 포함하지 않음.

<a id="semantic-notification-events"></a>
## `notification_events`

finding/상태 변경 시점의 immutable notification snapshot과 fan-out 상태를 저장한다.

**정적 접근** · reader: `db/notification.go`, `db/notification_delivery.go` · writer: `db/notification.go`

**transaction·retention** · FK delete 동작이 없거나 앱이 관계를 관리한다; schema에 TTL/partition 자동 보존 정책 없음; handler/cleanup/archive 삭제 경로와 운영 보존기간은 별도 확인(U)

**정확한 production SQL** · [symbol·verb·query access matrix](access-matrix.md#access-notification-events)

| 필드 | 타입 | null | default | 의미·encoding | 민감도 | 제약 | 판정 |
|---|---|---:|---|---|---|---|---|
| `id` | `BIGSERIAL` | 아니오 | `nextval(<implicit sequence>)` | row 식별자; BIGSERIAL | 일반(C) | PRIMARY KEY | C |
| `kind` | `TEXT` | 아니오 | `—` | finding notification event 종류다. 현재 producer 상수는 finding_created와 finding_status_changed다. 한계: RecordNotificationEventTx/AddNotificationEvent 인자와 DB는 임의 문자열을 받아 닫힌 enum을 강제하지 않는다. [의미 근거](evidence:db-field-notification-events-kind) | 미확정(U) | column-level 제약 없음 | P |
| `finding_id` | `BIGINT` | 아니오 | `—` | event를 발생시킨 finding id다. `RecordNotificationEventTx`는 실제 finding id를 쓰며, standalone `AddNotificationEvent`는 BIGINT 인자를 존재 검증 없이 받는다. 의도적으로 FK가 없어 finding 삭제 뒤에도 id와 snapshot을 보존한다. 한계: standalone helper의 production caller를 찾지 못했고, DB는 id의 실제 finding 존재를 강제하지 않는다. [의미 근거](evidence:db-field-notification-events-finding-id) | 일반(C) | column-level 제약 없음 | C |
| `snapshot` | `JSONB` | 아니오 | `—` | 시점 고정 snapshot; finding/event 발생 시점 render snapshot | 비밀/원문(C) | column-level 제약 없음 | A |
| `fanned_out` | `BOOLEAN` | 아니오 | `false` | delivery fan-out 생성 완료 여부; BOOLEAN | 일반(C) | column-level 제약 없음 | C |
| `created_at` | `TIMESTAMPTZ` | 아니오 | `now()` | 생성 시각; 절대 시각; DB `now()` 또는 앱 입력 | 일반(C) | column-level 제약 없음 | C |

**명시 index와 비용** · `idx_notification_events_pending` — 해당 equality/join/filter/정렬의 planner candidate; 실제 query plan·효과 미관찰; partial predicate 밖 row에는 미적용

<a id="semantic-notification-deliveries"></a>
## `notification_deliveries`

event×channel delivery 상태, retry, batch와 전송 시각을 저장한다.

**정적 접근** · reader: `db/notification.go`, `db/notification_delivery.go` · writer: `db/notification.go`, `db/notification_delivery.go`

**transaction·retention** · FK delete: CASCADE; schema에 TTL/partition 자동 보존 정책 없음; handler/cleanup/archive 삭제 경로와 운영 보존기간은 별도 확인(U)

**정확한 production SQL** · [symbol·verb·query access matrix](access-matrix.md#access-notification-deliveries)

| 필드 | 타입 | null | default | 의미·encoding | 민감도 | 제약 | 판정 |
|---|---|---:|---|---|---|---|---|
| `id` | `BIGSERIAL` | 아니오 | `nextval(<implicit sequence>)` | row 식별자; BIGSERIAL | 일반(C) | PRIMARY KEY | C |
| `event_id` | `BIGINT` | 아니오 | `—` | event row 식별자 또는 논리 참조; BIGINT | 일반(C) | FK notification_events(id) ON DELETE CASCADE | A |
| `channel_id` | `BIGINT` | 아니오 | `—` | channel row 식별자 또는 논리 참조; BIGINT | 일반(C) | FK notification_channels(id) ON DELETE CASCADE | A |
| `state` | `TEXT` | 아니오 | `'pending'` | notification delivery의 queue/lease/result 상태다. 현재 상태는 pending, sending, sent, failed, skipped다. 한계: DB CHECK가 없어 물리적으로는 이 상태 집합보다 넓다. [의미 근거](evidence:db-field-notification-deliveries-state) | 미확정(U) | column-level 제약 없음 | P |
| `attempts` | `INTEGER` | 아니오 | `0` | delivery 시도 횟수; INTEGER | 일반(C) | column-level 제약 없음 | C |
| `next_attempt_at` | `TIMESTAMPTZ` | 아니오 | `now()` | 다음 delivery 재시도 시각; 절대 시각; DB `now()` 또는 앱 입력 | 일반(C) | column-level 제약 없음 | C |
| `last_error` | `TEXT` | 아니오 | `''` | 마지막 실패 내용; UTF-8 text; 길이 제한 없음 | 민감 가능(A) | column-level 제약 없음 | C |
| `batch_id` | `BIGINT` | 예 | `—` | digest batch 구성원의 최소 delivery id를 안정적인 batch 번호로 공유한다; realtime은 NULL이다.; BIGINT [의미 근거](evidence:db-field-notification-deliveries-batch-id) | 일반(C) | column-level 제약 없음 | C |
| `created_at` | `TIMESTAMPTZ` | 아니오 | `now()` | 생성 시각; 절대 시각; DB `now()` 또는 앱 입력 | 일반(C) | column-level 제약 없음 | C |
| `sent_at` | `TIMESTAMPTZ` | 예 | `—` | 전송 성공 시각; 절대 시각; DB `now()` 또는 앱 입력 | 일반(C) | column-level 제약 없음 | C |

**명시 index와 비용** · `idx_notification_deliveries_due` — 해당 equality/join/filter/정렬의 planner candidate; 실제 query plan·효과 미관찰; partial predicate 밖 row에는 미적용<br>`idx_notification_deliveries_history` — 최근 순서 조회의 planner candidate; 실제 query plan·효과 미관찰<br>`idx_notification_deliveries_batch` — 해당 equality/join/filter/정렬의 planner candidate; 실제 query plan·효과 미관찰; partial predicate 밖 row에는 미적용<br>`idx_notification_deliveries_channel` — 최근 순서 조회의 planner candidate; 실제 query plan·효과 미관찰

<a id="semantic-llm-records"></a>
## `llm_records`

LLM request/response 원문과 token·latency·status를 기록하는 선택적 runtime ledger다.

**정적 접근** · reader: `db/commands.go`, `db/task_archives.go` · writer: `db/commands.go`, `db/task_archives_restore.go`, `db/tasks.go`

**transaction·retention** · startup `Ensure*Table` best-effort; 실패해도 server는 계속된다; FK delete 동작이 없거나 앱이 관계를 관리한다; schema에 TTL/partition 자동 보존 정책 없음; handler/cleanup/archive 삭제 경로와 운영 보존기간은 별도 확인(U)

**정확한 production SQL** · [symbol·verb·query access matrix](access-matrix.md#access-llm-records)

| 필드 | 타입 | null | default | 의미·encoding | 민감도 | 제약 | 판정 |
|---|---|---:|---|---|---|---|---|
| `id` | `BIGSERIAL` | 아니오 | `nextval(<implicit sequence>)` | row 식별자; BIGSERIAL | 일반(C) | PRIMARY KEY | C |
| `ts` | `TIMESTAMPTZ` | 예 | `now()` | event 시각; 절대 시각; DB `now()` 또는 앱 입력 | 일반(C) | column-level 제약 없음 | C |
| `model` | `TEXT` | 예 | `—` | 모델 식별자; UTF-8 text; 길이 제한 없음 | 미확정(U) | column-level 제약 없음 | C |
| `profile_name` | `TEXT` | 예 | `—` | LLM profile 이름; UTF-8 text; 길이 제한 없음 | 미확정(U) | column-level 제약 없음 | C |
| `session_id` | `TEXT` | 예 | `—` | agent/LLM session 식별자; UTF-8 text; 길이 제한 없음 | 일반(C) | column-level 제약 없음 | C |
| `task_id` | `TEXT` | 예 | `—` | full-body LLM trace가 귀속된 task registry id를 문자열로 저장한다. 빈 task id는 SQL NULL이며 task 단위 조회·삭제의 key다. 한계: TEXT이고 tasks FK가 없다. [의미 근거](evidence:db-field-llm-records-task-id) | 일반(C) | column-level 제약 없음 | C |
| `worker` | `TEXT` | 예 | `—` | worker 식별자; UTF-8 text; 길이 제한 없음 | 미확정(U) | column-level 제약 없음 | C |
| `latency_ms` | `INTEGER` | 예 | `—` | 호출 지연 시간(ms); INTEGER | 일반(C) | column-level 제약 없음 | C |
| `input_tokens` | `INTEGER` | 예 | `—` | 입력 token 수; INTEGER | 일반(C) | column-level 제약 없음 | C |
| `output_tokens` | `INTEGER` | 예 | `—` | 출력 token 수; INTEGER | 일반(C) | column-level 제약 없음 | C |
| `cache_read` | `INTEGER` | 예 | `—` | cache read token 수; INTEGER | 일반(C) | column-level 제약 없음 | C |
| `cache_write` | `INTEGER` | 예 | `—` | cache write token 수; INTEGER | 일반(C) | column-level 제약 없음 | C |
| `status` | `TEXT` | 예 | `—` | full-body LLM trace의 completion 결과다. Recorder는 provider 호출 성공이면 ok, error가 있으면 error를 쓴다. 한계: InsertLLMRecord와 DB는 임의 문자열을 허용한다. [의미 근거](evidence:db-field-llm-records-status) | 미확정(U) | column-level 제약 없음 | P |
| `error` | `TEXT` | 예 | `—` | 실패 내용; UTF-8 text; 길이 제한 없음 | 민감 가능(A) | column-level 제약 없음 | C |
| `request_body` | `TEXT` | 예 | `—` | LLM 요청 본문; provider 요청 JSON | 비밀/원문(C) | column-level 제약 없음 | C |
| `response_body` | `TEXT` | 예 | `—` | LLM 응답 본문; provider 응답 JSON | 비밀/원문(C) | column-level 제약 없음 | C |
| `raw_request` | `TEXT` | 예 | `—` | provider로 보낸 원시 요청; 전송 직전 provider payload | 비밀/원문(C) | column-level 제약 없음 | C |
| `raw_response` | `TEXT` | 예 | `—` | provider 원시 응답; provider 원시 payload | 비밀/원문(C) | column-level 제약 없음 | C |

**명시 index와 비용** · `idx_llm_records_ts` — 해당 equality/join/filter/정렬의 planner candidate; 실제 query plan·효과 미관찰<br>`idx_llm_records_session` — 해당 equality/join/filter/정렬의 planner candidate; 실제 query plan·효과 미관찰

<a id="semantic-llm-usage"></a>
## `llm_usage`

LLM 호출별 task/exploration/worker attribution과 token·latency 집계를 기록하는 runtime ledger다.

**정적 접근** · reader: `db/llm_usage.go`, `db/task_archives.go` · writer: `db/llm_usage.go`, `db/task_archives_restore.go`

**transaction·retention** · startup `Ensure*Table` best-effort; 실패해도 server는 계속된다; FK delete 동작이 없거나 앱이 관계를 관리한다; schema에 TTL/partition 자동 보존 정책 없음; handler/cleanup/archive 삭제 경로와 운영 보존기간은 별도 확인(U)

**정확한 production SQL** · [symbol·verb·query access matrix](access-matrix.md#access-llm-usage)

| 필드 | 타입 | null | default | 의미·encoding | 민감도 | 제약 | 판정 |
|---|---|---:|---|---|---|---|---|
| `id` | `BIGSERIAL` | 아니오 | `nextval(<implicit sequence>)` | row 식별자; BIGSERIAL | 일반(C) | PRIMARY KEY | C |
| `ts` | `TIMESTAMPTZ` | 아니오 | `now()` | event 시각; 절대 시각; DB `now()` 또는 앱 입력 | 일반(C) | column-level 제약 없음 | C |
| `task_id` | `TEXT` | 예 | `—` | always-on LLM usage row가 귀속된 task registry id이며 llm_records.task_id와 같은 문자열 key다. 빈 값은 SQL NULL이다. 한계: TEXT이고 tasks FK가 없다. [의미 근거](evidence:db-field-llm-usage-task-id) | 일반(C) | column-level 제약 없음 | C |
| `exploration_id` | `BIGINT` | 예 | `—` | LLM usage row가 귀속된 exploration id다. session에서 parse할 수 없거나 non-task이면 0이고 SQL NULL로 저장한다. [의미 근거](evidence:db-field-llm-usage-exploration-id) | 일반(C) | column-level 제약 없음 | C |
| `worker` | `TEXT` | 예 | `—` | worker 식별자; UTF-8 text; 길이 제한 없음 | 미확정(U) | column-level 제약 없음 | C |
| `model` | `TEXT` | 예 | `—` | 모델 식별자; UTF-8 text; 길이 제한 없음 | 미확정(U) | column-level 제약 없음 | C |
| `profile_name` | `TEXT` | 예 | `—` | LLM profile 이름; UTF-8 text; 길이 제한 없음 | 미확정(U) | column-level 제약 없음 | C |
| `latency_ms` | `INTEGER` | 예 | `—` | 호출 지연 시간(ms); INTEGER | 일반(C) | column-level 제약 없음 | C |
| `input_tokens` | `INTEGER` | 아니오 | `0` | 입력 token 수; INTEGER | 일반(C) | column-level 제약 없음 | C |
| `output_tokens` | `INTEGER` | 아니오 | `0` | 출력 token 수; INTEGER | 일반(C) | column-level 제약 없음 | C |
| `cache_read` | `INTEGER` | 아니오 | `0` | cache read token 수; INTEGER | 일반(C) | column-level 제약 없음 | C |
| `cache_write` | `INTEGER` | 아니오 | `0` | cache write token 수; INTEGER | 일반(C) | column-level 제약 없음 | C |
| `status` | `TEXT` | 예 | `—` | always-on LLM usage row의 completion 결과다. Recorder는 성공이면 ok, provider/stream error면 error를 기록한다. 한계: InsertLLMUsage와 DB는 임의 문자열을 허용한다. [의미 근거](evidence:db-field-llm-usage-status) | 미확정(U) | column-level 제약 없음 | P |

**명시 index와 비용** · `idx_llm_usage_task` — 해당 equality/join/filter/정렬의 planner candidate; 실제 query plan·효과 미관찰<br>`idx_llm_usage_model` — 해당 equality/join/filter/정렬의 planner candidate; 실제 query plan·효과 미관찰<br>`idx_llm_usage_exp` — 해당 equality/join/filter/정렬의 planner candidate; 실제 query plan·효과 미관찰

## 의미 완결성

- 물리 field 계약은 551/551개 C다. 별도 business/encoding 의미 판정은 source로 확인한 444개 C, 알려진 shape까지 확인한 93개 A, production allowlist 또는 shape는 확인했지만 DB가 더 넓거나 한계가 남은 14개 P, 미처분 U 0개다. 이는 운영 값·cardinality·query plan 검증을 포함하지 않는다.
- 14개 P 중 10개는 current production 값 집합을 확인했지만 PostgreSQL column 또는 low-level writer가 더 넓어 닫힌 enum으로 보장할 수 없는 경우다. 나머지 4개는 DB에 저장 가능한 shape가 production DTO보다 넓다. 각 field 행의 한계를 기준으로 해석한다.
- 민감도는 별도 분모로 일반 C 311, 미확정 U 173, 민감 가능 A 49, `settings.value` key 의존 비밀/원문 C/A 1, 비밀/원문 C 16, 민감 파생 hint C 1이다. business 의미를 C/P로 올렸더라도 실제 row 값의 민감도는 별도 판정이다.
- table 목적 51/51은 DDL 주석과 production query를 대조했다. reader/writer는 static SQL, 정적으로 평가한 expression, source fragment가 확인된 runtime template와 세 닫힌 identifier allowlist를 색인한다. Runtime template의 최종 predicate/order/placeholder, 임의 동적 identifier, 실제 call reachability와 transaction 범위는 U다.
- 실제 role/grant, row 규모, retention 기간, encryption-at-rest와 운영 migration 적용은 U다. 이 항목은 배포 DB 관찰 없이는 C로 올리지 않는다.
