A public draft · open to suggestions · not open source

Two clocks, and one wall

Lantern is the engine that will rank the Discover tab. It keeps a slow clock that remembers what people have vouched for, runs a fast clock over whatever a query touches, and never lets a credit balance reach the order of the results. Money changes brightness. People change rank. Position never moves. This is the design, written down before it is built, so that serious people can tell us where it is wrong.

Try the app on TestFlight → Read the design iPhone · free · Credits ✦ are play money
Draft — the ranking engine is in design, nothing is built The Discover tab exists · zero Lantern tables exist The gate is measured, not scheduled
Read this first

The design is finished. The app is not ready for it.

The reason is not search engineering. Fourteen people have ever taken an action in OuterTalk. Only 12 of 114 posts can carry a link at all. There are zero citations anywhere in the database. So the order of work is: build the text search, hold the graph behind a corpus trigger, and say so out loud.

A few words, defined once. The map is the app's spiral of 42 category rails; the code calls it the DAG. Discover is the app's search tab; today it is four flat lists matched by substring, and posts are not in it at all. A node is a thing that can be found: a post, a note, a comment, a poll option, a topic, a URL, a domain, a place. An edge is a human action that points from one node to another — a reply, a note, an endorsement, a citation — and credit flows along it. Yon is the app's assistant, a service account, and it is never a person.

PageRank is a damped random walk over the whole graph, computed once, with no idea what you asked. HITS builds a ranking from the query's own neighbourhood: hubs, which point well, and authorities, which are pointed at. Lantern is neither. It keeps both ideas and puts them on separate clocks.

The wall is the rule that a balance, a stake or a price may never be an input to rank or to position. It is defined on the concept-token page. This page is what the wall looks like inside a search engine. A lantern lights what is already there. It never moves a star.

SLOW CLOCK · THE PAGERANK IDEA · decidedunbuilt

Memory: what people have vouched for

A damped random walk over what humans have replied to, noted, endorsed and cited. Runs offline every 6 hours. Restarted from 44 teleport vectors: 42 rails, one structural "open question" vector, one global. One human is one unit of weight. Stored as one 64-wide vector per node, so at query time the whole prior is a single inner product.

FAST CLOCK · THE HITS IDEA · decidedunbuilt

The query's own neighbourhood

Text match forms a root set (top 60). One live hop over meaning edges forms a base set (cap 300). A relevance-damped, per-person-normalized hub/authority pass finds what this conversation converges on. Authorities enter the rank. Hubs — the notes and threads that point well — are shown as Guides, never fused into the answers.

Where it actually stands

Measured against production, 26 September 2026

A graph ranker over this graph would return its own starting assumptions. The offline walk would resolve to the teleport vectors it began with, and the citation channel would be a column of zeroes. That is not a reason to abandon the design. It is a reason to build it in two pieces and put a measured gate between them.

14
people have ever taken any action
12
of 114 posts can carry a link
0
citations anywhere in the database
13
of 42 rails have a human post
113
characters in the average post body

The corpus trigger · four counts against the gate · measured 26 September 2026

posts 114 / 500 human comments 29 / 100 distinct 30-day actors 14 / 30 activated citations 0 / 50 THE GATE today
The corpus trigger. Each bar is what exists now against the threshold that should have to be crossed before Migrations B and C are worth applying: 500 posts, 100 human comments, 30 distinct 30-day actors, 50 activated links. The citation row has no bar because the value is zero, so it is drawn as a tick at the origin rather than an invisible rectangle. The trigger is suggested in the plan, not decided. the plan
A second gate, proposed after the first. Part E of the plan argues the right instrument is citations per post, not a post count: below a threshold, no corpus size helps, and 100,000 posts at 1.5 citations each is exactly as undetectable as 114 posts at 1.5. The plan says to plan on about 10 citations per post, sustained. Today's measured value is 0.26: 15 post-to-post edges over 114 posts. At 114 posts that is 399 citation edges to clear the bar. The plan marks the threshold as inference (it assumes equal blocks and Poisson degrees). Writing it into the gate is decision 42, recommended yes, not made. open
WhatMeasured 26 Sep 2026Note
Distinct accounts that have ever taken any action15 → 14 peoplePost, comment, note-vote, bet, grade, repost, favorite. One of the 15 is Yon's service account.
Distinct humans who ever posted / commented / voted on a note11 · 9 · 12
Posts that can carry a link at all12 of 114Only posts of type talk. The other 102 are polls.
Citations anywhere in prod0 · 0 · 0The links jsonb, posts.url, and http in body text.
Accounts already 30 days old34 of 35So the age ramp is not the gate. Participation is.
Rails with at least one human-authored post13 of 42
Searchable text679 unitsAverage post body 113 characters; 33 posts under 40 characters; 3 posts with question info or resolution criteria.
Distinct users who have ever searched2All 48 search-log rows come from the notes bar. The Discover bar has never logged a query.
Notes already clearing the 3-endorser activation bar5
Graph edges that would exist today2 · 13 · ~230 · 0Replies, reposts, tag edges, links. The prior would be nearly its teleport; the base set would be under 30, so the local pass would be skipped.
Distinct bettors ever10So every poll is "thin" on the money side and heat counts as zero.
Scheduled jobs failing silentlynoneAll 11 existing pg_cron jobs succeeding, newest run that day.
Post-to-post average degree0.2615 edges over 114 posts; the meaning graph has 112 connected components over 143 nodes.
The two clocks

Where the PageRank idea lives, and where the HITS idea lives

Pure PageRank gives one global ranking that everyone shares. It is stable and cheap to read, but it has no idea what you asked. Pure HITS builds a ranking out of the query's own neighbourhood. It understands the question, but the neighbourhood is the ranking, so whoever gets into it controls the answer, and iterating it at query time does not hold a phone's latency budget as the corpus grows. Lantern runs both and keeps them on separate clocks.

The whole engine · two blue paths set the order · one gold path sets brightness · nothing writes position

