Skip to content

feat(cex-broker): add read/write broker surfaces for TEE split (FIET-… - #60

Closed
xlassix wants to merge 2 commits into
developfrom
FIET-902
Closed

feat(cex-broker): add read/write broker surfaces for TEE split (FIET-…#60
xlassix wants to merge 2 commits into
developfrom
FIET-902

Conversation

@xlassix

@xlassix xlassix commented Jul 6, 2026

Copy link
Copy Markdown
Collaborator

…902)

Summary by CodeRabbit

  • New Features
    • Added separate read vs write broker access controls, including per-stream permission gating.
    • Added CLI flags to disable read or write at startup.
    • Startup logging now reports enabled read/write access.
  • Bug Fixes
    • Requests are rejected earlier when the requested operation isn’t permitted.
    • Denied operations return clearer FAILED_PRECONDITION errors.
  • Documentation
    • Updated the sample environment file with the new broker surface settings and defaults.
  • Tests
    • Added coverage for broker-surface classification, RPC/subscribe denial behavior, and error messages.

@coderabbitai

coderabbitai Bot commented Jul 6, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: bab01687-c775-4d6e-9234-063dab97c928

📥 Commits

Reviewing files that changed from the base of the PR and between 4de9ca8 and 8269fbc.

📒 Files selected for processing (4)
  • .env.sample
  • src/handlers/execute-action/withdraw.ts
  • src/index.ts
  • test/subscribe-handler.test.ts
✅ Files skipped from review due to trivial changes (1)
  • .env.sample
🚧 Files skipped from review as they are similar to previous changes (3)
  • src/handlers/execute-action/withdraw.ts
  • test/subscribe-handler.test.ts
  • src/index.ts
📜 Recent review details
⚠️ CI failures not shown inline (2)

GitHub Actions: CI / test: feat(cex-broker): add read/write broker surfaces for TEE split (FIET-…

Conclusion: failure

View job details

##[group]Run bun run build
 �[36;1mbun run build�[0m
 shell: /usr/bin/bash -e {0}
 ##[endgroup]
 $ bun run proto-gen && bun run ./build.ts && bun run build:ts && bun run copy:dts && bun run copy:proto
 $ ./proto-gen.sh
 ✓ protobufjs field.js patched
 ✓ Wrote descriptor to /home/runner/work/cex-broker/cex-broker/src/proto/node.descriptor.ts
 ✓ Done
 �[0;31msrc/helpers/broker-surface.ts(194,2): error TS2322: Type '{ code: grpc.status.FAILED_PRECONDITION; message: string; }' is not assignable to type 'ServiceError'.
   Type '{ code: Status.FAILED_PRECONDITION; message: string; }' is missing the following properties from type 'StatusObject': details, metadata�[0m
 18 | function checkDiagnosticsErrors(diagnostics, failMessage) {
 19 |     if (diagnostics.length === 0) {
 20 |         return;
 21 |     }
 22 |     (0, logger_1.errorLog)(ts.formatDiagnostics(diagnostics, formatDiagnosticsHost).trim());
 23 |     throw new Error(failMessage);
                    ^
 error: Compiled with errors
       at checkDiagnosticsErrors (/home/runner/work/cex-broker/cex-broker/node_modules/dts-bundle-generator/dist/helpers/check-diagnostics-errors.js:23:15)
       at checkProgramDiagnosticsErrors (/home/runner/work/cex-broker/cex-broker/node_modules/dts-bundle-generator/dist/helpers/check-diagnostics-errors.js:15:5)
       at getDeclarationFiles (/home/runner/work/cex-broker/cex-broker/node_modules/dts-bundle-generator/dist/compile-dts.js:151:36)
       at compileDts (/home/runner/work/cex-broker/cex-broker/node_modules/dts-bundle-generator/dist/compile-dts.js:39:22)
       at generateDtsBundle (/home/runner/work/cex-broker/cex-broker/node_modules/dts-bundle-generator/dist/bundle-generator.js:14:63)
       at setup (/home/runner/work/cex-broker/cex-broker/node_modules/bun-plugin-dts/dist/index.js:1:1205)
       at /home/runner/work/cex-broker/cex-broker/build.ts:3:11
 Bun v1.3.9 (Linux x64 baseline)
 error: script "build" exited with code 1
 ##[error]Process completed with exit code 1.

GitHub Actions: CI / 0_test.txt: feat(cex-broker): add read/write broker surfaces for TEE split (FIET-…

Conclusion: failure

View job details

##[group]Run bun run build
 �[36;1mbun run build�[0m
 shell: /usr/bin/bash -e {0}
 ##[endgroup]
 $ bun run proto-gen && bun run ./build.ts && bun run build:ts && bun run copy:dts && bun run copy:proto
 $ ./proto-gen.sh
 ✓ protobufjs field.js patched
 ✓ Wrote descriptor to /home/runner/work/cex-broker/cex-broker/src/proto/node.descriptor.ts
 ✓ Done
 �[0;31msrc/helpers/broker-surface.ts(194,2): error TS2322: Type '{ code: grpc.status.FAILED_PRECONDITION; message: string; }' is not assignable to type 'ServiceError'.
   Type '{ code: Status.FAILED_PRECONDITION; message: string; }' is missing the following properties from type 'StatusObject': details, metadata�[0m
 18 | function checkDiagnosticsErrors(diagnostics, failMessage) {
 19 |     if (diagnostics.length === 0) {
 20 |         return;
 21 |     }
 22 |     (0, logger_1.errorLog)(ts.formatDiagnostics(diagnostics, formatDiagnosticsHost).trim());
 23 |     throw new Error(failMessage);
                    ^
 error: Compiled with errors
       at checkDiagnosticsErrors (/home/runner/work/cex-broker/cex-broker/node_modules/dts-bundle-generator/dist/helpers/check-diagnostics-errors.js:23:15)
       at checkProgramDiagnosticsErrors (/home/runner/work/cex-broker/cex-broker/node_modules/dts-bundle-generator/dist/helpers/check-diagnostics-errors.js:15:5)
       at getDeclarationFiles (/home/runner/work/cex-broker/cex-broker/node_modules/dts-bundle-generator/dist/compile-dts.js:151:36)
       at compileDts (/home/runner/work/cex-broker/cex-broker/node_modules/dts-bundle-generator/dist/compile-dts.js:39:22)
       at generateDtsBundle (/home/runner/work/cex-broker/cex-broker/node_modules/dts-bundle-generator/dist/bundle-generator.js:14:63)
       at setup (/home/runner/work/cex-broker/cex-broker/node_modules/bun-plugin-dts/dist/index.js:1:1205)
       at /home/runner/work/cex-broker/cex-broker/build.ts:3:11
 Bun v1.3.9 (Linux x64 baseline)
 error: script "build" exited with code 1
 ##[error]Process completed with exit code 1.

Walkthrough

Adds broker-surface read/write enablement controls, resolves them from env/CLI/config, and wires the checks through broker startup, gRPC server construction, execute-action and subscribe handlers. New tests cover classification, denial, and allowed-path behavior.

Changes

Broker Surface Access Control

Layer / File(s) Summary
Broker surface core module
src/helpers/broker-surface.ts, test/broker-surface.test.ts
Defines BrokerSurface, defaults, env resolution/validation, action/subscription/CCXT classification, access checks, and a denied-error builder, with unit tests.
CLI flags and broker startup resolution
src/cli.ts, src/commands/start-broker.ts, src/index.ts, .env.sample
CLI adds --no-read/--no-write flags; startBrokerCommand resolves and validates a merged surface; CEXBroker stores, validates, and logs the surface; .env.sample documents new env vars.
Server wiring
src/server.ts
getServer accepts a brokerSurface parameter and passes it into the execute-action and subscribe handler factories.
ExecuteAction handler and context guards
src/handlers/execute-action/handler.ts, src/handlers/execute-action/context.ts
ExecuteActionDeps/ExecuteActionContext gain brokerSurface; the handler classifies and denies disallowed actions with metrics; new rejectUnlessReadSurface/rejectUnlessWriteSurface guard helpers are exported.
Per-action handler guards
src/handlers/execute-action/order-book-call.ts, src/handlers/execute-action/orders.ts, src/handlers/execute-action/treasury-call.ts, src/handlers/execute-action/withdraw.ts
Order-book, order create/cancel/details, treasury-call, and withdraw handlers each perform an early read/write surface check before existing logic.
Subscribe handler surface gating
src/handlers/subscribe/handler.ts, test/subscribe-handler.test.ts
SubscribeDeps gains optional brokerSurface; subscriptions are classified and denied with a metric and error frame when disallowed.
Integration and unit tests
test/execute-action-surface-rpc.test.ts
New gRPC integration suite exercises read/write surface denial and success paths for direct actions and CCXT calls.

Estimated code review effort: 4 (Complex) | ~60 minutes

Possibly related PRs

Suggested reviewers: victorshevtsov, rsoury, csmithington

Poem

A rabbit hops through read and write,
and checks each gate by day and night.
If paws are barred, it says “not now,”
with FAILED_PRECONDITION’s solemn vow.
🐇

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly describes the main change: adding read/write broker surfaces to support the TEE split.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch FIET-902

Comment @coderabbitai help to get the list of available commands.

@xlassix
xlassix requested a review from csmithington July 6, 2026 10:18

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Nitpick comments (2)
test/subscribe-handler.test.ts (1)

487-511: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Consider adding a symmetric read-surface denial test.

Only write-surface denial is covered here (ORDERS subscription with writeEnabled: false). A companion case for readEnabled: false against a read subscription type (e.g. ORDERBOOK) would round out coverage of classifySubscription/isBrokerAccessAllowed in this handler test file.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@test/subscribe-handler.test.ts` around lines 487 - 511, Add a symmetric
read-surface denial test in subscribe-handler.test.ts to mirror the existing
write-surface case. Use createSubscribeHandler with brokerSurface set to
readEnabled: false and writeEnabled: true, then exercise a read subscription
such as ORDERBOOK via createSubscribeCall and verify the handler rejects it with
the expected access-disabled error and ends the call. This should cover the read
path in classifySubscription and isBrokerAccessAllowed alongside the current
ORDERS/write-disabled test.
test/execute-action-surface-rpc.test.ts (1)

108-348: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Extract a shared helper for server/client setup.

Every test repeats the same getServer(...) call with identical undefined, undefined placeholders, bindServer, and client construction. A small setupServer(pool, brokerSurface) helper returning { client } would remove this repetition and make future signature changes (like the one flagged above) a one-line update.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@test/execute-action-surface-rpc.test.ts` around lines 108 - 348, The tests
repeat the same server/client bootstrap logic in each case, making them noisy
and brittle. Extract a shared helper around getServer, bindServer, and
grpcObj.cex_broker.cex_service construction in
execute-action-surface-rpc.test.ts, ideally a setupServer(pool, brokerSurface)
utility that returns the bound client (and port if needed). Update each test to
use that helper so the repeated undefined placeholders and client setup live in
one place and future getServer signature changes only need a single update.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/cli.ts`:
- Around line 20-27: The negated Commander flags are being read from the wrong
option names, so the restrictions never take effect. Update the CLI handling in
cli.ts to use options.read and options.write for the --no-read and --no-write
flags, and adjust any related conditionals or defaults that currently reference
options.noRead or options.noWrite. Keep the fix centered on the option parsing
logic and the symbols that consume these flags.

In `@src/helpers/broker-surface.ts`:
- Around line 190-198: The denial message in buildBrokerSurfaceDeniedError is
misleading for access === "unknown" because it always maps to a write-disabled
message. Update the logic in buildBrokerSurfaceDeniedError to distinguish
unknown CCXT methods from true write-only denials and emit a message that
reflects the actual broker requirement in isBrokerAccessAllowed, using the
existing access value and the read/write enabled context rather than defaulting
unknown to write.

---

Nitpick comments:
In `@test/execute-action-surface-rpc.test.ts`:
- Around line 108-348: The tests repeat the same server/client bootstrap logic
in each case, making them noisy and brittle. Extract a shared helper around
getServer, bindServer, and grpcObj.cex_broker.cex_service construction in
execute-action-surface-rpc.test.ts, ideally a setupServer(pool, brokerSurface)
utility that returns the bound client (and port if needed). Update each test to
use that helper so the repeated undefined placeholders and client setup live in
one place and future getServer signature changes only need a single update.

In `@test/subscribe-handler.test.ts`:
- Around line 487-511: Add a symmetric read-surface denial test in
subscribe-handler.test.ts to mirror the existing write-surface case. Use
createSubscribeHandler with brokerSurface set to readEnabled: false and
writeEnabled: true, then exercise a read subscription such as ORDERBOOK via
createSubscribeCall and verify the handler rejects it with the expected
access-disabled error and ends the call. This should cover the read path in
classifySubscription and isBrokerAccessAllowed alongside the current
ORDERS/write-disabled test.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: b3d09285-e214-4584-9df9-cc18d262a6e5

📥 Commits

Reviewing files that changed from the base of the PR and between f6b5ed7 and 4de9ca8.

📒 Files selected for processing (16)
  • .env.sample
  • src/cli.ts
  • src/commands/start-broker.ts
  • src/handlers/execute-action/context.ts
  • src/handlers/execute-action/handler.ts
  • src/handlers/execute-action/order-book-call.ts
  • src/handlers/execute-action/orders.ts
  • src/handlers/execute-action/treasury-call.ts
  • src/handlers/execute-action/withdraw.ts
  • src/handlers/subscribe/handler.ts
  • src/helpers/broker-surface.ts
  • src/index.ts
  • src/server.ts
  • test/broker-surface.test.ts
  • test/execute-action-surface-rpc.test.ts
  • test/subscribe-handler.test.ts
📜 Review details
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2026-04-14T06:47:01.283Z
Learnt from: csmithington
Repo: usherlabs/cex-broker PR: 38
File: src/client.dev.ts:0-0
Timestamp: 2026-04-14T06:47:01.283Z
Learning: In this codebase, gRPC action constants such as `FetchTicker`, `FetchFees`, and `FetchAccountId` should be sourced from `src/helpers/constants.ts` and imported from there (e.g., used by both `src/client.dev.ts` and `src/server.ts`). Do not import or reference generated proto TypeScript artifacts (for example `./proto/cex_broker/Action`), since those generated files are git-ignored and won’t be available/committed consistently.

Applied to files:

  • src/handlers/execute-action/order-book-call.ts
  • src/handlers/execute-action/withdraw.ts
  • src/helpers/broker-surface.ts
  • src/commands/start-broker.ts
  • src/handlers/execute-action/context.ts
  • src/handlers/execute-action/treasury-call.ts
  • src/cli.ts
  • src/handlers/subscribe/handler.ts
  • src/index.ts
  • src/handlers/execute-action/orders.ts
  • src/handlers/execute-action/handler.ts
  • src/server.ts
🔇 Additional comments (18)
test/execute-action-surface-rpc.test.ts (2)

1-107: LGTM!


109-118: 🗄️ Data Integrity & Integration

No issue with getServer argument order

brokerSurface is already the 8th positional argument in src/server.ts, after the two optional otelMetrics and brokerArchiver slots, so these test calls match the signature.

			> Likely an incorrect or invalid review comment.
test/subscribe-handler.test.ts (1)

1-1: LGTM!

src/server.ts (1)

31-31: LGTM!

Also applies to: 42-49

src/handlers/execute-action/context.ts (1)

45-64: LGTM!

src/handlers/execute-action/handler.ts (1)

118-132: LGTM!

Also applies to: 177-177

src/handlers/execute-action/order-book-call.ts (1)

44-47: LGTM!

src/handlers/execute-action/orders.ts (1)

26-29: LGTM!

Also applies to: 177-179, 282-284

src/handlers/execute-action/treasury-call.ts (1)

23-29: LGTM!

src/handlers/execute-action/withdraw.ts (1)

21-24: LGTM!

src/handlers/subscribe/handler.ts (1)

58-58: LGTM!

Also applies to: 320-320, 391-407

src/helpers/broker-surface.ts (2)

1-166: LGTM!


190-198: 🎯 Functional Correctness

No type change needed here. src/helpers/grpc/callbacks.ts already returns grpc.ServiceError objects with just code and message, and the handlers pass that same shape to ctx.wrappedCallback, so this helper is consistent with the existing pattern.

			> Likely an incorrect or invalid review comment.
test/broker-surface.test.ts (1)

1-111: LGTM!

.env.sample (1)

11-17: LGTM!

src/cli.ts (1)

5-5: LGTM!

Also applies to: 58-64

src/commands/start-broker.ts (1)

1-33: LGTM!

src/index.ts (1)

15-19: LGTM!

Also applies to: 57-57, 206-211, 299-302, 317-317

Comment thread src/cli.ts
Comment on lines +20 to +27
.option(
"--no-read",
"Disable read operations (fetch actions and market streams)",
)
.option(
"--no-write",
"Disable write operations (orders, withdrawals, and user streams)",
)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Confirm the Commander option property naming convention for negatable options
rg -n "no-read|no-write|noRead|noWrite" src/cli.ts

Repository: usherlabs/cex-broker

Length of output: 252


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Inspect the relevant section of the CLI with line numbers.
sed -n '1,90p' src/cli.ts | cat -n

Repository: usherlabs/cex-broker

Length of output: 2654


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Look for the Commander version and any local typing that may affect option names.
rg -n '"commander"|from "commander"|from '\''commander'\''' package.json src -g '!node_modules'

Repository: usherlabs/cex-broker

Length of output: 250


Use options.read / options.write for these negated flags.

Commander maps --no-read and --no-write to the positive properties, so options.noRead and options.noWrite stay undefined and the surface restrictions never apply.

🐛 Proposed fix
-			if (options.noRead) {
+			if (options.read === false) {
 				brokerSurface.readEnabled = false;
 			}
-			if (options.noWrite) {
+			if (options.write === false) {
 				brokerSurface.writeEnabled = false;
 			}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/cli.ts` around lines 20 - 27, The negated Commander flags are being read
from the wrong option names, so the restrictions never take effect. Update the
CLI handling in cli.ts to use options.read and options.write for the --no-read
and --no-write flags, and adjust any related conditionals or defaults that
currently reference options.noRead or options.noWrite. Keep the fix centered on
the option parsing logic and the symbols that consume these flags.

Comment on lines +190 to +198
export function buildBrokerSurfaceDeniedError(
access: BrokerAccessClass | "unknown",
): grpc.ServiceError {
const surface = access === "write" || access === "unknown" ? "write" : "read";
return {
code: grpc.status.FAILED_PRECONDITION,
message: `${surface === "write" ? "Write" : "Read"} operations are disabled on this broker deployment`,
};
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Misleading denial message for "unknown" CCXT methods on a write-only broker.

When access === "unknown", the function always reports "Write operations are disabled", even though an unknown method is actually denied because it requires both read and write enabled (isBrokerAccessAllowed, Line 179). On a write-only broker (readEnabled: false, writeEnabled: true), this message is factually wrong and could mislead operators troubleshooting denials.

💬 Suggested clarification
 export function buildBrokerSurfaceDeniedError(
 	access: BrokerAccessClass | "unknown",
 ): grpc.ServiceError {
-	const surface = access === "write" || access === "unknown" ? "write" : "read";
+	if (access === "unknown") {
+		return {
+			code: grpc.status.FAILED_PRECONDITION,
+			message:
+				"This operation requires both read and write access, which is not fully enabled on this broker deployment",
+		};
+	}
+	const surface = access;
 	return {
 		code: grpc.status.FAILED_PRECONDITION,
 		message: `${surface === "write" ? "Write" : "Read"} operations are disabled on this broker deployment`,
 	};
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
export function buildBrokerSurfaceDeniedError(
access: BrokerAccessClass | "unknown",
): grpc.ServiceError {
const surface = access === "write" || access === "unknown" ? "write" : "read";
return {
code: grpc.status.FAILED_PRECONDITION,
message: `${surface === "write" ? "Write" : "Read"} operations are disabled on this broker deployment`,
};
}
export function buildBrokerSurfaceDeniedError(
access: BrokerAccessClass | "unknown",
): grpc.ServiceError {
if (access === "unknown") {
return {
code: grpc.status.FAILED_PRECONDITION,
message:
"This operation requires both read and write access, which is not fully enabled on this broker deployment",
};
}
const surface = access;
return {
code: grpc.status.FAILED_PRECONDITION,
message: `${surface === "write" ? "Write" : "Read"} operations are disabled on this broker deployment`,
};
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/helpers/broker-surface.ts` around lines 190 - 198, The denial message in
buildBrokerSurfaceDeniedError is misleading for access === "unknown" because it
always maps to a write-disabled message. Update the logic in
buildBrokerSurfaceDeniedError to distinguish unknown CCXT methods from true
write-only denials and emit a message that reflects the actual broker
requirement in isBrokerAccessAllowed, using the existing access value and the
read/write enabled context rather than defaulting unknown to write.

@rsoury

rsoury commented Aug 2, 2026

Copy link
Copy Markdown
Member

Closed as auth gate per read/write model is excessive.

@rsoury rsoury closed this Aug 2, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants