diff --git a/mateclaw-server/src/main/java/vip/mate/wiki/model/WikiRelationEntity.java b/mateclaw-server/src/main/java/vip/mate/wiki/model/WikiRelationEntity.java
new file mode 100644
index 00000000..59d98310
--- /dev/null
+++ b/mateclaw-server/src/main/java/vip/mate/wiki/model/WikiRelationEntity.java
@@ -0,0 +1,75 @@
+package vip.mate.wiki.model;
+
+import com.baomidou.mybatisplus.annotation.FieldFill;
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableField;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableLogic;
+import com.baomidou.mybatisplus.annotation.TableName;
+import lombok.Data;
+
+import java.math.BigDecimal;
+import java.time.LocalDateTime;
+
+/**
+ * Persistent cache of page-to-page multi-signal relations.
+ *
+ *
One row per ordered (page_a_id, page_b_id) pair within a knowledge base.
+ * The row materializes the aggregate score across registered signal strategies
+ * plus optional taxonomy / confidence / evidence metadata when the planning
+ * stage of the compile pipeline produced any.
+ *
+ *
Distinct from {@link WikiPageCitationEntity}, which models page-to-chunk
+ * citations. The two tables coexist; nothing in this row supersedes citation
+ * rows.
+ *
+ * @author MateClaw Team
+ */
+@Data
+@TableName("mate_wiki_relation")
+public class WikiRelationEntity {
+
+ @TableId(type = IdType.AUTO)
+ private Long id;
+
+ private Long kbId;
+
+ private Long pageAId;
+
+ private Long pageBId;
+
+ /** Aggregate score (sum of weighted signal contributions). */
+ private BigDecimal totalScore;
+
+ /** Per-signal breakdown serialized as JSON, e.g. {@code {"direct_link":2.0,"shared_chunk":5.0}}. */
+ private String signalsJson;
+
+ /** Relation taxonomy: {@code mention | cite | supports | contradicts | extends}. */
+ private String type;
+
+ /** Confidence taxonomy: {@code EXTRACTED | INFERRED | AMBIGUOUS | UNVERIFIED}. */
+ private String confidence;
+
+ /** Verbatim quote or paraphrased rationale supporting the relation; ≤ 500 chars. */
+ private String evidence;
+
+ /** When evidence is a quote, the raw material id it was pulled from. */
+ private Long evidenceRawId;
+
+ /** Provenance tag: {@code llm-extracted | wikilink-context | manual}. */
+ private String source;
+
+ private LocalDateTime computedAt;
+
+ /** Fingerprint of the inputs that produced {@link #totalScore}; used for cache invalidation. */
+ private String computedHash;
+
+ @TableField(fill = FieldFill.INSERT)
+ private LocalDateTime createTime;
+
+ @TableField(fill = FieldFill.INSERT_UPDATE)
+ private LocalDateTime updateTime;
+
+ @TableLogic
+ private Integer deleted;
+}
diff --git a/mateclaw-server/src/main/java/vip/mate/wiki/repository/WikiRelationMapper.java b/mateclaw-server/src/main/java/vip/mate/wiki/repository/WikiRelationMapper.java
new file mode 100644
index 00000000..f34b8b81
--- /dev/null
+++ b/mateclaw-server/src/main/java/vip/mate/wiki/repository/WikiRelationMapper.java
@@ -0,0 +1,22 @@
+package vip.mate.wiki.repository;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import org.apache.ibatis.annotations.Mapper;
+import vip.mate.wiki.model.WikiRelationEntity;
+
+/**
+ * Mapper for the page-to-page wiki relation cache.
+ *
+ *
Read paths fetch top-K rows by ({@code kb_id}, {@code page_a_id}) ordered
+ * by {@code total_score DESC}; write paths upsert (insert + on-duplicate-key
+ * update) keyed by ({@code kb_id}, {@code page_a_id}, {@code page_b_id}).
+ *
+ *
Cache invalidation paths use soft-delete (set {@code deleted=1}) keyed
+ * by either {@code page_id} or {@code kb_id}; the consuming services
+ * provide thin wrappers via custom SQL when needed.
+ *
+ * @author MateClaw Team
+ */
+@Mapper
+public interface WikiRelationMapper extends BaseMapper {
+}
diff --git a/mateclaw-server/src/main/resources/db/migration/h2/V77__wiki_relation_table.sql b/mateclaw-server/src/main/resources/db/migration/h2/V77__wiki_relation_table.sql
new file mode 100644
index 00000000..63e0457b
--- /dev/null
+++ b/mateclaw-server/src/main/resources/db/migration/h2/V77__wiki_relation_table.sql
@@ -0,0 +1,66 @@
+-- mate_wiki_relation: persistent cache of page-to-page multi-signal relations.
+--
+-- Distinct from mate_wiki_page_citation, which models page-to-chunk citations.
+-- A row here represents one directed (or undirected, see notes below) edge in
+-- the wiki page graph for a given knowledge base, materializing:
+-- * the aggregate relevance score across registered signal strategies
+-- (direct link, shared chunk, shared raw, semantic similarity, ...)
+-- * a per-signal breakdown for explainability
+-- * an optional taxonomy tag (mention / cite / supports / contradicts /
+-- extends) populated by the planning stage of the compile pipeline
+-- * confidence + evidence snippets sourced from the same compile output
+-- * cache invalidation metadata so readers can decide whether to recompute
+
+CREATE TABLE IF NOT EXISTS mate_wiki_relation (
+ id BIGINT AUTO_INCREMENT PRIMARY KEY,
+ kb_id BIGINT NOT NULL,
+ page_a_id BIGINT NOT NULL,
+ page_b_id BIGINT NOT NULL,
+
+ -- Aggregate relevance score (sum of weighted signal contributions).
+ total_score DECIMAL(8, 4),
+
+ -- Per-signal breakdown, e.g. {"direct_link":2.0,"shared_chunk":5.0}.
+ signals_json CLOB,
+
+ -- Relation type taxonomy: mention | cite | supports | contradicts | extends.
+ type VARCHAR(32),
+
+ -- Confidence taxonomy: EXTRACTED | INFERRED | AMBIGUOUS | UNVERIFIED.
+ confidence VARCHAR(16),
+
+ -- Verbatim or paraphrased justification (≤ 500 chars enforced in Java layer).
+ evidence CLOB,
+
+ -- When evidence is a quote, the raw material id it was pulled from.
+ evidence_raw_id BIGINT,
+
+ -- Provenance tag: llm-extracted | wikilink-context | manual.
+ source VARCHAR(32),
+
+ -- Cache invalidation metadata.
+ computed_at TIMESTAMP,
+ computed_hash VARCHAR(64),
+
+ -- Standard rows.
+ create_time TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
+ update_time TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
+ deleted INT NOT NULL DEFAULT 0
+);
+
+-- Unique edge identity within a KB (deleted column included so soft-deleted
+-- rows can coexist with re-inserted ones during re-compute cycles).
+CREATE UNIQUE INDEX IF NOT EXISTS uk_wr_pair
+ ON mate_wiki_relation (kb_id, page_a_id, page_b_id, deleted);
+
+-- "Top-K related to page X" queries.
+CREATE INDEX IF NOT EXISTS idx_wr_page_a
+ ON mate_wiki_relation (kb_id, page_a_id, total_score DESC);
+
+-- KB-wide ranking queries (e.g., strongest edges across the graph).
+CREATE INDEX IF NOT EXISTS idx_wr_kb_score
+ ON mate_wiki_relation (kb_id, total_score DESC);
+
+-- Cache invalidator scans (e.g., "everything older than X").
+CREATE INDEX IF NOT EXISTS idx_wr_computed_at
+ ON mate_wiki_relation (computed_at);
diff --git a/mateclaw-server/src/main/resources/db/migration/mysql/V77__wiki_relation_table.sql b/mateclaw-server/src/main/resources/db/migration/mysql/V77__wiki_relation_table.sql
new file mode 100644
index 00000000..d960ac41
--- /dev/null
+++ b/mateclaw-server/src/main/resources/db/migration/mysql/V77__wiki_relation_table.sql
@@ -0,0 +1,43 @@
+-- mate_wiki_relation: persistent cache of page-to-page multi-signal relations.
+--
+-- Distinct from mate_wiki_page_citation, which models page-to-chunk citations.
+-- A row here represents one directed (or undirected, see notes below) edge in
+-- the wiki page graph for a given knowledge base, materializing:
+-- * the aggregate relevance score across registered signal strategies
+-- (direct link, shared chunk, shared raw, semantic similarity, ...)
+-- * a per-signal breakdown for explainability
+-- * an optional taxonomy tag (mention / cite / supports / contradicts /
+-- extends) populated by the planning stage of the compile pipeline
+-- * confidence + evidence snippets sourced from the same compile output
+-- * cache invalidation metadata so readers can decide whether to recompute
+
+CREATE TABLE IF NOT EXISTS mate_wiki_relation (
+ id BIGINT AUTO_INCREMENT PRIMARY KEY,
+ kb_id BIGINT NOT NULL,
+ page_a_id BIGINT NOT NULL,
+ page_b_id BIGINT NOT NULL,
+
+ total_score DECIMAL(8, 4),
+ signals_json JSON,
+
+ type VARCHAR(32),
+
+ confidence VARCHAR(16),
+ evidence TEXT,
+ evidence_raw_id BIGINT,
+
+ source VARCHAR(32),
+
+ computed_at DATETIME(3),
+ computed_hash VARCHAR(64),
+
+ create_time DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
+ update_time DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3),
+ deleted TINYINT NOT NULL DEFAULT 0,
+
+ UNIQUE KEY uk_wr_pair (kb_id, page_a_id, page_b_id, deleted),
+ KEY idx_wr_page_a (kb_id, page_a_id, total_score DESC),
+ KEY idx_wr_kb_score (kb_id, total_score DESC),
+ KEY idx_wr_computed_at (computed_at)
+) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4 COLLATE = utf8mb4_unicode_ci
+ COMMENT = 'Page-to-page wiki relation cache (multi-signal score, taxonomy, confidence, provenance).';