<a id="storage-runtime"></a>
# 저장소·background service 모듈

ARTEX의 업무 상태는 PostgreSQL, HTTP capture는 별도 SQLite/blob tree, agent context와 artifact는 filesystem에 나뉜다. Manager가 이 저장소와 background service의 lifetime을 묶는다. 데이터 객체 전수는 [DB 독립 진입](../contracts/data-model.md#storage-topology), 경로/backup은 [운영](../operations.md#paths)을 본다.

<a id="manager-lifecycle"></a>
## Manager 구성과 종료

```mermaid
flowchart TD
  New[NewManager] --> PG[PostgreSQL open + schema/seed]
  New --> Traffic[Traffic store/proxy optional]
  New --> Asset[Asset/Company stores]
  New --> Enrich[Enrichment pool]
  New --> Registry[Task runtime registry]
  Registry --> Restore[existing task recovery]
  Registry --> Engine[Engine instances]
  New --> Archive[Archive worker]
  New --> Scheduler[Agent scheduler]
  New --> Notifier[Notification worker]
  Close[Manager.Close] --> Engine
  Close --> Enrich
  Close --> Traffic
  Close --> PG

  click PG "../contracts/data-model.md#postgres-map" "PostgreSQL objects"
  click Traffic "../contracts/db/traffic-sqlite.md#db-sqlite" "Traffic store"
  click Engine "../features/task-exploration.md#feedback-loop" "Engine"
  click Archive "../operations.md#archives" "Archive"
```

PostgreSQL DSN은 필수다. Traffic proxy/store 초기화가 실패하면 capture disabled 상태로 계속할 수 있으므로 Manager 생성 성공이 evidence capture readiness를 뜻하지 않는다. Startup은 schema와 built-in rows를 적용한 뒤 task runtime을 복원한다. [Manager](evidence:manager) [DB open](evidence:db-open)

<a id="postgres-ownership"></a>
## PostgreSQL 책임 영역

| 영역 | 권위 상태 | 주요 구현 |
|---|---|---|
| task/admission/archive | task status, queue, sources, package job | `db/tasks.go`, `db/intent_admission.go`, `db/task_archives*.go` |
| asset/scope | typed asset, company, task link/scope, coverage | `db/assets.go`, `db/company_scope.go`, `db/task_scope.go` |
| exploration | node/edge/anchor/frontier/activity/main session | `db/exploration*.go` |
| agent runtime config | profiles, agents, prompts, tools, MCP, visibility, trigger | `db/config.go`, `db/tools.go`, `db/triggers.go` |
| finding/evidence | finding row, retest, snapshot/binding/version | `db/findings.go`, `db/finding_traffic.go` |
| approval/notification | rule, pending/history/audit, event/delivery lease | `db/intercept*.go`, `db/notification*.go` |
| conversations/side questions | independent activity/history/memory | `db/conversation.go`, `db/side_questions.go` |
| observation | logs, LLM raw records/usage, tool/skill usage | 여러 ensure/query modules |

Schema는 단일 migration number ledger가 아니라 `schema.sql`과 두 `Ensure*Table` 함수의 idempotent DDL 조합이다. 실제 instance가 중간 DDL 실패 없이 최종 상태인지 version row 하나로 확인할 수 없다.

<a id="traffic-storage"></a>
## Traffic recorder와 evidence store

Traffic store는 SQLite `exchanges`, `exchange_bodies`, `blob_refs`와 큰 body용 CAS blob을 소유한다. 현재 capture는 per-request host tree를 만들지 않으며 non-empty `exchanges.path`와 host/method tree는 legacy 조회·삭제·archive 호환에만 남는다. Evidence store는 선택 exchange를 다시 읽어 별도 evidence blob과 PostgreSQL snapshot/binding으로 고정한다. Traffic delete/reclaim과 evidence retention은 다른 생명주기다. [Traffic](evidence:traffic) [Evidence](evidence:evidence-store)

| mutation | 원자 경계 | crash/cleanup 경계 |
|---|---|---|
| capture exchange | SQLite metadata/body refs와 filesystem write 절차 | body/blob 부분 상태 reclaim 필요 가능 |
| finding record | PostgreSQL finding/snapshot/binding transaction | staged evidence blob cleanup 별도 |
| traffic delete | SQLite/CAS reference와 존재 시 legacy host tree 정리 | incremental vacuum/reclaim background |
| evidence GC | unreferenced snapshot/blob grace 확인 | running process/timer와 filesystem 상태 |

<a id="filesystem-state"></a>
## Filesystem 상태

| 경로 | 소유자 | 의미 |
|---|---|---|
| `<data>/tasks/<task>` | file/default tools, upload, artifact | task workdir; archive 대상 일부 |
| `<data>/transcripts` | Norma transcript store | session resume 원문 |
| `<data>/traffic` | traffic recorder | SQLite, MITM CA, CAS blobs, 선택적 legacy host tree |
| `<data>/evidence` | evidence store | finding snapshot body CAS |
| `<data>/noa` | experimental Noa | session별 persistent context archive |
| `<BaseDir>/skills` | Skill registry/UI | executable instruction/resources |
| `<BaseDir>/jwt.key` | auth | workspace 밖 signing key |
| archive package/stage | archive worker | task snapshot과 restore journal |

Workspace API는 configured work root 안의 file manager이며 `jwt.key`를 data workspace 밖으로 옮기는 방어가 있다. Path validation이 있어도 업로드/도구가 만든 파일의 신뢰성·secret 여부를 자동 판정하지 않는다.

<a id="background-loops"></a>
## Background loop와 완료 신호

| loop | 입력 선택 | 재시도/제어 | 완료 판정 |
|---|---|---|---|
| task admission | queued tasks + provider/limit | FIFO 승격, runtime recovery | task running/engine 등록 |
| Engine | task frontier/triggers | worker/planner wake, deadline settlement | task/intent states |
| Scheduler | enabled interval triggers + scheduler_state | tick과 last fire | submitted agent run/후속 activity |
| Notifier | pending delivery lease/digest batch | backoff, rate token, retry budget | delivery sent/failed/deferred |
| Archive worker | task_archives queued state | stage journal/recovery | archived/restored/deleted state/phase |
| Enrichment | in-memory bounded jobs | cooldown, queue drop, no persistence | asset attrs/upsert; 실패는 best-effort |
| Evidence GC | unreferenced items | grace period/ticker | DB/file 제거 결과 |
| Self-update settle | staged binary marker | boot counter/rollback | settle marker 또는 old binary 복원 |

각 HTTP enqueue 응답은 위 loop에 작업이 등록됐다는 뜻이다. `state`, `phase`, activity 또는 delivery row를 확인해야 외부 효과 완료를 판단할 수 있다. [Scheduler](evidence:agent-scheduler) [Notifier](evidence:notifier) [Archive](evidence:task-archive-server)

<a id="concurrency"></a>
## 동시성·transaction·lock

- intent claim은 상태 조건부 update로 worker 경쟁을 제어한다.
- finding record는 task/finding 단위 advisory lock과 transaction으로 graph/row/evidence를 묶는다.
- active finding retest는 partial unique index로 finding당 하나를 제한한다.
- notification delivery는 lease claim으로 여러 worker의 중복 전송을 줄인다. crash 뒤 결과 불명 전송은 채널 idempotency와 함께 봐야 한다.
- task delete는 engine quiescence/barrier 뒤 DB/file cleanup을 진행한다.
- `DeleteTaskCascadePrepared`와 `CompleteTaskArchive`는 자산 삭제·archive exclusivity 재검사 전에 같은 순서로 `assets`, `exploration_anchors`에 `SHARE ROW EXCLUSIVE` table lock을 잡는다. transaction commit까지 두 table의 일반 INSERT/UPDATE/DELETE와 같은 lock을 요구하는 작업을 막아 새 소유권·anchor phantom을 줄인다. [delete lock](evidence:db-pg-access-assets-439a90dd4a) [archive lock](evidence:db-pg-access-assets-42719fef02)
- side question은 parent key command lock과 running-request unique를 사용한다.
- schema 적용은 PostgreSQL advisory lock과 일부 deadlock retry를 사용한다.

DB lock이 외부 HTTP, shell command, webhook delivery를 transaction처럼 rollback하지는 않는다.

<a id="retention"></a>
## 삭제·archive·보존 경계

Task delete/archive는 task-owned DB/file/traffic/evidence의 선별 목록을 사용한다. Global profiles/tools/settings, 다른 task가 공유하는 자산, independent conversation, notification/intercept history 등은 같은 생명주기가 아니다. Archive snapshot 목록에 없는 retest conversation이나 global state는 task package에 포함됐다고 가정하지 않는다. [Archive DB](evidence:task-archive-db)

<a id="storage-failures"></a>
## 실패 조건

- PostgreSQL commit과 filesystem blob write 사이 process crash는 한 transaction으로 덮이지 않는다.
- traffic/evidence/transcript/workspace를 PostgreSQL dump와 다른 시점에 복사하면 서로 참조가 어긋날 수 있다.
- `CREATE IF NOT EXISTS` 성공만으로 기존 column/constraint가 기대 정의와 같다고 증명하지 않는다.
- background queue의 memory 부분은 restart로 사라질 수 있으며 DB row가 있는 loop와 없는 enrichment를 구별한다.
- archive package checksum은 포함된 bytes 무결성을 돕지만 snapshot 분모가 전체 인스턴스 상태라는 뜻은 아니다.
