# Design Memo — v0.4 Lifecycle Semantics Roadmap

> **Status**: outline / Phase 2 planning, post v0.3.0 Platform Skeleton
> **작성일**: 2026-05-21
> **목적**: Layer 4 (Ontology Reasoner & Lifecycle Semantics) 의 6 영역 디자인 + 구현 계획
>
> **배경**: v0.3 cascade (Layer 3 Memory OS) 가 안정화됨. 다음 단계는 lifecycle 의 깊이 강화.
> v0.3 의 design memo (`v0.3-knowledge-cascade.md`) 와 동일 형식 따름.
>
> **Reference**: `docs/architecture/memory-lifecycle-architecture.md` §2.4 Layer 4 매핑

---

## 0. 한 줄 요약 (TL;DR)

> **"단순 'manual 면역 영원' 이 아닌, governance 가 있는 lifecycle.
> CASCADE (invalidation propagation) 와 EVENT/TEMPORAL (semantic
> evolution) 의 명확 분리 위에 6 영역 (T1–T6) + 1 신규 (T7
> supersede chain) 을 deterministic 으로 구현."**

**핵심 axiom** (`docs/architecture/memory-lifecycle-architecture.md §1.5`):

| 종류 | 트리거 | 처리 | 예시 |
|---|---|---|---|
| **A. Invalidated** → CASCADE (Layer 3) | Explicit invalidation 신호 (doc delete, ingestion 오류, source 철회) | Source 제거 + propagation + orphan sweep | 잘못 업로드된 문서 / 철회된 보고서 / 오탈자 정정 |
| **B. Superseded** → EVENT/TEMPORAL (Layer 4-A) | 새 문서 / 시간 흐름 / 정책 변경 / 상태 전이 | Validity window 종료 + supersede chain (past data 보존) | CEO 교체 / 정책 변경 / 계약 종료 / 조직 개편 |

두 영역을 같은 메커니즘 (overwrite / append) 으로 섞으면 **contradiction
폭발 / stale retrieval / hallucination 증가**. v0.4 는 두 영역을 분리된
track 으로 구현 + edge state 를 단순 boolean 이 아닌 rich temporal
metadata 로 (`validity`, `status: {active, superseded_by}`,
`mutation_type` 등).

---

## 1. Goals

### 1.1 핵심 목표

1. **CASCADE 와 EVENT/TEMPORAL 의 명확 분리** — 두 트랙이 같은 mutation
   을 서로 trigger 하지 않음. CASCADE 는 invalidation 만, EVENT 는
   transition 만. (§0 axiom 참조)
2. **Layer 4 7 영역 (T1–T7) 구현** — Mem0 / Letta / Cognee 와 명확 차별화
   - EVENT track: T1 (Temporal validity), T3 (Aging), T5 (Replay),
     T7 (Supersede chain, 신규)
   - GOVERNANCE track: T2 (Contradiction → A 면 CASCADE 라우팅 / B 면
     EVENT 라우팅), T4 (Reviewer)
   - CASCADE 확장: T6 (Causality chain — derived fact base 추적)
3. **Effect-based invariant** lock-in — Layer 3 의 12 invariants 패턴 확장
4. **Reference architecture 구현** — MIT 오픈소스로 시장 표준 정의
5. **i18n contract 유지** — v0.4 신규 UI-노출 라벨 (reviewer_rank /
   approval_state / contradiction reason / mutation_type 등) 은 모두
   backend `label_key` 패턴 필수. 2026-05-22 i18n sweep 으로 7 backend
   모듈에 invariant 가 되었음 (`tests/test_*_i18n.py` contract test 5개)
6. **Bench gate 준수** — `core/retrieval` / `core/graph` / `core/reasoning`
   를 건드리는 PR (T1 expiration cascade, T6 invalidation cascade 등)
   은 STEP 7 bench 결과를 PR body 에 paste (CLAUDE.md rule 2)
7. **Module size gate 준수** — 신규 `core/lifecycle/*.py` 모듈 각 < 20 KB
   (CLAUDE.md rule 5). 초과 시 분할

### 1.2 7 영역 목표 (T1-T7)

| Stage | 영역 | Track | 핵심 목표 |
|---|---|---|---|
| **T1** | Temporal Validity & Expiration | **EVENT** | Fact 의 valid window 정의, 만료 시 status=expired 로 mark (CASCADE delete 아님) |
| **T2** | Deterministic Contradiction Arbitration | **GOVERNANCE → 라우팅** | 동일 (s, p) 의 다른 o 발생 시 LLM 미사용 결정론. **A 면 CASCADE 로, B 면 EVENT supersede chain 으로 라우팅** |
| **T3** | Evidence Aging & Trust Decay | **EVENT** | Source weight 의 시간 기반 자동 decay (분야별 함수) |
| **T4** | Reviewer Authority Hierarchy | **GOVERNANCE** | Manual source 의 다층 governance (analyst / manager / admin) |
| **T5** | Replayable Audit Graph | **EVENT** | Event-sourced reconstruction, 임의 시점 graph 재현 |
| **T6** | Causality Chain Tracking | **CASCADE 확장** | Derived fact 의 base fact 추적, base invalidation 시 derived 자동 invalidation propagation |
| **T7** ⭐ | Supersede Chain (신규) | **EVENT** | Edge 의 `status: {active, superseded_by}` field. World-change 시 past data 보존 + 새 edge 생성 + 참조 |

