# Meshagora: Social Sandbox Specification (v2.3)

**Project**: Interactive local-network social platform for teaching digital resilience  
**Framework**: 3SC — Safety (Caution), Sanity (Courage), Serenity (Competence)  
**Values**: Trust (the value judgment), Respect (the mechanism), Freedom (the default state)  
**Target Hardware**: Legacy Android devices (7.0+); iOS via iSH as stretch goal  
**Deployment Context**: Classrooms and community spaces with unreliable or no internet  
**Integration Point**: Section 2 of the 3SC Digital Safety Foundations workshop ("The Places It Can Take You")

---

## 1. What This Is

Meshagora is a temporary, local-only social platform that runs on an old phone serving as a WiFi hotspot and web server. Participants connect their devices to the hotspot and interact through a browser-based social feed — posting, taking notes, managing privacy, and exchanging trust tokens.

It is a micro-fediverse. One server, one community, one session. It teaches the structure of networked social interaction by making participants the administrators of their own social space.

The system is designed to degrade gracefully into chaos, then be rebuilt by the participants using tools the facilitator provides. The arc of each session moves from freedom through disorder to competence.

---

## 2. Pedagogical Design

### The Arc

**Phase 1 — The Open Square (Freedom)**  
Everything is public. Profiles, notes, posts — all visible to all. This is the default state of "the internet before you configure it." The environment is functional but exposed.

