514 lines
26 KiB
Markdown
514 lines
26 KiB
Markdown
# Zapstore App Stacks — Privacy Review
|
|
|
|
A systematic privacy and security review of apps in the [Zapstore](https://zapstore.dev) catalog. Each app is cloned from its source repository, scanned for tracking SDKs, permissions, and privacy practices, then assigned a verdict. Approved apps are organized into curated stacks for publication to the Zapstore relay.
|
|
|
|
## Project Structure
|
|
|
|
```
|
|
.
|
|
├── README.md # This file
|
|
├── .gitignore
|
|
├── plans/ # Planning documents and methodology
|
|
│ ├── app-review-methodology.md
|
|
│ ├── proposed-categories.md
|
|
│ └── review-todo.md
|
|
├── scripts/ # Python scripts for review and publishing
|
|
│ ├── review_apps.py # Main review script (SSH-based)
|
|
│ ├── generate_stacks.py # Map apps to categories, generate stack events
|
|
│ ├── publish_stack.py # Sign and publish stacks to relay
|
|
│ └── ...
|
|
├── data/ # Review results and app data (JSON)
|
|
│ ├── review_results_final.json # All 280 apps with verdicts
|
|
│ ├── apps_data.json # Zapstore catalog data
|
|
│ └── ...
|
|
├── reports/ # Human-readable reports (Markdown)
|
|
│ ├── review_report_by_category.md # Full category-by-category report
|
|
│ ├── final_report.md # Top 2 apps per category
|
|
│ └── ...
|
|
└── stacks/ # Generated Nostr stack events (kind 30267)
|
|
├── all_stacks.json # All 20 stacks in one file
|
|
└── ...
|
|
```
|
|
|
|
## Review Results
|
|
|
|
| Verdict | Count | Meaning |
|
|
|---------|-------|---------|
|
|
| ✅ **APPROVED** | **175** | No tracking SDKs, minimal/justified permissions, open source |
|
|
| ⚠️ **FLAGGED** | **36** | Contains tracking SDKs or excessive permissions |
|
|
| ⏭️ **SKIPPED** | **41** | Could not clone repository (no mirror, no GitHub URL) |
|
|
| ❓ **UNCLEAR** | **28** | Needs human investigation (app not in catalog, auth required) |
|
|
|
|
## 20 Curated App Stacks
|
|
|
|
These stacks are ready to publish to the Zapstore relay as kind-30267 events.
|
|
|
|
### 1. Bitcoin & Lightning Wallets
|
|
Self-custodial Bitcoin and Lightning wallet apps.
|
|
- ZEUS, Nunchuk, Cake Wallet, Alby Go, Blitz Wallet, BlueWallet, Electrum, Oubli, MercaSats, Lightning Reaction
|
|
|
|
### 2. Nostr Clients
|
|
Nostr-native social and communication clients.
|
|
- Amethyst, Amber, Wisp, Dark Wisp, White Noise, Nospeak, Nostrord, Shosho, TravelTelly, Nmail, Divine, YakiHonne, Nests, Nostria, Zapstore Alpha, PearCal, PearCircle, Ditto, Flotilla
|
|
|
|
### 3. Secure Messaging
|
|
End-to-end encrypted messaging and email apps.
|
|
- SimpleX, Conversations, Delta Chat, aTalk, Element, Element X, Thunderbird, FairEmail, Tuta, Quiet, SpamBlocker, Deku SMS
|
|
|
|
### 4. VPN & Privacy Tools
|
|
VPN clients, firewalls, and network privacy tools.
|
|
- Orbot, Mullvad VPN, Tailscale, AmneziaVPN, Proton VPN, Rethink, PCAPdroid, WG Tunnel, ByeDPI, NeoStumbler, SD Maid, InviZible Pro, Private DNS Quick Setting
|
|
|
|
### 5. Password Managers & Auth
|
|
Password managers, 2FA authenticators, and identity tools.
|
|
- Bitwarden, KeePassDX, Aegis, Authnkey, Ente Auth, Keep, AliasVault, PassVault, LibreFind
|
|
|
|
### 6. Maps & Navigation
|
|
Offline maps, navigation, and location tools.
|
|
- Organic Maps, OsmAnd~, StreetComplete, CoMaps, OwnTracks, OSMTracker
|
|
|
|
### 7. Media Players & Streaming
|
|
Video and music players, streaming clients.
|
|
- VLC, NewPipe, PipePipe, Auxio, Musify, Metrolist, FreeTube, Zaptrax, Zappix, mpvEx, KurobaEx
|
|
|
|
### 8. Productivity & Notes
|
|
Note-taking, task management, and productivity tools.
|
|
- Flux, Notesnook, Quillpad, Saber, DAVx⁵, Super Productivity, Trilium Notes, SilentNotes, Manent, Meiso, Florid, Plektos, Urn, Screen Time, Grit, Numo, timeto.me, Converter NOW
|
|
|
|
### 9. File Management & Cloud Sync
|
|
Cloud storage, file sync, and file management tools.
|
|
- Nextcloud, Syncthing-Fork, Seafile, File transfer, primitive ftpd, Paperless Mobile, GitSync, wormhole, SFTP Documents Provider, OSS Document Scanner
|
|
|
|
### 10. Browsers
|
|
Privacy-focused web browsers.
|
|
- DuckDuckGo, Cromite
|
|
|
|
### 11. Social Media
|
|
Federated and alternative social media clients.
|
|
- Mastodon, Nekogram, Infinity+, NewsBlur, Claw, Polymarket Viewer
|
|
|
|
### 12. Finance & Budgeting
|
|
Personal finance, expense tracking, and budgeting.
|
|
- Flow, Pennywise AI Tracker, Dev Stocks Widget, Seeker, PearGuard, Mostro, Shopt
|
|
|
|
### 13. Health & Fitness
|
|
Health tracking, diet, and fitness apps.
|
|
- Daily Dozen, Screen Time, Astronia
|
|
|
|
### 14. Education & Reference
|
|
Learning, dictionary, and reference tools.
|
|
- freeCodeCamp, freeDictionary, Ciyue, Keyman, NeverTooManyBooks, Calibre Web Companion, Repertoire, ListenBrainz, CPU Info, microMathematics Plus, Mental Math
|
|
|
|
### 15. Photography & Image Tools
|
|
Camera, photo editing, scanning, and gallery apps.
|
|
- Image Toolbox, FairScan, PhotoPrism, PicGuard, Gallery apps, YTDLnis, ElCaju, Espy, PDF Wallet
|
|
|
|
### 16. Utilities & Tools
|
|
System utilities, converters, and general-purpose tools.
|
|
- Binary Eye, CPU Info, Converter NOW, Catima, BT Remote, Lawnicons, Peristyle, BinEd, MMRL, Canta, Amarok, ServerBox, wX, Mako, Rush, HeliBoard, DeskClock, Redomi, Scrobble, Feeder, SMS2Email, Inure, Unciv
|
|
|
|
### 17. Games
|
|
Open source games across genres.
|
|
- Shattered Pixel Dungeon, Wesnoth, Feudal Tactics, Burger Party, ChipDefense, Roboyard, Breakout 71, Damas Clash, Unciv
|
|
|
|
### 18. Communication (Non-Nostr)
|
|
Voice/video calls, remote desktop, and messaging.
|
|
- Telegram, RustDesk, Sideband, Meshtastic
|
|
|
|
### 19. Development Tools
|
|
Code editors, Git clients, and developer tools.
|
|
- Acode, GitSync, BinEd, freeCodeCamp, GitHub Store, Kai 9000
|
|
|
|
### 20. Calendar & Scheduling
|
|
Calendar apps, scheduling, and time management.
|
|
- Calendar by Form*, PearCal, timeto.me, Sidestep
|
|
|
|
## Review Methodology
|
|
|
|
Each app is reviewed using a structured process:
|
|
|
|
1. **Clone** from local Gitea mirror (or GitHub fallback)
|
|
2. **Scan AndroidManifest.xml** for requested permissions
|
|
3. **Check build.gradle\*** for actual dependency declarations (distinguishing `implementation` vs `playImplementation` vs `compileOnly`)
|
|
4. **Verify API calls** — search for actual SDK method invocations (not just dependency names)
|
|
5. **Check manifest metadata** that disables analytics
|
|
6. **Check build flavors** (Google Play vs F-Droid)
|
|
7. **Determine verdict**: ✅ APPROVED or ⚠️ FLAGGED
|
|
|
|
See [`plans/app-review-methodology.md`](plans/app-review-methodology.md) for full details.
|
|
|
|
## Publishing
|
|
|
|
Stack events (kind 30267) are published to `wss://relay.zapstore.dev` using the [`publish_all.py`](publish_all.py) script, which signs via `qrexec` (Qubes OS `nostr_signer`).
|
|
|
|
## Zapstore Display Requirements
|
|
|
|
The Zapstore Android app ([source](../zapstore_app_stacks/references/zapstore/)) filters which stacks appear on the home screen. A kind 30267 stack event must satisfy **all three** of these conditions to appear with apps:
|
|
|
|
### Quick Reference: The Three Rules
|
|
|
|
1. **`h` tag = community pubkey** — The app filters by `#h` matching `acfeaea6e51420e8068fac446ca9d17d7a9ef6a5d20d93894e50fee3d4902a84`. Set to `""` to hide a stack from the app.
|
|
|
|
2. **`content` = empty string** — The app treats any non-empty `content` as encrypted/private data. If `content` is non-empty, the app skips the `a` tags and shows "no apps". Always use `content: ""` for public stacks.
|
|
|
|
3. **`a` tags reference real kind 32267 events** — Each `a` tag must be `32267:<real-developer-pubkey>:<package-id>` pointing to an actual kind 32267 event on the relay. Apps without kind 32267 events won't appear in the stack.
|
|
|
|
### Complete Example
|
|
|
|
```json
|
|
{
|
|
"kind": 30267,
|
|
"content": "",
|
|
"tags": [
|
|
["d", "privacy-approved-health-fitness"],
|
|
["t", "app-stack"],
|
|
["name", "Privacy Approved — Health & Fitness"],
|
|
["description", "Health tracking, diet, and fitness apps..."],
|
|
["f", "android-arm64-v8a"],
|
|
["h", "acfeaea6e51420e8068fac446ca9d17d7a9ef6a5d20d93894e50fee3d4902a84"],
|
|
["a", "32267:78ce6faa72264387284e647ba6938995735ec8c7d5c5a65737e55130f026307d:org.nutritionfacts.dailydozen", "wss://relay.zapstore.dev"],
|
|
["a", "32267:78ce6faa72264387284e647ba6938995735ec8c7d5c5a65737e55130f026307d:com.antoniegil.astronia", "wss://relay.zapstore.dev"]
|
|
]
|
|
}
|
|
```
|
|
|
|
### Required Tags
|
|
|
|
| Tag | Value | Purpose |
|
|
|-----|-------|---------|
|
|
| `h` | `acfeaea6e51420e8068fac446ca9d17d7a9ef6a5d20d93894e50fee3d4902a84` | **Zapstore community pubkey** — the app filters by `#h` matching this value. Set to empty string to hide the stack from the zapstore app. |
|
|
| `f` | `android-arm64-v8a` | Platform filter — the app filters by `#f` matching the device platform |
|
|
| `d` | e.g. `privacy-approved-nostr-clients` | Unique identifier for the stack |
|
|
| `name` | e.g. `Privacy Approved — Nostr Clients` | Display name |
|
|
| `description` | Human-readable description | Shown in stack details |
|
|
| `a` | `32267:<pubkey>:<identifier>` | References to kind 32267 app events — **must point to real events on the relay** |
|
|
|
|
### The `h` Tag (Community Pubkey)
|
|
|
|
The Zapstore app's [`app_stacks_screen.dart`](../zapstore_app_stacks/references/zapstore/lib/screens/app_stacks_screen.dart) queries stacks with:
|
|
|
|
```dart
|
|
Map<String, Set<String>> get _tags => {
|
|
'#f': {platform},
|
|
'#h': {kZapstoreCommunityPubkey},
|
|
};
|
|
```
|
|
|
|
Where `kZapstoreCommunityPubkey` is defined in [`app_constants.dart`](../zapstore_app_stacks/references/zapstore/lib/constants/app_constants.dart) as:
|
|
|
|
```
|
|
acfeaea6e51420e8068fac446ca9d17d7a9ef6a5d20d93894e50fee3d4902a84
|
|
```
|
|
|
|
(npub: `npub14nl2afh9zsswsp5043zxe2w304afaa496gxe8z2w2rlw84ys92zqlnjx5u`)
|
|
|
|
**If the `h` tag is empty or doesn't match this value, the stack will NOT appear in the Zapstore app.**
|
|
|
|
### The `content` Field (Must Be Empty)
|
|
|
|
The Zapstore app checks `stack.content.isNotEmpty` to determine if a stack is **encrypted/private**. If the content is non-empty, the app attempts to decrypt it and parse private app IDs from it. If decryption fails, it shows "no apps" or "could not be decrypted" instead of reading the `a` tags.
|
|
|
|
**The `content` field must be an empty string `""` for public stacks.** The stack name and description are read from the `name` and `description` tags, not from the content field.
|
|
|
|
### The `a` Tags (App References)
|
|
|
|
The `a` tags in stacks reference kind 32267 (Software Application) events. The Zapstore app resolves these references to display app cards within each stack. The `a` tag format is:
|
|
|
|
```
|
|
32267:<app-developer-pubkey>:<android-package-identifier>
|
|
```
|
|
|
|
The pubkey must be the **actual app developer's pubkey** who published the kind 32267 event — not the stack publisher's pubkey. If the referenced kind 32267 event doesn't exist on the relay, the app won't show that app in the stack.
|
|
|
|
A lookup file of app identifiers to real developer pubkeys is maintained at [`app_pubkeys.json`](app_pubkeys.json), fetched from `wss://relay.zapstore.dev`.
|
|
|
|
### Stack Comments (Kind 1111)
|
|
|
|
The Zapstore app displays comments under each stack. Comments are kind 1111 events (not kind 1 text notes) with an `A` tag referencing the stack's addressable ID.
|
|
|
|
The zapstore app queries comments in [`comments_section.dart`](../zapstore_app_stacks/references/zapstore/lib/widgets/comments_section.dart) with:
|
|
|
|
```dart
|
|
query<Comment>(
|
|
tags: {
|
|
'#A': {stack.id}, // stack.id = "30267:<pubkey>:<d-tag>"
|
|
},
|
|
source: LocalAndRemoteSource(stream: true, relays: 'AppCatalog'),
|
|
)
|
|
```
|
|
|
|
#### Comment Event Structure
|
|
|
|
```json
|
|
{
|
|
"kind": 1111,
|
|
"content": "LLM Privacy Review: Both apps have been scanned...",
|
|
"tags": [
|
|
["A", "30267:<stack-pubkey>:<stack-d-tag>", "wss://relay.zapstore.dev"],
|
|
["K", "30267"]
|
|
],
|
|
"created_at": 1785840000,
|
|
"pubkey": "<commenter-pubkey>"
|
|
}
|
|
```
|
|
|
|
| Tag | Value | Purpose |
|
|
|-----|-------|---------|
|
|
| `A` | `30267:<pubkey>:<d-tag>` | Addressable reference to the stack event (NIP-22 root) |
|
|
| `K` | `30267` | Root kind (the kind of the referenced event) |
|
|
|
|
#### Important: Kind 1 is Rejected
|
|
|
|
`relay.zapstore.dev` does **not** accept kind 1 (text note) events. Comments **must** use kind 1111. The relay's allowed kinds are: `[0 5 11 1111 1984 9735 10222 3063 30063 30509 30267 30078 32267]`.
|
|
|
|
#### Use Case: LLM Review Comments
|
|
|
|
Comments can be used to attach LLM privacy review summaries to stacks. The comment content is free text (supports markdown via the app's note parser). This allows posting review results like:
|
|
|
|
> "LLM Privacy Review: Both apps in this stack have been scanned for tracking SDKs, excessive permissions, and privacy concerns. No issues found — both are approved at privacy level 1."
|
|
|
|
### Relay Acceptance Rules
|
|
|
|
| Relay | Kind 30267 (Stacks) | Kind 30078 (App Definitions) | Kind 32267 (App Listings) | Kind 1111 (Comments) |
|
|
|-------|---------------------|------------------------------|---------------------------|----------------------|
|
|
| `wss://relay.zapstore.dev` | ✅ Accepts (requires `a` tags use kind 32267) | ❌ Rejects `d: app-*` (only allows `zapstore-device-*`) | ✅ Accepts | ✅ Accepts |
|
|
| `wss://nos.lol` | ✅ Accepts | ✅ Accepts | ✅ Accepts | ✅ Accepts |
|
|
| `wss://relay.primal.net` | ✅ Accepts | ✅ Accepts | ✅ Accepts | ✅ Accepts |
|
|
| `wss://laantungir.net/relay` | ✅ Accepts | ✅ Accepts | ✅ Accepts | ✅ Accepts |
|
|
|
|
## Nostr Kinds Reference
|
|
|
|
The following Nostr event kinds are relevant to this project:
|
|
|
|
| Kind | Name | Usage | Status |
|
|
|------|------|-------|--------|
|
|
| **32267** | Software Application | The app listing event published by developers. Identified by `d` tag (Android package name) and publisher pubkey. Referenced in stacks via `a` tags as `32267:<pubkey>:<identifier>`. | Core data source |
|
|
| **30267** | App curation set | Curated collections of apps (stacks). We publish privacy-approved stacks as kind 30267 events with `a` tags referencing each approved app. | Used for output |
|
|
| **30078** | App-specific data | Generic parameterized replaceable event. Already used by Zapstore for device state, bookmarks, etc. Candidate for storing per-app review results from LLM agents. | Planned for reviews |
|
|
| **3063** | Software Asset | APK metadata including hash, size, version code, platform. Used to track current version info for reviewed apps. | Used for version tracking |
|
|
| **30063** | Release artifact set | Group of artifacts for a software release. Links to kind 3063 assets and kind 32267 app events. | Reference |
|
|
| **1111** | Comment | Purplebase comment kind (NIP-22). Used by the Zapstore app for stack and app comments. References the root event via `A` tag. **Not** kind 1 (text note) — `relay.zapstore.dev` rejects kind 1. | Used for stack comments |
|
|
| **1986** | Relay reviews | Existing review kind, but scoped to Nostr relays rather than software apps. | Not applicable |
|
|
|
|
See [`references/registry-of-kinds/schema.yaml`](references/registry-of-kinds/schema.yaml) for the full kinds registry and [`references/nips/51.md`](references/nips/51.md) for list/set definitions.
|
|
|
|
## How Kinds Interact on Zapstore
|
|
|
|
The diagram below shows how the different Nostr event kinds relate to each other
|
|
in the zapstore ecosystem — from app discovery to installation to review.
|
|
|
|
```
|
|
ZAPSTORE APP (Android)
|
|
═════════════════════
|
|
│
|
|
┌───────────────┼───────────────┐
|
|
│ │ │
|
|
▼ ▼ ▼
|
|
┌──────────┐ ┌──────────┐ ┌──────────┐
|
|
│ DISCOVER │ │ INSTALL │ │ UPDATE │
|
|
│ (browse) │ │ (download│ │ (notify) │
|
|
└────┬─────┘ └────┬─────┘ └────┬─────┘
|
|
│ │ │
|
|
│ │ │
|
|
┌─────▼──────────────▼──────────────▼─────┐
|
|
│ NOSTR RELAY │
|
|
│ (relay.zapstore.dev) │
|
|
│ │
|
|
│ ┌────────────────────────────────────┐ │
|
|
│ │ KIND 30267 (App Stack/Curation) │ │
|
|
│ │ ───────────────────────────────── │ │
|
|
│ │ • d: privacy-approved-nostr-clients│ │
|
|
│ │ • h: <community pubkey> │ │
|
|
│ │ • f: android-arm64-v8a │ │
|
|
│ │ • a: 32267:<pubkey>:<app-id> ─────┼──┼──┐
|
|
│ │ • content: "" (must be empty!) │ │ │
|
|
│ └────────────────────────────────────┘ │ │
|
|
│ │ │
|
|
│ ┌────────────────────────────────────┐ │ │
|
|
│ │ KIND 32267 (Software Application)│◄─┼──┘
|
|
│ │ ───────────────────────────────── │ │
|
|
│ │ • d: com.vitorpamplona.amethyst │ │
|
|
│ │ • name: Amethyst │ │
|
|
│ │ • f: android-arm64-v8a │ │
|
|
│ │ • Published by APP DEVELOPER │ │
|
|
│ └───────────────┬────────────────────┘ │
|
|
│ │ │
|
|
│ │ (linked via) │
|
|
│ │ │
|
|
│ ┌───────────────▼────────────────────┐ │
|
|
│ │ KIND 30063 (Release Artifact Set)│ │
|
|
│ │ ───────────────────────────────── │ │
|
|
│ │ • a: 32267:<pubkey>:<app-id> │ │
|
|
│ │ • e: <kind 3063 event id> ────────┼──┼──┐
|
|
│ │ • Groups assets for a release │ │ │
|
|
│ └────────────────────────────────────┘ │ │
|
|
│ │ │
|
|
│ ┌────────────────────────────────────┐ │ │
|
|
│ │ KIND 3063 (Software Asset/APK) │◄─┼──┘
|
|
│ │ ───────────────────────────────── │ │
|
|
│ │ • i: com.vitorpamplona.amethyst │ │
|
|
│ │ • version: 1.13.1 │ │
|
|
│ │ • versionCode: 11301 │ │
|
|
│ │ • x: <SHA256 hash of APK> │ │
|
|
│ │ • url: https://cdn.zapstore.dev/..│ │
|
|
│ │ • size: 84849702 │ │
|
|
│ │ • f: android-arm64-v8a │ │
|
|
│ │ • THIS IS THE INSTALLABLE BINARY │ │
|
|
│ └────────────────────────────────────┘ │
|
|
│ │
|
|
│ ┌────────────────────────────────────┐ │
|
|
│ │ KIND 1111 (Comment/Review) │ │
|
|
│ │ ───────────────────────────────── │ │
|
|
│ │ • A: 30267:<pubkey>:<stack-d-tag> │ │
|
|
│ │ • K: 30267 │ │
|
|
│ │ • content: "LLM Privacy Review..."│ │
|
|
│ │ • Appears under stack in app │ │
|
|
│ └────────────────────────────────────┘ │
|
|
│ │
|
|
│ ┌────────────────────────────────────┐ │
|
|
│ │ KIND 30078 (App-Specific Data) │ │
|
|
│ │ ───────────────────────────────── │ │
|
|
│ │ • t: app-definition │ │
|
|
│ │ • d: app-<identifier> │ │
|
|
│ │ • Our app catalog metadata │ │
|
|
│ │ • (NOT used by zapstore app — │ │
|
|
│ │ │ only our frontend uses these) │ │
|
|
│ └────────────────────────────────────┘ │
|
|
│ │
|
|
└──────────────────────────────────────────┘
|
|
|
|
|
|
OUR PRIVACY REVIEW FLOW
|
|
═══════════════════════
|
|
|
|
┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐
|
|
│ GITEA │ │ AI AGENT│ │ KIND │ │ KIND │
|
|
│ MIRROR │────▶│ SCANS │────▶│ 3063 │────▶│ 1111 │
|
|
│ │ │ SOURCE │ │ (APK │ │ COMMENT│
|
|
│ Source │ │ CODE │ │ HASH) │ │ ON │
|
|
│ code │ │ │ │ │ │ STACK │
|
|
└─────────┘ └─────────┘ └─────────┘ └─────────┘
|
|
│
|
|
│ If approved,
|
|
│ app is included
|
|
│ in kind 30267
|
|
│ stack
|
|
▼
|
|
┌───────────┐
|
|
│ KIND 30267│
|
|
│ (Stack) │
|
|
│ a: 32267 │
|
|
└───────────┘
|
|
|
|
KEY INSIGHT: An app must have BOTH kind 32267 AND kind 3063
|
|
events on the relay to be installable on zapstore. Without
|
|
kind 3063, there is no APK to download, no version to display,
|
|
and nothing for AI agents to verify.
|
|
```
|
|
|
|
## Kind 30078 Event Data Model
|
|
|
|
We use **kind 30078** (App-specific data) events for two purposes: app definitions and app reviews. The frontend queries the Nostr relay directly for these events on page load, rather than reading a static file.
|
|
|
|
### App Definition Events
|
|
|
|
Each app in the catalog is defined by a kind 30078 event with `#t: app-definition`. These are published when an app is added via the PHP backend.
|
|
|
|
```jsonc
|
|
{
|
|
"kind": 30078,
|
|
"content": "{\"name\":\"Amethyst\",\"identifier\":\"com.vitorpamplona.amethyst\",\"repository\":\"https://github.com/vitorpamplona/amethyst\",\"description\":\"...\"}",
|
|
"tags": [
|
|
["d", "app-com.vitorpamplona.amethyst"],
|
|
["t", "app-definition"],
|
|
["t", "nostr-clients"], // ← category tag
|
|
["name", "Amethyst"],
|
|
["repository", "https://github.com/vitorpamplona/amethyst"],
|
|
["gitea", "com.vitorpamplona.amethyst"],
|
|
["url", "https://cdn.zapstore.dev/..."],
|
|
["f", "android-arm64-v8a"],
|
|
["published_at", "1729302793"]
|
|
]
|
|
}
|
|
```
|
|
|
|
### App Review Events
|
|
|
|
Each model's review of an app is stored as a kind 30078 event with `#t: app-review`. The review is pinned to a specific version via SHA256 hash.
|
|
|
|
```jsonc
|
|
{
|
|
"kind": 30078,
|
|
"content": "{\"verdict\":\"APPROVED\",\"level\":1,\"summary\":\"...\",\"findings\":{...}}",
|
|
"tags": [
|
|
// Unique ID: model + app identifier + version
|
|
["d", "review-deepseek/deepseek-v4-flash-com.vitorpamplona.amethyst-v1.2.3"],
|
|
|
|
// Reference to the app definition event
|
|
["a", "30078:<pubkey>:app-com.vitorpamplona.amethyst", "wss://relay.zapstore.dev"],
|
|
|
|
// The SPECIFIC asset/APK that was reviewed (kind 3063 event)
|
|
["e", "<kind_3063_event_id>", "wss://relay.zapstore.dev"],
|
|
|
|
// Version info for querying
|
|
["version", "1.2.3"],
|
|
["version_code", "1234"],
|
|
|
|
// SHA256 hash of the reviewed APK — cryptographic pin
|
|
["x", "a1b2c3d4e5f6..."],
|
|
|
|
// Review metadata
|
|
["model", "deepseek/deepseek-v4-flash"],
|
|
["model_name", "DeepSeek Flash 4"],
|
|
["verdict", "APPROVED"],
|
|
["level", "1"],
|
|
["p", "<agent_pubkey>"],
|
|
["t", "app-review"]
|
|
]
|
|
}
|
|
```
|
|
|
|
### Tag Reference
|
|
|
|
| Tag | Event Type | Purpose |
|
|
|-----|-----------|---------|
|
|
| `d` | Both | Unique identifier (`app-{identifier}` or `review-{model}-{identifier}-v{version}`) |
|
|
| `t` | Both | `app-definition` or `app-review` — also carries category for app definitions |
|
|
| `name` | App definition | Human-readable app name |
|
|
| `repository` | App definition | Source code URL |
|
|
| `gitea` | App definition | Gitea mirror identifier |
|
|
| `url` | App definition | APK download URL |
|
|
| `f` | App definition | Platform (e.g. `android-arm64-v8a`) |
|
|
| `a` → kind 30078 | App review | Reference to the app definition event |
|
|
| `e` → kind 3063 | App review | The specific APK asset that was reviewed |
|
|
| `x` | App review | SHA256 hash — cryptographic pin of the reviewed binary |
|
|
| `version` / `version_code` | App review | Version info |
|
|
| `model` | App review | LLM model string (e.g. `deepseek/deepseek-v4-flash`) |
|
|
| `verdict` | App review | Review verdict |
|
|
| `level` | App review | Privacy level (1-4) |
|
|
| `p` | App review | Agent pubkey who performed the review |
|
|
|
|
### Page Load Flow
|
|
|
|
```
|
|
Page opens
|
|
→ Queries relay for kind 30078, #t: app-definition
|
|
→ Groups apps by #t category tags → builds categories
|
|
→ Queries relay for kind 30078, #t: app-review
|
|
→ Matches reviews to apps via a tags
|
|
→ Renders categories + apps + per-model review status
|
|
```
|
|
|
|
### Version Pinning Flow
|
|
|
|
```
|
|
App v1.2.3 defined (kind 30078, #t: app-definition)
|
|
└── APK asset (kind 3063, SHA256: abc...)
|
|
└── Review (kind 30078, #t: app-review, pinned via x tag)
|
|
|
|
App v1.2.4 published (kind 3063, SHA256: def...)
|
|
└── NEW review needed — old review's SHA256 doesn't match
|
|
```
|
|
|
|
If a bad actor modifies the app in v1.2.4, the review for v1.2.3 (with its SHA256 `abc...`) does not carry over. A new review must be performed on the new binary.
|