SLOW CLOCK · offline, every 6 h · the PageRank idea meaning edges replies · notes · citations nightly damped walk 44 teleport vectors 6 h rank_vectors 64 numbers per node FAST CLOCK · at query time · the HITS idea text match lexical + trigram top 60 root 60 → base 300 one live hop |B| ≥ 30 hub / authority local pass prior support rank fusion six rank lists, no raw scores S ranked results order comes only from here MONEY · a different function entirely bets + slot backing netted per person, then √ hourly glow a brightness, 0.25 to 1 tints the tile · never orders the list POSITION · welded · the query returns a rail and a timestamp, never a coordinate
The whole engine on one page. The two blue paths are the only ones that can change what order you see things in. The gold path ends at a dashed arrow, because brightness is the only thing it is allowed to touch. The grey rule at the bottom is the part no process on this page can write to.
How the design was producedJudge 1 · mechanismJudge 2 · engineeringJudge 3 · product / App Review
1 · Query-time HITS444946
2 · Rail-sensitive PageRank475448
3 · WALL-first three-channel485247
4 · Adversary-first525252
5 · Two-clock, Postgres-only skeleton495754
6 · Camera engine (product)495250

Six independent designs, three judges on seven criteria, one synthesis, three adversarial reviews (3 blockers, 23 majors, all three verdicts "needs repair"), a repair pass, and a completeness critic. 15 agents, 213 file reads. Skeleton: design 5. Grafted: design 4's wall constructions and anti-farm primitives; design 6's camera command and "answers inherit the question's Q"; design 2's closed teleport table and one-vector-per-node storage; design 3's hash-locked constants file; design 1's relevance damping and neutrality guard.

The graph

Nodes, edges, and what a person weighs decidedunbuilt

Direction is credit flows from source to destination. Human-action edges are one unit per distinct person, multiplied by that person's weight, and dropped when the origin and the target's author are the same person: no self-citation, self-reply, self-note, self-endorse. Structural edges are facts about a post, not votes by its author: they carry no person weight and no self-drop. No amount, balance, stake or tier is an input.

Node kindSourceRole in the walk
postpostsAuthority; a hub when it links or replies. Rail from its category.
notecomments · is_community_note or linksPrimary hub: points at URLs, at its post, at a poll side.
commentcomments · the restText-searchable; weak hub.
optionpoll_optionsAuthority proxy for a claim until claim nodes exist (phase 3).
topictopics · approved onlyAuthority when tagged; back-edges to posts so it never sinks rank.
url / domainlinks · link_domains · newAuthority: the external record. A domain aggregates its URLs.
governancegovernancesAuthority; the "Near me" filter key.
slot (a category)categories · 42Not a node. Rail membership is the teleport vector. Slot glow is money-side.
userusersNot a node. Identity is used only for the same-person drop, the person weight, and the per-author cap.
claimonchain_claims · phase 3Tables missing in prod. Replaces option.
EdgeFrom → toBase weightNotes
replypost → parent post0.6One per person per parent.
comment_oncomment → post0.4
note_onnote → post; note → option0.8Side-scoped when the note names a poll side.
endorsedpost → its own note1.0 × Σ pwSum of the weights of distinct endorsers other than the author, collapsed by person. The row, never an amount. Corrected after the panel: the edge reads comment_votes where vote_type = 'endorsement' (45 rows; the button people press), not note_endorsements (0 rows; the money mirror, which has never been client-writable — a missing grant, not user behaviour). A "lacking" vote is read in phase 2 as a per-person −1 inside the hub term, never as an edge.
links_topost or note → URL0.7 × verified × domain_age × actSee activation, below.
url_domain / domain_urlurl → domain 1.0; domain → url 1/|urls|structuralPhase 2.
taggedpost → topic 0.3; topic → post 1/|posts|structuralApproved topics only. The back-edge makes topics authorities without sinking rank.
placedpost → governance 0.3; governance → post 1/nstructural
repostpost → post0.5One per person.
cotagtopic ↔ topic0.3 × JDerived nightly, Jaccard ≥ 0.1 over human-authored posts. Phase 2.
provenanceclaim → post1.0Phase 3.
spun_from opennote → the poll it seeded0.8Proposed 29 September (decision 92): a human action by the Investigator who spun it.
located openpost → location 0.3; location → post 1/nstructuralProposed 29 September (decision 92), exactly like placed. Dates stay a filter, not credit.

Deliberately absent: pinned — the pin path requires the caller to be an active Investigator and the poll author and an admin settlement row, which is one person's choice twice over, so pins are a display affordance until settlement is crowd-ratified; follows, favorites, saves, views, searches and hides (private rows, personalization only); every money table; poll votes as edges (votes feed only the entropy term of Q); users as nodes.

Person weight · pwValueReads
Service account (Yon)0users.is_service_account
Account under 7 days old0.25users.created_at
7 to 30 days0.5
30 days and older1.0
tier, business_verified, is_investigatornever readOne verified organization can mint unlimited verified accounts on day zero, and both flags are founder-granted. A guard forbids all three tokens in every meaning body.

Three definitions the whole design reuses. human(u) is not a service account and pw = 1.0: the only predicate any head-count uses. A seven-day account weighs edges at 0.5 but is never counted as a human. person_key(u) is the organization id if there is one, else the user id: an organization and all its employee accounts are one person in every count. Phase 2 multiplies pw by rail diversity and an automatic ring penalty; phase 3 replaces the body with a personhood score. Today 34 of 35 accounts are already 30 days old, so the ramp gates nothing yet; participation does.

// citation activation · the anti-farm primitive · a link carries nothing until other people vouch
act(src) = min(1, E(src) / 3)
E(note) = distinct persons with pw ≥ 0.5 who endorsed the note, ≠ author
E(post) = distinct persons with pw ≥ 0.5 who replied to, reposted, or wrote a note on the post, ≠ author
verified(url) ∈ {0, 1} · 1 on a 2xx/3xx fetch, or a 403/429 from a domain first seen ≥ 90 d ago
domain_age(url) = 0.5 if the domain was first seen < 90 d ago, else 1 · retracted → 0
caps one unit per (person, url) and per (person, domain, target) · no self-vouch for any flag
// honest at 10²: 2 replies, 13 reposts, 0 links · act(src) ≈ 0 for nearly everything · it wakes up when humans vouch
// the query neighbourhood · what the fast clock ranks in · exactly as the spec writes it
R = top-60 by text relevance money is NEVER in R
B = R ∪ 1-hop over the LIVE meaning_edges view WHERE dst = ANY(R) OR src = ANY(R)
edges walked: reply, comment_on, note_on, links_to, repost, tagged-topic graph_edges is for the offline walk only
in-neighbours per root sampled top-20 by global_rank · truncate to 300 by prior · drop v with ρ(v) < 0.1
no hard rail bound: a physics note cited from a faith thread survives if it is relevant
A query

