<a id="db-functions-triggers"></a>
# PostgreSQL function·trigger·migration 참조

core `db/schema.sql`의 table 바깥 상태 변경을 정적으로 추출한다. 고정 source revision에는 function 4개, trigger 22개(공용 `updated_at` 20 + asset provenance 1 + retest 정리 1), `ALTER TABLE` 문 86개, 명시 index 97개가 있다. Core 49개 table의 최종 column 합집합 520개는 raw DDL을 정적으로 합친 수이며 PostgreSQL을 실행해 검증한 결과가 아니다. [core schema](evidence:schema-core)

<a id="schema-functions"></a>
## Function 4개

| function | function effect + trigger/call-site table effect | trigger/caller |
|---|---|---|
| [`set_updated_at()`](#function-set-updated-at) | `companies` write(updated_at); `assets` write(updated_at); `explorations` write(updated_at); `exploration_nodes` write(updated_at); `llm_profiles` write(updated_at); `task_categories` write(updated_at); `tasks` write(updated_at); `task_archives` write(updated_at); `task_templates` write(updated_at); `task_asset_links` write(updated_at); `task_llm_profiles` write(updated_at); `agents` write(updated_at); `mcp_servers` write(updated_at); `tools` write(updated_at); `conversations` write(updated_at); `agent_triggers` write(updated_at); `intercept_rules` write(updated_at); `asset_intercept_rules` write(updated_at); `task_intercept_rules` write(updated_at); `notification_channels` write(updated_at) | `trg_companies_upd`, `trg_av2_upd`, `trg_exp_upd`, `trg_expnodes_upd`, `trg_llm_upd`, `trg_task_categories_upd`, `trg_tasks_upd`, `trg_task_archives_upd`, `trg_task_templates_upd`, `trg_task_asset_links_upd`, `trg_task_llm_profiles_upd`, `trg_agents_upd`, `trg_mcp_upd`, `trg_tools_upd`, `trg_conversations_upd`, `trg_agent_triggers_upd`, `trg_intercept_rules_upd`, `trg_asset_intercept_rules_upd`, `trg_task_intercept_rules_upd`, `trg_notification_channels_upd` |
| [`try_inet()`](#function-try-inet) | 직접 table read/write 없음; text→inet/NULL 변환. `assets`·`company_scope`·`task_scope`는 이 함수를 호출하는 query의 table | `try_inet(...)` production query |
| [`sync_task_asset_links()`](#function-sync-task-asset-links) | `assets` read trigger row(id,task_ids); `tasks` read(id,deleted_at); `task_asset_links` write(task_id,asset_id,source,source_summary) | `trg_assets_task_links` |
| [`stop_deleted_conversation_retest()`](#function-stop-deleted-conversation-retest) | `conversations` read trigger row(id); `finding_retests` write(status,error,finished_at) | `trg_conversation_retest_delete` |

<a id="schema-triggers"></a>
## Trigger 22개

| trigger | timing/event/level | fire table | function | 영향 열·상태 |
|---|---|---|---|---|
| [`trg_companies_upd`](#trigger-trg-companies-upd) | `BEFORE UPDATE` / `ROW` | [`companies`](access-matrix.md#access-companies) | [`set_updated_at()`](#function-set-updated-at) | row update 전에 updated_at을 now()로 덮어씀 |
| [`trg_av2_upd`](#trigger-trg-av2-upd) | `BEFORE UPDATE` / `ROW` | [`assets`](access-matrix.md#access-assets) | [`set_updated_at()`](#function-set-updated-at) | row update 전에 updated_at을 now()로 덮어씀 |
| [`trg_exp_upd`](#trigger-trg-exp-upd) | `BEFORE UPDATE` / `ROW` | [`explorations`](access-matrix.md#access-explorations) | [`set_updated_at()`](#function-set-updated-at) | row update 전에 updated_at을 now()로 덮어씀 |
| [`trg_expnodes_upd`](#trigger-trg-expnodes-upd) | `BEFORE UPDATE` / `ROW` | [`exploration_nodes`](access-matrix.md#access-exploration-nodes) | [`set_updated_at()`](#function-set-updated-at) | row update 전에 updated_at을 now()로 덮어씀 |
| [`trg_llm_upd`](#trigger-trg-llm-upd) | `BEFORE UPDATE` / `ROW` | [`llm_profiles`](access-matrix.md#access-llm-profiles) | [`set_updated_at()`](#function-set-updated-at) | row update 전에 updated_at을 now()로 덮어씀 |
| [`trg_task_categories_upd`](#trigger-trg-task-categories-upd) | `BEFORE UPDATE` / `ROW` | [`task_categories`](access-matrix.md#access-task-categories) | [`set_updated_at()`](#function-set-updated-at) | row update 전에 updated_at을 now()로 덮어씀 |
| [`trg_tasks_upd`](#trigger-trg-tasks-upd) | `BEFORE UPDATE` / `ROW` | [`tasks`](access-matrix.md#access-tasks) | [`set_updated_at()`](#function-set-updated-at) | row update 전에 updated_at을 now()로 덮어씀 |
| [`trg_task_archives_upd`](#trigger-trg-task-archives-upd) | `BEFORE UPDATE` / `ROW` | [`task_archives`](access-matrix.md#access-task-archives) | [`set_updated_at()`](#function-set-updated-at) | row update 전에 updated_at을 now()로 덮어씀 |
| [`trg_task_templates_upd`](#trigger-trg-task-templates-upd) | `BEFORE UPDATE` / `ROW` | [`task_templates`](access-matrix.md#access-task-templates) | [`set_updated_at()`](#function-set-updated-at) | row update 전에 updated_at을 now()로 덮어씀 |
| [`trg_task_asset_links_upd`](#trigger-trg-task-asset-links-upd) | `BEFORE UPDATE` / `ROW` | [`task_asset_links`](access-matrix.md#access-task-asset-links) | [`set_updated_at()`](#function-set-updated-at) | row update 전에 updated_at을 now()로 덮어씀 |
| [`trg_assets_task_links`](#trigger-trg-assets-task-links) | `AFTER INSERT OR UPDATE OF TASK_IDS` / `ROW` | [`assets`](access-matrix.md#access-assets) | [`sync_task_asset_links()`](#function-sync-task-asset-links) | asset task_ids 뒤 provenance link insert/delete |
| [`trg_task_llm_profiles_upd`](#trigger-trg-task-llm-profiles-upd) | `BEFORE UPDATE` / `ROW` | [`task_llm_profiles`](access-matrix.md#access-task-llm-profiles) | [`set_updated_at()`](#function-set-updated-at) | row update 전에 updated_at을 now()로 덮어씀 |
| [`trg_agents_upd`](#trigger-trg-agents-upd) | `BEFORE UPDATE` / `ROW` | [`agents`](access-matrix.md#access-agents) | [`set_updated_at()`](#function-set-updated-at) | row update 전에 updated_at을 now()로 덮어씀 |
| [`trg_mcp_upd`](#trigger-trg-mcp-upd) | `BEFORE UPDATE` / `ROW` | [`mcp_servers`](access-matrix.md#access-mcp-servers) | [`set_updated_at()`](#function-set-updated-at) | row update 전에 updated_at을 now()로 덮어씀 |
| [`trg_tools_upd`](#trigger-trg-tools-upd) | `BEFORE UPDATE` / `ROW` | [`tools`](access-matrix.md#access-tools) | [`set_updated_at()`](#function-set-updated-at) | row update 전에 updated_at을 now()로 덮어씀 |
| [`trg_conversations_upd`](#trigger-trg-conversations-upd) | `BEFORE UPDATE` / `ROW` | [`conversations`](access-matrix.md#access-conversations) | [`set_updated_at()`](#function-set-updated-at) | row update 전에 updated_at을 now()로 덮어씀 |
| [`trg_agent_triggers_upd`](#trigger-trg-agent-triggers-upd) | `BEFORE UPDATE` / `ROW` | [`agent_triggers`](access-matrix.md#access-agent-triggers) | [`set_updated_at()`](#function-set-updated-at) | row update 전에 updated_at을 now()로 덮어씀 |
| [`trg_intercept_rules_upd`](#trigger-trg-intercept-rules-upd) | `BEFORE UPDATE` / `ROW` | [`intercept_rules`](access-matrix.md#access-intercept-rules) | [`set_updated_at()`](#function-set-updated-at) | row update 전에 updated_at을 now()로 덮어씀 |
| [`trg_conversation_retest_delete`](#trigger-trg-conversation-retest-delete) | `BEFORE DELETE` / `ROW` | [`conversations`](access-matrix.md#access-conversations) | [`stop_deleted_conversation_retest()`](#function-stop-deleted-conversation-retest) | conversation delete 전에 active retest를 stopped로 전이 |
| [`trg_asset_intercept_rules_upd`](#trigger-trg-asset-intercept-rules-upd) | `BEFORE UPDATE` / `ROW` | [`asset_intercept_rules`](access-matrix.md#access-asset-intercept-rules) | [`set_updated_at()`](#function-set-updated-at) | row update 전에 updated_at을 now()로 덮어씀 |
| [`trg_task_intercept_rules_upd`](#trigger-trg-task-intercept-rules-upd) | `BEFORE UPDATE` / `ROW` | [`task_intercept_rules`](access-matrix.md#access-task-intercept-rules) | [`set_updated_at()`](#function-set-updated-at) | row update 전에 updated_at을 now()로 덮어씀 |
| [`trg_notification_channels_upd`](#trigger-trg-notification-channels-upd) | `BEFORE UPDATE` / `ROW` | [`notification_channels`](access-matrix.md#access-notification-channels) | [`set_updated_at()`](#function-set-updated-at) | row update 전에 updated_at을 now()로 덮어씀 |

각 trigger는 `DROP TRIGGER IF EXISTS` 뒤 재생성된다. schema file이 startup transaction에서 끝까지 commit되기 전에 실패하면 새 정의는 적용되지 않는다. 실제 운영 schema drift는 관찰하지 않았다.

<a id="schema-migrations"></a>
## 조건부 legacy migration과 최종 정의

아래 블록은 fresh `CREATE`만으로는 보이지 않는 기존 설치 변환이다. 조건이 맞는 row/constraint에만 실행되며, block 안 SQL이 실패하면 startup schema transaction이 rollback되고 server startup도 실패한다.

| 객체 | fresh 정의 | legacy 조건과 변경 | 실패·경계 |
|---|---|---|---|
| `exploration_nodes.ck_node_kind` | `begin, goal, intent, fact, finding, hint, digest` | 같은 이름 constraint의 `pg_get_constraintdef` text에 `digest` token이 없을 때 drop/recreate | lexical substring 탐지라 token만 포함한 wrong/weaker/stricter 식은 skip될 수 있음; 다른 이름 제약은 바꾸지 않으며 legacy kind가 최종 allowlist 밖이면 ADD CHECK 실패·rollback |
| `exploration_nodes.ck_node_state` | intent에 `paused/deleted`, digest에 `superseded`를 포함한 kind별 state check | 같은 이름 constraint text에 `paused`, `superseded`, `deleted` token 중 하나라도 없을 때 drop/recreate | lexical substring 탐지라 세 token만 포함한 wrong/weaker/stricter 식은 skip될 수 있음; 기존 row가 최종 check를 위반하면 add 실패·rollback |
| `exploration_edges.ck_edge_rel` | `spawns, derived_from, yields, proves, covers` | constraint text에 `rel`을 포함하고 `covers`가 없는 CHECK의 동적 legacy 이름을 찾아 drop한 뒤 `ck_edge_rel` 추가 | lexical substring 선택이라 unrelated/wrong CHECK도 후보가 될 수 있음;  일치가 0개면 drifted table에 최종 check를 추가하지 않음; non-STRICT `SELECT INTO`와 `ORDER BY` 부재로 여러 일치 중 임의 하나만 drop해 남은 stricter check가 `covers`를 거부할 수 있음; legacy row 위반 시 add 실패·rollback |
| `llm_profiles.thinking_type` | `TEXT NOT NULL DEFAULT ''` | column이 없을 때만 추가하고 old `reasoning_effort`: low/medium/high/max→enabled, off→disabled+effort 빈 값 | `information_schema.columns` lookup에 `table_schema` filter가 없어 search path 밖 동명 table이 있으면 잘못 skip할 수 있음(U); column이 이미 있으면 backfill 전체를 건너뛰어 사용자의 이후 조합을 보존 |
| `agents.current_prompt_id` FK | `agent_prompts(id) ON DELETE SET NULL` | 두 table 생성 뒤 `fk_agents_curprompt`를 추가; `duplicate_object`는 정의를 대조하지 않고 무시 | 같은 이름의 기존 FK가 잘못된 column/target/delete action이어도 조용히 수용·미수리; 다른 이름의 같은 FK는 중복될 수 있고 orphan legacy 값이면 add 실패·rollback |

### Fresh·upgrade 제약 및 반복 migration

| owning object | startup 행위 | fresh·legacy 차이와 실패 경계 |
|---|---|---|
| [`agents.trigger_run_mode/trigger_merge_mode`](access-matrix.md#access-agents) | legacy DB에 column/default/not-null을 `ADD COLUMN IF NOT EXISTS` | fresh `CREATE` 경로에만 mode allowlist CHECK가 있고 legacy ADD에는 CHECK가 없다. upgraded DB의 무결성은 backend 쓰기 allowlist에 의존하며 DB 전역 보장이 아님 |
| [`task_intercept_rules.action`](access-matrix.md#access-task-intercept-rules) | legacy DB에 `action TEXT NOT NULL DEFAULT 'block'` 추가 | fresh는 `block/allow` CHECK가 있지만 legacy ADD에는 CHECK가 없어 동일한 divergence가 있음 |
| [`intercept_pending.decision_source`](access-matrix.md#access-intercept-pending) | column 추가 후 빈 값을 `rule_id`→`rule`, `[模型]%`→`model`, 나머지→`unknown`으로 CASE UPDATE | `WHERE decision_source=''`를 매 startup 반복 실행하므로 이후 빈 값을 저장하면 다음 startup에 다시 분류됨; statement 실패 시 transaction rollback |
| [`task_scope`](access-matrix.md#access-task-scope) | old `uq_task_scope`를 조건 없이 drop하고 `value`까지 포함한 `uq_task_scope_v2`를 `IF NOT EXISTS`로 생성 | `IF NOT EXISTS`는 이름만 검사하므로 동명 stale/wrong 정의를 수리하지 않음; 신규 생성을 거부하는 legacy 중복이 있으면 statement 실패가 transaction rollback되어 old drop도 되돌아가야 함; 실제 DB는 미관찰 |
| [`mcp_servers`](access-matrix.md#access-mcp-servers) | `ScopeSentry` 이름이 없을 때 disabled HTTP placeholder(URL null, empty API-key env) insert | name conflict면 `DO NOTHING`; 사용자가 수정한 row를 덮어쓰지 않지만, 다른 이름의 동일 server는 중복 개념으로 남을 수 있음 |
| `company_scope`, `llm_profiles`, `task_scope`, `mcp_servers`, `assets.company_source` | 매 startup에 소스가 지정한 exact constraint name만 `DROP CONSTRAINT IF EXISTS`한 뒤 현재 allowlist/payload/format/max/retry/transport/source CHECK를 다시 추가 | `NOT VALID`가 아니라 기존 row를 매번 전수 재검증하고 ALTER table lock이 normal writer를 block할 수 있음. initializer의 session advisory lock은 schema initializer와 명시적으로 참여하는 archive/aux-ledger transaction을 직렬화하지만 일반 connection은 gate하지 않음. invalid legacy row면 schema transaction과 startup이 중단되며, 다른 이름의 old/stricter semantic CHECK는 남아 새 named CHECK와 병존할 수 있음; 자동 정규화는 `assets.company_source` null→explicit 외에 없음 |
| [`task_archives.format_version`](access-matrix.md#access-task-archives), [`agents.run_seconds`](access-matrix.md#access-agents) | column default를 각각 2, 1200으로 ALTER | 기존 row backfill이 아니며 이후 default를 쓰는 INSERT에만 적용 |

`ADD COLUMN IF NOT EXISTS`는 column이 없을 때만 선언한 type/null/default/FK를 적용한다. 같은 이름의 legacy column이 다른 property를 가져도 별도 `ALTER`가 없는 한 수렴하지 않는다. 따라서 core 520-column 합집합은 이 revision의 declared target이며 upgraded 운영 catalog의 520개 property가 모두 같다는 보장이 아니다.

### Startup data backfill

| owning object | exact mutation | 조건·멱등성 | 부분 실패 경계 |
|---|---|---|---|
| [`assets.company_source`](access-matrix.md#access-assets) | `UPDATE assets SET company_source='explicit' WHERE company_source IS NULL` 뒤 default/not-null/check 적용 | null legacy row만 매 startup 변환; 이미 `explicit/scope`인 row는 유지 | legacy null update 또는 뒤 constraint 적용 실패 시 core schema transaction 전체 rollback |
| [`assets.task_ids` → `task_asset_links`](access-matrix.md#access-task-asset-links) | array를 unnest해 존재하며 `deleted_at IS NULL`인 task만 `(task_id,asset_id,source=legacy)` insert | `ON CONFLICT DO NOTHING`으로 재실행 멱등; stale/deleted task id는 link를 만들지 않음 | statement 실패 시 core schema transaction rollback; 기존 detailed source row는 conflict로 덮지 않음 |
| [`tasks.llm_profile_id` → `task_llm_profiles`](access-matrix.md#access-task-llm-profiles) | chain이 없는 pinned task에 position 0 row insert 후 `active_llm_profile_id`가 null이고 chain row가 존재할 때 legacy profile로 설정 | `NOT EXISTS`+`ON CONFLICT DO NOTHING`; user가 chain을 지우는 update는 legacy column도 비워 다음 startup 재생성을 막음 | insert/update 둘 중 실패하면 같은 schema transaction rollback; existing non-null active profile은 보존 |
| [`llm_profiles.reasoning_effort` → `thinking_type`](access-matrix.md#access-llm-profiles) | column이 없을 때 low/medium/high/max→enabled, off→disabled와 effort 빈 값 | column 존재 자체가 one-time marker; 이후 사용자 조합은 startup이 다시 쓰지 않음 | add 또는 두 UPDATE 중 실패하면 core schema transaction rollback |

`CREATE TABLE` column 510개와 `ALTER ... ADD COLUMN` 58개 중 48개 중복·10개 additive-only를 합치면 core 520개다. `llm_records`·`llm_usage` 31개는 이 파일 밖 best-effort `Ensure*Table`이며 core migration transaction과 가용성 보장이 다르다. [보조 ledger](agent-runtime.md#table-llm-records)

<a id="schema-validation"></a>
## 확인 범위

| 층 | 확인 결과 |
|---|---|
| 고정 소스 정적 추출 | function 4, trigger 22, 명시 index 97, core table 49, 최종 column 합집합 520 |
| 실행 검증 | 수행하지 않음; PostgreSQL instance·migration log artifact 없음 |
| 미확인 | 운영 instance drift, legacy row backfill 성공, role/grant, extension, query plan |

`reset-password.sh`의 `pgcrypto` 사용은 운영 script 의존이다. core schema가 extension을 생성하거나 모든 배포에서 availability를 보장하지 않는다.

<a id="function-set-updated-at"></a>
### `set_updated_at()`

`NEW.updated_at = now()` 뒤 `NEW` 반환. [정확한 DDL 근거](evidence:db-ddl-function-set-updated-at)

| table | access/effect | 열 | 조건 |
|---|---|---|---|
| [`companies`](access-matrix.md#access-companies) | write | `updated_at` | BEFORE UPDATE ROW trigger `trg_companies_upd` |
| [`assets`](access-matrix.md#access-assets) | write | `updated_at` | BEFORE UPDATE ROW trigger `trg_av2_upd` |
| [`explorations`](access-matrix.md#access-explorations) | write | `updated_at` | BEFORE UPDATE ROW trigger `trg_exp_upd` |
| [`exploration_nodes`](access-matrix.md#access-exploration-nodes) | write | `updated_at` | BEFORE UPDATE ROW trigger `trg_expnodes_upd` |
| [`llm_profiles`](access-matrix.md#access-llm-profiles) | write | `updated_at` | BEFORE UPDATE ROW trigger `trg_llm_upd` |
| [`task_categories`](access-matrix.md#access-task-categories) | write | `updated_at` | BEFORE UPDATE ROW trigger `trg_task_categories_upd` |
| [`tasks`](access-matrix.md#access-tasks) | write | `updated_at` | BEFORE UPDATE ROW trigger `trg_tasks_upd` |
| [`task_archives`](access-matrix.md#access-task-archives) | write | `updated_at` | BEFORE UPDATE ROW trigger `trg_task_archives_upd` |
| [`task_templates`](access-matrix.md#access-task-templates) | write | `updated_at` | BEFORE UPDATE ROW trigger `trg_task_templates_upd` |
| [`task_asset_links`](access-matrix.md#access-task-asset-links) | write | `updated_at` | BEFORE UPDATE ROW trigger `trg_task_asset_links_upd` |
| [`task_llm_profiles`](access-matrix.md#access-task-llm-profiles) | write | `updated_at` | BEFORE UPDATE ROW trigger `trg_task_llm_profiles_upd` |
| [`agents`](access-matrix.md#access-agents) | write | `updated_at` | BEFORE UPDATE ROW trigger `trg_agents_upd` |
| [`mcp_servers`](access-matrix.md#access-mcp-servers) | write | `updated_at` | BEFORE UPDATE ROW trigger `trg_mcp_upd` |
| [`tools`](access-matrix.md#access-tools) | write | `updated_at` | BEFORE UPDATE ROW trigger `trg_tools_upd` |
| [`conversations`](access-matrix.md#access-conversations) | write | `updated_at` | BEFORE UPDATE ROW trigger `trg_conversations_upd` |
| [`agent_triggers`](access-matrix.md#access-agent-triggers) | write | `updated_at` | BEFORE UPDATE ROW trigger `trg_agent_triggers_upd` |
| [`intercept_rules`](access-matrix.md#access-intercept-rules) | write | `updated_at` | BEFORE UPDATE ROW trigger `trg_intercept_rules_upd` |
| [`asset_intercept_rules`](access-matrix.md#access-asset-intercept-rules) | write | `updated_at` | BEFORE UPDATE ROW trigger `trg_asset_intercept_rules_upd` |
| [`task_intercept_rules`](access-matrix.md#access-task-intercept-rules) | write | `updated_at` | BEFORE UPDATE ROW trigger `trg_task_intercept_rules_upd` |
| [`notification_channels`](access-matrix.md#access-notification-channels) | write | `updated_at` | BEFORE UPDATE ROW trigger `trg_notification_channels_upd` |

<a id="function-try-inet"></a>
### `try_inet()`

text argument를 `inet`로 cast하고 모든 예외를 `NULL`로 변환한다. 함수 자체는 table을 읽거나 쓰지 않으며 `IMMUTABLE STRICT`다. 아래 행은 production call site가 읽는 table이다. [정확한 DDL 근거](evidence:db-ddl-function-try-inet)

| call-site table | caller access/effect | 열 | 조건 |
|---|---|---|---|
| [`assets`](access-matrix.md#access-assets) | read/transform | `ip, domain` | company/task scope query에서 자유 text를 안전하게 inet 변환 |
| [`company_scope`](access-matrix.md#access-company-scope) | read predicate | `net` | `net >>= try_inet(assets.ip)` attribution predicate |
| [`task_scope`](access-matrix.md#access-task-scope) | read predicate | `net` | coverage/scope predicate에서 assets text 변환 |

<a id="function-sync-task-asset-links"></a>
### `sync_task_asset_links()`

`NEW.task_ids`의 live task를 provenance link로 insert하고 빠진 link를 delete. [정확한 DDL 근거](evidence:db-ddl-function-sync-task-asset-links)

| table | access/effect | 열 | 조건 |
|---|---|---|---|
| [`assets`](access-matrix.md#access-assets) | read trigger row | `id, task_ids` | AFTER INSERT OR UPDATE OF task_ids의 NEW row |
| [`tasks`](access-matrix.md#access-tasks) | read | `id, deleted_at` | 요청 task id 중 live task만 선택 |
| [`task_asset_links`](access-matrix.md#access-task-asset-links) | write | `task_id, asset_id, source, source_summary` | link insert/do-nothing + NEW.task_ids 밖 link delete |

<a id="function-stop-deleted-conversation-retest"></a>
### `stop_deleted_conversation_retest()`

삭제될 conversation의 pending/running retest를 stopped로 전이. [정확한 DDL 근거](evidence:db-ddl-function-stop-deleted-conversation-retest)

| table | access/effect | 열 | 조건 |
|---|---|---|---|
| [`conversations`](access-matrix.md#access-conversations) | read trigger row | `id` | BEFORE DELETE의 OLD row |
| [`finding_retests`](access-matrix.md#access-finding-retests) | write | `status, error, finished_at` | conversation_id=OLD.id AND status pending/running |

<a id="trigger-direct-entry"></a>
## Trigger 개별 진입

개별 목적지는 trigger에서 fire table과 function, timing/event/row level, 영향 열을 모두 역탐색한다.

<a id="trigger-trg-companies-upd"></a>
### `trg_companies_upd`

`BEFORE UPDATE` / `FOR EACH ROW` on [`companies`](access-matrix.md#access-companies); [`set_updated_at()`](#function-set-updated-at)을 실행한다. row update 전에 updated_at을 now()로 덮어씀 [정확한 DDL 근거](evidence:db-ddl-trigger-trg-companies-upd)

<a id="trigger-trg-av2-upd"></a>
### `trg_av2_upd`

`BEFORE UPDATE` / `FOR EACH ROW` on [`assets`](access-matrix.md#access-assets); [`set_updated_at()`](#function-set-updated-at)을 실행한다. row update 전에 updated_at을 now()로 덮어씀 [정확한 DDL 근거](evidence:db-ddl-trigger-trg-av2-upd)

<a id="trigger-trg-exp-upd"></a>
### `trg_exp_upd`

`BEFORE UPDATE` / `FOR EACH ROW` on [`explorations`](access-matrix.md#access-explorations); [`set_updated_at()`](#function-set-updated-at)을 실행한다. row update 전에 updated_at을 now()로 덮어씀 [정확한 DDL 근거](evidence:db-ddl-trigger-trg-exp-upd)

<a id="trigger-trg-expnodes-upd"></a>
### `trg_expnodes_upd`

`BEFORE UPDATE` / `FOR EACH ROW` on [`exploration_nodes`](access-matrix.md#access-exploration-nodes); [`set_updated_at()`](#function-set-updated-at)을 실행한다. row update 전에 updated_at을 now()로 덮어씀 [정확한 DDL 근거](evidence:db-ddl-trigger-trg-expnodes-upd)

<a id="trigger-trg-llm-upd"></a>
### `trg_llm_upd`

`BEFORE UPDATE` / `FOR EACH ROW` on [`llm_profiles`](access-matrix.md#access-llm-profiles); [`set_updated_at()`](#function-set-updated-at)을 실행한다. row update 전에 updated_at을 now()로 덮어씀 [정확한 DDL 근거](evidence:db-ddl-trigger-trg-llm-upd)

<a id="trigger-trg-task-categories-upd"></a>
### `trg_task_categories_upd`

`BEFORE UPDATE` / `FOR EACH ROW` on [`task_categories`](access-matrix.md#access-task-categories); [`set_updated_at()`](#function-set-updated-at)을 실행한다. row update 전에 updated_at을 now()로 덮어씀 [정확한 DDL 근거](evidence:db-ddl-trigger-trg-task-categories-upd)

<a id="trigger-trg-tasks-upd"></a>
### `trg_tasks_upd`

`BEFORE UPDATE` / `FOR EACH ROW` on [`tasks`](access-matrix.md#access-tasks); [`set_updated_at()`](#function-set-updated-at)을 실행한다. row update 전에 updated_at을 now()로 덮어씀 [정확한 DDL 근거](evidence:db-ddl-trigger-trg-tasks-upd)

<a id="trigger-trg-task-archives-upd"></a>
### `trg_task_archives_upd`

`BEFORE UPDATE` / `FOR EACH ROW` on [`task_archives`](access-matrix.md#access-task-archives); [`set_updated_at()`](#function-set-updated-at)을 실행한다. row update 전에 updated_at을 now()로 덮어씀 [정확한 DDL 근거](evidence:db-ddl-trigger-trg-task-archives-upd)

<a id="trigger-trg-task-templates-upd"></a>
### `trg_task_templates_upd`

`BEFORE UPDATE` / `FOR EACH ROW` on [`task_templates`](access-matrix.md#access-task-templates); [`set_updated_at()`](#function-set-updated-at)을 실행한다. row update 전에 updated_at을 now()로 덮어씀 [정확한 DDL 근거](evidence:db-ddl-trigger-trg-task-templates-upd)

<a id="trigger-trg-task-asset-links-upd"></a>
### `trg_task_asset_links_upd`

`BEFORE UPDATE` / `FOR EACH ROW` on [`task_asset_links`](access-matrix.md#access-task-asset-links); [`set_updated_at()`](#function-set-updated-at)을 실행한다. row update 전에 updated_at을 now()로 덮어씀 [정확한 DDL 근거](evidence:db-ddl-trigger-trg-task-asset-links-upd)

<a id="trigger-trg-assets-task-links"></a>
### `trg_assets_task_links`

`AFTER INSERT OR UPDATE OF TASK_IDS` / `FOR EACH ROW` on [`assets`](access-matrix.md#access-assets); [`sync_task_asset_links()`](#function-sync-task-asset-links)을 실행한다. asset task_ids 뒤 provenance link insert/delete [정확한 DDL 근거](evidence:db-ddl-trigger-trg-assets-task-links)

<a id="trigger-trg-task-llm-profiles-upd"></a>
### `trg_task_llm_profiles_upd`

`BEFORE UPDATE` / `FOR EACH ROW` on [`task_llm_profiles`](access-matrix.md#access-task-llm-profiles); [`set_updated_at()`](#function-set-updated-at)을 실행한다. row update 전에 updated_at을 now()로 덮어씀 [정확한 DDL 근거](evidence:db-ddl-trigger-trg-task-llm-profiles-upd)

<a id="trigger-trg-agents-upd"></a>
### `trg_agents_upd`

`BEFORE UPDATE` / `FOR EACH ROW` on [`agents`](access-matrix.md#access-agents); [`set_updated_at()`](#function-set-updated-at)을 실행한다. row update 전에 updated_at을 now()로 덮어씀 [정확한 DDL 근거](evidence:db-ddl-trigger-trg-agents-upd)

<a id="trigger-trg-mcp-upd"></a>
### `trg_mcp_upd`

`BEFORE UPDATE` / `FOR EACH ROW` on [`mcp_servers`](access-matrix.md#access-mcp-servers); [`set_updated_at()`](#function-set-updated-at)을 실행한다. row update 전에 updated_at을 now()로 덮어씀 [정확한 DDL 근거](evidence:db-ddl-trigger-trg-mcp-upd)

<a id="trigger-trg-tools-upd"></a>
### `trg_tools_upd`

`BEFORE UPDATE` / `FOR EACH ROW` on [`tools`](access-matrix.md#access-tools); [`set_updated_at()`](#function-set-updated-at)을 실행한다. row update 전에 updated_at을 now()로 덮어씀 [정확한 DDL 근거](evidence:db-ddl-trigger-trg-tools-upd)

<a id="trigger-trg-conversations-upd"></a>
### `trg_conversations_upd`

`BEFORE UPDATE` / `FOR EACH ROW` on [`conversations`](access-matrix.md#access-conversations); [`set_updated_at()`](#function-set-updated-at)을 실행한다. row update 전에 updated_at을 now()로 덮어씀 [정확한 DDL 근거](evidence:db-ddl-trigger-trg-conversations-upd)

<a id="trigger-trg-agent-triggers-upd"></a>
### `trg_agent_triggers_upd`

`BEFORE UPDATE` / `FOR EACH ROW` on [`agent_triggers`](access-matrix.md#access-agent-triggers); [`set_updated_at()`](#function-set-updated-at)을 실행한다. row update 전에 updated_at을 now()로 덮어씀 [정확한 DDL 근거](evidence:db-ddl-trigger-trg-agent-triggers-upd)

<a id="trigger-trg-intercept-rules-upd"></a>
### `trg_intercept_rules_upd`

`BEFORE UPDATE` / `FOR EACH ROW` on [`intercept_rules`](access-matrix.md#access-intercept-rules); [`set_updated_at()`](#function-set-updated-at)을 실행한다. row update 전에 updated_at을 now()로 덮어씀 [정확한 DDL 근거](evidence:db-ddl-trigger-trg-intercept-rules-upd)

<a id="trigger-trg-conversation-retest-delete"></a>
### `trg_conversation_retest_delete`

`BEFORE DELETE` / `FOR EACH ROW` on [`conversations`](access-matrix.md#access-conversations); [`stop_deleted_conversation_retest()`](#function-stop-deleted-conversation-retest)을 실행한다. conversation delete 전에 active retest를 stopped로 전이 [정확한 DDL 근거](evidence:db-ddl-trigger-trg-conversation-retest-delete)

<a id="trigger-trg-asset-intercept-rules-upd"></a>
### `trg_asset_intercept_rules_upd`

`BEFORE UPDATE` / `FOR EACH ROW` on [`asset_intercept_rules`](access-matrix.md#access-asset-intercept-rules); [`set_updated_at()`](#function-set-updated-at)을 실행한다. row update 전에 updated_at을 now()로 덮어씀 [정확한 DDL 근거](evidence:db-ddl-trigger-trg-asset-intercept-rules-upd)

<a id="trigger-trg-task-intercept-rules-upd"></a>
### `trg_task_intercept_rules_upd`

`BEFORE UPDATE` / `FOR EACH ROW` on [`task_intercept_rules`](access-matrix.md#access-task-intercept-rules); [`set_updated_at()`](#function-set-updated-at)을 실행한다. row update 전에 updated_at을 now()로 덮어씀 [정확한 DDL 근거](evidence:db-ddl-trigger-trg-task-intercept-rules-upd)

<a id="trigger-trg-notification-channels-upd"></a>
### `trg_notification_channels_upd`

`BEFORE UPDATE` / `FOR EACH ROW` on [`notification_channels`](access-matrix.md#access-notification-channels); [`set_updated_at()`](#function-set-updated-at)을 실행한다. row update 전에 updated_at을 now()로 덮어씀 [정확한 DDL 근거](evidence:db-ddl-trigger-trg-notification-channels-upd)
