Skip to content

Commit e8fbe34

Browse files
committed
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
1 parent 41e9918 commit e8fbe34

28 files changed

+358
-3585
lines changed

.specify/memory/constitution.md

Lines changed: 205 additions & 177 deletions
Large diffs are not rendered by default.

CLAUDE.md

Lines changed: 52 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,12 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co
44

55
## Project Overview
66

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
813

914
## Development Commands
1015

@@ -22,70 +27,75 @@ mix dialyzer # Type checking (first run is slow — builds PLT
2227
mix test.e2e # All e2e tests
2328
mix test.e2e.distributed # Distributed subset only
2429
MIX_ENV=e2e_test mix test e2e_test/distributed/leader_election_test.exs # Specific
25-
26-
mix start # HTTP API server (dev)
2730
```
2831

2932
## Architecture
3033

31-
### State Machine (V3) — The Core
34+
### State Machine — The Core
3235

33-
`Concord.StateMachine` implements `:ra_machine`. This is the most critical file in the project.
36+
`Concord.StateMachine` implements `:ra_machine`. Most critical file.
3437

3538
**State shape:**
3639
```elixir
3740
{:concord_kv, %{
3841
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},
4442
command_count: non_neg_integer()
4543
}}
4644
```
4745

48-
**Correctness invariants — do NOT violate these:**
46+
**Correctness invariants — do NOT violate:**
4947

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)`.
5149

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.
5351

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`).
5553

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.
5755

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.
5957

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.
6159

6260
### Data Flow
6361

64-
**Writes**: `Concord.put/3`Auth`:ra.process_command({:concord_cluster, node()}, cmd, timeout)` → Leader replicates → `StateMachine.apply_command/3` → ETS insert + index update + telemetry
62+
**Writes**: `Concord.put/3`Validation`:ra.process_command({:concord_cluster, node()}, cmd, timeout)` → Leader replicates → `StateMachine.apply_command/3` → ETS insert + index update + telemetry
6563

66-
**Reads**: `Concord.get/2`Auth → `:ra.consistent_query` or `:ra.local_query``StateMachine.query/2` → ETS lookup → decompress → return
64+
**Reads**: `Concord.get/2``:ra.consistent_query` or `:ra.local_query``StateMachine.query/2` → ETS lookup → decompress → return
6765

6866
**Key detail**: Ra wraps query results as `{:ok, {:ok, result}, leader_info}`. Always unwrap the nested tuple. Server ID format is `{:concord_cluster, node()}`.
6967

7068
### Module Responsibilities
7169

7270
| Module | Role |
7371
|--------|------|
74-
| `Concord` | Public API — all client-facing functions |
72+
| `Concord` | Public API — put, get, delete, get_all, CAS, bulk ops |
7573
| `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 |
8075
| `Concord.Index` | Secondary indexes using `Index.Extractor` specs |
8176
| `Concord.Index.Extractor` | Declarative extractor specs (no closures) |
82-
| `Concord.Backup` | Backup/restore — restore submits `{:restore_backup, entries}` via Raft |
77+
| `Concord.Backup` | Backup/restore — restore via `{:restore_backup, entries}` Raft command |
8378
| `Concord.TTL` | Key expiration — GenServer for periodic cleanup |
84-
| `Concord.Web` | HTTP/HTTPS API (Plug + Bandit) |
79+
| `Concord.Query` | Key filtering, range queries, value predicates |
80+
| `Concord.Compression` | Transparent value compression for large values |
81+
| `Concord.Telemetry` | Telemetry event definitions and helpers |
82+
83+
### Modules to Remove
84+
85+
These exist in the codebase but are out of scope for an embedded database:
86+
87+
- `Concord.Auth` — token authentication → host app concern
88+
- `Concord.RBAC` — role-based access control → host app concern
89+
- `Concord.MultiTenancy` — tenant isolation → host app concern
90+
- `Concord.AuditLog` — compliance logging → host app concern
91+
- `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
8595

8696
### Adding a New Feature
8797

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.
8999

90100
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.
91101

@@ -95,17 +105,16 @@ mix start # HTTP API server (dev)
95105

96106
## Testing Notes
97107

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
102112

103113
## Configuration
104114

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`
106116
- `default_read_consistency`: `:eventual`, `:leader` (default), `:strong`
107-
- Auth disabled in dev, enabled in prod
108-
- HTTP API, Prometheus, and OpenTelemetry tracing are opt-in
117+
- `flush_interval_ms`: Ra log flush interval (default: 1000)
109118

110119
## Commit Conventions
111120

@@ -119,12 +128,14 @@ See `docs/` for architectural documents:
119128
- `docs/ArchitecturalAudit.md` — Audit of correctness issues and their fixes
120129
- `docs/CorrectRaftStateMachinePattern.md` — V3 migration design and rationale
121130
- `docs/DESIGN.md` — Original design blueprint
122-
- `docs/API_DESIGN.md` — HTTP API design
123-
- `docs/API_USAGE_EXAMPLES.md` — HTTP API usage examples
124131

125-
## Active Technologies
126-
- Elixir 1.18 / OTP 28 + Ra 2.17.1 (Raft), libcluster 3.5.0, Bandit 1.8.0, Plug 1.18.1 (001-fix-review-issues)
127-
- ETS (in-memory) with Ra snapshots for persistence (001-fix-review-issues)
132+
## Dependencies (target after cleanup)
128133

129-
## Recent Changes
130-
- 001-fix-review-issues: Added Elixir 1.18 / OTP 28 + Ra 2.17.1 (Raft), libcluster 3.5.0, Bandit 1.8.0, Plug 1.18.1
134+
- `ra` — Raft consensus
135+
- `libcluster` — node discovery
136+
- `telemetry` + `telemetry_poller` — event emission (no export deps)
137+
- `jason` — JSON encoding for values
138+
139+
## Active Technologies
140+
- Elixir 1.18 / OTP 28 + Ra 2.17.1 (Raft), libcluster 3.5.0
141+
- ETS (in-memory) with Ra snapshots for persistence

config/config.exs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@ import Config
44
config :concord,
55
cluster_name: :concord_cluster,
66
data_dir: "./data",
7-
auth_enabled: false,
87
max_batch_size: 500,
98
# Default read consistency level: :eventual, :leader, or :strong
109
default_read_consistency: :leader,

config/dev.exs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@ import Config
22

33
config :concord,
44
data_dir: "./data/dev",
5-
auth_enabled: false,
65
http: [
76
enabled: true,
87
port: 4000,

config/e2e_test.exs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,7 @@ import Config
33
# E2E test configuration
44
config :concord,
55
cluster_name: :concord_cluster,
6-
data_dir: "./data/e2e_test",
7-
auth_enabled: false
6+
data_dir: "./data/e2e_test"
87

98
# Libcluster configuration - gossip strategy for multi-node testing
109
config :libcluster,

config/prod.exs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@ import Config
22

33
config :concord,
44
data_dir: {:system, "CONCORD_DATA_DIR", "/var/lib/concord"},
5-
auth_enabled: true,
65
# Production HTTP API configuration
76
http: [
87
enabled: {:system, "CONCORD_HTTP_ENABLED", true},

config/test.exs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@ import Config
22

33
config :concord,
44
data_dir: "./data/test",
5-
auth_enabled: false,
65
http: [enabled: false]
76

87
config :logger, level: :warning

0 commit comments

Comments
 (0)