What happens when someone types decidedunbuilt

One query, end to end. Everything that decides order runs down the centre. Money joins at the very end, after the list is already sorted, and all it can do is make a tile brighter.

One query, end to end · order runs down the centre in blue · brightness joins last in gold

a keystroke debounced 250 ms while typing typeahead: prefix lane only 10 rows · no expansion on submit text lanes lexical (title-weighted) · trigram fallback · semantic, phase 2 rank by relevance root set — the top 60 money can never put a row here one live hop one hop → base set ≤ 300 drop rows under the relevance floor the drift fence: relevance scales every edge if |B| ≥ 30 hub / authority pass only if the base set reached 30 hubs become "Guides", never mixed into answers six lists six lists: lexical · semantic · prior · support · question-ness · fresh by rank, not score fuse by rank, then filter → 120 candidates hide mask · 3 rows per author · near-duplicate collapse glow, fetched in parallel a second function, a second table, its own round trip brightness only
The one rule to read off this picture: everything that decides order runs down the centre in blue. The gold box on the right joins at the very end, after the list is already sorted, and all it can do is make a tile brighter.
// the local pass · runs only when |B| ≥ 30, else skipped and its rank list is null
A[h,a] = w(h→a) · ρ(a) · pw(origin(h)) · s(origin(h)) · e(h)
s(x) = 1/√(edges person x contributes inside B) per-person normalization, so three friends are not an authority
e(h) = 1 + log1p(Σ pw(endorsers ≠ author(h)) by person) the SAME number as the endorsed edge · never raw rows
a₀ = ρ ; repeat 8×: h ← A a ; a ← Aᵀ h ; L2-normalize
L_sup = B ranked by a
GUIDES = B ranked by h · kinds note, thread root, topic · only when ≥ 3 humans sit beneath · never fused
small_circle = (≤ 3 persons supply ≥ 70% of hub mass into the top-10) AND (top-10 hubs have ≥ 10 distinct authors in B)
rendered as fixed copy with no number · no re-rank

// post-filters · over the whole candidate set before any page cut
drop rows hidden by the caller · per-author cap: humans keep 3, service accounts keep 10, the rest go to the tail
collapse near-duplicates (trigram > 0.9) among the top 60 only · ties: pw(author) desc, created_at desc
a Yon row never outranks a human row at equal S · a query in a Yon-only rail still returns up to 10 Yon rows per page

// lenses · every one orders by S · there is NO 'hot' branch in this function
meaning default, "Relevance" · w_Q = 0
near phase 1 · WHERE place matches the caller's home: town (10 chars) → county (5) → state (2), widening until ≥ 10 rows
questions phase 2 · w_Q = 1.2 · question nodes first
settled phase 2 · WHERE ratified status ∈ {settled, strong}
hot after review · a client-side sort of the returned candidates by glow · labelled "play-credit heat" · opt-in
StageServerEnd-to-end p50p95 target
typeahead2–5 ms60–100 ms≤ 200 ms
Stage A · full search, no embedding5–20 ms today · 50–100 ms at 10⁷ posts100–150 ms≤ 300 ms
glow lookup · parallel, off the critical path2 ms——
Stage B · semantic + local pass, phase 2+5 ms cache hit · +120 ms miss200–300 ms≤ 800 ms
Relevance / Open questions / Hot flip0 · client re-fuses the 120one frame—
Near / Settled flipone RPC call100–150 ms≤ 300 ms

Two-stage paint: stage A paints in about 150 ms; stage B swaps in only once the user has been idle 300 ms and is not scrolling, so no result moves under a thumb. Phase 1 ships stage A only. Postgres only. The text index is a side table, never a generated column on posts, because 14 client paths select every column and a tsvector would double every feed payload. At 10⁷ posts: monthly partitions, per-partition indexes, and a named PageRank worker at 10⁶ edges. Nothing in the equations changes; only where the arithmetic runs.

The four equations

Colour-coded by which side of the wall each term sits on

This is the argument made checkable. Every symbol is tinted by the channel it belongs to. If a gold term ever appears inside the score that orders results, the wall has been breached, and you can see it without reading the code.

meaning — people, and only people money — brightness, and only brightness structure — fixed constants

// prior(v) · the slow clock, read as one inner product · offline, every 6 h, over the meaning graph only
R ← (1 − d)·V + d·Pᵀ R d = 0.85 · five iterations, stop early at ‖ΔR‖ < 1e-4 · warm-started
R ← 0.7·R_prev + 0.3·R so one burst cannot own a refresh
V = 44 teleport vectors · 42 rails (tag share 1/|rails tagged|) · one open-question vector weighted by Q · one global
// at query time
w = normalize(0.70·w_query + 0.30·w_user) rail mix of the top-20 text hits · the caller's private rail vector
prior(v) = ⟨ w , vec(v) ⟩ a new node gets its rail's mean vector, never rank-zero · 18 rail-less posts get the global vector
// Q(v) · question-ness · the shape of a question, never its subject · weights are decision 11, recommended yes
Q(v) = 0.30·open ratified status; a flat 0.5 until the crowd ratifies · never reads the admin settle button
+ 0.20·contest vote entropy over ≥ 10 humans; direction discarded · Smart Polls have no vote rows, so 0 there
+ 0.20·depth distinct humans who cited a reachable source on a domain ≥ 90 d old · min(1, N/3)
+ 0.15·reach how many of the 42 rails it touches · min(1, (R−1)/3)
+ 0.10·sustain weeks with human activity, 8-week bitmap; 0 under 3 humans; 0 once settled
+ 0.05·gap belief running ahead of evidence · belief·(1 − depth); 0 under 10 voters