**Phase 2 — The Noise (Loss of Sanity)**  
The facilitator introduces disruption using the Admin Panel, ideally from a separate device (an old iPad or iPhone — see Section 7). This is announced in advance: *"At some point in this session, the system will be stressed. Your job is to recognize what's happening and build defenses."* This is a fire drill, not an ambush. Disruptions may include: creating sock puppet accounts that flood the feed, rotating the Default Post Key (silently breaking all default-tier users' posting ability), restricting a user (triggering a silent key rotation that affects everyone), showing participants their "private" data in the raw database, or demonstrating that anonymous posts aren't actually anonymous to the server admin.

**Phase 3 — The Build (Courage + Competence)**  
All tools — privacy toggles, friend handshakes, friends-only view, report buttons — are available from the start. Participants simply have no motivation to use them until Phase 2 creates the need. Phase 3 is the moment participants start using what was always there. The facilitator may point out features participants haven't noticed ("Did you know you can make your notes private?") but does not unlock anything. The learning is in the transition from "I don't need that" to "I need that now."

**Phase 4 — The Reflection (Safety)**  
Discussion: What happened? What did it feel like when the space was chaotic? When your notes were exposed? What changed when you had tools? How does this map to the platforms you actually use?

### 3SC Mapping

| Goal | Method | Sandbox Expression |
|------|--------|--------------------|
| **Safety** | Caution | Inspecting what's public before posting. Using the Profile Key to protect notes. Reporting posts that harm the space. Verifying links and content before trusting them. |
| **Sanity** | Courage | Setting boundaries in a noisy space. Choosing to filter rather than flee — or choosing to retreat to friends-only when the square becomes untenable. Confronting the discomfort of exposed data. Deciding whether to add your report to the count. |
| **Serenity** | Competence | Understanding *how* the privacy filters work. Knowing that "delete" means something specific in a database. Building the skill to configure your own environment. Recognizing that retreating to friends-only has consequences for the public square you left behind. |

### Connection to Broader Workshop

Meshagora gives participants direct experience of concepts introduced elsewhere in the 3SC workshop:

- **Section 1 (The Thing in Your Pocket)**: The server device demonstrates that phones are computers, not just screens. Participants see a phone *serving* content, not just consuming it.
- **Section 2 (The Places It Can Take You)**: Meshagora *is* the social platform. Its structure mirrors the Fediverse model from the ATProto/Fediverse primer — one server, community rules, local moderation. When discussing Mastodon or Bluesky later, participants have a visceral reference point.
- **Section 3 (The Creatures You Meet There)**: If a chatbot or automated poster is added in future extensions, participants have already seen how a "feed" works from the inside.

---

## 3. Technical Architecture

### Stack

| Component | Technology | Rationale |
|-----------|------------|-----------|
| Host environment | Termux (Android) | Reliable, well-maintained, real Linux userland |
| Language | Python 3 | Pre-installed in Termux, lowest barrier |
| Web framework | Flask | Single-file deployable, minimal dependencies |
| Data layer | SQLite (in-memory or file) | No external database server needed |
| Network | Android mobile hotspot | Creates LAN with no ISP dependency |
| Client | Any web browser | No app installation required for participants |

### Deployment

1. Facilitator installs Termux on the server device (Android 7.0+).
2. Facilitator runs `pkg install python` and `pip install flask qrcode[pil]`.
3. Facilitator enables mobile hotspot on the server device.
4. Facilitator runs the server script. On startup, the script:
   - Creates a fresh SQLite database (no data persists from prior sessions).
   - Detects the device's hotspot IP address.
   - Generates a QR code image pointing to `http://[IP]:5000`.
   - Serves the QR code at a facilitator-only URL (e.g., `/qr`) that can be shown or hidden. The facilitator displays this on the server device's screen during onboarding, then hides it once all participants have joined.
5. Participants join the hotspot WiFi and scan the QR code (or navigate to the displayed URL).

**Critical UX detail**: Nobody types IP addresses. The QR code is non-negotiable for classroom usability.

### Session Lifecycle

- The database is created when the server starts.
- The database is destroyed when the server stops.
- There is no persistent storage between sessions. This is a feature: every class starts clean.
- The facilitator controls the lifecycle by starting (`python app.py`) and stopping (`Ctrl+C`) the Flask process.
- Optional: The facilitator can trigger a mid-session database wipe to demonstrate data impermanence.

---

## 4. Data Model

### Users

| Field | Type | Notes |
|-------|------|-------|
| `id` | INTEGER, PK | Auto-increment |
| `session_label` | TEXT | System-assigned. Derived from id at creation (e.g., "Anon #1"). Used in public square and admin panel. |
| `username` | TEXT | Participant-chosen, case-sensitive, secret. Only visible to confirmed friends. |
| `profile_notes` | TEXT | Class notes, personal workspace |
| `notes_visible` | BOOLEAN | Default: TRUE (public). Toggled by Profile Key. |
| `post_tier` | TEXT | One of: `ADMIN`, `TRUSTED`, `DEFAULT`, `RESTRICTED`. Default: `DEFAULT`. |
| `created_at` | TIMESTAMP | Session-relative |

No email. No password hash. No PII. Identity exists only for the duration of the session.

Usernames are **pseudonymous by default**. In the public square, posts from non-friends display with an opaque session label (e.g., "Anon #7"). Posts from confirmed friends display the friend's username. This means the public square looks anonymous to newcomers and gradually becomes populated with known identities as friendships form. The only way to learn someone's username is through real-world interaction.

### Keys (Per-User)

| Field | Type | Notes |
|-------|------|-------|
| `id` | INTEGER, PK | Auto-increment |
| `user_id` | FK → Users | Owner |
| `key_type` | TEXT | One of: `PROFILE`, `FRIEND` |
| `emoji_code` | TEXT | Two-emoji string (e.g., "🍎🐶") |

Each user has exactly one key of each type. The FRIEND key is personal (not per-relationship).

### Post Keys (Server-Level)

Post keys are not per-user. They are server-wide secrets managed by the facilitator.

| Key | Set At | Changes? | Who Uses It |
|-----|--------|----------|-------------|
| **Admin Post Key** | Server startup (4-emoji) | Never | Facilitator. Also authenticates `/admin` panel and co-signs restricted user posts. |
| **Trusted Post Key** | Server startup (2-emoji) | Never | Participants promoted to TRUSTED tier by the facilitator. |
| **Default Post Key** | Server startup (2-emoji) | **Rotates** every time a user is restricted | All DEFAULT-tier participants. |
| **Restricted posting** | N/A | N/A | RESTRICTED users can only post when an admin co-signs with the Admin Post Key. |

**The rotation mechanic is the point.** When a user is restricted — whether by the facilitator or by community reports reaching 51% — the Default Post Key changes for *everyone*. No notification is sent. Participants discover the change when their next post attempt fails with a standard error message. They must come to the facilitator for the new key. The facilitator has a "distribute new Default Post Key" button in the Admin Panel but controls the pace and method of distribution.

### Handshakes

| Field | Type | Notes |
|-------|------|-------|
| `id` | INTEGER, PK | Auto-increment |
| `from_user_id` | FK → Users | The submitter |
| `to_user_id` | FK → Users | The target |
| `submitted_code` | TEXT | 4-emoji string: submitter's friend key + target's friend key |
| `created_at` | TIMESTAMP | When submitted |

A friendship is confirmed when the Handshakes table contains matching inverse entries: A→B with code F1F2 and B→A with code F2F1, where F1 is A's Friend Key and F2 is B's Friend Key.

### Posts

| Field | Type | Notes |
|-------|------|-------|
| `id` | INTEGER, PK | Auto-increment |
| `author_id` | FK → Users | Who posted |
| `content` | TEXT | Post body |
| `score` | INTEGER | Default: 0. Sum of weighted votes. |
| `created_at` | TIMESTAMP | For feed ordering |

The `score` field is the net result of all weighted votes, recalculated on each vote (write-time, not read-time). Most-positive score pins to top, most-negative pins to bottom.

### Votes

| Field | Type | Notes |
|-------|------|-------|
| `id` | INTEGER, PK | Auto-increment |
| `post_id` | FK → Posts | The post being voted on |
| `user_id` | FK → Users | The voter |
| `direction` | INTEGER | +1 (like) or -1 (dislike) |

One vote per user per post. The vote's *weight* is determined by the voter's tier at the time of voting:

| Voter Tier | Vote Weight |
|------------|-------------|
| ADMIN | +1 / -1 |
| TRUSTED | +2 / -2 |
| DEFAULT | +1 / -1 |
| RESTRICTED | 0 (vote is recorded but has no effect) |

Admin votes are deliberately weighted the same as default. The facilitator's power is structural (key management, user restriction), not social (inflated votes). Trusted users are the only tier with amplified voice — a privilege that's invisible unless the facilitator reveals it.

Vote weight is locked at time of voting. If a user is promoted from DEFAULT to TRUSTED, their past votes do not retroactively gain weight.

### Reports

| Field | Type | Notes |
|-------|------|-------|
| `id` | INTEGER, PK | Auto-increment |
| `post_id` | FK → Posts | The reported post |
| `reporter_id` | FK → Users | Who filed the report |
| `created_at` | TIMESTAMP | When reported |

One report per reporter per post. Reports have two escalating effects:

**Spoiler (per-post, immediate):** A single report on a post wraps it in a spoiler overlay — content hidden until clicked. The spoiler is a visual flag, not a removal. Anyone can click through to read the content. Additional reports on the same post don't change the spoiler — one is enough.

**Restriction (per-user, threshold):** When unique reporters across all of a user's posts reach 51% of total registered participants (excluding the reported user), the system **auto-restricts** that user. This triggers the Default Post Key rotation.

**"Active participants"** means total registered users in this session, minus the reported user. Flask doesn't maintain persistent connections, so there is no presence detection. The threshold is based on registration count, not who's currently looking at the screen.

**Design implications:**

- **The report button is on each post.** Tapping "report" on a post by Anon #7 reports that specific post and spoilers it. The system tracks unique reporters per user across all their posts for the restriction threshold.
- **Reported users are notified that they've been reported, but not by whom.** This creates accountability without enabling retaliation. The notification says something like "One of your posts has been reported" — no specifics about which post or who reported it.
- **The facilitator can restrict a user on a single report.** The community needs 51%; the admin needs only their judgment. This asymmetry between democratic and authoritarian moderation is the point.
- **Mob rule is not prevented.** Nothing stops a majority from restricting someone for a bad joke, an unpopular opinion, or for fun. Community moderation can be just and it can be tyrannical. The system doesn't distinguish.
- **Unrestriction is admin-only.** The community can restrict easily but cannot restore. This asymmetry between "easy to punish, hard to forgive" is real, worth experiencing, and worth discussing.
- **Friends can report friends.** Friendship does not grant immunity from accountability.
- **Restricted users can still report others.** Losing your voice doesn't strip your ability to flag behaviour. Whether that's fair is a discussion question.

### Trust Relationships (Linked Palindromic Pair)

Each user has a personal Friend Key — a 2-emoji combo chosen at profile creation.

**Handshake flow (user-facing):**
1. Select the target's username from a list of all registered usernames in the session.
2. Enter your own Friend Key (2 emoji).
3. Enter the target's Friend Key (2 emoji).
4. The system validates the username exists, concatenates (yours + theirs), and submits.

**Pseudonymity note:** The handshake screen exposes all registered usernames. Participants can browse the full list but cannot map names to real people in the room or to session labels — that still requires IRL exchange. If someone shouts "who's CoolPenguin42?" in class, the pseudonymity is broken socially, not technically. The system provides the cover; maintaining it requires discipline. This is an operational security lesson, not a bug.

The target must complete the same flow in reverse. The server validates:
- The first pair of the submitted code matches the submitter's stored Friend Key.
- The second pair matches the target's stored Friend Key.
- A matching inverse entry exists from the other side (target → submitter, with target's key first).

Example:
- A's Friend Key: 🍎🐶 — B's Friend Key: 🌙🔥
- A's submission (system-built): 🍎🐶🌙🔥 — B's submission: 🌙🔥🍎🐶

**Security analysis and pedagogical framing: see Section 5 (Friend Key).**

---

## 5. The Three Keys

### Profile Key 🔑

- **Purpose**: Ownership. Required to edit your profile notes and toggle note visibility.
- **Assigned**: On user creation. The participant chooses their own 2-emoji combo.
- **Lesson**: Your identity and your data require a key you control. If you lose it (forget it), you lose access to your own notes. Nobody else can recover it for you.

### Post Keys 📝 (Tiered, Server-Level)

Post keys are not personal — they are platform credentials managed by the facilitator. See Section 4 (Post Keys) for the key table and rotation trigger rules.

- **Admin Post Key** (4-emoji): Known only to the facilitator. Never changes. Used to post, co-sign restricted users' posts, perform admin actions, and authenticate the Admin Panel. The 4-emoji length (rather than 2) makes it harder to guess and provides a future teaching hook for pen-testing exercises.
- **Trusted Post Key**: Distributed by the facilitator to participants who earn TRUSTED status. Never changes. A stable credential that rewards demonstrated competence and good faith participation.
- **Default Post Key**: Distributed to all participants at the start of the session. **Rotates every time a user is restricted.** When it rotates, no one is notified. Participants discover their key is broken when they try to post and receive a standard error message. They must ask the facilitator for the new key. The facilitator has a "distribute new Default Post Key" button in the Admin Panel but chooses when and how to share it — verbally, on the board, whispered to individuals.
- **Restricted Posting**: A RESTRICTED user cannot post independently. They can only post if the facilitator co-signs with the Admin Post Key — meaning an admin must actively approve the post. This is supervised access.

**Lessons taught by this structure:**
- The difference between personal credentials (Profile Key, Friend Key) and platform access (Post Keys). You own one; someone else owns the other.
- Hierarchies of access exist on every platform. Some users are verified, some are shadowbanned, some are suspended. Now you've felt what each level is like.
- Moderation has externalities. Restricting one user disrupts every default user — and the disruption is silent. You only find out when you try to act.
- Stability as privilege. TRUSTED users are unaffected by key rotations. That stability is itself a form of power.
- Access recovery is a social process. The new key exists, but you have to ask for it. Who you have to ask, and how they choose to distribute it, shapes the community.

### Friend Key 🤝

- **Purpose**: Identity-bound trust. Your personal Friend Key is a 2-emoji combo set at profile creation. See Section 4 (Trust Relationships) for the handshake validation logic and data model.
- **Assigned**: Chosen by the participant at profile creation.
- **The Exchange Ritual**: To become friends, two participants must meet in person and share two things: their username (secret until this moment) and their Friend Key emoji pair. Each then uses the handshake screen to select the other's username from the registered list, enter both keys, and submit. The system concatenates and validates. The physical exchange is the entire point: trust is built face-to-face, then registered on the network.
- **Lesson**: Trust requires mutual action, shared knowledge, *and* verified identity. Knowing someone's secret isn't enough to impersonate them — the system checks who you are, not just what you know.
- **Security model**: The handshake is identity-bound. If C learns both A's and B's Friend Keys (eavesdropping, social engineering), C still cannot forge the A↔B connection. The system would build C's submission as [C's key + B's key] — which fails validation because C's key isn't A's key. The only way C can impersonate A is by changing C's own Friend Key to match A's — which requires compromising A's Profile Key. This creates a dependency chain: **Profile Key protects Friend Key protects trust relationships.** All keys are secrets. All are attackable. But knowledge alone isn't sufficient without identity.
- **Pseudonymity implication**: Friendship doesn't just unlock private notes — it unlocks *who someone is*. In the public square, non-friends are anonymous. Forming a friendship transforms an opaque session label into a known person. This directly teaches the difference between pseudonymity and identity, and why de-anonymization is a meaningful act of trust.

---

## 6. User Interface Constraints

The UI must work on damaged, old, and small-screen devices. These are hard design requirements, not suggestions.

**Aesthetic direction**: Post-soviet cyberpunk. Functional, raw, slightly brutalist. Monospace fonts, visible borders, no rounded-corner polish. The interface should feel like it was built in a garage from salvaged parts — because it was. This aesthetic is honest about what the system is (a prototype on old hardware) and appealing to younger participants who respond to the vibe of "we built this ourselves."

Functional constraints:

- **Large touch targets**: Minimum 48x48px for all interactive elements.
- **High contrast**: Dark text on light background. No subtle grays. No low-contrast placeholder text.
- **Minimal text**: Labels are short. Instructions use icons + words. The emoji-key input is the primary interaction, not typing.
- **No JavaScript frameworks**: The UI is server-rendered HTML with minimal inline JS. This keeps page weight low and avoids compatibility issues on old WebViews.
- **Works on cracked screens**: No interactions that require precision tapping in corners. Primary actions are centered.
- **Mobile-first**: The UI assumes a phone screen. Desktop is not a design target.

### Key Screens

1. **Join**: Choose a username (secret — explain this clearly) → choose Profile Key and Friend Key → receive the current Default Post Key. The system assigns a session label (e.g., "Anon #7") for public display.
2. **Square**: The public feed. Highest-scoring post pinned at top. Lowest-scoring post pinned at bottom. Both always visible (a toy recommendation algorithm — "this is what 'trending' means"). Remaining posts newest-first between the pinned extremes. Each post shows the author's session label or username (if the viewer is a confirmed friend). Like/dislike buttons on each post — weighted by voter tier, but the weighting is invisible to participants until the facilitator reveals it. Report button on each post; a single report spoilers the post (content hidden behind a click-to-reveal overlay). Post input requires the appropriate Post Key for the user's tier. **Friends-only toggle**: switches the feed to show only posts from confirmed friends, hiding the public square entirely.
3. **Profile**: Your notes editor. Toggle visibility (requires Profile Key). Friend Key management: select target's username from the registered list, enter your Friend Key, enter their Friend Key, submit. View confirmed friends and their usernames.
4. **Admin Panel** (`/admin`, authenticated by the 4-emoji Admin Post Key, entered once per browser session): Restrict or promote users by session label. View and distribute the current Default Post Key (the key is displayed on this screen so the facilitator can read it out, write it on a board, or whisper it). Trigger attack simulations (see Section 7). View raw database. Unrestrict users. Show/hide the join QR code.

   **Platform View — "WHAT THE PLATFORM SEES"** (bottom of admin panel): A surveillance dashboard designed for mid-session or end-of-session projection. Three panels:
   - *Deanonymization table*: every session label alongside real username, tier, post count, and net score — one screen collapses the pseudonymity the whole session was built on.
   - *Social graph*: all handshake pairs (CONFIRMED / PENDING). The platform has always known the full network topology; participants knew only their own connections.
   - *Behavioral correlations*: who voted for whom and who reported whom, cross-referenced against confirmed friendships. Shows that the platform can infer relationships from behavior alone — the metadata lesson. A `[FRIENDS]` badge appears where a correlation pair are confirmed friends.

---

## 7. Hardware Constraints: Honest Assessment

### Primary Target: Android via Termux

- **Minimum viable server device**: Any Android phone running 7.0+ with 1GB+ RAM.
- **Realistic capacity**: Flask on SQLite on a budget Android phone comfortably handles 15-20 concurrent browser clients on a local hotspot. This matches typical classroom size.
- **Battery**: A full session (2-3 hours) will drain the server device. Keep it plugged in.
- **Background kill**: Android aggressively kills background apps. The facilitator must ensure Termux has a persistent notification and battery optimization is disabled for Termux in device settings.

### Stretch Goal: iOS via iSH

- **What works**: iSH runs Alpine Linux on iOS 11+, which covers iPhone 7 and newer. Python 3 installs. Flask runs. It can serve pages on a local network.
- **What's slow**: iSH emulates x86 on ARM. Everything runs at a fraction of native speed. Server response times will be noticeably sluggish.
- **What doesn't work**: iOS aggressively suspends background apps. The iSH app must stay in the foreground with the screen on. This is fragile for a server role.
- **Recommendation**: Use iOS devices as *clients* (browsers), not servers. If an Android device is available, it should be the server.

### Legacy Apple Devices as Teaching Props and Attack Platforms

The iPad Air (1st gen) and iPhone 7 are not good Meshagora servers, but they have two valuable roles: demonstrating hardware repurposing and running adversarial simulations.

**Tinkering and Repurposing Demos:**

- **iSH install walkthrough**: Show participants that a Linux terminal can run on an old iPhone. The act of installing and typing `python3` on a phone is itself a teaching moment — the device is more than what Apple says it is.
- **Client participation**: Old iOS devices work fine as browsers connecting to Meshagora. They're participants in the network, not infrastructure.
- **Hardware teardown context**: "This phone has a GPS, accelerometer, gyroscope, barometer, camera, microphone, and a multi-core processor. It cost someone $800 in 2016. It now costs $40. What can you do with it?" This connects directly to Section 1 of the workshop.
- **Limitations as lessons**: The fact that iOS *restricts* background processes, server roles, and sideloading is itself a teaching point about platform control and walled gardens.

**The Attack Platform (Phase 2 Tool):**

Legacy iOS devices are ideal for running adversarial simulations because they're physically separate from the server, visible to the class, and just capable enough to fire HTTP requests.

- **MVP approach — Admin Panel as remote control**: The facilitator opens the Agora's `/admin` route in Safari on the iPad. From there they can trigger canned disruptions: create sock puppet accounts that flood the feed, rotate the Default Post Key, restrict users, and display the raw database. The iPad becomes a visible "attack console" — the class can see the facilitator tapping buttons on a separate device and watch the effects propagate to their screens in real time.
- **Post-MVP approach — Visible scripts**: A standalone Python script running in iSH on the iPad or iPhone 7 that hits Meshagora's API endpoints as fake users. The facilitator shows the script running on the device's screen: "This is what a bot looks like. These are the HTTP requests it sends. This is how a sock puppet account works." iSH is slow, but for sending POST requests to a local Flask server, it's more than adequate. Learners can read the code, see it execute, and correlate each request with what appears in the feed.
- **Why a separate device matters**: Running attacks from the same device as the server muddles the lesson. A physically distinct attack device teaches that threats come from outside your system, through the same network interface that legitimate users connect through. The iPad sitting on the teacher's desk running attack scripts is a tangible threat model.

### What Was Cut (and Why)

- **QPython**: Largely abandoned, unreliable. Not included.
- **BeeWare/Kivy packaging**: Building .apk files is out of scope for a classroom exercise.
- **Local LLM inference**: Not viable on target hardware. An iPhone 7 with 2GB RAM cannot usefully run even TinyLlama through iSH's x86 emulation layer.
- **Captive portal**: Requires root or custom firmware on most Android devices. The QR code approach solves the same discovery problem without this complexity.

---

## 8. Facilitator Guide Notes

### Before the Session

- Test the full setup on the actual server device at least once.
- Pre-install Termux, Python, Flask, and qrcode library.
- Set up the attack device (iPad/iPhone): bookmark the Admin Panel URL in Safari, or pre-install iSH with attack scripts if using the richer approach.
- Print 2-3 backup QR codes in case the on-screen code is hard to scan.
- Prepare a simple one-page handout: "Join the WiFi called [X], scan this code."
- Decide in advance which disruptions you'll run in Phase 2 and in what order.

### During the Session

- **Announce the threat model early**: "This system will be stressed during our session. Part of your job is to notice when that happens and respond."
- **Control pacing**: Don't rush from Phase 1 to Phase 2. Let participants settle into the open square before introducing disruption. 10-15 minutes of calm posting first.
- **Let pseudonymity develop naturally**: Don't explain the friend-reveals-username mechanic upfront. Let participants discover that their friends' posts suddenly have names while strangers remain anonymous. The surprise is the lesson.
- **Use the like/dislike pinning early**: Once a few posts accumulate, point out what's pinned at top and bottom. Ask: "Who decided what you see first? You did — collectively. Is the result good?" This is the gentlest possible introduction to algorithmic ranking.
- **Reveal vote weighting later**: After participants have internalized the ranking, reveal that trusted users' votes count double and restricted users' votes count for nothing. Ask: "Did you notice? Does this change how you feel about what's pinned at the top?" This is the covert-influence lesson — equal-looking buttons producing unequal outcomes.
- **Demonstrate the database**: At some point, show participants the raw SQLite file via the Admin Panel. Let them see their "private" notes and "secret" usernames in plain text. This is the single most impactful teaching moment in the session.
- **Restrict someone and let the ripple hit**: When you restrict a user, the Default Post Key rotates silently. Don't announce it. Wait for participants to discover their posts are failing and come to you. The queue of confused people at the facilitator's desk is the lesson. Discuss afterward: "Why did your key break? Who got restricted? Was it the admin or was it the community? Is this fair?"
- **Let the community report mechanic play out**: Don't intervene when spoilered posts start appearing. If the class auto-restricts someone unfairly, that's a better teaching moment than preventing it. Discuss afterward: "Was that just? What would due process look like? Who decides what's 'bad behaviour' when the majority rules?"
- **Key distribution is social, not automatic**: When the Default Post Key rotates, participants must come to you. You choose how to distribute: announce to the whole room, whisper to individuals, write it on the board, or make them earn it. Each distribution method teaches something different about platform access and information asymmetry.
- **Watch for the friends-only retreat**: When participants start switching to friends-only view, the public square will thin out. Voting activity drops, pinned posts stagnate, the feed feels dead. Point this out: "What happened to the public square? Where did everyone go? What does it mean when the engaged users leave?" This is platform death spiral in miniature.
- **Resist fixing things for participants**: When the noise hits, point participants toward features they haven't used yet — but don't implement anything for them. The learning is in the doing.

### After the Session

- Stop the server. The database is gone. Emphasize this: "Everything we built today is gone. What remains is what you learned."
- In multi-session courses: the clean slate is intentional. Learners carry forward the social norms, agreements, and competence they built — but the technical environment resets. They rebuild faster each time. The knowledge is in the humans, not the machine.
- Connect to the broader workshop: "The platforms you use every day have this same structure — servers, databases, keys, feeds, ranking algorithms, moderation tiers. The difference is you can't see inside them. Now you know what's in there."

---

## 9. Minimum Viable Product

The MVP is the smallest buildable version that delivers the pedagogical arc. Everything below this line must work before any extensions are considered.

### MVP Features

1. User creation with secret username (case-sensitive string) and system-assigned sequential session label.
2. Pseudonymous public square: posts show session labels to non-friends, usernames to confirmed friends.
3. Tiered Post Keys: Admin (4-emoji), Trusted (2-emoji), Default (2-emoji, rotating), and Restricted posting (admin co-sign required). Admin Post Key also authenticates the Admin Panel.
4. Like/dislike on posts with tier-weighted scoring (Trusted +2, Default/Admin +1, Restricted 0). Highest-scoring pinned to top, lowest to bottom. Weight locked at time of vote.
5. Per-post report button. Single report spoilers the post (click-to-reveal). 51% unique reporters across a user's posts auto-restricts them. Facilitator can restrict on a single report. Reported users are notified but not told who reported them. Unrestriction is admin-only.
6. Friends-only feed toggle: hides the public square, shows only posts from confirmed friends.
7. Profile page with notes editor (requires Profile Key to edit).
8. Notes visibility toggle (public/private, requires Profile Key).
9. Friend Key handshake: select target from registered username list, enter your key, enter their key, system concatenates and validates the linked palindromic pair. Friendship reveals username on the public feed.
10. Private note viewing for confirmed friends.
11. Admin Panel (authenticated by 4-emoji Admin Post Key): user management, distribute new Default Post Key, attack triggers, unrestriction, raw database view, QR show/hide.
12. QR code generation on server startup with show/hide control.
13. Session reset on server restart. Learners carry forward social norms and knowledge; the database does not persist.

### MVP Delivers

- The full Phase 1–4 pedagogical arc.
- Tangible experience of pseudonymity, trust hierarchies, platform moderation externalities, algorithmic content ranking, community justice (and its failures), and the retreat-to-trust-network dynamic.
- Both top-down (admin) and bottom-up (community report) moderation in the same system, with visible tradeoffs.
- A working micro-fediverse that runs on a single old phone, attacked from an old iPad.

---

## 10. Extensions

### Implemented (Beyond MVP)

**Platform View — Admin Surveillance Dashboard** *(implemented)*
Described in Section 6. No schema changes — pure SQL joins on existing tables, rendered as three panels at the bottom of the Admin Panel. See Section 6.4 for full description.

### Future Extensions (Post-MVP)

These are documented for future development. None are required for the core experience.

### Chaos Bot (Advanced)
The MVP admin panel supports basic sock puppet and flood attacks. This extension adds autonomous adversarial agents: bots that adapt their behaviour based on the feed state (e.g., targeting the most-liked post with dislikes, mimicking a specific user's posting style, or gradually escalating provocative content). Teaches pattern recognition and the difference between scripted spam and adaptive manipulation.

### Privacy Filter Workshop
Instead of a simple visibility toggle, participants write or configure their own filter rules (e.g., "show my notes only to users with reputation > X"). Teaches algorithmic moderation.

### Reputation System
A community-driven trust score based on peer endorsements. Teaches how reputation systems work (and how they can be gamed).

### Auto-Trust by Social Graph
A facilitator-toggled mechanic: when enabled, any user with confirmed friendships exceeding half the active participants is automatically promoted to TRUSTED tier. This teaches that platforms often grant authority based on network metrics (follower count, engagement, connections) rather than demonstrated competence. Best introduced mid-session after participants have experienced manual trust promotion, so the class can contrast the two models and discuss what changes when popularity becomes power. Creates a perverse incentive to collect friendships as tokens rather than trust relationships — surfacing that incentive *is* the lesson.

### Offline Knowledge Library
Integrate Kiwix (offline Wikipedia/Project Gutenberg) as a second service on the server device. The "survival information" component — demonstrating that a phone can be a library, not just a communication device.

### Cross-Device Federation
Run two Meshagora instances on two devices on the same network. Posts federate between them. Directly demonstrates the Fediverse model with physical hardware participants can see and touch.

### Agent Integration
Use an AI coding agent to generate the Flask server code from this spec, with participants auditing the output. Teaches code review and AI literacy simultaneously.

---

## 11. Specification Status

This is a **v2.3 hybrid human-model design document**. It has been:

- Generated through conversation with Gemini (v1.0 and v1.1).
- Reviewed and critiqued by Opus for technical accuracy, framework alignment, and scope control.
- Refined with corrected framework language (Safety/Sanity/Serenity), linked palindromic friend-key mechanics, honest hardware constraints, iOS tinkering use cases, and integration with existing 3SC workshop materials.
- Extended with tiered post keys, like/dislike ranking, pseudonymous identity, community reports, friends-only feed, and legacy iOS as attack platform.
- Final review pass: resolved agent-facing ambiguities (admin auth, handshake flow, key distribution, vote weight locking, report notification, active participant threshold, Phase 3 feature-gating), reduced duplication between Sections 4 and 5, documented pseudonymity implications of username list.

It is ready for:

- Implementation by a coding agent (targeting a single `app.py` file).
- Human audit of the generated code.
- Pilot testing on actual legacy hardware.

It is **not** a finished product. It is a buildable specification for a prototype.

---

*Design note: This specification is intended to be stored on the server device itself. The documentation is the first artifact in the lab.*