---

## 2. Non-Goals

### 2.1 본 v0.4 에서 다루지 않음

1. **Layer 5 변경** — agentic reasoning, planner 등은 Phase 3 의 영역
2. **분산 시스템 (CRDT)** — 단일 node 유지. 분산은 별도 고려
3. **외부 ontology (DBpedia, Wikidata) 통합** — 자메스 내부 ontology 위주
4. **자동 entity resolution** — 일부만 (T2 의 base key 매칭), 완전 ER 은 별도 stage
5. **LLM 기반 conflict resolution** — 의도적으로 결정론 유지 (Mem0 와 차별점)

### 2.2 v0.4.1, v0.5 로 미루는 항목

- T3-T5 의 일부 (T1, T2 가 가장 우선)
- 다국어 ontology 정합 (외부 namespace)
- Provenance signature (cryptographic)

---

## 3. Data Model — 6 영역의 schema 확장

### 3.1 sources 배열 확장 (현 schema + governance)

**현재 (v0.3)**:
```yaml
sources:
  - doc_id: "report_Q1.pdf"
    weight: 0.7
    role: "extract"
    ts: "2026-04-01"
```

**v0.4 (Layer 4 통합)**:
```yaml
sources:
  - doc_id: "report_Q1.pdf"
    weight: 0.7
    role: "extract"
    ts: "2026-04-01"
    # T1: Temporal Validity
    valid_from: "2026-04-01"
    valid_until: "2027-04-01"      # null = 무기한 (default)
    # T3: Evidence Aging
    aging_policy: "exponential"     # "none" | "linear" | "exponential" | "step"
    half_life_days: 365             # decay 함수의 파라미터
    # T6: Causality Chain
    derived_from: ["fact_X", "fact_Y"]  # 추론된 fact 면 base fact 명시
    inference_type: "explicit" | "transitive" | "merge"

  - doc_id: null
    weight: 0.85
    role: "manual"
    ts: "2026-05-01"
    # T4: Reviewer Authority
    author: "alice@company.com"
    reviewer_rank: "manager"        # "analyst" | "manager" | "admin"
    approval_state: "approved"      # "pending" | "approved" | "revoked"
    superseded_by: null             # null 또는 다른 source ID
    reviewed_at: "2026-05-01"
    # T1: Manual 도 expiration 가능
    valid_until: "2027-05-01"       # 1년 후 자동 archive 후보
```

### 3.2 Audit log 확장 (T5)

**현재 (v0.3)**:
```jsonl
{"ts": "...", "event": "file_delete", "filename": "...", "affected_relations": 5}
```

**v0.4 (event sourcing)**:
```jsonl
{
  "event_id": "evt_2026_05_21_001",
  "ts": "2026-05-21T12:21:00",
  "type": "file_delete",
  "actor": "user:alice@...",
  "before": {"sources": [...]},     # full before snapshot
  "after": {"sources": [...]},      # full after snapshot
  "metadata": {...},
  "causality": {                    # T6
    "triggered_by": "evt_2026_05_20_999",
    "triggers": ["evt_..."]
  }
}
```

→ 임의 시점 graph 를 event 만으로 재구성 가능.

### 3.3 Relation (edge) level metadata 확장 — CASCADE + EVENT 통합

```yaml
edge:                                # was: "relation"
  head: Joby
  predicate: partner_of
  tail: NVIDIA
  confidence: 0.95                   # derived (Layer 3 noisy-OR)

  # ── EVENT/TEMPORAL track (Layer 4-A) ────────────────────
  validity:                          # T1: validity window
    from: "2024-01-15"
    to:   "2025-06-30"               # null = 무기한 (default)
  status:                            # T7 (신규): supersede chain
    active: false
    superseded_by: "edge_442"        # 새 edge ID — Bob CEO 처럼 transition
    superseded_at: "2025-06-30"
  mutation_type:                     # CASCADE 와 EVENT 명시 구분
    # one of:
    #   "active"       — 현재 valid
    #   "superseded"   — 시간 경과 / 정책 변경 등으로 새 fact 가 대체 (EVENT)
    #   "invalidated"  — 기반 source 가 무효화됨 (CASCADE)
    #   "derived"      — 다른 fact 에서 추론됨 (T6)
    superseded

  # ── CASCADE track (Layer 3) ─────────────────────────────
  sources:                           # v0.3 provenance
    - doc_id: "...", weight: 0.7, role: "extract", ts: "..."

  # ── CASCADE 확장 (T6 causality chain) ───────────────────
  inferred: true                     # derived fact 여부
  derived_from:                      # base fact list — base 무효화 시
                                     # 본 edge 도 invalidation
    - base_fact_id: "fact_a629d17f"
      derivation: "transitive"      # | "explicit" | "merge"

  # ── GOVERNANCE track (Layer 4-B) ────────────────────────
  contradictions:                    # T2: 동일 (head, predicate) 의
                                     # 거부된 후보 (audit 용)
    - tail: "Tesla"
      reason: "rejected_by_arbitration"
      arbitration_rule: "higher_confidence_wins"
      routed_to: "cascade"           # | "event" — A vs B 분류 결과
      rejected_at: "2026-04-15"
```