answers inherit their question's Q, so "yes" and "no" always tie · one raw scale everywhere, no per-rail percentile
proof that adding a source never lowers Q: dQ/dN_cite = 0.20/3 − 0.05·belief/3 ≥ 0.05 > 0 for all belief ≤ 1
// S(v) · the fused score · this is what orders every default surface
S(v) = 1.0/(k+r_lex) + 1.0/(k+r_sem) + 0.7/(k+r_prior)
+ 0.6/(k+r_support) + w_Q/(k+r_q) + 0.4/(k+r_fresh)
S(v) ← S(v) · max(ρ(v), 0.5) for v absent from both text lanes a fresh high-prior neighbour cannot beat the best match

k = 60. w_Q = 0 on the default chip — "Relevance" means relevance. A missing rank contributes 0. No reputation multiplier.
Every term is blue. There is no gold anywhere in this line.
// glow(v) · money · a separate function, a separate table, a separate round trip
net_p = stake by person p on the leading option − stake on the others, floored at 0 over a ≥ 24 h window
heat = Σp √net_p · 2^(−age/24h) per person, then square-rooted · 24 h half-life
thin = fewer than 10 distinct people with heat → heat counts as 0
slot_glow = log1p(slot score)/log1p(max) · a 15% cap per backer binds at ≥ 10 backers
glow(v) = 0.25 + 0.75·( 0.7·heat/max + 0.3·slot_glow(rail) )

Every term is gold. It reaches a tile's brightness and nothing else.
today: 10 distinct bettors ever, so every poll is thin and glow = 0.25 + 0.225·slot_glow · the 42 slot rows are seed values with 0 holdings
Why the square root matters. Heat is square-rooted per person before it is summed, so spending a hundred times more makes a thing about ten times brighter, not a hundred times. And because it is netted per person first, splitting one wallet into ten does not recover the difference.

glow · what 100× the stake buys · the money channel is damped by a square root

0× 50× 100× the stake 0 10× 50× 100× brightness if brightness tracked money: 100× stake, 100× glow what actually happens 100× the stake buys 10× the brightness
Quadratic dampening on the money channel. The dashed diagonal is what an undampened market would do to the map. The gold curve is what actually happens, and the gap between the two lines is the whale's money failing to buy proportional attention.

Why fuse ranks instead of scores

Each lane produces numbers on its own scale. A text-match score and a random-walk probability are not comparable, and if you add them the lane with the bigger numbers silently wins. So Lantern throws the scores away and keeps only the position in each list. That is reciprocal-rank fusion. The constant k = 60 then decides how much being first is worth.

1/(k + rank) · how much a lane's vote falls off with position · k = 60 against k = 1

0 100% rank 1 rank 10 rank 20 k = 60 — what Lantern uses rank 20 still worth 76% k = 1 — one lane dominates share of rank 1
The k constant is the anti-tyranny knob. At k = 60 a lane that puts something twentieth still contributes three quarters of what a first place contributes, so agreement between lanes matters more than any single lane's enthusiasm. At k = 1 the first result is worth ten times the twentieth and whichever lane shouts first wins.
The wall, drawn

Three channels that may never cross decided

The wall is defined on the concept-token page: a price may never change a name, a position or a truth status, and no evidence process may ever read a price. Inside a search engine it means three columns, two barriers, and four tests that read the SQL and fail the build on a forbidden word.

Three channels · money sets brightness · meaning sets order · position is set by nothing here

