# Architecture & Privacy Guide

## not.bot™ Verify: Architecture & Privacy Guide

## Contents

1. [Introduction](#1-introduction)
2. [Decentralized Identifiers](#2-decentralized-identifiers)
3. [System Components](#3-system-components)
4. [Architecture at a Glance](#4-architecture-at-a-glance)
5. [The Verification Flow](#5-the-verification-flow)
6. [Signature Server Identity](#6-signature-server-identity)
7. [The honest.bot Credential](#7-the-honestbot-credential)
8. [Claims and Verification Levels](#8-claims-and-verification-levels)
9. [Data Flows and Privacy](#9-data-flows-and-privacy)
10. [Operational Independence](#10-operational-independence)
11. [Support](#11-support)

---

## 1. Introduction

not.bot Verify is the server-side infrastructure that connects your application to the not.bot app on users' phones. It runs entirely inside your infrastructure. No traffic from the user verification flow passes through Julia Social.

This document covers the architecture, trust model, and privacy properties of not.bot Verify. For infrastructure prerequisites, see the **Pre-flight Checklist**. For the step-by-step install, see the **Deployment Checklist**. For runtime configuration, scaling, and failure modes, see the **Operations & Reference Guide**.

---

## 2. Decentralized Identifiers

not.bot Verify uses Chia DIDs (Decentralized Identifiers). A DID is an identifier on the Chia blockchain. Whoever holds the corresponding private key controls the DID and can sign messages with it. Verifiers check those signatures against the on-chain record without trusting any central authority.

You will work with two kinds of DIDs:

- **Business DID.** You get one. It represents your company. After a DNS check, Julia Social issues a credential that ties the DID to your domain.
- **Signature DIDs.** One per running signature server. The admin service maintains the pool, minting new DIDs as servers need them. Each signature server picks up an available DID at startup.

Every DID's private key lives in your OpenBao instance. None of these keys leave your infrastructure.

---

## 3. System Components

There are four components to not.bot Verify, plus two prerequisites.

**Core components:**

- **Admin service.** One instance required. It hosts the web interface where you create your business DID, register Chia nodes, and mint signature DIDs. It also reports your monthly active user count to Julia Social once an hour for billing.
- **Signature server.** Run as many as you need, behind a load balancer. Each one handles user-facing verification requests. Run at least two for redundancy.
- **Chia node.** Run at least one; three is recommended. Each node maintains a current view of the Chia blockchain, used by the admin service and the signature servers. Chia nodes do not mine cryptocurrency by default.
- **OpenBao with the Chiakeys plugin.** OpenBao holds every DID's private key. OpenBao does not support Chia's BLS12-381 signature scheme out of the box, so the deployment includes a plugin that adds support.

**Prerequisites:**

- **PostgreSQL** for application data.
- **Keycloak** for admin operator login.

---

## 4. Architecture at a Glance

not.bot Verify runs as a set of components inside your infrastructure. Your frontend and the user's phone reach one public-facing service: your application backend, with the SDK adapter mounted, behind your existing edge. The adapter proxies the verification exchange to your signature servers over an internal path. Nothing else in the deployment is exposed to the public internet. Julia Social sits outside your perimeter, reached only for administrative operations and two hourly aggregate counts.

```mermaid
---
config:
  layout: elk
---
flowchart TB
  subgraph internet["Public internet"]
    users["End users' browsers / your frontend"]
    app["not.bot app (user's phone)"]
  end

  subgraph perimeter["Your security perimeter"]
    edge["Your existing edge: WAF / reverse proxy<br/>(not part of Verify)"]
    backend["Application backend + SDK adapter<br/>(your only public-facing service; runs outside the cluster)"]
    operators["Operators (internal network)"]
    pg["PostgreSQL (you provide)"]
    kc["Keycloak (you provide)"]

    subgraph cluster["Your Kubernetes cluster (private, no public ingress)"]
      adminlb["Admin LB (internal)"]
      admin["Admin service"]
      siglb["Signature-server LB (internal)<br/>→ nginx ingress"]
      sig["Signature servers (load balanced)"]
      bao["OpenBao + Chiakeys (DID keys)"]
      chia["Chia nodes"]
    end
  end

  subgraph external["External to your perimeter"]
    js["Julia Social"]
    chianet["Public Chia network"]
  end

  users --> edge
  app --> edge
  edge --> backend
  backend -->|"proxies /signature/*"| siglb
  siglb --> sig
  operators --> adminlb
  adminlb --> admin
  sig <-->|"scoped token → DID keys"| bao
  admin --> bao
  sig <-->|"mTLS"| chia
  admin <-->|"mTLS"| chia
  sig --> pg
  admin --> pg
  admin <-->|"OIDC"| kc
  chia <-->|"peer sync"| chianet
  admin -->|"hourly MAU count (billing)"| js
  sig -->|"honest.bot credential at startup + hourly count"| js
```

---

## 5. The Verification Flow

### From the user's perspective

1. Your application backend uses the not.bot Verify SDK to start a signature request. The SDK is a server-side library that runs in your backend. You configure it with the address of your signature server load balancer, the claims you require, and an `on_success` callback the SDK invokes when a verification completes.
2. The load balancer routes the call to one of your signature servers. That server returns a request ID.
3. The SDK wraps the request ID into a universal link URL and generates a QR code for it. Your backend delivers the URL or the QR to whatever frontend your user is looking at.
4. The user opens the link. On a phone, the universal link opens the not.bot app. On a desktop browser, the user scans the QR with their phone, which opens the not.bot app. If the app is not installed, the link sends the user to the app store.
5. The not.bot app communicates, via your SDK adapter, with the same signature server that issued the request ID. The user approves or declines inside the app. The signature server signs the response using its signature DID's key.
6. The SDK delivers the result to your backend. On success, it invokes your `on_success` callback with the verified response and the signed data. On failure, it invokes `on_failure` with the reason. Your frontend learns the outcome by polling `GET /signature/status`.

At no point in this flow does any network traffic hit Julia Social. The user's phone talks only to your SDK adapter, which proxies the exchange to your signature servers. Those servers sign through your OpenBao instance and read chain state from your Chia nodes. Julia Social sees none of it.

### From your backend's perspective

1. Your frontend calls `GET /signature/notbot` on the SDK adapter. The adapter calls `POST /signature/start` on one of your signature servers through the load balancer. The signature server returns a request ID. The adapter maps the request ID to the user's session and returns the ID to your frontend.
2. Your frontend builds a universal link URL from the request ID and presents it to the user as a tappable link (on mobile) or a QR code (on desktop). If the user does not have the not.bot app installed, the link redirects them to the appropriate app store.
3. The not.bot app opens and connects to your SDK adapter's endpoints. The cryptographic exchange happens across `POST /signature/notbot/{request_id}`, `POST /signature/verify/{request_id}`, and the WebSocket routes. The adapter proxies each call to your signature servers. Your backend code does not participate in this exchange.
4. When verification succeeds, the adapter calls your `on_success` callback with the verified response and the original session. The response contains the user's alias DID, any claims you requested, the site pass (if enabled), a timestamp, and the cryptographic presentation.
5. Your frontend can poll `GET /signature/status` or use its own signaling mechanism to detect that verification completed, then update the UI.

### Network path

During the verification exchange, traffic flows between the user's phone and your signature servers through your SDK adapter. No packets reach Julia Social at any point. The user's phone never contacts Julia Social. Your signature servers never contact Julia Social. The entire exchange stays within your infrastructure and the user's device.

### Session behavior

The adapter maps each request ID to the session that initiated it. When verification completes, the adapter looks up the originating session and passes it to your `on_success` callback, even if the verification response arrives on a different HTTP connection (which it will, since the not.bot app is a separate client). This is how the verified result ends up in the correct user's session despite the verification being completed by a different device.

The request-to-session mapping lives in memory in the adapter process. If you run multiple instances of your backend behind a load balancer, you will need sticky sessions or a shared session store so that the verification response routes back to the adapter instance that holds the mapping.

---

## 6. Signature Server Identity

### DID assignment at startup

When a signature server starts up, it contacts the admin service and asks for an assignment. The admin service hands back an unused signature DID and a scoped OpenBao token for that DID's key. The business DID then delegates your domain credential to the signature server's DID, which is what lets the signature server present proof that it represents your domain.

### Heartbeat and release

While running, each signature server sends a heartbeat to the admin service. On clean shutdown, the signature server releases its DID back to the pool. If heartbeats stop, the admin service releases the DID after a timeout. It is not possible for two running processes to hold the same signature DID at once.

### Scaling

Your scale ceiling is your replica count and HPA configuration. The signature DID pool auto-sizes: the admin service maintains at least two unallocated DIDs at all times and mints a replacement whenever a signature server consumes one. Operators do not size the pool.

---

## 7. The honest.bot Credential

Each signature server acquires an honest.bot™ credential from Julia Social at startup, bound to the server's signature DID. Julia Social enforces that only one process in the world holds a given honest.bot credential at a time. This is defense in depth against a compromise of a signature server's signing access.

OpenBao is your first line of defense. A signature DID's private key lives in OpenBao and never enters the signature server process; the server signs by calling OpenBao with a scoped token, so a compromised server holds no raw key to copy. The honest.bot credential covers the residual risk. An attacker who gets far enough to use a server's OpenBao access, its signature DID, and your domain credential could stand up a server that presents as your site, and users would extend it the trust they extend you. The single-process enforcement closes that gap. The moment the attacker's instance comes up, that enforcement revokes the legitimate server's honest.bot credential. A credential that stops validating means your signing identity is in use somewhere you did not put it, so the revocation is your signal that a compromise has occurred.

Today that detection is the protection: the revoked credential tells you the access has leaked. An attacker who reached your OpenBao instance holds your business DID key as well, so the response resets the business DID that sits above your signature DIDs and re-establishes fresh credentials down the chain, rendering the attacker's instance useless. This response is planned, not yet implemented.

The not.bot app verifies the honest.bot credential on every interaction without contacting Julia Social, so this protection adds no round-trip at verification time. Acquiring the credential is the one reason a signature server contacts Julia Social at startup. After that, a running server serves verifications with no further Julia Social involvement (see Section 10).

---

## 8. Claims and Verification Levels

The SDK defines claim properties for bot detection (`Notbot0`), age verification (`AgeOver18`, `AgeOver21`, and a full range of age brackets), PII fields (`FirstName`, `FamilyName`, `Nationality`), and site passes (`SitePass`). The full list of available claims is in `shared/claim_properties.txt` in the SDK repository.

**Site passes.** When `require_site_pass` is enabled, the verification generates a site pass: a per-user-per-site identifier that lets you recognize returning users without learning their real identity.

### Verification levels

A claim's verification level tells you how strongly the person behind it was identity-proofed at enrollment.

- **Notbot0** (available now). The user scanned the NFC chip in a government passport; the app read the chip and a certified identity-verification partner validated its signatures against the ICAO Public Key Directory. This is the level behind every not.bot verification today.
- **Notbot1** (planned). The user was proofed in person at a third-party enrollment partner following NIST SP 800-63A IAL2 procedures.
- **Notbot2** (planned). The user was proofed at a first-party facility contracting directly with Julia Social.

Every Verify deployment operates at Notbot0 today. Notbot1 and Notbot2 are on the roadmap.

---

## 9. Data Flows and Privacy

### What leaves your infrastructure

Two outbound data flows reach Julia Social, both on a scheduled cadence:

- The admin service sends your monthly active user count once per hour for billing. The count is a single integer. No user identifiers, IP addresses, request details, or session data accompany it.
- Each signature server sends the count of successful signature requests once per hour for diagnostics on Julia Social's side. Again, a single integer. No request content, user data, or signature payloads are included.

These two integers are the sum total of what Julia Social receives from your deployment.

### What stays inside

Everything else. User verification requests, the cryptographic exchange between the not.bot app and your signature servers, the signed responses, the verified results the SDK passes to your backend, DID keys, database records, session state, Chia blockchain queries. All of it stays within your infrastructure and the user's device. No packet from the user verification flow reaches a Julia Social server.

### Julia Social is not a supply-chain data risk

No customer data, user data, request content, or signature payloads flow to Julia Social at any point, whether during setup, normal operation, or failure. The two hourly counts described above are aggregate integers with no identifying information attached. There is no telemetry, no analytics pipeline, no log shipping, and no crash reporting directed at Julia Social.

Your DID keys live in your OpenBao instance. Your user records live in your PostgreSQL instance. Your Chia nodes sync from the public blockchain, not from Julia Social. The SDK runs in your backend. The not.bot app talks to your signature servers. Julia Social has no access to any of it.

---

## 10. Operational Independence

### Julia Social is not a runtime dependency

A running not.bot Verify deployment does not depend on Julia Social for day-to-day operation. Your signature servers contact Julia Social at startup to acquire the honest.bot credential (see Section 7), and then never again except for the hourly diagnostic count. Once a signature server has its credential, it serves verification requests with no further Julia Social involvement.

If Julia Social becomes unreachable:

- **Running signature servers continue to operate.** Existing honest.bot credentials remain valid. Users continue to verify. Your application backend continues to receive results.
- **New signature servers cannot start** because they cannot acquire the honest.bot credential. If you need to scale up or replace a crashed server during a Julia Social outage, the replacement blocks at the credential acquisition step until connectivity returns.
- **The admin service's hourly MAU report queues and retries.** No data is lost; the counts catch up once the connection resumes.
- **The admin interface remains usable.** You can still log in, view status, and manage blockchain nodes. The only operations that require Julia Social are the initial deployment-config upload, domain credential issuance and signature DID minting, none of which are needed for steady-state processing.

In short: Julia Social connectivity matters during initial setup and when starting new signature servers. For a running deployment, it does not matter at all.

---

## 11. Support

Email [support@julia.social](mailto:support@julia.social) for deployment issues, configuration questions, or credential reissuance.