**핵심**: `mutation_type` field 가 CASCADE 처리 영역 (`invalidated`,
`derived`) 와 EVENT 처리 영역 (`active`, `superseded`) 을 단일 enum 으로
구분. 각 track 의 코드 path 가 이 field 로 라우팅.

---

## 4. T1 — Temporal Validity & Expiration

### 4.1 Problem

현 cascade 는 시간 차원 없음. `report_Q1.pdf` 가 영원히 source 로 남음.

실제 운영:
- 분기 보고서 → 1년 후 stale
- 정책 문서 → 개정 시 invalid
- 가격 정보 → 분기마다 갱신

### 4.2 Design

**Validity Window per Source**:
```python
source = {
    "doc_id": "...",
    "weight": 0.7,
    "valid_from": "2026-04-01",
    "valid_until": "2027-04-01"  # None = 무기한
}
```

**Confidence at time t**:
```python
def compute_confidence_with_time(sources, current_time):
    active_sources = [s for s in sources
                      if s["valid_from"] <= current_time < s["valid_until"]]
    return compute_confidence_from_sources(active_sources)
```

**Auto-expiration Cascade**:
- 매일 배치 작업: `valid_until` 경과한 source 자동 archive
- archived source 는 cascade 대상에서 제외, audit log 에 기록
- relation 의 active source 0 → relation 자동 사라짐 (Layer 3 cascade 와 동일 메커니즘)

### 4.3 New Invariants (T1)

| Test | 검증 |
|---|---|
| `test_source_expires_at_valid_until` | valid_until 경과 후 confidence 에서 제외 |
| `test_relation_dropped_when_all_active_sources_expire` | 모든 active source 만료 → relation 자동 제거 |
| `test_valid_until_null_means_indefinite` | null 인 source 는 영구 활성 |
| `test_temporal_cascade_preserves_manual_immunity` | manual source 의 expiration 은 reviewer authority 와 정합 |

### 4.4 Reference Implementation Sketch

```python
# core/relations_schema.py (확장)
def compute_confidence_from_sources(sources, current_time=None):
    """Noisy-OR over active (non-expired) sources."""
    if not sources:
        return 0.0
    p = 1.0
    for s in sources:
        if not isinstance(s, dict):
            continue
        # T1: temporal validity filtering
        if current_time and not _is_active(s, current_time):
            continue
        w = s.get("weight")
        if isinstance(w, (int, float)):
            w_clip = max(0.0, min(1.0, float(w)))
            p *= (1.0 - w_clip)
    return round(1.0 - p, 4)

def _is_active(source, t):
    """Returns True if source is currently valid (T1 logic)."""
    vf = source.get("valid_from")
    vu = source.get("valid_until")
    if vf and t < vf:
        return False
    if vu and t >= vu:
        return False
    return True


# core/lifecycle/expiration_cascade.py (신규)
def expiration_cascade(entity_root: Path, current_time: datetime) -> Dict[str, int]:
    """매일 호출되어 expired source 처리."""
    counts = {"sources_expired": 0, "relations_dropped": 0, ...}
    for entity_path in entity_root.rglob("*.md"):
        # ... cascade logic similar to cascade_remove_doc_from_sources ...
    return counts
```

---

## 5. T2 — Deterministic Contradiction Arbitration (GOVERNANCE → 라우팅)

### 5.1 Problem

두 source 가 다른 tail 주장. 두 가지 본질적 시나리오:

**시나리오 A — Invalidated**: `report_A.pdf` (2026-04) 와 `report_B.pdf`
(2026-05) 가 동일 시점에 모순. 한쪽이 틀린 정보 → CASCADE 로 잘못된
source 만 제거.

**시나리오 B — Superseded**: `report_2024.pdf` (CEO = Alice) 와
`report_2026.pdf` (CEO = Bob) — 시점이 다름, 둘 다 당시 시점에는 맞음
→ EVENT supersede chain 으로 Alice 보존 + Bob 신규.

현 Memory Loom Gate 5 는 둘 다 reject (silent). T2 는 **A vs B 분류
후 적절한 track 으로 라우팅**.

### 5.2 Design

**Stage 1 — A vs B 분류 (deterministic)**:

