zc2 0.0.11

P2P compute broker with credit-based billing, WAL, and broker mesh support
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
# zc2 Usage Guide

This guide covers the complete usage of `zc` (built from `zc2`), the Zakuro AI command-line tool. `zc` is a P2P compute broker that routes compute requests to workers across a distributed network, with credit-based billing via PostgreSQL stored procedures, P2P broker-to-broker communication, write-ahead logging, automatic worker discovery, and real-time monitoring.

---

## Table of Contents

- [Prerequisites]#prerequisites
- [Installation]#installation
- [Quick Start]#quick-start
- [Broker]#broker
  - [Starting the Broker]#starting-the-broker
  - [Foreground Mode]#foreground-mode
  - [TUI Dashboard]#tui-dashboard
  - [Daemon Mode]#daemon-mode
- [Worker Discovery]#worker-discovery
  - [Tailscale Mode]#tailscale-mode
  - [Local Mode]#local-mode
  - [Manual Registration]#manual-registration
- [API Reference]#api-reference
  - [Health & Status]#health--status
  - [Worker Management]#worker-management
  - [Compute Execution]#compute-execution
  - [Credit Management]#credit-management
  - [Ledger Administration]#ledger-administration
  - [P2P Broker Endpoints]#p2p-broker-endpoints
- [Routing Strategies]#routing-strategies
- [Benchmarking]#benchmarking
  - [Basic Benchmark]#basic-benchmark
  - [Strategy Comparison]#strategy-comparison
  - [Benchmark Options]#benchmark-options
- [Remote Monitoring]#remote-monitoring
- [System Diagnostics]#system-diagnostics
- [Credit System]#credit-system
  - [PostgreSQL Ledger]#postgresql-ledger
  - [Reserve-Commit Model]#reserve-commit-model
  - [API Keys]#api-keys
  - [P2P Broker-to-Broker Communication]#p2p-broker-to-broker-communication
  - [Write-Ahead Log (WAL)]#write-ahead-log-wal
- [Cluster Commands]#cluster-commands
- [Environment Variables]#environment-variables
- [Configuration Reference]#configuration-reference

---

## Prerequisites

- **Rust** toolchain (1.75+) for building from source
- **Docker** and **Docker Compose** (for containerized deployments)
- **PostgreSQL** (optional, for centralized credit ledger with restricted broker role)
- **Tailscale** (optional, for P2P network discovery)
- `ZAKURO_API_KEY` environment variable must be set

## Installation

### From Source

```bash
git clone https://github.com/zakuro-ai/zc.git
cd zc
task build:bin
```

This compiles the release binary and installs it to `/usr/local/bin/zc`.

### With Docker

```bash
task build
```

This builds the binary, then builds and starts the Docker services via `docker compose`.

### Verify Installation

```bash
zc --version
```

---

## Quick Start

1. Set your authentication token:

```bash
export ZAKURO_API_KEY="your-token-here"
```

2. Start the broker with the interactive TUI:

```bash
zc -t broker
```

3. The broker will automatically discover workers on your network and begin accepting compute requests on port `9000`.

---

## Broker

The broker is the core component of `zc`. It accepts compute requests from clients, selects an optimal worker, forwards the request, and handles billing.

### Starting the Broker

```bash
# Default: foreground mode on 0.0.0.0:9000
zc broker

# Custom port
zc broker 8080

# Custom host and port
zc broker 0.0.0.0 9000
```

### Foreground Mode

```bash
zc broker
```

Foreground mode displays a startup banner listing all API endpoints and then prints a live transaction log to stdout. Each request is shown with its status, user, cost, and latency. Press `Ctrl+C` to stop.

Example output:

```
  ╔═══════════════════════════════════════════╗
  ║          Zakuro Compute Broker            ║
  ╚═══════════════════════════════════════════╝

  Listening:  http://0.0.0.0:9000

  API Endpoints:
  ─────────────────────────────────────────────
    GET  /health              Health check
    GET  /workers             List workers
    POST /workers             Register worker
    POST /workers/heartbeat   Worker heartbeat
    DEL  /workers/:id         Unregister worker
    POST /execute             Execute request
    POST /price               Estimate price
    GET  /credits/:user       Get balance
    POST /credits/:user/add   Add credits
  ─────────────────────────────────────────────

  Press Ctrl+C to stop. Use --tui for interactive TUI.
```

### TUI Dashboard

```bash
zc -t broker
zc --tui broker
zc -t broker 8080     # TUI on custom port
```

The TUI dashboard provides a real-time interactive view, similar to `htop` for compute. It has four panels:

