MasteryMade · Foundation PRD

PRD 2: Gate Architecture + Suggest-Don't-Inject Engine

PRD 2 of 12 Depends on PRD 1 Owner: Lane A + C
Parent: Master Registry v1.0 — Sections 03, 05

2.1 Purpose

Gates prevent content contamination across contexts. The suggest engine prevents clutter by requiring approval before connections become retrievable. Without these, every vector similarity match auto-injects — burning tokens, costing money, degrading output quality.

2.2 Gate Assignment Logic

When content enters the system via universal ingest, it must be tagged with a gate. Assignment rules, evaluated in order:

  1. Source is Jason's personal RSS/email/calendar/session → Gate 1
  2. Source tagged to a prospect entity with pipeline_stage IN ('identified','researched','pre-pitch','reveal') → Gate 2
  3. Source tagged to a signed expert entity with pipeline_stage IN ('signed','deployed') → Gate 3
  4. Source tagged as competitor-of-[entity] → Gate 4
  5. Ambiguous → Flag for manual assignment (don't auto-assign)

Gate assignment happens in the universal ingest service (PRD 3) at write time. The gate column is NOT NULL — content cannot be stored without a gate.

2.3 Supabase Gate Enforcement

Content table gate column

ALTER TABLE content ADD COLUMN gate INT NOT NULL CHECK (gate IN (1,2,3,4));
ALTER TABLE content ADD COLUMN entity_id UUID REFERENCES entities(id);
CREATE INDEX idx_content_gate ON content(gate);
CREATE INDEX idx_content_entity ON content(entity_id);
CREATE INDEX idx_content_gate_entity ON content(gate, entity_id);

Gate-scoped retrieval — single gate

CREATE FUNCTION retrieve_content(
  p_gate INT,
  p_entity_id UUID DEFAULT NULL,
  p_embedding vector(1536) DEFAULT NULL,
  p_limit INT DEFAULT 20
) RETURNS SETOF content AS $$
  SELECT * FROM content
  WHERE gate = p_gate
    AND (p_entity_id IS NULL OR entity_id = p_entity_id)
  ORDER BY CASE WHEN p_embedding IS NOT NULL
    THEN embedding <=> p_embedding ELSE 0 END
  LIMIT p_limit;
$$ LANGUAGE sql;

Cross-gate retrieval — approved edges only

CREATE FUNCTION retrieve_cross_gate(
  p_content_id UUID,
  p_target_gate INT
) RETURNS SETOF content AS $$
  SELECT c.* FROM content c
  INNER JOIN suggested_edges e ON (
    (e.source_content_id = p_content_id AND e.target_content_id = c.id)
    OR (e.target_content_id = p_content_id AND e.source_content_id = c.id)
  )
  WHERE c.gate = p_target_gate AND e.status = 'approved'
  ORDER BY e.confidence DESC;
$$ LANGUAGE sql;

Hard rules enforced at data layer:

Gate 4 → Gate 3 blocked structurally. No SQL function exists that allows Gate 4 content to appear in Gate 3 retrieval. This is architecture, not policy.

Cross-gate requires approved edge. The retrieve_cross_gate function INNER JOINs on suggested_edges WHERE status='approved'. No approved edge = no results. No exception path.

2.4 Suggest-Don't-Inject Engine

Table: suggested_edges

CREATE TABLE suggested_edges (
  edge_id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
  source_content_id UUID NOT NULL REFERENCES content(id),
  target_content_id UUID NOT NULL REFERENCES content(id),
  edge_type TEXT NOT NULL CHECK (edge_type IN (
    'same-topic','extends','contradicts',
    'competitive-positioning','same-framework','updates'
  )),
  crosses_gates BOOLEAN NOT NULL DEFAULT FALSE,
  source_gate INT NOT NULL,
  target_gate INT NOT NULL,
  confidence FLOAT NOT NULL CHECK (confidence >= 0 AND confidence <= 1),
  reason TEXT NOT NULL,
  status TEXT NOT NULL DEFAULT 'suggested' CHECK (status IN (
    'suggested','approved','suppressed','deferred'
  )),
  reviewed_by TEXT,
  pattern_id UUID,
  suggested_at TIMESTAMPTZ NOT NULL DEFAULT now(),
  reviewed_at TIMESTAMPTZ,
  CONSTRAINT no_self_edge CHECK (source_content_id != target_content_id),
  CONSTRAINT unique_edge UNIQUE (source_content_id, target_content_id, edge_type)
);

CREATE INDEX idx_edges_status ON suggested_edges(status);
CREATE INDEX idx_edges_pending ON suggested_edges(status)
  WHERE status = 'suggested';
CREATE INDEX idx_edges_crosses ON suggested_edges(crosses_gates)
  WHERE crosses_gates = TRUE;

Layer 1: Passive Edge Detection (same-gate, automatic)

Trigger: Runs after every content INSERT via Supabase trigger or n8n webhook.

def detect_same_gate_edges(new_content_id, gate, embedding):
    similar = supabase.rpc('match_embeddings', {
        'query_embedding': embedding,
        'match_gate': gate,
        'match_threshold': 0.6,
        'match_count': 10,
        'exclude_id': new_content_id
    })
    for match in similar:
        edge_type = classify_edge(new_content, match)
        confidence = match.similarity_score

        if confidence > 0.85:
            status = 'approved'    # auto-approve same-gate high confidence
            reviewed_by = 'auto'
        else:
            status = 'suggested'   # surface for review

        insert_edge(source=new_content_id, target=match.id,
                    edge_type=edge_type, crosses_gates=False,
                    confidence=confidence, status=status)

Edge type classification (Claude or lightweight model)

same-topic: Both discuss same subject. extends: New adds depth. updates: New supersedes older (same topic, newer date). contradicts: Disagreement. same-framework: Both reference same named methodology.

Layer 2: Cross-Gate Detection (scheduled)

Trigger: n8n cron — daily at 2 AM CT, or manual trigger.

def detect_cross_gate_edges():
    new_content = get_content_since(yesterday)
    for item in new_content:
        for target_gate in [1, 2, 3, 4]:
            if target_gate == item.gate:
                continue
            # HARD BLOCK: Never suggest Gate 4 → Gate 3
            if item.gate == 4 and target_gate == 3:
                continue

            similar = match_embeddings(
                embedding=item.embedding,
                gate=target_gate,
                threshold=0.7,  # higher for cross-gate
                count=5
            )
            for match in similar:
                insert_edge(
                    crosses_gates=True,
                    status='suggested',  # ALWAYS review, never auto
                    ...
                )
    notify_pending_count()

Layer 3: Architect Agent Review

Option A: Telegram bot (fast, mobile)

Forge sends pending edges one at a time. Format: "[Source: Gate 1 — HN article about agentic frameworks] ↔ [Target: Gate 2 — Samuel's coaching methodology] | Confidence: 0.73 | Type: same-framework". Buttons: ✅ Approve | ❌ Suppress | ⏸ Defer. Tap to decide — 5 seconds per edge.

Option B: NowPage dashboard (batch review)

Published at plan.jasondmacdonald.com/edge-review. Table of pending suggestions sorted by confidence DESC. Bulk approve/suppress/defer. Filter by gate pair, edge type, confidence range.

Option C: Forge autonomous (low-stakes same-gate)

Forge reviews using prompt: "Should these two pieces of content from the same gate be connected? Would retrieving one when working with the other add value or noise?" Auto-approve if Forge confidence >0.9 AND same-gate. Auto-suppress if <0.3. Escalate to human if between 0.3-0.9 or cross-gate.

Learning Loop

Table: detection_patterns

CREATE TABLE detection_patterns (
  id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
  pattern_type TEXT NOT NULL,
  pattern_config JSONB NOT NULL,
  weight FLOAT NOT NULL DEFAULT 0.5,
  approve_count INT NOT NULL DEFAULT 0,
  suppress_count INT NOT NULL DEFAULT 0,
  created_at TIMESTAMPTZ DEFAULT now(),
  last_triggered TIMESTAMPTZ
);

On approve: weight += 0.05 (capped at 1.0). On suppress: weight -= 0.1 (floored at 0.0). Suppress decays faster than approve grows — conservative by design. Over time, patterns that produce approved edges lower the review threshold. Patterns that produce suppressed edges eventually stop triggering.

2.5 n8n Notification Workflow

Trigger: Supabase webhook on INSERT to suggested_edges WHERE crosses_gates = TRUE.

  1. Webhook fires → n8n receives edge data
  2. Enriches: fetches source and target content summaries from content table
  3. Formats Telegram message with inline keyboard (approve/suppress/defer)
  4. On button press → updates suggested_edges.status, reviewed_at, reviewed_by
  5. Updates detection_patterns weight based on decision
  6. Logs to registry_changelog

2.6 Acceptance Criteria

MASTERYMADE — PRD 2 of 12 — plan.jasondmacdonald.com

Dominia Facta. Build what compounds.