| 신호 | A (invalidated) | B (superseded) |
|---|---|---|
| 두 source 의 ts diff | 작음 (< N days) | 큼 (분기/연 단위) |
| 한쪽이 다른 쪽 정정/철회 명시 | 있음 (`supersedes:`/`corrects:`) | 없음 |
| 두 fact 가 동시에 valid 가능한가 | No (mutex) | Yes (시간 분리 시) |
| 결과 | CASCADE 로 라우팅 — 한쪽 source 제거 | EVENT 로 라우팅 — supersede chain 생성 |

**Stage 2 — A (invalidated) arbitration rules** (CASCADE 결과):

| Rule | 우선순위 | 결정 |
|---|---|---|
| 1. Manual override | 최우선 | reviewer_rank == "admin" 의 manual source 가 무조건 win |
| 2. Higher confidence | 2순위 | source weight 가 더 높은 쪽 win |
| 3. More recent | 3순위 | ts 가 더 최근인 쪽 win |
| 4. Temporal validity | 4순위 | valid_from 이 더 최근 쪽 win (정정본 우선) |
| 5. Default reject | fallback | 둘 다 거부 + audit log |

**Stage 2 — B (superseded) handling** (EVENT 결과):

- 기존 edge: `status.active = false`, `status.superseded_by = <new_edge_id>`
- 신규 edge 생성: `status.active = true`, `validity.from = <new_fact_ts>`
- 기존 edge 의 `validity.to = <new_fact_ts - 1day>` (또는 명시 종료일)
- CASCADE 는 호출하지 않음 (기존 source 보존)

→ **LLM 미사용**. 모든 rule 결정론적. **Routing 결정도 deterministic**.

### 5.3 New Invariants (T2)

| Test | 검증 |
|---|---|
| `test_admin_manual_overrides_any_extract` | admin manual source > extract |
| `test_higher_confidence_wins_among_equal_role` | 동일 role 시 confidence 비교 |
| `test_more_recent_wins_among_equal_confidence` | confidence 동일 시 ts 비교 |
| `test_contradictions_logged_in_relation_metadata` | 거부된 후보 metadata 에 기록 |
| `test_arbitration_is_deterministic` | 동일 input → 항상 동일 output (idempotent) |

### 5.4 Reference Sketch

```python
# core/lifecycle/contradiction_arbitrator.py (신규)
def arbitrate_contradiction(
    existing: Dict, candidate: Dict
) -> Tuple[Literal["accept", "reject", "supersede"], str]:
    """결정론적 충돌 중재.
    
    Returns:
        (action, reason_code)
    """
    # Rule 1: admin manual override
    if (candidate.get("role") == "manual" and
        candidate.get("reviewer_rank") == "admin"):
        return ("supersede", "admin_override")
    if (existing.get("role") == "manual" and
        existing.get("reviewer_rank") == "admin"):
        return ("reject", "admin_existing")
    
    # Rule 2: confidence comparison
    if candidate["weight"] > existing["weight"]:
        return ("supersede", "higher_confidence")
    if candidate["weight"] < existing["weight"]:
        return ("reject", "lower_confidence")
    
    # Rule 3: timestamp comparison (equal weight)
    cts = candidate.get("ts")
    ets = existing.get("ts")
    if cts and ets and cts > ets:
        return ("supersede", "more_recent")
    if cts and ets and cts < ets:
        return ("reject", "older")
    
    # Rule 4 + 5: fall through to default
    return ("reject", "default")
```

---

## 6. T3 — Evidence Aging & Trust Decay

### 6.1 Problem

5년 전 source 와 어제 source 가 동일 weight 로 신뢰됨. 부적절.

### 6.2 Design

**Per-Source Aging Function**:
```yaml
source:
  weight: 0.85
  ts: "2026-04-01"
  aging_policy: "exponential"
  half_life_days: 365
```

**Effective Weight at time t**:
```python
def effective_weight(source, current_time):
    base_weight = source["weight"]
    policy = source.get("aging_policy", "none")
    if policy == "none":
        return base_weight
    elif policy == "exponential":
        half_life = source.get("half_life_days", 365)
        days_old = (current_time - source["ts"]).days
        decay_factor = 0.5 ** (days_old / half_life)
        return base_weight * decay_factor
    elif policy == "linear":
        max_age = source.get("max_age_days", 365)
        days_old = (current_time - source["ts"]).days
        return base_weight * max(0, 1 - days_old / max_age)
    # 등등
```

**Domain-specific Defaults**:
- 의료: half_life = 90 일 (6개월)
- 법률: half_life = 730 일 (2년)
- 기술: half_life = 365 일 (1년)
- 인물 정보: half_life = 1825 일 (5년)

### 6.3 New Invariants (T3)

| Test | 검증 |
|---|---|
| `test_aging_none_preserves_weight` | aging_policy="none" → 시간 무관 |
| `test_exponential_aging_at_half_life` | half_life 후 effective_weight = 0.5 × base |
| `test_aging_does_not_violate_monotonicity` | aging 적용 후에도 strict 단조 유지 |
| `test_domain_specific_defaults_loaded` | ontology 의 domain 마다 default 다름 |

### 6.4 Reference Sketch

