npm package: @gatekpr/gatekeeper
Description: Solana-native API gateway backend with a public Devnet CLI client
Latest version: 0.1.1

# Gatekeeper

Gatekeeper is a Solana-native backend runtime that splits durable control-plane state from real-time execution state.

This refactor keeps the original backend story intact:

- organizations
- roles
- API keys
- policy enforcement
- quota admission
- audit visibility

But it now does so across two execution layers:

- Base Solana for durable control-plane state
- MagicBlock Ephemeral Rollups for delegated real-time runtime state

## Project Overview

Gatekeeper models a familiar Web2 API gateway backend in Solana terms:

- `Organization` is the tenant record
- `Role` is the RBAC policy object
- `ApiKey` is the credential record
- `QuotaPolicy` is the durable rate-limit policy
- `QuotaRuntime` is the fast mutable execution state
- `AuditCheckpoint` is the durable settled summary

The result is a two-layer design:

- base layer stores the canonical configuration and checkpointed runtime state
- delegated runtime handles low-latency request admission and rolling audit history

## Architecture Explanation

### How this works in Web2

Gatekeeper follows the same split a normal API platform would use in a Web2 stack:

- a database stores tenants, roles, API keys, and quota rules
- a fast mutable store like Redis tracks rolling usage and recent request outcomes
- request middleware checks authorization and quota on the hot path
- a background process compacts recent runtime activity into durable audit state

In Web2 terms, `Organization`, `Role`, `ApiKey`, and `QuotaPolicy` are control-plane records, while `QuotaRuntime` behaves like the fast execution-state cache and `AuditCheckpoint` behaves like the durable settlement record.

### How this works on Solana

On Solana, the same backend split is expressed with two execution layers:

- Base Solana stores the durable PDAs: `Gateway`, `Organization`, `Role`, `ApiKey`, `QuotaPolicy`, and `AuditCheckpoint`
- MagicBlock ER handles the high-frequency mutable account: `QuotaRuntime`
- `consume_request` is the shared admission path, so the request logic stays the same whether runtime writes happen on base or through delegated execution
- `commit_quota_runtime`, `commit_and_undelegate_quota_runtime`, and `settle_runtime_checkpoint` move fast-path state back into durable base-layer state

This keeps configuration and audit truth on base while moving the hottest mutable path to delegated runtime execution.

### Tradeoffs & constraints

- Only `QuotaRuntime` is delegated; this keeps the design legible, but it means settlement is an explicit operator step rather than an invisible background guarantee
- Durable audit is checkpointed instead of writing one base account per request; this reduces hot-path cost, but long-term per-request history needs off-chain indexing if full retention is required
- The plain base-RPC path still works without MagicBlock infrastructure; that improves portability, but it does not deliver the same latency profile as delegated execution
- Full ER behavior depends on router-aware transport and compatible infrastructure; local tests focus on the base-compatible path, while ER validation is done with dedicated diagnostics and smoke flows

## Devnet Transaction Links