MONEY pool bets slot backing note stake amounts balances netted per person, then √ MEANING replies · notes endorsement rows verified citations ratified status one unit per person, ever POSITION the rail address φ geometry depth and time written by nothing on this page, ever no balance is a rank input no score moves an address brightness the order where it sits what the reader actually sees a ranked list of tiles, each with a brightness, each at a fixed address on the map Enforced by four guard tests that read the SQL function bodies and fail the build on a forbidden token.
The claim the guards have to defend. Gold reaches only brightness, blue reaches only order, and the dashed grey column is written by nothing in the engine. The two red bars are the crossings that a test is supposed to make impossible.
1
No money in meaning. The meaning path is eight named function bodies; the money path is one. They share no table. The sharpest case is endorsements: the same table feeds both sides, so the meaning view reads only the row, and a guard asserts the token amount is absent.
2
No score moves position. The geometry helper imports nothing. No Lantern function can update posts, categories or governances. The RPC returns a rail, a sub-slot and a timestamp, never a coordinate; the client computes (θ, r, z). Geometry itself is discussed on the second-atom page.
3
Money never orders a default surface. Every lens orders by S. Glow is not a column of any meaning RPC and appears in no ORDER BY anywhere in a meaning body. The Hot chip, after review, sorts already-returned candidates on the client.
4
The recall lane cannot leak. If a "Backed" strip is ever enabled (decision 1, recommended no), its rows never seed the root set, never expand into the base set, never enter any fusion list.
5
Constants are auditable, not silent. Every weight lives in a lock file and in one SQL function that returns the same values. Changing a weight is a lock edit plus a migration plus a changelog line: a visible diff. After M7, when the meaning ledger moves on-chain, anyone can recompute the prior from the edge log. The founder can change a weight; he cannot change one quietly.
GuardWhat it assertsStatus today
search-wallNo meaning function body contains a money token. The glow function touches no meaning table. No ORDER BY in a meaning body mentions glow. Every Lantern function has extensions in its search path, and every trigger function is SECURITY DEFINER.no runner
search-neutralityThe question-ness function contains no topic name, no category id, no string literal. Two structurally identical nodes with opposite content score identically to 1e-9. A golden case pins SQL and the TypeScript mirror to the same numbers.no runner
search-constantsEvery weight in the lock file appears verbatim in the SQL, and the hash matches.no runner
search-positionNo search code references the geometry helpers. The geometry file imports nothing. The map component never imports the search client.no runner
The honest status. Nothing in the repository executes the guard suite today. There is one hook file and no continuous integration, so all four of these would be written and then never run. Until a runner exists the four wall tests are decorative, and the wall proof rests entirely on them. Building the runner is a prerequisite, not a follow-up. Three guard contracts must also be fixed before a single file is written: the body extractor finds nothing in 89 of 208 migrations (they use a different delimiter), so a "does not contain" assertion would pass on an empty string and must first assert the body was found; the hasher must normalize line endings or it is red on one machine and green on another; and "passes until the migrations exist" is a guard that can never go red, so it needs a phase pin in the lock file. Every earlier design had mis-named its guard file so it would never have run. unbuilt
AttackDefense
Sybils (free signup, 10,000 ✦ each)Age ramp; only 30-day accounts are humans in any count; one unit per person per target; organization collapse; phase 2: rail-diversity factor and a nightly co-action ring job (pairs acting on the same targets within 60 minutes 5 or more times; dense young components get ×0.1 for 60 days, automatic, with a sunset, no appeal path); phase 3: personhood.
Organization-minted accountsThe flags are never read; an organization and its employees collapse to one person.
Link farms, fabricated citations (the X pipeline fabricates 97% of its drafts)Reachability check; activation by 3 other persons; young domains at half weight; Yon links weigh 0; phase 2 structural domain seeds: a domain seeds trust only when cited by ≥ 5 humans from ≥ 3 rails over ≥ 90 days, recomputed nightly, no hand-picked table.
Tightly-knit community capture (HITS's failure)Base set ≤ 300 seeded by text; relevance floor 0.1; per-person normalization; the global prior in the same fusion; "small circle" copy with no re-rank.
Topic drift (HITS's other failure)Relevance multiplies every column of the adjacency; in-hub sampling by global rank; S damped ×0.5 for nodes with no text match; no hard rail wall.
Rich-get-richer (PageRank's failure)d = 0.85 with 44 teleport vectors; EMA between refreshes; the prior enters as a rank, not a magnitude; a freshness lane at 0.4.
Wash heat, whale glowPer-person netting over 24 hours, square root, zero under 10 persons, 15% slot cap at ≥ 10 backers, 24-hour half-life; heat reaches only glow and the opt-in Hot sort.
Yon self-citation (70 of 114 posts are Yon's)Yon's weight is 0 on every human-action edge it originates; Yon posts stay text-searchable, get the rail's provisional prior, and gain authority only from human edges into them.
Status gaming ("everything is a mystery")Self-declared = 0; 10 humans, 3 rails, 60%, a 7-day challenge window; under 60% = contested; Yon's proposal is not read.
Fetching a link (SSRF, resource exhaustion)http and https only; private, link-local and loopback ranges rejected after DNS; max 3 redirects; 64 KB read cap; 10 s timeout; 1 request per second per domain; a per-user daily link quota.
The founder editing weightsLock file + migration + changelog + hash guard; the ratification RPC treats the admin as one row; no admin write path to the score tables.
Money first, information follows

The loop the token structure is supposed to drive the plan

The thesis is that money should arrive first and pull information behind it, on repeat. Search is what closes that loop, because it is the surface where a rail that has been paid for but never sourced becomes visibly hollow.

The repeated cycle · backing buys brightness · brightness buys attention · attention writes the only edges that move rank

THE REPEATED CYCLE back a slot play credits are locked to a rail glow the rail glows brightness only, dampened, decaying eyes attention arrives people look at what is lit edges they write replies, notes, citations rank S and Q rise the only inputs that move the ranking search names the "dark stars" — high meaning, no backing — and the loop runs again a rail that is bright but unsourced is visibly hollow: money% high, people% low → routed to Investigators
Search closes the loop rather than bypassing it. Backing buys brightness, brightness buys attention, and attention is the only thing that can produce the human edges that actually move rank. A rail that is paid for and never written about shows a high money share beside a low people share, and that gap is the thing the engine reports rather than hides.

Dark stars — high S, glow at the floor — are the on-ramp: meaning nobody has backed. Their tile carries "Back this slot" after review, so money flows to under-lit meaning first. Information debt is routed, not hoped for: a function returns, per rail, slot glow against the sum of Q and prior, and "backed, needs sources" rails go to Yon's draft queue and to Investigators by beat. Search demand per rail (humans only, at least 10 distinct per rail, query-cluster and week, shown as a range) appears beside the slot standings and is never added to any score. Repeated unmet searches in a rail with no subcategory are the "verified demand" a new slot needs. Search never mints, pays, refunds or reorders a slot.

Tilting toward the great questions

Without putting a thumb on which answer is right decidedphase 2

The goal is that consciousness, the layer under the Standard Model and the structure of the universe outrank the news cycle. The constraint is that nothing in the ranker may name a subject, because a founder-editable list of favoured topics is both a mission violation and an App Store review risk. The resolution is to rank the shape of a question rather than its content. Those questions win because they are open by the crowd's own ratification, they split any crowd, they generate deep sourced chains, they touch many rails at once, they stay alive for years, and belief about them runs far ahead of evidence. A question about next month's interest rate scores well for three weeks and then settles.

Q · the six weights · decision 11, recommended yes

open status 0.30 split of the crowd 0.20 citation depth 0.20 rails it reaches 0.15 years it stays alive 0.10 belief beyond evidence 0.05 0 0.30
Depth outweighs the belief gap by four to one, on purpose. That ordering is what makes adding a source mathematically unable to lower a question's score, so there is never an incentive to keep a question mysterious by withholding evidence.

Eight reasons it cannot be aimed

1
No term is keyed to a topic, category, keyword, embedding or author, and a guard fails the build on any literal inside the function.
2
The weights are locked constants with a hash. There is no per-topic row anywhere to edit. Raising the chip's weight lifts every open question in every rail by the same factor.
3
Status is crowd-ratified only: 10 humans at full weight, collapsed by organization, spanning 3 rails, 60% support, with a 7-day challenge window during which the tile says "challenged". The founder's account is one row. Yon's proposal renders inside the why bubble and is never read.
4
One raw scale everywhere, so a rail cannot be lifted by seeding it past a node count, and a big rail cannot outrank a small one by construction.
5
Answers inherit their question's score, so "hologram: yes" and "hologram: no" tie, and only person-counted replies, notes and citations separate them.
6
Adding a verified source can never lower the score.
7
A symmetry test: relabel the options, rename the theory, move it to a rail with the same reach, and the score must not move.
8
The tilt is not in the default order at all. It lives under a chip that says "Open questions", worth 1.2 lanes, with the rail mix leaning 15% toward the open-question vector.
The price, accepted deliberately. A sports question of equal structural openness ranks alongside a physics one. That is what neutrality costs, and paying it is what lets the feature exist at all. The one-sentence version for a reviewer: questions that are open, wide, deep and long-lived rank first.
The honest state at launch. With 36 users, no status will be ratified for months, so open = 0.5 for everything and the tilt is flat. Phase 1 computes Q and gives it weight 0 everywhere, not even in the why bubble (decision 20, recommended yes). The chip, the Open Mysteries room and the galaxy lens ship in phase 2 together with the ring job and the rail-diversity defenses, never on seven-day thresholds. Until ratification produces rows, the room shows the top raw-Q questions under the cold-graph copy so a reviewer never sees an unexplained empty shelf.
Added 29 September 2026

F15 — Tree view ⊂ Lantern: one graph, three views open

The app's thread Tree view and Lantern are not tied together today. They should be, and the fix is smaller than it sounds, because both already eat the same raw material. The Tree view is the viewer for Lantern's B-set. The spec already defines B = R ∪ 1-hop over the live meaning-edges view as the neighbourhood a query is ranked in. A thread is just R = {root} and B walked along reply.

SurfaceWhat it reads todayVia
Tree view spinePosts where id = root or thread root = root, chained by posts.parent_post_id (falls back to root)The thread loader and the tree layout helper
Tree view branchesposts.source_comment_id (a poll spun off a community note); pinned consensus notes; fork colour gold / teal / grey = the fork's own vote with a quorumDisplay only; cross-Investigator branching is scaffolded, inactive
Connections chipscategory_id, related categories, governances, topics, locations, plus event dates on the postsThe batch tag-name loader
Lantern edgesreply = posts.parent_post_id 0.6 · tagged = topics · placed = governances · rail membership = category + related categories · note_on, endorsed, links_to, repostThe meaning_edges view — exists only on paper (Migration B)

So the tree's spine is Lantern's reply edge, and every Connections chip is a Lantern structural node. They are two code paths over one graph that nobody has built yet. What Lantern does not have that the thread has: source_comment_id (no spun-from edge), locations (no located edge; the spec places by governance only), event dates (not in the graph at all), and pinned notes (deliberately absent; the pin path is founder-gated). Today the tree stops at its own thread root; walking B instead gives it, for free, the cross-thread replies, reposts and citations that phase 2 "cross-Investigator branching" was going to need its own backend for. One backend, not two.

One graph, three views · today the tree walks reply inside one thread · proposed: it reads B

TODAY · the tree walks reply inside one thread stops at thread_root_id root reply reply another thread's reply never reached PROPOSED · the tree reads B = R ∪ 1-hop · #91 B · one live hop over meaning_edges root reply reply reply · other thread repost · 0.5 URL links_to · 0.7 note spun_from 0.8 · #92 open
Same spine, one more hop. Gold is the thread as the tree already draws it. Teal and blue are what Lantern's base set adds without a second backend: the cross-thread reply, the repost, the citation. The rose edge is the one new edge the tree needs and the spec did not have, spun_from, which is decision 92. Off-thread hops render as the teal and grey off-rails the tree already knows how to draw.

What changes, concretely

1
Build meaning_edges as a view first — the data-plumbing half of Migration B. It reads only tables that exist. It is safe before the corpus trigger because a view ranks nothing.
2
Two edges added to the spec: spun_from (note → the poll it seeded, 0.8, a human action by the Investigator who spun it) and located (post → location 0.3, location → post 1/n, structural, exactly like placed). Dates stay a filter, not credit — time is a frame, not an endorsement.
3
The thread loader reads B. Root set = the thread; one hop over reply, spun_from, repost, links_to; off-thread hops render as the teal and grey off-rails the tree already draws.
4
Connections chips become Lantern queries. Tapping a topic frames the lit set on that node — frames it, dims the rest to 0.25, draws arcs along thread roots — instead of only highlighting inside the current thread. Same list, same colours, one more hop.
5
Discover → "open its spiral". A result card gets one action: Tree view rooted at its thread root. Search and the tree stop being different places.
6
Colour stays local, size becomes global. Gold, teal and grey keep meaning which answer won here (the fork's own vote, people). Node size or brightness gets Lantern's rank, how much the world builds on it. Two different facts, both shown, neither read from money. Pinned notes still render but stay out of rank until the pin path is crowd-ratified.
7
One bug fixed for both. parent_post_id is a freely editable pointer any author can re-aim. It is the tree's spine and the reply edge. Seal it once.

Order: step 1 now (cheap while the tables are small, and it is a view); 2 with it; 3 to 5 after the site work; 6 whenever rank exists; 7 in this week's guards. The corpus trigger still gates the ranking; nothing above needs rank to be useful.

91
The Tree view is Lantern's B-set viewer. Recommend yes. open
92
Add spun_from and located to the edge table. Recommend yes. open
93
Keep pinned display-only until the pin path is crowd-ratified. Recommend keep. open
94
Seal parent_post_id with the other guards this week. Recommend yes. open
The order of work

What has to be true before any of this is worth building the plan

Everything to the left of Migration A is cheap right now precisely because the tables are empty, and gets expensive the moment a graph depends on them. Everything to the right of the diamond waits for a number, not a date.

Phase 0 · 14–18 founder-days, solo · then Migration A · then a measured gate

PHASE 0 · 14–18 DAYS, SOLO PHASE 1A measurement cannot be backdated then integrity one migration then link path repair the wipe then guard runner or the wall is prose then Migration A — text search posts become findable at last re-measure corpus trigger met? no keep shipping content, re-measure — do not build yes Migrations B and C — the graph, the prior, glow
The gate is measured, not scheduled. Everything to the left of Migration A is cheap right now precisely because the tables are empty. Everything to the right of the diamond waits for a number, not a date. The original plan said phase 0 was 2 days with no migration and phase 1 was 4 calendar weeks with a contractor in parallel; all three of those were wrong, and the readiness audit corrected them.
PhaseWhatEstimate
0 · readinessThe prerequisites that are only cheap while the tables are empty: the link-edit data-loss fix, a unique index on the endorsement table, sealing the reply pointer, a shape check on post links, one canonical URL helper, the guard runner plus the four corrected guard contracts, the measurement instrumentation, and the one-way key decisions. Carries one integrity migration — phase 0 is not migration-free.14–18 days, not 2
1A · build 9Migration A only: extensions, graph nodes minted by trigger, the text side table with SECURITY DEFINER sync triggers, person weights, the constants function, search v0 (lexical + trigram + freshness, author cap, hide mask, a why payload). Client: search tile, one ranked list in Discover, Relevance and Near chips, typeahead, why bubble, cold-graph copy. This is where all the user-visible gain is.~2.5 weeks solo
gateThe corpus trigger. Do not build 1B until the graph has mass: 500 posts · 100 human comments · 30 distinct 30-day actors · 50 activated links. Today: 114 · 29 · 14 · 0.measured, not scheduled
1BMigration B (the meaning-edges view, graph edges, links, domains, 44 teleport vectors, rank vectors, question scores, glow, the caller's rail vector, the refresh functions, cron with a heartbeat row per job) and Migration C (search v1 with the live 1-hop and the local pass, the glow reader, the rail-debt function); the link verifier on a shared safe-fetch helper.~3 weeks solo
2Embeddings (512-wide, HNSW, a query cache, two-stage paint); the ratification migration; ring job, rail diversity, endorsers'-rail reach, bridging; then the Open questions chip, Settled chip, Open Mysteries room, galaxy lens, Guides row, "Ask the Outernet", demand panels; a 100-query labelled set; the post-star layer and camera handle.4–5 weeks
3Claim nodes once the claim-ledger tables reach prod; the personhood swap; the calibration decision; triggers replace the nightly rebuild; the PageRank worker at 10⁶ edges; partitions; chain glow through the existing seam.scale-driven

There is no client contractor. Every outside artefact in the repo is iOS build work, so these are solo, serial estimates (about 29 founder-days for phase 1). Migrations are applied by hand, one at a time: prod has 203 applied, newest 20260923170924, and the push tool fails on a version-string mismatch, not a gap in history. They sequence after build 8 ships and the blocked billing migration is decided. Migration A's trigger is the sharpest applied risk in the set — there is no insert trigger on posts or comments today, both are written directly by every installed binary, and a raise inside the new trigger is an app-wide posting outage; the body must swallow every failure and the one-line disable belongs in the file header. Nothing watches the cron jobs; the precedent is a share-card function registered in April and returning 404 on every share for five months unnoticed. Decide the heartbeat before B.

Do now, while it is freeWhy it is free todayCost
Stop post edits wiping their own links live bugThe edit composer starts with an empty link list and never loads the post's existing links; on save it sends null, and the update assigns it with no guard. It has never fired — there are 0 link rows, so there is nothing to repair yet.1–2 h
Unique index on the endorsement tableThe key is on the row id alone; uniqueness is a read-then-write in the client. 0 duplicate groups exist today; after a graph depends on it, a dedupe also invalidates the prior.~1 h
Seal the reply pointerAny author can re-aim a reply at any post, and the thread-purity trigger fires only on insert. The 0.6 reply edge inherits all of this.~2 h
Cap and shape-check post links on the serverThe comments table has a max-4 check; the posts column has none. The cap of 4 is client-only.~30 min
One canonical URL helperThree copies of the domain parser are wrong the same way (an unanchored replace turns newww.com into necom), before anything keys on a domain.~1 h
Start logging searches and result tapsThis is the only item whose cost is calendar; it cannot be backdated.~1 d

The four blocks on the link channel

The design ranks on citations. The product barely lets anyone make one, and that is a product problem rather than a ranking problem.

1
A poll question cannot cite at all. The link control sits in the composer's non-poll branch, and 102 of 114 posts are polls. Notes attached to a poll can cite, so this is the question itself being unable to, not total exclusion.
2
Thread replies cannot carry a link at the database layer. The thread-extend function takes a single URL and no link list.
3
The comment composer on a talk post accepts links in its API and never passes them.
4
Nothing turns a typed URL into a link, so a pasted source is dead text nobody can tap. The only content scanner matches four characters, and bare URLs are not autolinked. Both halves are needed, or neither is worth doing. Where it must run: an insert/update trigger on posts and comments, because posts, comments and Yon each write through a different path and no client hook sees all three.
And both shortcuts are empty. The column that already exists for a source URL has zero rows, and the legislation table that might have seeded a starting link graph does not exist in production at all. There is no way to skip the part where people start citing things.

Measurement, the only item whose cost is calendar

The Discover bar has never logged a query. All 48 search-log rows come from the notes bar, a different surface with a different intent, from 2 distinct users, and they are keystroke prefixes, not intents: logged while typing with a 250 ms debounce and no submit boundary. No result tap is logged anywhere, so the outcome half of every search is missing. The zero-result rate cannot be reconstructed later. So: log on submit with two arguments only — never a matched category id, which would switch on a dormant weight in the category ranking and move the live category bar under the measurement; add a small opens table and wrap the four tap handlers; freeze a fixture of today's results as the before number; hand-write the labelled query set from real content. Also on the record: a 1.2× Smart Poll boost is live in the default Home feed today. It reads a post-type boolean, never a balance, so it is not a wall breach, but it is a flat prominence boost on the content class that carries a pool, and it should be a written decision before the baseline is frozen.

What the map can and cannot do yet

The design describes a search as a camera command: light the stars that match, dim the rest, draw the arcs between them. That is the right destination and it is not buildable today, so it should not be described as though it were. The renderer draws 42 category bands, 42 cards and a ring of decorative dots. There is no star for an individual post, no column anywhere that would give a post a position along its rail (the sub_index the RPC names exists nowhere in code or schema), and no depth axis in use. The display radius is not the geometry helper's radius. Nothing in the app can move the viewport programmatically. The rail index is an array position, not a stored address, so adding a category rotates every future star; the stable address must be decided before anything persists a rail. Geometry is the subject of the second-atom page.

The sentence to use until that changes: a flat 2D map of 42 category rails with pan and zoom, where brightness is the only data-driven channel. No per-post stars, no depth axis. The stars, the depth axis and the camera belong to phase 2, alongside the question chip they share a release with.
What exists today

The honest ledger

Measured against production on 26 September 2026; the Tree-view rows are dated 29 September 2026. Nothing on this page should be read as shipped unless it is in this table.

ThingStateDetail
Discover tabexists · four flat listsSubstring match over four groups. Posts are absent from it entirely; topics are filtered to approved. It has never logged a query.
Lantern ranking enginein designSpec final as of 26 September 2026. Nothing built, committed or applied. Even the name is decision 16.
Lantern tables0 existAbout 12 sealed tables and 5 cron jobs are specified. The meaning_edges view exists only on paper.
Guard tests4 specified · no runnerOne hook file, no CI, no root instructions file. Three contracts to fix before any file is written.
The corpus trigger114 · 29 · 14 · 0Against a suggested 500 · 100 · 30 · 50. Not decided.
Citations per post0.26Against a proposed ~10 sustained (decision 42). 15 post-to-post edges over 114 posts.
People who have ever acted1415 accounts; one is Yon.
Posts114 · 12 can link102 are polls, which cannot carry a link. 18 have no rail. 70 are Yon's.
Citations0In the links column, the URL column, and body text alike.
Endorsements45 rows · 0 rowscomment_votes endorsement rows, the button people press; note_endorsements, the money mirror, never client-writable. 5 notes already clear the 3-endorser bar.
Edges that would exist2 · 13 · ~230 · 0Replies, reposts, tag edges, links.
Distinct bettors ever10Every poll is thin on the money side. Slot rows are 42 seed values with 0 holdings.
Accounts 30 days or older34 of 35The age ramp gates nothing yet.
Rails with a human post13 of 42
Search logs48 rows · 2 usersAll from the notes bar; keystroke prefixes. No result tap logged anywhere.
Editing a post wipes its linkslive bug · never fired0 link rows, so nothing has been lost yet. Free to fix now.
Scheduled jobs11 · all succeedingNothing reads the job-run table; a heartbeat is owed before Migration B.
Migrations applied in prod203Newest 20260923170924. Hand-applied, one at a time.
Vector extension0.8.0 availableSo 512-wide half-precision vectors and iterative scan need no workaround.
The rendererflat 2D · 42 railsPan and zoom. No per-post star, no sub-slot column, no depth axis, no camera handle. Brightness is the only data channel.
Tree view ↔ Lanternnot tied · 29 SepTwo code paths over one graph nobody has built. Decisions 91–94 open.
Decisions27 + 42 + 91–94 · none madeThe 27 the design needs are in Part C of the plan; 42 and 91–94 were added after.
Still open

Decisions only the founder can make

Numbered as the spec numbers them, with the spec's recommendation. None has been made. Decisions 21 to 27 are in Part C of the plan and cover, among other things, post likes and reply hearts, place grades (158 rows, the largest human signal in the app, kept out because it grades a place rather than content), comment-on-comment replies, and the English-only text config. Tell us which way, and why.

1
Heat recall lane. A: 0, money never enters the query path. B: a ≤ 10% labelled "Backed" strip that never seeds the base set or any fusion list. Recommend A for the review build.
2
Ship the Hot chip (client-side sort by glow) in build 9? Recommend no; glow ships, the money sort waits until after review.
3
Default chip. A: "Relevance" with w_Q = 0. B: "Relevance · open questions weighted" with w_Q = 0.5. Recommend A; the tilt lives only under its own chip.
4
Yon-originated human-action edges. A: weight 0. B: 0.25. Recommend A; Yon content stays searchable by text, its tags still build structure.
5
Amount-blind bettor calibration. A: meaning-side. B: money-side, glow only. C: hold dark. Recommend C until settlements are crowd-ratified, then B.
6
People as ranked hubs, or a "curator" badge? Recommend no in phases 1 and 2.
7
A Guides (hubs) row in Discover? Recommend yes in phase 2 with the ≥ 3-human rule.
8
Slot demand beside the standings, visible in the review build? Recommend no.
9
Embedding dimension. A: 512. B: 256. Recommend A.
10
Ratification quorum: 10 humans at full weight, organization-collapsed, ≥ 3 rails, ≥ 60%. A: accept months of "not yet ratified". B: a temporary lower quorum. Recommend A; a lower quorum is a founder-shaped status.
11
Accept the locked Q weights 0.30 / 0.20 / 0.20 / 0.15 / 0.10 / 0.05? Recommend yes; depth above gap is load-bearing.
12
Install the vector extension in phase 1 (A) or phase 2 (B)? Recommend A.
13
Sequencing. A: phase 1 migrations wait for build 8 live and the billing-migration decision. B: go now onto the drift queue. Recommend A.
14
"Back this slot" on result tiles in the review build? Recommend no; after review, yes.
15
"Ask the Outernet" on zero results? Recommend yes in phase 2.
16
Name the engine Lantern? Recommend yes.
17
A free, stake-independent head-count vote on Smart Polls (phase 2)? Recommend yes; until then people% reads "no head count yet" on every money poll.
18
Drop the pinned edge until settlement is crowd-ratified (pins are display only)? Recommend yes; today's pin path is Investigator + author + admin settlement.
19
Count an organization and all its employee accounts as one human in every count? Recommend yes; without it one gold organization can mint unlimited humans.
20
Phase 1 ships Q computed but dark — weight 0 everywhere, not in the why bubble? Recommend yes; the tilt arrives with its defenses.
42
Write the spectral trigger into the gate as citations per post ≈ 10 sustained, not as a post count? Recommend yes; a post-count gate is the wrong instrument and would pass while the method still cannot work.
91–94
The Tree-view tie. See the section above: B-set viewer, two new edges, pins stay display-only, seal the reply pointer. Recommend yes, yes, keep, yes.

Tell us where this is wrong

This is not open source. It is open to suggestions, from people who take it seriously. If a lane is gameable, a guard is hollow, or the wall has a door we did not draw, we would rather hear it now than after it is built.