```python
# core/lifecycle/aging.py (신규)
AGING_POLICIES = {"none", "linear", "exponential", "step"}

DOMAIN_DEFAULTS = {
    "medical": {"policy": "exponential", "half_life_days": 90},
    "legal": {"policy": "exponential", "half_life_days": 730},
    "technology": {"policy": "exponential", "half_life_days": 365},
    "person": {"policy": "exponential", "half_life_days": 1825},
}

def effective_weight(source, current_time):
    # ... (위 sketch)
```

---

## 7. T4 — Reviewer Authority Hierarchy

### 7.1 Problem

`role: manual` 만으로는 governance 부족. analyst 가 잘못 입력한 manual fact 가 admin 검토 없이 영구 보존.

### 7.2 Design

**Reviewer Ranks**:
- `analyst` (lowest) — 일반 사용자, 임시 manual 입력
- `manager` — 검토자, analyst manual 을 supersede 가능
- `admin` (highest) — 최종 권한, 모든 manual 을 supersede 가능

**Approval State Machine**:
```
pending → approved (by manager 이상)
       → rejected (by manager 이상)
       
approved → revoked (by admin)
```

**Hierarchy Rule**:
- analyst manual: 임시 (pending 상태로 시작)
- manager 가 review → approved 또는 rejected
- admin manual: 즉시 approved
- 상위 rank 가 하위 rank 의 결정 override 가능

### 7.3 New Invariants (T4)

| Test | 검증 |
|---|---|
| `test_analyst_manual_starts_pending` | analyst manual → approval_state="pending" |
| `test_manager_can_approve_analyst_manual` | manager 가 analyst manual approval 가능 |
| `test_admin_overrides_manager_decision` | admin > manager > analyst hierarchy |
| `test_pending_manual_is_immune_but_invisible_in_retrieval` | pending 은 retrieval 제외 (visibility=internal) |

### 7.4 Reference Sketch

```python
# core/lifecycle/reviewer_authority.py (신규)
REVIEWER_RANKS = ["analyst", "manager", "admin"]
APPROVAL_STATES = ["pending", "approved", "rejected", "revoked"]

def can_override(actor_rank: str, target_rank: str) -> bool:
    """actor 가 target manual 을 override 할 수 있는가?"""
    return REVIEWER_RANKS.index(actor_rank) >= REVIEWER_RANKS.index(target_rank)

def initial_approval_state(reviewer_rank: str) -> str:
    """신규 manual source 의 초기 approval_state."""
    if reviewer_rank == "admin":
        return "approved"
    return "pending"
```

---

## 8. T5 — Replayable Audit Graph

### 8.1 Problem

현재 audit log 는 텍스트 jsonl. 임의 시점의 graph 상태를 재구성하기 어려움.

### 8.2 Design

**Event Sourcing 추가**:
- 모든 mutation 을 event 로 기록 (Layer 3 cascade 와 병행)
- Event 만으로 임의 시점 graph 재구성 가능
- 기존 state-based 와 dual 운영

**Event Schema**:
```json
{
  "event_id": "evt_2026_05_21_001",
  "ts": "2026-05-21T12:21:00.123Z",
  "type": "source_added|source_removed|relation_dropped|...",
  "actor": "user:alice|system:cascade|llm:extract",
  "subject": {"entity_id": "...", "relation_idx": 3},
  "before": {...},
  "after": {...},
  "causality": {
    "triggered_by": "evt_...",
    "triggers": ["evt_..."]
  }
}
```

**Replay Engine**:
```python
def reconstruct_graph_at(target_time, event_log_path):
    """event log 의 시작부터 target_time 까지 replay → graph 재구성."""
    graph = empty_graph()
    for event in iter_events(event_log_path):
        if event["ts"] > target_time:
            break
        apply_event(graph, event)
    return graph
```

### 8.3 New Invariants (T5)

| Test | 검증 |
|---|---|
| `test_event_log_is_append_only` | 절대 수정/삭제 X (immutable) |
| `test_replay_produces_current_state` | 처음부터 replay → 현재 state 와 동일 |
| `test_replay_at_arbitrary_time` | 임의 t 에서 state 재구성 가능 |
| `test_causality_chain_preserved` | trigger 관계 보존 |

### 8.4 Reference Sketch

```python
# core/lifecycle/event_store.py (신규)
@dataclass
class MutationEvent:
    event_id: str
    ts: datetime
    type: str  # "source_added" | "source_removed" | etc.
    actor: str
    subject: Dict
    before: Optional[Dict]
    after: Optional[Dict]
    causality: Dict

class EventStore:
    def append(self, event: MutationEvent) -> None:
        """append-only — 절대 수정 X."""
        ...
    
    def iter_events(self, since: datetime = None) -> Iterator[MutationEvent]:
        ...
    
    def replay_to(self, target_time: datetime) -> 'GraphState':
        ...
```

---

## 9. T6 — Causality Chain Tracking

### 9.1 Problem

추론된 fact 의 base fact 가 사라지면 derived fact 도 invalid 되어야 함. 현재는 추적 안 됨.