- Program: [Gatekeeper on Devnet](https://explorer.solana.com/address/5UetKs63bZxoYy5dZvJxYjUSTBmaF5tN7ADR8pB6SMZu?cluster=devnet)
- Multi-tenant onboarding: [3J7HCNNm9NeoHSS9Zn2oZfHA8fLy5mQ9fRg28KJeGF13z3x35Ppg1QkFPf82YxKqymBUqQAhRVyNGxgpaTqaDvi](https://explorer.solana.com/tx/3J7HCNNm9NeoHSS9Zn2oZfHA8fLy5mQ9fRg28KJeGF13z3x35Ppg1QkFPf82YxKqymBUqQAhRVyNGxgpaTqaDvi?cluster=devnet)
- RBAC allow: [5MoB1k7F3r48ZfSAhLtoQkQhP5fmCQuQgzpkSF7n1GMQpDuLAkFHdiAo4cHpVfJo4To5Tr3GGYtH4UDc5TDMVpHd](https://explorer.solana.com/tx/5MoB1k7F3r48ZfSAhLtoQkQhP5fmCQuQgzpkSF7n1GMQpDuLAkFHdiAo4cHpVfJo4To5Tr3GGYtH4UDc5TDMVpHd?cluster=devnet)
- RBAC reject: [4GbHueHxxbvjSUhw2E1M5P7S6Y8FVxc4R5TxBS6cjEuQLsadbLZNhyuNHQFwpAQFmChYrqhPAV1s2XMBnKRvnGGi](https://explorer.solana.com/tx/4GbHueHxxbvjSUhw2E1M5P7S6Y8FVxc4R5TxBS6cjEuQLsadbLZNhyuNHQFwpAQFmChYrqhPAV1s2XMBnKRvnGGi?cluster=devnet)
- Quota exceeded: [193LyGPZjGsZXjBWxt9UTXQCktFDG3HAgPjZUEgTZqbBcLc1u94S7x23fvZETdJsxxk5ezMwCginbh1nR5WmGUc](https://explorer.solana.com/tx/193LyGPZjGsZXjBWxt9UTXQCktFDG3HAgPjZUEgTZqbBcLc1u94S7x23fvZETdJsxxk5ezMwCginbh1nR5WmGUc?cluster=devnet)
- Initial settlement: [oDAeJbXxDxgYmBWrsysybFqa1zPdMKbexTAe64hgtD9YeAXDzrPjtB5RMMFutqFvaQw5KaoChAQAbuEW6fscgir](https://explorer.solana.com/tx/oDAeJbXxDxgYmBWrsysybFqa1zPdMKbexTAe64hgtD9YeAXDzrPjtB5RMMFutqFvaQw5KaoChAQAbuEW6fscgir?cluster=devnet)
- Revoke key: [48UUPj6FmRHzBEDG2ins7qm3V3KncDVK11XQaHRnJJzREbnhTsR1Q8AmKo2SE65Hrk6sFueoxrK9mofKWwHxvXPP](https://explorer.solana.com/tx/48UUPj6FmRHzBEDG2ins7qm3V3KncDVK11XQaHRnJJzREbnhTsR1Q8AmKo2SE65Hrk6sFueoxrK9mofKWwHxvXPP?cluster=devnet)

## Why This Matters

A normal Web2 gateway typically splits responsibilities anyway:

- durable config in a database
- hot counters in Redis or memory
- request middleware at the edge
- periodic background compaction or log shipping

Gatekeeper maps that directly onto Solana:

- PDAs on base for durable control-plane truth
- delegated writable runtime accounts for fast admission
- periodic commit and settlement for durability

That makes the architecture readable to backend judges without pretending that every state mutation belongs on the slowest possible path.

## Architecture

### Base Solana: durable control plane

These accounts remain canonical on base:

- `Gateway`
- `Organization`
- `Role`
- `ApiKey`
- `QuotaPolicy`
- `AuditCheckpoint`

Base instructions are used for:

- setup
- admin mutations
- durable policy changes
- session finalization
- settled checkpoint updates

### MagicBlock ER: execution plane

Fast-path request admission runs against delegated runtime state:

- `QuotaRuntime`

`QuotaRuntime` contains:

- live window usage
- total allowed/rejected counters
- rolling request sequence
- rolling in-memory-style audit buffer
- delegation metadata

The same `consume_request` instruction works in both modes:

- plain Solana mode: mutate `QuotaRuntime` directly on base
- ER mode: mutate delegated `QuotaRuntime` through Magic Router

## Durable vs Runtime State

### Durable base state

#### `Organization`

Seed:

- `["organization", gateway, name]`

Fields:

- `authority`
- `role_count`
- `api_key_count`
- `settlement_count`

#### `Role`

Seed:

- `["role", organization, role_name]`

Fields:

- `policy_mask`
- `created_at`

#### `ApiKey`

Seed:

- `["api_key", organization, key_id]`

Fields:

- `role`
- `label`
- `active`
- `created_by`
- `created_at`
- `revoked_at`

#### `QuotaPolicy`

Seed:

- `["quota_policy", api_key]`

Fields:

- `max_requests`
- `window_seconds`

This is the durable contract for request admission. It does not store mutable usage.

#### `AuditCheckpoint`

Seed:

- `["audit_checkpoint", api_key]`

Fields:

- last settled sequence
- settled allowed/rejected totals
- settled window state
- last settled reason/action
- last settlement timestamp

This is the durable base-layer summary of what the runtime has processed.

### Runtime ER state

#### `QuotaRuntime`

Seed:

- `["quota_runtime", api_key]`

Fields:

- `current_window_used`
- `current_window_started_at`
- `total_allowed`
- `total_rejected`
- `rolling_sequence`
- `rolling_audit`
- `is_delegated`
- `delegated_validator`
- `commit_frequency_ms`
- `last_settled_sequence`
- `last_settled_at`

This is the hot mutable account intended for delegation to MagicBlock.

## Request Flow

### Plain Solana compatibility path

1. Create `QuotaPolicy`
2. Initialize `QuotaRuntime`
3. Call `consume_request` on base
4. Periodically call `settle_runtime_checkpoint`

This keeps the project usable without MagicBlock infrastructure.

### ER fast path

1. Create `QuotaPolicy` on base
2. Initialize `QuotaRuntime` on base
3. Delegate `QuotaRuntime` with `delegate_quota_runtime`
4. Route `consume_request` through Magic Router
5. Periodically `commit_quota_runtime`
6. When closing the delegated session, call `commit_and_undelegate_quota_runtime`
7. Finalize durable summary with `settle_runtime_checkpoint` on base

## Instruction Set

### Base-layer control plane

- `initialize_gateway`
- `create_organization`
- `create_role`
- `attach_policy_to_role`
- `create_api_key`
- `create_quota_policy`
- `initialize_quota_runtime`
- `revoke_api_key`
- `settle_runtime_checkpoint`

### ER integration hooks

- `delegate_quota_runtime`
- `commit_quota_runtime`
- `commit_and_undelegate_quota_runtime`

### Shared request path

- `consume_request`

The request instruction only mutates runtime state. That is the key design change that makes it safe for delegated execution.

## Permission Model

Roles use a simple `u64` bitmask:

- bit 0: `metrics:read`
- bit 1: `metrics:write`
- bit 2: `users:read`
- bit 3: `users:write`
- bit 4: `admin`

`admin` is treated as an override bit.

## Quota Model

The quota model is intentionally split:

- `QuotaPolicy` says what the rules are
- `QuotaRuntime` says what has happened recently

Admission logic:

1. reject if the key is revoked
2. reject if the action is invalid
3. reject if the role bitmask does not allow the action
4. if the current window expired, reset runtime window usage
5. reject if `current_window_used >= max_requests`
6. otherwise increment `current_window_used`
7. append a rolling runtime audit record

## Audit Model

This refactor no longer writes a new base PDA per request on the hot path.

Instead it uses two audit layers:

### Runtime rolling audit

Stored inside `QuotaRuntime` as a capped rolling buffer.

Each entry records:

- sequence
- action
- allowed/rejected
- reason
- timestamp
- quota usage after the decision

### Base settled checkpoint

Stored in `AuditCheckpoint`.

This is updated by `settle_runtime_checkpoint` after committed runtime state is safely back on base and no longer delegated.

## MagicBlock Integration

### Delegation hooks

The program now uses the official MagicBlock Rust SDK:

- `#[ephemeral]` on the program
- `#[delegate]` on the runtime delegation accounts
- `#[commit]` on commit instructions

This keeps the ER-specific logic isolated to the runtime lifecycle instead of bleeding into the entire control plane.

### Why only runtime is delegated

Only `QuotaRuntime` is on the high-frequency mutable path.

That makes it the right account to delegate. The rest of the model should remain simple base-layer state:

- orgs
- roles
- keys
- durable quota policy
- settled checkpoints

This is the highest-signal architecture for judges because it matches the real separation between config and execution.

## Magic Router Client Flow

The CLI now supports two transport modes:

### Base RPC

Use normal RPC for:

- setup
- admin
- policy changes
- key management
- runtime initialization
- checkpoint settlement

### Magic Router

Use Magic Router for transactions that touch delegated writable accounts:

- `consume_request`
- `commit_quota_runtime`
- `commit_and_undelegate_quota_runtime`

The CLI supports:

- `--magic-router <url>`
- or `MAGIC_ROUTER_URL`

Under the hood it uses `magic-router-sdk` and its account-aware blockhash flow.

### Blockhash / routing note

This matters:

- delegated writable transactions must not assume a normal base-layer blockhash is sufficient
- Magic Router derives a blockhash from the writable account set
- the CLI uses `sendMagicTransaction(...)` for routed runtime writes

In other words: normal `.rpc()` is fine for base control-plane instructions, but delegated runtime writes should go through Router-aware transaction sending.

## Repository Layout

```text
programs/gatekeeper/    Anchor program with ER hooks
client/gatekeeper.ts    Shared TS PDA + router helpers
cli/gatekeeper.ts       Base + Magic Router CLI
tests/gatekeeper.ts     Plain-Solana runtime compatibility tests
scripts/*.ts            Ops + diagnostics for local/devnet runtime flows
```

## Local Development

### Prerequisites

- Rust + Cargo
- Solana CLI
- AVM / Anchor 0.32.1
- Node.js 24+

### Setup

```bash
avm use 0.32.1
npm install
anchor build
```

## Test Instructions

### Rust check

```bash
cargo check -p gatekeeper
```

### TypeScript check

```bash
node_modules/.bin/tsc.cmd --noEmit
```

### Web2 demo test

This scenario-driven test is designed for demos. It walks through:

- tenant onboarding
- RBAC
- API key issuance
- quota enforcement
- audit settlement
- credential revocation
- tenant isolation

```bash
npm run test:demo
```

### Full local validator flow

```bash
avm use 0.32.1
anchor test --skip-build
```

The current automated suite validates the plain-Solana compatibility path:

1. gateway initialization
2. organization creation
3. role creation
4. API key creation
5. quota policy creation
6. runtime + checkpoint initialization
7. authorized request admission
8. unauthorized rejection
9. quota exceeded rejection
10. window expiry reset
11. checkpoint settlement
12. invalid-action rejection without quota consumption
13. rolling runtime audit cap at eight entries
14. revoked-key rejection after settlement

### What is not locally automated

Full ER delegation and Router execution require MagicBlock infrastructure. This repo wires the hooks and client flow, but the local automated tests stay focused on the base-compatible path.

## Local ER Diagnostics

The repo now includes focused diagnostics for local MagicBlock bring-up.

### Router capability probe

This checks whether the endpoint you plan to use for delegated writes actually supports the router-only methods the CLI depends on:

```bash
npm run diag:router -- --magic-router http://127.0.0.1:7799
npm run diag:router -- --magic-router http://127.0.0.1:7799 --strict true
```

The strict form fails unless the endpoint supports:

- `getIdentity`
- `getBlockhashForAccounts`
- `getDelegationStatus`

### Local transport doctor

This verifies:

- base RPC health
- base blockhash fetch
- router capability when `--magic-router` is provided
- a plain JavaScript system transfer on base RPC

```bash
npm run diag:transport -- --cluster http://127.0.0.1:8899 --wallet wallets/localnet-authority.json
npm run diag:transport -- --cluster http://127.0.0.1:8899 --magic-router http://127.0.0.1:7799 --wallet wallets/localnet-authority.json
```

This is the fastest way to distinguish:

- broken base validator setup
- missing router capabilities
- JavaScript client transport issues

### Local runbook

For the full recommended local validation order, see [docs/LOCAL_ER_RUNBOOK.md](/C:/Users/ezevi/Documents/GateKpr/docs/LOCAL_ER_RUNBOOK.md).

## Public Devnet Client

The public shared client for this submission is the CLI in this repo. It is designed to work against the deployed Devnet program without requiring a local program build.

### Quickstart

Use a funded Devnet wallet JSON file and point the client at Devnet explicitly:

```bash
npm install
npm exec --yes --package=@gatekpr/gatekeeper gatekpr help
node ./bin/gatekeeper.mjs help
node ./bin/gatekeeper.mjs init-gateway --cluster https://api.devnet.solana.com --wallet /path/to/devnet-wallet.json
```

### Self-contained Devnet demo

This is the fastest public test path for reviewers because it provisions fresh tenant state under their own wallet and prints a JSON report:

```bash
npm install
npx tsx scripts/devnet-web2-demo.ts --cluster https://api.devnet.solana.com --wallet /path/to/devnet-wallet.json --run-id reviewer01
```

### Public CLI examples

Once a reviewer has created their own org, role, key, and runtime state on Devnet, the shared client can be exercised directly:

```bash
node ./bin/gatekeeper.mjs create-org --cluster https://api.devnet.solana.com --wallet /path/to/devnet-wallet.json --name reviewer-acme
node ./bin/gatekeeper.mjs create-role --cluster https://api.devnet.solana.com --wallet /path/to/devnet-wallet.json --org-name reviewer-acme --role-name metrics-reader
node ./bin/gatekeeper.mjs attach-policy --cluster https://api.devnet.solana.com --wallet /path/to/devnet-wallet.json --org-name reviewer-acme --role-name metrics-reader --policy metrics:read
```

## CLI Usage

Show help:

```bash
node ./bin/gatekeeper.mjs help
npx tsx cli/gatekeeper.ts help
```

### Base setup flow

```bash
npx tsx cli/gatekeeper.ts init-gateway
npx tsx cli/gatekeeper.ts create-org --name acme
npx tsx cli/gatekeeper.ts create-role --org-name acme --role-name metrics-reader
npx tsx cli/gatekeeper.ts attach-policy --org-name acme --role-name metrics-reader --policy metrics:read
npx tsx cli/gatekeeper.ts create-key --org-name acme --role-name metrics-reader --label primary --key-material acme-key-1
npx tsx cli/gatekeeper.ts create-quota-policy --org-name acme --key-material acme-key-1 --max-requests 5 --window-seconds 60
npx tsx cli/gatekeeper.ts init-runtime --org-name acme --key-material acme-key-1
```

### ER runtime flow

```bash
npx tsx cli/gatekeeper.ts delegate-runtime --org-name acme --key-material acme-key-1 --commit-frequency-ms 500 --magic-router https://your-magic-router
npx tsx cli/gatekeeper.ts call-endpoint --org-name acme --key-material acme-key-1 --action metrics:read --magic-router https://your-magic-router
npx tsx cli/gatekeeper.ts commit-runtime --org-name acme --key-material acme-key-1 --magic-router https://your-magic-router
npx tsx cli/gatekeeper.ts undelegate-runtime --org-name acme --key-material acme-key-1 --magic-router https://your-magic-router
npx tsx cli/gatekeeper.ts settle-checkpoint --org-name acme --key-material acme-key-1
npx tsx cli/gatekeeper.ts runtime-status --org-name acme --key-material acme-key-1
npx tsx cli/gatekeeper.ts view-audit --org-name acme --key-material acme-key-1
```

## Ops Tooling

The repo now includes production-oriented operator tooling beyond the CLI:

### Runtime health snapshot

Returns JSON with:

- delegation state
- rolling vs settled sequence drift
- allowed/rejected totals
- rolling audit entries
- recommended next action

```bash
npm run ops:runtime-health -- --org-name acme --key-material acme-key-1
```

### Runtime reconciliation

Supports safe operator workflows:

- `status`
- `commit-only`
- `settle-if-safe`
- `undelegate-and-settle`

```bash
npm run ops:reconcile -- --org-name acme --key-material acme-key-1 --mode status
npm run ops:reconcile -- --org-name acme --key-material acme-key-1 --mode settle-if-safe
npm run ops:reconcile -- --org-name acme --key-material acme-key-1 --mode undelegate-and-settle --magic-router https://your-magic-router
```

### Devnet ER smoke flow

This script performs an idempotent control-plane setup and then runs:

- delegate
- routed request admission
- commit
- commit + undelegate
- checkpoint settlement

```bash
npm run smoke:devnet:er -- --cluster https://api.devnet.solana.com --magic-router https://your-magic-router --org-name acme --role-name metrics-reader --key-material acme-key-1
```

## Failure Recovery Runbook

### Case: runtime is delegated and traffic has accumulated

Expected operator path:

1. `runtime-status`
2. `commit-runtime` if you want to keep the session open
3. `undelegate-runtime` when closing the delegated window
4. `settle-checkpoint` on base

### Case: runtime is not delegated but drift is non-zero

That means runtime state has advanced beyond the durable checkpoint.

Use:

```bash
npm run ops:reconcile -- --org-name acme --key-material acme-key-1 --mode settle-if-safe
```

### Case: runtime status is unclear

Use:

```bash
npm run ops:runtime-health -- --org-name acme --key-material acme-key-1
```

The script prints a recommended action based on:

- delegated vs undelegated state
- rolling sequence drift
- rolling audit occupancy

## Production Hardening Gaps

This repo is stronger operationally now, but it is still not production-ready.

Remaining gaps:

- live MagicBlock ER integration testing on persistent infrastructure
- security review / formal audit
- alerting and dashboards around missed commits or checkpoint lag
- multi-operator auth and key management policies
- archival/indexing for long-term audit retention
- throughput and cost benchmarking under real traffic
- incident playbooks for router outages and validator-specific failures

## Design Tradeoffs

- Only the hot runtime account is delegated.
- `consume_request` mutates runtime only.
- Durable audit is checkpointed, not expanded into one base PDA per request.
- Full local ER execution is not mocked in tests.
- The checkpoint finalization step is intentionally explicit so the architecture stays inspectable.

## Why This Is A Better Judge Story

This version tells a clearer Solana-native backend story than a single-layer contract:

- base Solana is the durable control plane
- MagicBlock ER is the execution plane
- Router-aware clients send delegated writes correctly
- settlement is explicit
- the plain-Solana path still works without ER

That is a stronger backend-system translation than pretending every request mutation belongs in the same durability tier.