# ContextLift — Deterministic Rules Reference

No AI. No guessing. No semantic inference. Pure structure-based logic.

---

## 1. CONTEXT LEARNING RULES

### Content Sources
Index ONLY: Published posts, Published pages, Published products.
Ignore: Drafts, Noindex posts, Attachments, Widgets, Menus.

### Primary Keyword (Per Page)
Priority:
1. RankMath focus keyword (`rank_math_focus_keyword`)
2. Yoast focus keyphrase (`_yoast_wpseo_focuskw`)
3. Fallback: first 2–3 meaningful words of `post_title`

Each primary_keyword must be UNIQUE site-wide.
If duplicate detected: Append suffix to secondary occurrence. Never overwrite owner.

### Secondary Keywords
Extract ONLY from: H2, H3, Categories.
Rules: Lowercase, Remove punctuation, Min 3 characters, Remove stopwords.

### Topic Clusters
Group pages by: Category, Shared secondary keywords.
Do NOT infer topics.

### Keyword Map Table
Store: keyword → post_id → url.
Exclude: primary_keyword of other pages, keywords shorter than 3 chars.

### Update Rules
- Manual rebuild: User initiated only (Free).
- Cron rebuild (Pro): Daily (new posts only), Weekly (full rebuild).

### Failsafe
If keyword extraction returns empty: Skip page, Log warning, Do not fabricate keywords.

### Existing Link Scanning
During rebuild, each post is scanned for existing internal `<a href>` links.
- Store anchor text + target URL for every internal link found.
- Register existing anchors in the anchor registry.
- RULE: An anchor already used in an existing link CANNOT be assigned as a keyword variation for a DIFFERENT target post.
- Data stored: JSON array in `existing_links` column of context index.

---

## 1B. ANCHOR DIVERSIFICATION RULES

### Purpose
Prevent over-optimization by ensuring the same anchor text is not used repeatedly across the site to link to a given page. Creates a natural anchor profile.

### Keyword Rotation Strategy
When suggesting links to a target page:
1. Collect ALL keywords (primary + variations) for that target from keyword map.
2. Check site-wide anchor usage map: count how many times each keyword is already used as an anchor.
3. Sort by usage count (ascending) — least-used keyword comes first.
4. Pick the first keyword that exists in the source post's plain text content.
5. If no keyword exists in source content → skip target (never force a link).

### Tie-Breaking
If two keywords have equal usage count, prefer the primary keyword.

### Anchor Usage Sources
The usage map counts anchors from:
- Existing internal links found during context rebuild (all posts).
- Plugin-inserted links tracked in `contextlift_link_log` (non-reverted only).

### Example
Target post `/running-shoes/` has: primary="running shoes", variations=["best running shoes", "trail footwear", "running shoe"].
- "running shoes" used 3 times across site → usage=3
- "best running shoes" used 0 times → usage=0
- "trail footwear" used 1 time → usage=1
→ Suggestion picks "best running shoes" (usage=0) if it exists in source content.

---

## 2. CLICK DEPTH RULES

### Definition
Click Depth = Minimum number of internal links required to reach a page from the homepage.
Homepage depth = 0. Each internal link = +1 level.

### Data Source
Use only: Published posts, Published pages, Published products.
Ignore: Drafts, Noindex posts, External links, Media attachments.

### Link Graph Build
1. Parse all internal `<a href>` links.
2. Only include links that: Belong to same domain, Are not nofollow, Point to published content.
3. Build adjacency list: source_post_id → target_post_id.

### Depth Calculation (BFS)
1. Set homepage depth = 0.
2. Visit all directly linked pages → depth = 1.
3. Continue level by level.
4. Store minimum depth per page.
5. If page unreachable: Mark as "Orphan Page".

### Display Flags
- Depth 0–2 → Healthy
- Depth 3 → Moderate
- Depth 4+ → Deep
- Unreachable → Orphan

### Limits
FREE: Show top 20 deepest pages only.
PRO: Show full site depth map.

### Failsafe
- If homepage not detected: Use `site_url()` root as homepage.
- If no internal links found: Mark all pages as depth = 1.

---

## 3. BULK LINKING RULES

### Eligible Posts
Only include: Published posts, Published pages, Published products.
Exclude: Drafts, Noindex pages, Homepage, Pages already processed in current batch.

### Pre-Filter (Mandatory)
For each post:
1. Run standard Internal Linking Engine first.
2. Collect valid suggestions.
3. If suggestions < 1 → SKIP post.
Never force links.

### Batch Limits
- 10 posts per batch (Free — disabled)
- 50 posts per batch (Pro)

### Insertion Rules (Per Post)
- Max 3 links per post
- First occurrence only
- Never inside headings
- Never inside inline HTML elements (`<strong>`, `<em>`, `<a>`, `<button>`, etc.)
- Never reuse primary keyword
- Never duplicate target URLs
- Never exceed limits

### Anchor Safety
Before insertion:
- Check anchor ≠ any primary keyword of other pages
- Check anchor length ≥ 3 chars
- Skip stopwords
- Skip anchors already linked

### Transaction Safety
Each post processed independently.
If one post fails: Continue others.
Store: post_id, inserted_links, timestamp.

### Rollback Support
Log every bulk insertion.
Allow rollback per batch: Undo by restoring previous post_content.

### Protected Pages
Never bulk link: Homepage, Contact pages, Privacy policy, Terms pages.

### Failsafe
If valid links < 1 for post: Do nothing. Do NOT invent anchors.

### Free vs Pro
FREE: Bulk disabled.
PRO: Bulk enabled.

---

## 4. CHANGE LOGGING RULES

### Actions to Log
Log ONLY when plugin modifies content:
- Manual internal link insertion
- Bulk internal linking
- Context rebuild affecting keywords
- Click depth recalculation

### Log Data (Per Action)
Table: `wp_contextlift_change_logs`
Fields: log_id (auto), post_id, action_type (manual_link | bulk_link | context_update), old_content, new_content, inserted_links_count, timestamp.

### Transaction Rule
For each post:
1. Save old_content
2. Apply changes
3. Save new_content
4. Commit log
If step fails → rollback immediately.

### Undo System
Provide "Undo Last Change" per post.
Logic: Fetch last log entry for post_id → Replace content with old_content → Delete log entry.

### Retention
Keep logs for 30 days. Cron cleanup removes older entries.
Max 5 logs per post (oldest removed first).

### Failsafe
If logging fails: DO NOT modify content.

---

## 5. LICENSE RE-VALIDATION RULES

### Schedule
Cron job: `contextlift_license_check`. Run once every 24 hours.

### Process
1. Read stored license_key + domain.
2. Send validation request to: `POST https://api.lemonsqueezy.com/v1/licenses/validate`
3. Evaluate response.

### If License Valid
- Update local status = active
- Keep Pro enabled
- Update `last_checked` timestamp

### If License Invalid or Expired
- Set status = inactive
- Disable all Pro features
- Show admin notice: "Your ContextLift license is no longer valid."

### If API Unreachable
- Do NOT disable Pro immediately
- Retry next cron run
- Allow 48-hour grace period

### If Domain Mismatch
- Disable Pro instantly

### Data Stored
`license_status`, `last_checked`, `grace_until`