예시:
- base fact: "Joby 본사 in California" (from doc A)
- derived fact: "Joby 미국 회사" (inferred from base)
- doc A 삭제 시 → base fact 사라짐 → derived 도 자동 invalidate 되어야

### 9.2 Design

**derived_from Field**:
```yaml
relation:
  head: Joby
  tail: USA
  predicate: BASED_IN
  inferred: true
  derived_from:
    - base_fact_id: "fact_xxx"   # (Joby, BASED_IN, California)
      derivation: "transitive"
    - base_fact_id: "fact_yyy"   # (California, IS_IN, USA)
      derivation: "transitive"
```

**Invalidation Cascade**:
- Base fact 의 confidence 가 임계값 미만으로 떨어지면 → derived 도 invalidation 후보
- 또는 base fact 자체가 사라지면 → derived 즉시 invalidation

### 9.3 New Invariants (T6)

| Test | 검증 |
|---|---|
| `test_derived_fact_invalidated_when_base_removed` | base 제거 → derived 자동 사라짐 |
| `test_partial_base_loss_reduces_derived_confidence` | base N 개 중 일부만 사라지면 derived confidence 감소 |
| `test_causality_chain_acyclic` | derived 가 자신을 base 로 가질 수 없음 (cycle X) |
| `test_inferred_field_set_correctly` | inferred=true 인 relation 만 derived_from 보유 |

### 9.4 Reference Sketch

```python
# core/lifecycle/causality.py (신규)
def invalidate_derived_facts(base_fact_id: str) -> List[str]:
    """base fact 가 제거됨 → 의존하는 derived fact 들 식별 후 invalidation."""
    invalidated = []
    for entity_path in iter_entity_files():
        for relation in entity.relations:
            if not relation.get("inferred"):
                continue
            for derivation in relation.get("derived_from", []):
                if derivation["base_fact_id"] == base_fact_id:
                    relation["sources"] = []  # 강제 cascade drop
                    invalidated.append(relation["id"])
    return invalidated
```

---

## 9.5 T7 — Supersede Chain (신규, EVENT track)

### 9.5.1 Problem

T1 (Temporal validity) 는 *한 source 의 valid window* 를 정의하나,
**world change** 시 두 fact 의 관계 (old → new transition) 을 표현
못함. 예시:

- 2024-01: `(Joby, CEO, Alice)` — 당시 사실
- 2026-03: `(Joby, CEO, Bob)` — 새 사실

T1 만으로는 Alice edge 의 `valid_until = 2026-03` 만 set. 그러나
"누가 Alice 를 대체했나?" 의 명시적 chain 부재 →
historical / forensic 분석 시 transition 재구성 어려움.

또한 **CASCADE 와의 명확 분리** 가 필요. 위 예시는 invalidation 이
아니라 supersession. CASCADE 가 트리거되면 Alice 의 source 가
제거됨 → "2025-09 시점 시스템은 무엇을 알았나?" replay 불가.

### 9.5.2 Design

**Edge level status field** (`§3.3` schema 참조):

```yaml
edge:
  head: Joby
  predicate: CEO
  tail: Alice
  validity: {from: "2024-01-01", to: "2026-03-15"}
  status:
    active: false
    superseded_by: "edge_bob_ceo_2026"   # 신규 Bob edge ID
    superseded_at: "2026-03-15"
  mutation_type: superseded             # NOT invalidated
  sources: [...]                        # 보존 (CASCADE 미실행)
```

**Supersede chain operations**:

```python
# core/lifecycle/supersede_chain.py (신규)
def supersede_edge(
    old_edge: Edge, new_fact: Dict, supersede_ts: datetime
) -> Tuple[Edge, Edge]:
    """Old edge 를 EVENT supersede chain 으로 종료 + new edge 생성.

    CASCADE 트리거하지 않음 (sources 보존).
    """
    new_edge = create_edge_from_fact(new_fact, validity_from=supersede_ts)
    old_edge.validity.to = supersede_ts
    old_edge.status.active = False
    old_edge.status.superseded_by = new_edge.id
    old_edge.status.superseded_at = supersede_ts
    old_edge.mutation_type = "superseded"
    return (old_edge, new_edge)


def walk_supersede_chain(edge: Edge) -> List[Edge]:
    """Supersede chain 끝까지 따라가서 현 valid edge 반환.

    중간 노드 모두 보존 — historical replay 가능.
    """
    chain = [edge]
    while edge.status.superseded_by:
        edge = load_edge(edge.status.superseded_by)
        chain.append(edge)
    return chain
```

### 9.5.3 New Invariants (T7)