| Panel | Content |
|-------|---------|
| **Header** | Operating mode (P2P/LOCAL), ledger status (PostgreSQL/Local), uptime, listen address |
| **Transactions** | Live scrollable log showing user, action, cost, latency, and status icons |
| **Workers** | Connected workers with name, status, resource availability, and latency |
| **Metrics** | Total requests, requests/second sparkline, latency percentiles (p50/p90/p99), total credits spent |

**Keyboard shortcuts:**

| Key | Action |
|-----|--------|
| `Tab` | Switch between panels |
| `j` / `k` or `Up` / `Down` | Scroll transactions |
| `g` | Jump to top |
| `G` | Jump to bottom |
| `?` | Toggle help overlay |
| `q` or `Esc` | Quit |

### Daemon Mode

```bash
zc -d broker
zc --daemon broker
zc -d broker 8080     # Daemon on custom port
```

Daemon mode runs the broker in the background with minimal output. Useful for production deployments or running the broker as a service.

---

## Worker Discovery

The broker automatically discovers compute workers based on the available network interfaces.

### Tailscale Mode

**Trigger:** A `tailscale0`, `wg0`, or `ts*` network interface is detected with an IP in the `100.x.x.x` or `10.x.x.x` range.

**Behavior:**

- Scans the Tailscale subnet (e.g., `10.13.13.2` through `10.13.13.254`) on port `3960` (configurable via `ZAKURO_WORKER_PORT`)
- Also checks extra ports: `3961`, `3962`
- Probes each address for `/health` and `/info` endpoints
- Only registers services that return a valid `worker_type` in their `/info` response
- Refreshes heartbeats for existing workers every 15 seconds (configurable via `ZAKURO_SCAN_INTERVAL`)
- **Credits are enforced** in this mode — all executions are billed

### Local Mode

**Trigger:** No Tailscale interface is detected.

**Behavior:**

- Scans `127.0.0.1` on ports `3960`, `3961`, `3962` (or `ZAKURO_SCAN_RANGE` if set)
- Same probing logic as Tailscale mode
- **Execution is free** — no credits are charged
- Useful for development and testing

### Manual Registration

Workers can always register themselves via the REST API regardless of discovery mode. When `ZAKURO_WORKER_KEY` is configured, include the key in the `X-Worker-Key` header:

```bash
curl -X POST http://localhost:9000/workers \
  -H "Content-Type: application/json" \
  -H "X-Worker-Key: $ZAKURO_WORKER_KEY" \
  -d '{
    "name": "my-worker",
    "uri": "http://10.13.13.5:3960",
    "worker_type": "zakuro",
    "resources": {
      "cpus_total": 8.0,
      "cpus_available": 8.0,
      "memory_total": 17179869184,
      "memory_available": 17179869184,
      "gpus_total": 1,
      "gpus_available": 1
    },
    "pricing": {
      "cpu_price": 0.001,
      "memory_price": 0.0001,
      "gpu_price": 0.01,
      "min_charge": 0.001
    },
    "tags": ["gpu", "cuda"]
  }'
```

Workers must send periodic heartbeats to remain healthy:

```bash
curl -X POST http://localhost:9000/workers/heartbeat \
  -H "Content-Type: application/json" \
  -H "X-Worker-Key: $ZAKURO_WORKER_KEY" \
  -d '{"worker_id": "uuid-of-worker"}'
```

Workers that miss heartbeats for longer than `worker_timeout` (default: 30 seconds) are marked unhealthy and eventually removed.

---

## API Reference

The broker exposes an HTTP API on its configured host and port (default: `0.0.0.0:9000`).

### Health & Status

#### `GET /health`

Returns `200 OK` if the broker is running.

#### `GET /stats`

Returns broker statistics in JSON format. Used by the remote TUI to render the dashboard.

**Authentication:** Requires `Authorization: Bearer <key>`. Non-admin callers only see their own transactions; admin callers see all.

**Response fields:** transactions, workers, metrics (request count, RPS, latency percentiles, credits spent).

### Worker Management

#### `GET /workers`

List all registered workers and their current status.

**Response:**

```json
{
  "total": 2,
  "healthy": 2,
  "workers": [
    {
      "id": "uuid",
      "name": "worker-1",
      "uri": "http://10.13.13.5:3960",
      "status": "Healthy",
      "resources": { "cpus_total": 8, "cpus_available": 6, ... },
      "pricing": { "cpu_price": 0.001, ... },
      "avg_latency_ms": 12.5
    }
  ]
}
```

#### `POST /workers`

Register a new worker. See [Manual Registration](#manual-registration) for the request body schema.

**Authentication:** Requires `X-Worker-Key` header matching `ZAKURO_WORKER_KEY` env var (when configured). Without a configured key, registration is open (local dev).

#### `POST /workers/heartbeat`

Refresh a worker's heartbeat to keep it marked as healthy.

**Authentication:** Same as `POST /workers` — requires `X-Worker-Key` when configured.

#### `DELETE /workers/:id`

Unregister a worker by its UUID.

**Authentication:** Same as `POST /workers` — requires `X-Worker-Key` when configured.

### Compute Execution

#### `POST /execute`

The primary endpoint. Sends a compute request that the broker routes to the best available worker.

**Request headers:**

| Header | Required | Description |
|--------|----------|-------------|
| `Authorization` | Yes (remote mode) | `Bearer <api_key>` — resolves user ID for billing |
| `X-Zakuro-User` | No | Fallback user ID in local mode only (defaults to `anonymous`) |
| `X-Zakuro-Requirements` | No | JSON resource requirements (see below) |
| `Content-Type` | No | `application/octet-stream` for binary payloads |

**Requirements JSON:**

```json
{
  "cpus": 1.0,
  "memory_bytes": 1073741824,
  "gpus": 0,
  "estimated_duration_secs": 1.0,
  "strategy": "best_price",
  "worker_type": "zakuro",
  "tags": ["gpu"]
}
```

All fields are optional and have sensible defaults (1 CPU, 1 GiB memory, 1 second duration, `best_price` strategy).

**Response headers:**

| Header | Description |
|--------|-------------|
| `X-Zakuro-Request-Id` | Unique request ID |
| `X-Zakuro-Cost` | Actual cost in credits |
| `X-Zakuro-Credits-Remaining` | User's balance after the transaction |
| `X-Zakuro-Duration-Ms` | Total request duration in milliseconds |

#### `POST /price`

Estimate the cost of a request without executing it.

**Request body:** Same `requirements` JSON as `/execute`.

**Response:**

```json
{
  "min_cost": 0.001,
  "max_cost": 0.005,
  "workers_available": 3
}
```

### Credit Management

#### `GET /credits/:user`

Get the credit balance for a user.

**Authentication:** Requires `Authorization: Bearer <key>`. Non-admin callers can only view their own balance (403 otherwise).

```bash
curl http://localhost:9000/credits/my-user \
  -H "Authorization: Bearer $API_KEY"
```

#### `POST /credits/:user/add`

Add credits to a user's account. Requires an API key.

```bash
curl -X POST http://localhost:9000/credits/my-user/add \
  -H "Content-Type: application/json" \
  -H "X-Api-Key: your-api-key" \
  -d '{"amount": 100.0, "description": "Initial deposit"}'
```

### Ledger Administration

#### `GET /ledger/status`

Check the PostgreSQL ledger connection status. The database URL password is redacted in the response.

**Authentication:** Requires `Authorization: Bearer <master_key>` (admin only).

```bash
curl http://localhost:9000/ledger/status \
  -H "Authorization: Bearer $ZAKURO_MASTER_KEY"
```

### P2P Broker Endpoints

These endpoints are **only available when `ZAKURO_P2P=true`** and are used for broker-to-broker communication.

#### `GET /peer/health`

Check if the peer broker is healthy and reachable.

**Authentication:** Requires `X-Peer-Key` header matching `ZAKURO_PEER_KEY`.

```bash
curl http://peer-broker:9000/peer/health \
  -H "X-Peer-Key: $ZAKURO_PEER_KEY"
```

#### `POST /peer/reserve`

Reserve credits for a user owned by this broker (called by non-authoritative brokers).

**Authentication:** Requires `X-Peer-Key` header.

**Request body:**
```json
{
  "user_id": "9000000001",
  "amount": 0.005,
  "reservation_id": "res-uuid-123"
}
```

**Response:**
```json
{
  "success": true,
  "balance_remaining": 99.995
}
```

#### `POST /peer/commit`

Commit a transaction after worker execution (finalizes the reservation).

**Authentication:** Requires `X-Peer-Key` header.

**Request body:**
```json
{
  "reservation_id": "res-uuid-123",
  "actual_cost": 0.003,
  "worker_id": "worker-uuid",
  "duration_ms": 250.0
}
```

#### `POST /peer/cancel`

Cancel a reservation and refund the full amount.

**Authentication:** Requires `X-Peer-Key` header.

**Request body:**
```json
{
  "reservation_id": "res-uuid-123"
}
```

#### `GET /peer/balance`

Get the balance for a user owned by this broker.

**Authentication:** Requires `X-Peer-Key` header.

**Query parameters:** `user_id=9000000001`

```bash
curl "http://peer-broker:9000/peer/balance?user_id=9000000001" \
  -H "X-Peer-Key: $ZAKURO_PEER_KEY"
```

**Response:**
```json
{
  "user_id": "9000000001",
  "balance": 99.995
}
```

#### `POST /peer/earn`

Credit earnings to the broker owner's account (used when a remote broker pays for execution on a local worker).

**Authentication:** Requires `X-Peer-Key` header.

**Request body:**
```json
{
  "request_id": "req-uuid-123",
  "amount": 0.003
}
```

#### `GET /peer/workers`

List workers that are local to this broker (at `127.0.0.1` or `localhost`). Used by peer brokers to discover available workers across the mesh.

**Authentication:** Requires `X-Peer-Key` header.

---

## Routing Strategies

When sending a compute request via `/execute`, you can specify a routing strategy in the `X-Zakuro-Requirements` header. The strategy determines how the broker selects which worker handles the request.

| Strategy | Aliases | Description |
|----------|---------|-------------|
| `best_price` | `price`, `cheap`, `cheapest` | Selects the worker with the lowest estimated cost. **(Default)** |
| `best_latency` | `latency`, `fast`, `fastest` | Selects the worker with the lowest average response time. |
| `best_availability` | `availability`, `available` | Selects the worker with the most available resources and fewest active requests. |
| `round_robin` | `robin`, `rr` | Distributes requests evenly across all healthy workers in order. |
| `random` | `rand` | Selects a random healthy worker. |
| `weighted_capacity` | `weighted`, `capacity` | Randomly selects a worker weighted by available CPU and memory. |

### Worker Scoring Algorithm

For the default `best_price` strategy, the overall worker score is computed as:

```
score = price_score * 0.5 + load_score * 0.3 + latency_score * 0.2
```

Where:

- **Price score (50%):** `cpu_price * 0.6 + memory_price * 0.3 + gpu_price * 0.1`
- **Load score (30%):** `(1 - cpus_available/total + 1 - memory_available/total) / 2`
- **Latency score (20%):** `avg_latency_ms / 100`

Lower scores indicate better workers.

---

## Benchmarking

`zc bench` measures the broker's throughput and latency under load.

### Basic Benchmark

```bash
# Default: 10 concurrent workers, 1000 requests, best_price strategy
zc bench

# Benchmark with a specific strategy
zc bench -s best_latency

# High-load benchmark
zc bench -c 100 -n 10000

# Benchmark a remote broker
zc bench http://broker.tailnet:9000
```

The benchmark:

1. Verifies broker connectivity via `/health`
2. Checks available workers via `/workers`
3. Sends requests concurrently to `/execute`
4. Displays a progress spinner during execution
5. Prints a detailed report with throughput and latency percentiles

**Example output:**

```
  ═══════════════════════════════════════════════
  ◆  Benchmark Results
  ═══════════════════════════════════════════════

  Summary
  ────────────────────────────────
    Total Requests:    1000
    Successful:        987 (98.7%)
    Failed:            13 (1.3%)
    Duration:          4.21s
    Throughput:        237.53 req/s

  Latency (ms)
  ────────────────────────────────
    Min:               0.84
    Avg:               3.21
    Max:               42.10
    p50:               2.15
    p75:               3.80
    p90:               6.20
    p95:               9.45
    p99:               28.30

  Latency Histogram
  ────────────────────────────────
      ≤1ms [██████                        ]    120 ( 12.2%)
      ≤5ms [██████████████████████████████]    650 ( 65.9%)
     ≤10ms [████████████                  ]    150 ( 15.2%)
     ≤25ms [██                            ]     40 (  4.1%)
     ≤50ms [█                             ]     27 (  2.7%)
```

### Strategy Comparison

Compare all six routing strategies side by side:

```bash
zc bench --compare
zc bench --compare -c 50 -n 5000
```

This runs the benchmark separately for each strategy and outputs a comparison table highlighting the best throughput and lowest latency.

### Benchmark Options

| Option | Short | Default | Description |
|--------|-------|---------|-------------|
| `--concurrency` | `-c` | `10` | Number of concurrent workers |
| `--requests` | `-n` | `1000` | Total requests to send |
| `--url` | `-u` | `http://127.0.0.1:9000` | Broker URL |
| `--user` | | `bench-user` | User ID for requests |
| `--strategy` | `-s` | `best_price` | Routing strategy to test |
| `--compare` | | | Compare all strategies |
| `--list-strategies` | | | List available strategies |
| `--help` | `-h` | | Show help |

---

## Remote Monitoring

Attach to a running broker from another machine to view its TUI dashboard:

```bash
# Attach to a local broker
zc attach localhost:9000

# Attach to a remote broker on the Tailscale network
zc attach http://broker.tailnet:9000
```

The remote TUI connects to the broker's `/stats` endpoint and refreshes every 500ms. It provides the same interface as the local TUI, plus:

| Key | Action |
|-----|--------|
| `r` | Force refresh |

---

## System Diagnostics

View system information, network status, and detected compute clusters:

```bash
zc info
```

**Output sections:**

| Section | Content |
|---------|---------|
| **Network** | Hostname, operating mode (Tailscale P2P / Local), local IP, Tailscale IP |
| **Compute Clusters** | Availability of Ray, Dask, and Spark clusters with version and node count |
| **Environment** | Status of `ZAKURO_API_KEY`, `ZAKURO_MASTER_KEY`, `DATABASE_URL`, `TAILSCALE_AUTHKEY` (secrets are masked) |
| **Broker Ports** | Connectivity status for Broker API (9000), Worker (3960), Ray (10001), Dask (8786), Spark (7077), PostgreSQL (5432) |

---

## Credit System

The broker uses a credit-based billing system to charge users for compute requests.

### PostgreSQL Ledger

The broker uses PostgreSQL as the single source of truth for credit balances. For security, it connects as a restricted `zakuro_broker` role that has **zero direct table access** — all operations go through **11 SECURITY DEFINER stored procedures**:

| Stored Procedure | Purpose |
|-----------------|---------|
| `broker_auth_api_key(key_hash)` | Authenticate API key → returns zakuro_user_id |
| `broker_resolve_user_id(zakuro_uid)` | Resolve PG users.id from zakuro_user_id |
| `broker_get_balance(zakuro_uid)` | Get credit balance |
| `broker_reserve_credits(amount, zakuro_uid)` | Atomic credit reservation |
| `broker_refund_credits(amount, zakuro_uid)` | Refund credits (commit/cancel) |
| `broker_insert_transaction(...)` | Insert transaction record (triggers PG NOTIFY) |
| Plus 5 more for transaction state management, balance snapshots, and user lookup |

**Connection pooling:** r2d2_postgres pool (max 8 connections) for high concurrency.

**PG triggers:** `trg_notify_transaction` (INSERT) + `trg_notify_credit_update` (UPDATE credits_balance) broadcast changes to dashboard via LISTEN/NOTIFY.

This ensures the broker cannot access `email`, `hashed_password`, `role`, or any other user columns, and cannot query balances across users.

### Reserve-Commit Model

Each compute request follows a reserve-commit flow:

1. **Reserve:** The estimated cost is atomically deducted from the user's balance and stored as a reservation (5-minute TTL).
2. **Execute:** The request is forwarded to the selected worker.
3. **Commit:** On success, the reservation is finalized with the actual cost, and any difference is refunded. On failure, the full amount is refunded (cancel).

This ensures users are never overcharged and credits are not lost on failures.

### API Keys

- **Master Key:** Set via `ZAKURO_MASTER_KEY`. Has full permissions (admin operations like adding credits, viewing ledger status).
- **User API Keys:** Follow the `zk_{user_id}_{hex}` format. The broker extracts the `user_id` from the key format for billing. In PG mode, keys are also verified via the `broker_auth_api_key` stored procedure.
- **Storage:** In PG mode, API keys are stored as SHA256 hashes in the `api_keys` table (accessed only via stored procedures). In API mode, key resolution uses the key format or the dashboard API.

### Local Fallback

When PostgreSQL is unavailable (`allow_local_fallback: true` by default), the broker falls back to in-memory credit management. Balances are lost on restart.

### Local Mode (Free Execution)

When running in local discovery mode (no Tailscale), all executions are free and no credits are charged.

### Per-Worker Local Detection (P2P Mesh)

In a multi-node P2P mesh, each broker detects its own Tailscale IP at startup and uses it to distinguish local vs remote workers:

- **Local workers** (at `127.0.0.1` or matching the broker's own Tailscale IP) execute for **free** — no credit reservation or billing
- **Remote workers** (on other Tailscale IPs) are **charged** via the normal reserve-commit flow

This enables a mesh topology where each node acts as both broker and worker. When `best_price` routing is used, the local worker is always preferred (cost=0). Other strategies like `round_robin` distribute across local and remote workers.

### P2P Broker-to-Broker Communication

Enable distributed broker mesh with `ZAKURO_P2P=true`. This mode adds broker-to-broker communication for credit operations, eliminating PostgreSQL calls on the hot path for authoritative users.

**Architecture:**

Each user is assigned an **authoritative broker** using a consistent hash (FNV-1a):
```
authority_index = hash(zakuro_user_id) % num_brokers
```

**Authority modes:**

| Mode | Trigger | Hot Path | Behavior |
|------|---------|----------|----------|
| **Authority::Local** | User owned by this broker | **Zero PG** | DashMap in-memory credit ops (reserve, commit, balance) |
| **Authority::Peer(url)** | User owned by remote broker | **~1ms HTTP** | Forward credit ops to authoritative broker via `/peer/*` endpoints |
| **Authority::Standalone** | Peer broker unreachable | **API / PG fallback** | Ledger API or PostgreSQL path for resilience |

**Transaction flow (P2P authoritative):**
1. Auth (DashMap cache) → 2. Balance (DashMap) → 3. Reserve (DashMap atomic deduct)
4. WAL: RESERVED → 5. Forward to worker → 6. WAL: EXECUTED
7. Commit (DashMap refund diff) → 8. Queue in flush buffer → 9. WAL: COMMITTED → 10. Return

**Transaction flow (P2P non-authoritative):**
1. Auth (DashMap cache) → 2. HTTP `/peer/reserve` to authoritative (~1ms)
3. WAL: RESERVED → 4. Forward to worker → 5. WAL: EXECUTED
6. HTTP `/peer/commit` to authoritative (~1ms) → 7. WAL: COMMITTED → 8. Return

**Peer endpoints:**

All peer endpoints require `X-Peer-Key` header authentication:

| Endpoint | Method | Purpose |
|----------|--------|---------|
| `/peer/health` | GET | Peer broker health check |
| `/peer/reserve` | POST | Reserve credits for remote user (body: `{user_id, amount, reservation_id}`) |
| `/peer/commit` | POST | Commit transaction (body: `{reservation_id, actual_cost, worker_id, duration_ms}`) |
| `/peer/cancel` | POST | Cancel reservation (body: `{reservation_id}`) |
| `/peer/earn` | POST | Credit earnings to broker owner (body: `{request_id, amount}`) |
| `/peer/balance` | GET | Get balance (query: `?user_id=...`) |
| `/peer/workers` | GET | List this broker's local workers |

**Flush thread:**

In P2P mode, authoritative brokers maintain a `TransactionBuffer` that batches writes every **5 seconds** (every health-check tick) via the dashboard API or PostgreSQL:
- Pending transactions (all state transitions)
- Balance snapshots (for dashboard/audit)

This decouples the hot path from PostgreSQL latency while ensuring eventual consistency.

**Environment variables:**

| Variable | Required | Default | Description |
|----------|----------|---------|-------------|
| `ZAKURO_P2P` | No | `false` | Enable P2P mode |
| `ZAKURO_PEER_KEY` | Yes (in P2P) || Shared secret for `X-Peer-Key` auth |
| `ZAKURO_OWNER_ID` | Yes (in P2P) || This broker's unique ID (used in authority hash) |
| `ZAKURO_PEERS` | Yes (in P2P) || Comma-separated peer broker URLs: `http://10.13.13.5:9000,http://10.13.13.6:9000` |

**Resilience:**

- If authoritative peer is down → `Authority::Standalone` → Ledger API or PostgreSQL fallback
- WAL replay on crash: `reserved→cancel`, `executed→commit` (same as before)
- Peer discovery via `ZAKURO_PEERS` + health probes on startup

---

## Write-Ahead Log (WAL)

The broker maintains a write-ahead log for crash recovery. Candidate paths (first writable wins): `ZAKURO_WAL_PATH` env → `$HOME/.zakuro/wal.jsonl` → `/tmp/zakuro-wal.jsonl`. Each transaction progresses through three states:

| Status | Description |
|--------|-------------|
| `Reserved` | Credits reserved, request sent to worker |
| `Executed` | Worker returned successfully, actual cost calculated |
| `Committed` | Credits committed (or refunded on failure) |

**Implementation:**

- **Append-only JSONL file** — one JSON object per line, never modified in-place
- **DashMap in-memory index** — O(1) lookup by `request_id` for status checks and updates
- **Batched fsync** — writes are flushed every **100ms** OR after **64 entries** (whichever comes first)
- **Thread-safe**`Arc<RwLock<BufWriter>>` for concurrent writes from multiple request handlers

This design provides durability without sacrificing performance: the batched fsync amortizes disk I/O cost across multiple transactions.

### Crash Recovery

On startup, the broker replays any uncommitted WAL entries via `recovery.rs`:

- **Reserved** entries: credits are refunded (worker may have crashed before responding)
- **Executed** entries: credits are committed at the actual cost (worker succeeded but broker crashed before committing)

**P2P mode:** WAL replay works seamlessly with P2P — authoritative brokers apply state changes to their DashMap, non-authoritative brokers skip or retry peer calls.

The WAL is **automatically compacted** every ~5 minutes: committed and failed entries are removed from disk, keeping only uncommitted entries. The in-memory index is also cleared of completed entries.

### WAL Format

Append-only JSONL file. Each line is a JSON object:

```json
{"request_id":"uuid","user_id":"user","reservation_id":"res-id","estimated_cost":0.001,"actual_cost":0.0008,"worker_id":"worker-uuid","duration_ms":250.0,"timestamp":"2024-01-01T00:00:00Z","status":"Committed"}
```

---

## P2P Mesh Deployment

Deploy a multi-node mesh where each node runs a broker + worker pair connected via Tailscale.

### Architecture

```
  Node 1 (zk0-node01)                       Node 2 (zk0-node02)
  ┌─────────────────────┐                   ┌─────────────────────┐
  │  Tailscale sidecar  │◄══ Tailscale ════►│  Tailscale sidecar  │
  │  (shared netns)     │    mesh           │  (shared netns)     │
  │                     │                   │                     │
  │  Broker (:9000)     │                   │  Broker (:9000)     │
  │    ├ PostgreSQL     │◄══ shared PG  ════►    ├ PostgreSQL     │
  │    ├ WAL + DashMap  │    (optional)     │    ├ WAL + DashMap  │
  │    └ Discovery      │                   │    └ Discovery      │
  │                     │                   │                     │
  │  Worker (:3960)     │                   │  Worker (:3960)     │
  │    type: standard   │                   │    type: premium    │
  │    (free to self)   │                   │    (free to self)   │
  └─────────────────────┘                   └─────────────────────┘
```

Each node's Tailscale sidecar and worker share the broker's network namespace via Docker `network_mode: "service:..."`. All three containers get the same Tailscale IP.

### Prerequisites

- Two Tailscale auth keys (one per node)
- Docker and Docker Compose

### Quick Start

```bash
cd zak-zakuro/docker

# Set environment variables
export ZK0NODE01_API_KEY=tskey-auth-...
export ZK0NODE02_API_KEY=tskey-auth-...
export NODE1_TAILSCALE_IP=100.x.x.x   # from `tailscale ip -4` on node1
export NODE2_TAILSCALE_IP=100.y.y.y   # from `tailscale ip -4` on node2

# Start the 2-node mesh
docker compose -f docker-compose.mesh.yml up -d --build
```

### Verification

```bash
# Both brokers should see 2 workers each
curl http://localhost:9001/workers   # node1: local standard + remote premium
curl http://localhost:9002/workers   # node2: local premium + remote standard

# Check credits
curl http://localhost:9001/credits/node1-user

# Run the demo
python docker/mesh-demo.py http://localhost:9001 node1-user
```

### How Discovery Works in the Mesh

1. Each broker reads `ZAKURO_TAILSCALE_IP` and `ZAKURO_PEERS` environment variables
2. Discovery scans localhost ports first (finds local worker)
3. Then probes explicit peers (finds remote workers via Tailscale)
4. CGNAT subnet scanning (100.x.x.x) is skipped — Tailscale IPs are not on contiguous /24s
5. Existing workers get heartbeat refreshed via TCP-connect only (no full HTTP probe needed)
6. Worker deduplication by name prevents the same worker appearing twice

### Environment Variables (Mesh)

| Variable | Description |
|----------|-------------|
| `ZAKURO_TAILSCALE_IP` | This node's Tailscale IP (for local worker detection) |
| `ZAKURO_PEERS` | Comma-separated `ip:port` of remote workers to probe |
| `NODE1_TAILSCALE_IP` | Node 1's Tailscale IP (used in compose) |
| `NODE2_TAILSCALE_IP` | Node 2's Tailscale IP (used in compose) |
| `ZK0NODE01_API_KEY` | Tailscale auth key for node 1 |
| `ZK0NODE02_API_KEY` | Tailscale auth key for node 2 |

---

## Cluster Commands

| Command | Description |
|---------|-------------|
| `zc up` | Start 1 worker + broker locally (foreground) |
| `zc up --workers N` | Start N workers + broker (ports 3960–3960+N) |
| `zc up --workers N -d` | Start N workers + broker in the background (daemon) |
| `zc down` | Stop all workers + broker started by `zc up` |
| `zc workers` | List workers registered with the local broker |
| `zc me` / `zc credits` | Show authenticated user info and credits |
| `zc attach <url>` | Attach remote TUI to a running broker |
| `zc update` | Update the `zc` CLI from `get.zakuro-ai.com` |
| `zc pull` | Pull updated Docker images |
| `zc images` | List Zakuro images built on the machine |
| `zc ps` | List currently running Zakuro containers |
| `zc kill` | Stop and remove running Zakuro containers |
| `zc restart` | Restart containers with updated images |

---

## Environment Variables

| Variable | Required | Default | Description |
|----------|----------|---------|-------------|
| `ZAKURO_API_KEY` | Yes || Authentication token. Also used for dashboard API calls in API mode. |
| `ZAKURO_API_URL` | No || Dashboard API URL (e.g., `https://my.zakuro-ai.com`). When set with `ZAKURO_API_KEY`, enables API mode (no direct PG access). |
| `ZAKURO_MASTER_KEY` | No || Master API key for admin operations (adding credits, ledger status). |
| `DATABASE_URL` | No | `postgresql://zakuro_broker:broker_secret_change_me@localhost:5432/zakuro` | PostgreSQL connection URL (restricted broker role). Ignored in API mode. |
| `ZAKURO_WORKER_KEY` | No || Shared secret for worker management endpoints (`POST/DELETE /workers`, heartbeat). When unset, these endpoints are open. |
| `TAILSCALE_AUTHKEY` | No || Tailscale auth key. Automatically retrieved from dashboard API when `ZAKURO_API_URL` is set. |
| `ZAKURO_TAILSCALE_IP` | No || Override Tailscale IP detection (for Docker sidecar setups). |
| `ZAKURO_PEERS` | No || Comma-separated `ip:port` peers for worker discovery, or `http://host:port` for broker peers in P2P mode. |
| `ZAKURO_P2P` | No | `false` | Enable P2P broker-to-broker communication. |
| `ZAKURO_PEER_KEY` | No (Yes in P2P) || Shared secret for P2P broker authentication (`X-Peer-Key` header). |
| `ZAKURO_OWNER_ID` | No (Yes in P2P) || This broker's unique ID for authority assignment (FNV-1a hash). Auto-derived from `ZAKURO_API_KEY` if in `zk_{user}_{hex}` format. |
| `ZAKURO_NODE_NAME` | No | Auto-detected | Human-readable node name (Tailscale hostname → system hostname). |
| `ZAKURO_WORKER_PORT` | No | `3960` | Primary port workers listen on (discovery). |
| `ZAKURO_SCAN_RANGE` | No || Port range for discovery scan, e.g. `3960-3999`. |
| `ZAKURO_SCAN_INTERVAL` | No | `15` | Seconds between discovery scans. |

---

## Configuration Reference

### BrokerConfig

| Field | Default | Description |
|-------|---------|-------------|
| `host` | `0.0.0.0` | Host to bind the broker server |
| `port` | `9000` | Port to bind the broker server |
| `health_check_interval` | `5` | Worker health check interval in seconds |
| `worker_timeout` | `30` | Seconds before marking a worker unhealthy |
| `min_credits` | `0.001` | Minimum credits required for any request |
| `daemon` | `false` | Run in daemon (background) mode |
| `verbose` | `true` | Show live transaction logs (foreground mode) |
| `tui_mode` | `false` | Enable interactive TUI dashboard |
| `enable_discovery` | `true` | Enable automatic worker discovery |

### DiscoveryConfig

| Field | Default | Description |
|-------|---------|-------------|
| `subnet` | `10.13.13` | Tailscale subnet to scan |
| `worker_port` | `3960` | Primary port workers listen on (from `ZAKURO_WORKER_PORT` env) |
| `extra_ports` | `[3961, 3962]` | Additional ports to scan |
| `scan_port_range` | `None` | Full port range scan, e.g. `(3960, 3999)` (from `ZAKURO_SCAN_RANGE` env) |
| `interval_secs` | `15` | Seconds between discovery scans (from `ZAKURO_SCAN_INTERVAL` env) |
| `enable_scan` | `true` | Enable active network scanning |
| `enable_dns` | `true` | Enable DNS-based discovery |
| `peers` | `[]` | Explicit peer addresses from `ZAKURO_PEERS` env var |

### LedgerConfig

| Field | Default | Description |
|-------|---------|-------------|
| `database_url` | `postgresql://zakuro_broker:broker_secret_change_me@localhost:5432/zakuro` | PostgreSQL connection URL (restricted broker role) |
| `master_key` | `None` | From `ZAKURO_MASTER_KEY` env |
| `default_credits` | `0.0` | Credits for new users (no free credits by default) |
| `allow_local_fallback` | `true` | Fall back to in-memory when PostgreSQL is unavailable |
| `api_url` | `None` | From `ZAKURO_API_URL` env — dashboard API URL |
| `api_key` | `None` | From `ZAKURO_API_KEY` env — when both `api_url` and `api_key` are set, the broker runs in API mode (PG pool is never created) |

### Worker Pricing Model

Workers advertise their pricing when registering:

| Field | Description |
|-------|-------------|
| `cpu_price` | Cost per CPU-core per second |
| `memory_price` | Cost per GiB of memory per second |
| `gpu_price` | Cost per GPU per second |
| `min_charge` | Minimum charge per request |

The estimated cost for a request is:

```
cost = max(
  cpu_price * cpus * duration_secs +
  memory_price * memory_gib * duration_secs +
  gpu_price * gpus * duration_secs,
  min_charge
)
```