You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
refactor: Remove token auth, RBAC, and multi-tenancy features
- Delete Auth, RBAC, MultiTenancy, and AuthPlug modules
- Simplify state machine from 7 fields to 2 (indexes, command_count)
- Remove auth checks from all public API functions
- Remove auth-related ETS tables and re-initialization
- Simplify backup/restore to only handle KV data and indexes
- Remove token management from mix tasks
- Update all tests to reflect simplified state
- Update documentation (CLAUDE.md) with simplified architecture
Copy file name to clipboardExpand all lines: CLAUDE.md
+52-41Lines changed: 52 additions & 41 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -4,7 +4,12 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co
4
4
5
5
## Project Overview
6
6
7
-
Concord is a distributed, strongly-consistent **embedded** key-value store built in Elixir using the Raft consensus algorithm (Ra library). CP system — think SQLite for distributed coordination. Starts with your application, no separate infrastructure.
7
+
Concord is an **embedded key-value database** for Elixir with Raft consensus (Ra library). Think SQLite or CubDB, but with distributed replication.
8
+
9
+
-**Library, not a service** — no HTTP API, no auth, no RBAC, no multi-tenancy
10
+
-**In-memory with periodic flush** — ETS for reads, Ra log flushed to disk every ~1s (configurable in ms). Data since last flush can be lost on crash. This is by design.
11
+
-**Restore from source** — Concord stores data that doesn't change frequently. After a crash, data is restored from an authoritative external source (database, config, API). Crash durability of every write is explicitly not a goal.
12
+
-**CP system** — consistency over availability during partitions
8
13
9
14
## Development Commands
10
15
@@ -22,70 +27,75 @@ mix dialyzer # Type checking (first run is slow — builds PLT
22
27
mix test.e2e # All e2e tests
23
28
mix test.e2e.distributed # Distributed subset only
24
29
MIX_ENV=e2e_test mix test e2e_test/distributed/leader_election_test.exs # Specific
25
-
26
-
mix start # HTTP API server (dev)
27
30
```
28
31
29
32
## Architecture
30
33
31
-
### State Machine (V3) — The Core
34
+
### State Machine — The Core
32
35
33
-
`Concord.StateMachine` implements `:ra_machine`. This is the most critical file in the project.
36
+
`Concord.StateMachine` implements `:ra_machine`. Most critical file.
34
37
35
38
**State shape:**
36
39
```elixir
37
40
{:concord_kv, %{
38
41
indexes: %{name => extractor_spec},
39
-
tokens: %{token => permissions},
40
-
roles: %{role => permissions},
41
-
role_grants: %{token => [roles]},
42
-
acls: [{pattern, role, permissions}],
43
-
tenants: %{tenant_id => tenant_definition},
44
42
command_count:non_neg_integer()
45
43
}}
46
44
```
47
45
48
-
**Correctness invariants — do NOT violate these:**
46
+
**Correctness invariants — do NOT violate:**
49
47
50
-
1.**Deterministic replay**: `apply/3` is a pure function of `(meta, command, state)`. Time comes from `meta.system_time` (leader-assigned milliseconds), never `System.system_time`. The helper `meta_time(meta)` converts to seconds.
48
+
1.**Deterministic replay**: `apply/3` is a pure function of `(meta, command, state)`. Time from `meta.system_time` (leader-assigned ms), never `System.system_time`. Helper: `meta_time(meta)`.
51
49
52
-
2.**No anonymous functions in Raft state/log**: Index extractors use declarative specs (`Concord.Index.Extractor`) — tuples like `{:map_get, :email}`, `{:nested, [:address, :city]}`, `{:identity}`, `{:element, n}`. Anonymous functions cause `:badfun` on deserialization across code versions.
50
+
2.**No anonymous functions in Raft state/log**: Index extractors use declarative specs — `{:map_get, :email}`, `{:nested, [:a, :b]}`, `{:identity}`, `{:element, n}`. Closures cause `:badfun` on deserialization.
53
51
54
-
3.**All mutations through Raft**: Auth tokens, RBAC roles/grants/ACLs, tenant definitions, and backup restores all route through `:ra.process_command`as state machine commands. Direct ETS writes are only acceptable as fallbacks when the cluster isn't ready yet (`:noproc`).
52
+
3.**All mutations through Raft**: `:ra.process_command`for every state change. Direct ETS writes only as fallback when cluster isn't ready (`:noproc`).
55
53
56
-
4.**ETS tables are materialized views**: Rebuilt from authoritative Raft state on `snapshot_installed/4`. Never the source of truth.
54
+
4.**ETS = materialized views**: Rebuilt from Raft state on `snapshot_installed/4`. Never source of truth.
57
55
58
-
5.**Snapshots via `release_cursor` effect**: Ra does NOT have a `snapshot/1` callback. Snapshots are emitted every 1000 commands as `{:release_cursor, index, state}`effects. The state passed includes ETS data captured by `build_release_cursor_state/1`.
56
+
5.**Snapshots via `release_cursor`**: Ra has no `snapshot/1` callback. Emit `{:release_cursor, index, state}`every N commands.
59
57
60
-
6.**Pre-consensus evaluation**: `put_if`/`delete_if` evaluate condition functions at the API layer, then convert to CAS commands with `expected: current_value` before entering the Raft log.
58
+
6.**Pre-consensus evaluation**: `put_if`/`delete_if` evaluate conditions at API layer, convert to CAS commands with `expected: current_value` before Raft log.
**Key detail**: Ra wraps query results as `{:ok, {:ok, result}, leader_info}`. Always unwrap the nested tuple. Server ID format is `{:concord_cluster, node()}`.
69
67
70
68
### Module Responsibilities
71
69
72
70
| Module | Role |
73
71
|--------|------|
74
-
|`Concord`| Public API — all client-facing functions|
72
+
|`Concord`| Public API — put, get, delete, get_all, CAS, bulk ops|
75
73
|`Concord.StateMachine`| Ra state machine — commands, queries, snapshots |
76
-
|`Concord.Application`| Supervisor tree — Ra cluster, HTTP, telemetry |
77
-
|`Concord.Auth`| Token auth — mutations via Raft commands |
78
-
|`Concord.RBAC`| Roles, grants, ACLs — mutations via Raft commands |
79
-
|`Concord.MultiTenancy`| Tenant definitions via Raft; usage counters node-local |
74
+
|`Concord.Application`| Supervisor tree — Ra cluster, telemetry poller |
80
75
|`Concord.Index`| Secondary indexes using `Index.Extractor` specs |
81
76
|`Concord.Index.Extractor`| Declarative extractor specs (no closures) |
-`Concord.Web` — HTTP API → separate wrapper library
92
+
-`Concord.Tracing` — OpenTelemetry → host app via telemetry hooks
93
+
-`Concord.Prometheus` — metrics export → host app via telemetry hooks
94
+
-`Concord.EventStream` — CDC streaming → host app via telemetry hooks
85
95
86
96
### Adding a New Feature
87
97
88
-
1.**API layer** (`lib/concord.ex` or `lib/concord/feature.ex`): Validate inputs, call `:ra.process_command` for writes or `:ra.consistent_query` for reads. Handle nested Ra result tuples.
98
+
1.**API layer** (`lib/concord.ex`): Validate inputs, call `:ra.process_command` for writes or `:ra.consistent_query` for reads. Handle nested Ra result tuples.
89
99
90
100
2.**State machine command** (`lib/concord/state_machine.ex`): Add `apply_command/3` clause. Return `{{:concord_kv, new_state}, result, effects}`. Use `meta_time(meta)` for timestamps. Emit telemetry.
91
101
@@ -95,17 +105,16 @@ mix start # HTTP API server (dev)
95
105
96
106
## Testing Notes
97
107
98
-
- All tests use `async: false` — Ra cluster is shared state
99
-
-`--no-start` alias prevents auto-starting the application; tests call `start_test_cluster()` explicitly
100
-
- E2E tests use `MIX_ENV=e2e_test` with separate config (`config/e2e_test.exs`)
101
-
-When testing state machine directly, `apply/3` increments `command_count` on every call — don't assert exact state equality, pattern-match on the fields you care about
108
+
- All tests `async: false` — Ra cluster is shared state
109
+
-`--no-start` alias prevents auto-starting; tests call `start_test_cluster()` explicitly
110
+
- E2E tests use `MIX_ENV=e2e_test` with separate config
111
+
-State machine `apply/3` increments `command_count` on every call — pattern-match fields you care about, don't assert exact state equality
102
112
103
113
## Configuration
104
114
105
-
-`config/runtime.exs` — Data directory: prod uses `CONCORD_DATA_DIR` env var (default `/var/lib/concord/data/`), dev/test use `/tmp`
115
+
-`config/runtime.exs` — Data dir: prod uses `CONCORD_DATA_DIR` env var, dev/test use `/tmp`
0 commit comments