| Test | 검증 |
|---|---|
| `test_supersede_preserves_old_sources` | supersede 후 old edge 의 sources 배열 그대로 (CASCADE 미실행) |
| `test_supersede_chain_walks_to_active` | walk_supersede_chain 이 가장 최신 active edge 반환 |
| `test_supersede_chain_acyclic` | A → B → A 같은 cycle 불가 (insertion 시점 거부) |
| `test_historical_replay_via_chain` | 임의 시점 t 에 어떤 edge 가 active 였는지 chain + validity 로 정확히 재구성 |
| `test_supersede_does_not_trigger_cascade` | EVENT supersede 가 Layer 3 cascade_remove 호출 X |
| `test_cascade_preserves_supersede_chain` | Layer 3 cascade 가 supersede chain 의 active history (status.superseded_by link) 깨지 않음 |

### 9.5.4 Effect — historical replay

```python
# core/lifecycle/temporal_replay.py (신규)
def reconstruct_view_at(head: str, predicate: str, t: datetime) -> Optional[Edge]:
    """주어진 (head, predicate) 의 시점 t 에서의 active edge 반환.

    Supersede chain 을 시간 순으로 따라가며 validity window 가 t 를
    포함하는 edge 를 찾음. CASCADE 로 invalidated 된 edge 는 제외
    (mutation_type 으로 분리).
    """
    candidates = [
        e for e in walk_supersede_chain(load_edge_root(head, predicate))
        if e.mutation_type != "invalidated"  # CASCADE 제외
        and e.validity.from <= t <= (e.validity.to or datetime.max)
    ]
    return candidates[0] if candidates else None
```

→ "2025-09 시점에 Joby CEO 는?" 같은 forensic query 가 가능.

---

## 10. Migration — v0.3 → v0.4

### 10.1 Schema Migration

기존 sources 배열 + relation level 에 신규 필드 추가:
```python
# scripts/migrate_v04_lifecycle.py (신규)
def migrate_source_to_v04(source: Dict) -> Dict:
    """add T1/T3/T4 source-level fields with safe defaults."""
    source.setdefault("valid_from", None)      # T1
    source.setdefault("valid_until", None)
    source.setdefault("aging_policy", "none")  # T3
    source.setdefault("half_life_days", None)
    if source.get("role") == "manual":
        source.setdefault("reviewer_rank", "analyst")     # T4
        source.setdefault("approval_state", "approved")   # backward compat
    return source


def migrate_edge_to_v04(edge: Dict) -> Dict:
    """add T1/T7 edge-level fields with safe defaults."""
    edge.setdefault("validity", {"from": None, "to": None})       # T1
    edge.setdefault("status",   {"active": True,                  # T7
                                 "superseded_by": None,
                                 "superseded_at": None})
    edge.setdefault("mutation_type", "active")                    # T1/T7
    if edge.get("inferred"):
        edge.setdefault("derived_from", [])                       # T6
    return edge
```

→ 모든 기존 source / edge 는 backward-compatible (default = active
/ none / approved).

### 10.2 Audit Migration

기존 jsonl audit → event store dual write:
- v0.3 audit 유지 (legacy)
- v0.4 부터 event store 에 추가 기록
- 일정 기간 dual write → 검증 후 event store 전환

---

## 11. Risks

### 11.1 Layer 4 prior work 비교

| 영역 | 학계·산업 prior work | 자메스 차별화 |
|---|---|---|
| T1 Temporal | Bitemporal DB, temporal KG | RAG/KG 특화, EVENT vs CASCADE 명확 분리 |
| T2 Contradiction | TMS belief revision, Mem0 LLM | deterministic, no-LLM, **A/B 라우팅** (CASCADE 또는 EVENT) |
| T3 Aging | Decay in search ranking | per-domain 함수, source-level |
| T4 Reviewer | RBAC general | KG specific source governance |
| T5 Replayable | Event sourcing (general) | KG graph state reconstruction + supersede chain |
| T6 Causality | Provenance DB (W3C PROV) | RAG/KG 특화 inference graph, CASCADE 확장 |
| T7 Supersede chain | Bitemporal "valid time + transaction time" | edge level status + chain walk, **CASCADE 와 분리 invariant** |

→ 각 영역마다 specific 차별화 (구현 단계에서 design memo 보강 필요).
**가장 강한 차별화**: CASCADE (invalidation) 와 EVENT (supersede chain)
의 명확 분리 + 한 schema 안에서 mutation_type field 로 라우팅 — 대부분
경쟁 시스템 (Mem0/Letta/Cognee) 이 두 영역을 섞어 처리.

### 11.2 구현 복잡도

| 영역 | 구현 복잡도 | 우선도 |
|---|---|---|
| T1 Temporal | 중 (배치 작업 추가) | ⭐⭐⭐ |
| T7 Supersede chain | 중 (edge schema + chain walk) | ⭐⭐⭐ (T1 과 짝) |
| T2 Contradiction | 낮 (rule-based 명확) + A/B routing | ⭐⭐⭐ |
| T3 Aging | 중 (함수 family) | ⭐⭐ |
| T4 Reviewer | 중 (state machine) | ⭐⭐ |
| T5 Replayable | 고 (event sourcing) | ⭐⭐ |
| T6 Causality | 고 (derived_from graph) | ⭐⭐⭐ |

→ T1 + T7 + T2 부터 시작 권장 (EVENT track 핵심 + GOVERNANCE 라우팅).

