This repository has no description
0

Configure Feed

Select the types of activity you want to include in your feed.

stigmergic / src / contrail.config.ts
6.0 kB 213 lines
1import type { ContrailConfig, PipelineQueryHandler } from "@atmo-dev/contrail"; 2 3// ── Pipeline queries: the Yoneda probes ───────────────────────────────── 4// 5// The Yoneda lemma: an object is determined by its relationships. 6// Hom(C(a, -), F) ≅ F(a) 7// 8// Two fundamental queries: 9// getIncoming -- all morphisms TO a resource (covariant probe: C(-, a)) 10// getOutgoing -- all morphisms FROM a resource (contravariant probe: C(a, -)) 11// 12// A resource "emerges" from the sum of its connections. 13 14const getIncoming: PipelineQueryHandler = async (db, params, config) => { 15 const uri = params.get("uri"); 16 if (!uri) throw new Error("uri is required"); 17 18 const limit = Math.min(parseInt(params.get("limit") || "50"), 100); 19 const cursor = params.get("cursor"); 20 const types = params.get("types"); // comma-separated connection types 21 22 const conditions = ["json_extract(record, '$.target') = ?"]; 23 const sqlParams: (string | number)[] = [uri]; 24 25 if (types) { 26 const typeList = types.split(",").map((t) => t.trim()); 27 const placeholders = typeList.map(() => "?").join(","); 28 conditions.push(`json_extract(record, '$.connectionType') IN (${placeholders})`); 29 sqlParams.push(...typeList); 30 } 31 32 if (cursor) { 33 conditions.push("time_us < ?"); 34 sqlParams.push(parseInt(cursor)); 35 } 36 37 const where = conditions.join(" AND "); 38 39 return { 40 joins: undefined, 41 conditions: [where], 42 params: sqlParams, 43 }; 44}; 45 46const getOutgoing: PipelineQueryHandler = async (db, params, config) => { 47 const uri = params.get("uri"); 48 if (!uri) throw new Error("uri is required"); 49 50 const limit = Math.min(parseInt(params.get("limit") || "50"), 100); 51 const cursor = params.get("cursor"); 52 const types = params.get("types"); 53 54 const conditions = ["json_extract(record, '$.source') = ?"]; 55 const sqlParams: (string | number)[] = [uri]; 56 57 if (types) { 58 const typeList = types.split(",").map((t) => t.trim()); 59 const placeholders = typeList.map(() => "?").join(","); 60 conditions.push(`json_extract(record, '$.connectionType') IN (${placeholders})`); 61 sqlParams.push(...typeList); 62 } 63 64 if (cursor) { 65 conditions.push("time_us < ?"); 66 sqlParams.push(parseInt(cursor)); 67 } 68 69 const where = conditions.join(" AND "); 70 71 return { 72 joins: undefined, 73 conditions: [where], 74 params: sqlParams, 75 }; 76}; 77 78export const config: ContrailConfig = { 79 namespace: "org.latha.island", 80 collections: { 81 // ── Semble records (indexed from the network) ────────────────── 82 83 card: { 84 collection: "network.cosmik.card", 85 queryable: { 86 type: {}, 87 "content.url": {}, 88 }, 89 searchable: ["content.url", "content.text", "content.metadata.title", "content.metadata.description"], 90 references: { 91 parentCard: { 92 collection: "card", 93 field: "parentCard.uri", 94 }, 95 }, 96 }, 97 98 collection: { 99 collection: "network.cosmik.collection", 100 queryable: { 101 name: {}, 102 accessType: {}, 103 }, 104 searchable: ["name", "description"], 105 relations: { 106 cards: { 107 collection: "card", 108 count: true, 109 }, 110 }, 111 }, 112 113 // ── Connection: the fundamental primitive (Yoneda) ────────────── 114 // 115 // Connections are morphisms in the knowledge graph. 116 // Objects (cards, collections, URLs) emerge from their connections. 117 // This is the Yoneda embedding: every object is determined by 118 // the set of morphisms pointing at it and originating from it. 119 120 connection: { 121 collection: "network.cosmik.connection", 122 queryable: { 123 source: {}, 124 target: {}, 125 connectionType: {}, 126 }, 127 searchable: ["note"], 128 pipelineQueries: { 129 getIncoming, 130 getOutgoing, 131 }, 132 }, 133 134 // ── Follow: social graph for feed construction ───────────────── 135 136 follow: { 137 collection: "network.cosmik.follow", 138 queryable: { 139 subject: {}, 140 }, 141 discover: false, // only index follows from known users 142 }, 143 144 // ── Strata analysis records (generated by this appview) ──────── 145 146 citation: { 147 collection: "org.latha.island.citation", 148 queryable: { 149 url: {}, 150 confidence: {}, 151 domain: {}, 152 }, 153 searchable: ["title", "takeaway"], 154 relations: { 155 reasoning: { 156 collection: "reasoning", 157 groupBy: "method", 158 count: true, 159 }, 160 }, 161 }, 162 163 entity: { 164 collection: "org.latha.island.entity", 165 queryable: { 166 entityType: {}, 167 name: {}, 168 }, 169 searchable: ["name", "description"], 170 }, 171 172 reasoning: { 173 collection: "org.latha.island.reasoning", 174 queryable: { 175 method: {}, 176 confidence: {}, 177 }, 178 searchable: ["question", "evidence"], 179 references: { 180 citation: { 181 collection: "citation", 182 field: "justifies", 183 }, 184 }, 185 }, 186 187 // ── Island records: the stigmergic signal ──────────────────── 188 // 189 // When a user runs Strata analysis on a Semble card, the output 190 // is an org.latha.island record on their PDS. It's a pheromone 191 // trail — others discover it, build on it, the network effect 192 // compounds. Carry stays local-first; only summaries go into 193 // the record unless explicitly published as companion records. 194 195 island: { 196 collection: "org.latha.island", 197 queryable: { 198 "source.uri": {}, 199 "source.collection": {}, 200 updatedAt: { type: "range" }, 201 }, 202 searchable: ["analysis.synthesis", "analysis.themes"], 203 references: { 204 sourceCard: { 205 collection: "card", 206 field: "source.uri", 207 }, 208 }, 209 }, 210 }, 211 profiles: ["app.bsky.actor.profile"], 212 notify: true, 213};