# Mechanics — what exactly happens, checkable

Numbers are the diagram's. Every rule below is enforced in one place; the replay page (`/replay?share=<id>`) shows the actual texts and decisions of a real share next to this list.

| # | Rule (diagram) | Implementation | Evidence in the replay |
|---|---|---|---|
| 1 | Human and private AI chat in iMessage; onboarding, profiling, share help are private | `server-actions` → `agent-core.handleTurn`; prompt sections from `onboarding`, `profiling`, `share-assist`, `proactivity`, `credits`, `tools` | the chat bubbles before the share; `fact_prompt` and `share_prompt` bubbles |
| 2 | A reaction, reply or poll tap is a **server-side action**, the only way anything becomes public | `server-actions`: 👍 on a `fact_prompt` → confirm; ‼️ (or ♥) on a `share_prompt` or on your own message → share; reply on a `share_prompt` → share with details; poll tap → confirmed fact; reply on a `received_item` → thread; ❓ on a `received_item` → portal request | decisions `fact-confirm`, `share-by-reaction` / `share-by-reply`, `poll-answer`, `thread-*`, `portal-request` |
| 3 | Confirmed facts and shares land in the Village store, labelled | `village-store.publish` labels at post time (`village-store.label`), derives inferences in the background | decision `village-store published` with the labels; the share card |
| 4 | Recommendation uses public data only | `recommendation.candidates(share)` reads `villageStore.publicProfile` (confirmed facts, public shares, inferences); Jev: one typed question per candidate | decision `recommendation N/M candidates impl=jev` with every candidate's P(offer) in `inputs` |
| 5 | The candidate's public AI drops, forwards, or asks | `public-ai-human.evaluate` sees ONLY the sharer's name + share and the recipient's PUBLIC profile; returns `drop | forward | ask` + presentation / question | decision `public-ai-human forward → Dragos` with the reason; `inputs.question` when it asked |
| 6 | Asking opens a thread, private to the two; the sharer's public AI answers from public info when it can | `thread.open`; `public-ai-human.answer` is given ONLY the sharer's public data; during the share flow `activities.threadStep` loops it; after delivery `server-actions` runs it for a recipient's question | thread turns `public_ai_recipient` / `public_ai_sharer`; decision `answered-from-public` |
| 7 | When it can't, the question goes to the sharer in chat; replies and reactions for the sharer use the same filtered route | `public-ai-human.filterForSharer` (spam) → `inbound-gate.decide(sharer)` → `agent-core.present` → a `thread_question` bubble the sharer can reply to; the answer returns through `threadHumanAnswer` (flow) or is relayed to the asker (post-delivery) | decisions `escalate-to-sharer`, `thread-question-escalated`, `thread-answer`; the `thread_question` and `thread_reply` bubbles |
| 8 | A forwarded item reaches the recipient's private AI, which has the final say | `activities.deliver` → `inbound-gate.decide(recipient)` (quiet hours, caps, paused; `llm` variant sees the private context) → `agent-core.present` words it | decision `inbound-gate show/silent` with the reason; delivery `gate` column |
| 9 | The recipient sees it in iMessage with the sharer's name | the `received_item` bubble (`settings.show_sharer_name`) | the bubble text starts with the sharer's name |
| 10 | The recipient's like, reply or "?" is again a server-side action; it goes back onto the thread and to the sharer | ❤️/👍 → `notification-coalescing.enqueue(sharer, like)`; reply → thread turn + (question → rule 6/7, else coalesced reply); ❓ → `portal.request` | decisions `received-like`, `thread-reply`, `thread-question-*`, `portal-request`; the sharer's `notification` bubble ("1 like from Dragos …") |
| R | The private AI searches public shares; reads, never writes | `tools.searchShares` → `store.shares.search`; `agent-core` has no reference to `villageStore.publish` | a chat bubble "In the Village I found …" |
| P | "?" + confirmation from both sides → 60 s of direct, unfiltered relay; one portal per user; /stop closes | `portal.request/confirm/open/relay/close`; the portal flow owns the timer; relay bubbles read "Name says: …" | `portal_request` bubbles on both phones, `portal_relay` bubbles, decisions `requested / confirmed 2/2 / closed:stop` |

Invariants you can check on any replay:
- No `messages` row sent to a human contains another human's words **except** `portal_relay` bubbles, `notification` / `thread_reply` bubbles that quote a reply **after** the sharer's public AI filter and the recipient's inbound gate, and the thread answer relayed with attribution.
- Every `received_item` has a `delivery` with `decision=forward` (or a thread with `outcome=forward`), `gate=show`, and the decisions `recommendation → public-ai-human → inbound-gate` in that order.
- The private side never publishes: every share has a `share-assist sent` decision whose reason is a reaction or a reply by the human.