### 11.3 운영 안정성

- Phase A-E 의 안정성 검증 후 → v0.4 작업 시작
- multi-source 데이터 자연 누적 후 → T1 (temporal) 의미 살아남
- T6 (causality) 는 inference 가 있는 경우만 활성화

---

## 12. Phase Plan

### Phase v0.4.0 — T1 + T7 + T2 (EVENT track 핵심 + 라우팅)
- **목표 일정**: 2026 Q4 ~ 2027 Q1
- **scope**: Temporal Validity (T1) + Supersede Chain (T7) +
  Contradiction Arbitration with A/B routing (T2)
- **Why this bundle**: T1 (validity window) + T7 (supersede chain) 이
  같이 가야 CASCADE vs EVENT 분리가 의미있음. T2 의 A/B routing 이
  분리를 강제 (잘못 분류 시 즉시 testable).
- **PRs**:
  - PR-T1.A: source / edge schema 확장 (validity / status / mutation_type)
  - PR-T1.B: expiration cascade 배치 작업 — `status: expired` mark only
    (CASCADE delete 와 분리)
  - PR-T7.A: supersede_edge / walk_supersede_chain / reconstruct_view_at
  - PR-T7.B: invariant tests (supersede_does_not_trigger_cascade /
    cascade_preserves_supersede_chain 등)
  - PR-T2.A: A vs B 분류 (Stage 1)
  - PR-T2.B: A path (CASCADE 라우팅) arbitration rules
  - PR-T2.C: B path (EVENT supersede_edge 호출)

### Phase v0.4.1 — T6 (인과 추적, CASCADE 확장)
- **목표 일정**: 2027 Q1 ~ Q2
- **scope**: Causality Chain Tracking
- **PRs**:
  - PR-T6.A: derived_from field + inference detection
  - PR-T6.B: invalidation cascade for derived facts

### Phase v0.4.2 — T3 (Aging, EVENT)
- **목표 일정**: 2027 Q2
- **scope**: Evidence Aging
- **PRs**:
  - PR-T3.A: aging function family + domain defaults

### Phase v0.5 — T4 + T5 (Governance + Audit)
- **목표 일정**: 2027 Q3 ~ Q4
- **scope**: Reviewer Authority + Replayable Audit
- **PRs**: T4 (state machine), T5 (event sourcing) 각각

---

## 13. Dependencies

### 외부 의존성
- 없음 (자메스 내부 작업)

### 다른 stage 와의 의존성
- v0.3 Phase A-E (Layer 3 Memory OS) — 완료 ✅
- v0.3 Hotfix 1, 2 (PR #349/350) — 완료 ✅
- v0.3 Postmortem 원칙 ("코드 → 청구") — 정합

---

## 14. Open Questions

1. **T1 의 시간 정밀도** — 일 단위? 분 단위? → 일 단위 (default) + 옵션 분 단위
2. **T2 의 confidence tie-break** — confidence 동일 + ts 동일 시 → random (deterministic seed)
3. **T3 의 default aging policy** — 전체 시스템 default 는 "none"? "exponential 1년"? → "none" (안전)
4. **T4 의 reviewer 인증** — 누가 reviewer_rank 결정? → 별도 user management
5. **T5 의 event store 보관** — 영구? 1년 후 archive? → 1년 보관 + cold storage
6. **T6 의 transitive depth 제한** — 무한 chain 방지 → max_depth = 3

---

## 15. Success Criteria

### Phase v0.4.0 성공 기준
- [ ] T1 + T2 구현 완료
- [ ] 4 + 5 = 9 신규 invariant 테스트 lock-in
- [ ] 기존 12 invariant + 신규 9 = 21 invariant 모두 green
- [ ] STEP 7 baseline 무회귀
- [ ] User 운영 시나리오 (분기 보고서 expire) 동작

### Phase v0.4.1, 0.4.2 성공 기준
- [ ] T6, T3 구현 완료
- [ ] 추가 invariant lock-in
- [ ] Layer 3 (v0.3) 의 cascade 와 정합

### Phase v0.5 성공 기준
- [ ] T4, T5 구현 완료
- [ ] Event store replay 가 현재 state 와 일치
- [ ] Reviewer hierarchy state machine 정확

---

## 16. 참고

- 본 메모는 `docs/design/v0.3-knowledge-cascade.md` 와 동일 형식
- Layer 3 (cascade) 가 안정화된 후의 next phase
- Reference architecture: `docs/architecture/memory-lifecycle-architecture.md`
- 메인 roadmap: `ROADMAP.md` (v0.4 = Layer 4 Lifecycle Semantics)

---

**End of v0.4 Lifecycle Semantics Roadmap.**

본 메모는 v0.4 Layer 4 의 구현 계획. 사용자 비판 시리즈 (2026-05-21) 의 6 영역 (temporal / contradiction / aging / reviewer / replayable / causality) 을 deterministic + invariant-locked 방식으로 구현하여, 자메스 의 reference architecture 구축.
