100 Commits
Author SHA1 Message Date
zeekayandHanzo Dev 442058df58 Workflows ask for the runner label that exists
One label per runner, spelled <os>-<arch>. The aliases these jobs named
were removed, and a job naming a label no runner carries waits instead of
failing.

Co-authored-by: Hanzo Dev <[email protected]>
2026-09-16 01:54:36 -07:00
zeekayandHanzo Dev 6b176637d5 State a version once
A version written into a manifest is written into every manifest that wants it,
which is how eslint came to exist in a hundred and four forms across this estate
and how a package came to resolve twice at run time. The catalog moves the number
out of the manifest: a manifest names the package, the workspace names the
version.

Four are held below their latest because the newer release cannot be used, and
the reason is recorded rather than the version alone: @noble/hashes 2 renamed
every subpath the web3 stack imports, @splinetool/runtime 2 depends on a package
that was never published, @dynamic-labs 5.4.1 the same, and react-resizable-panels
3 points its types condition at the bundle.

Co-authored-by: Hanzo Dev <[email protected]>
2026-09-08 02:36:39 -07:00
zeekay 64bc93e6f8 the face is Zen, and it ships from @hanzo/font
One family. Font stacks name Zen and fall back to the native faces, tokens
name the role rather than a face, and the packages that used to supply a
face now point at @hanzo/font.

Self-hosted @font-face blocks are removed rather than renamed: rewriting the
family in one would leave a src pointing at a file this repo no longer ships.
@hanzo/font carries the binaries and declares the faces, so the local copies
go with the declaration that used them.

Comments naming a face we no longer use are deleted, not reworded.
2026-09-01 08:15:55 -07:00
zeekayandHanzo Dev cd7672d14b the corporate name is Lux Industries Inc
Lux Partners Limited is not the entity behind this work. Every place the
old name stood — copyright lines, site footers, brand config, legal and
compliance text — now names the company that actually holds it.

lux.fund is the exception and is not renamed to match: that site belongs
to Lux Assets Management LLC, a different company, and it says so.

Two kinds of line were left for a person rather than rewritten. A dated
news item recording that Lux Partners Limited was established in the Isle
of Man is a historical fact, and renaming it would falsify the record. And
a sentence asserting that the company is domiciled in the Isle of Man is a
claim about a jurisdiction, not a name — an Inc or an LLC is not an Isle
of Man company, so swapping the name there would assert something untrue.

Co-authored-by: Hanzo Dev <[email protected]>
2026-08-28 12:56:26 -07:00
zeekayandHanzo Dev a29939d2a8 ci: move the build onto our own forge, and leave only one copy of it
These workflows already ran green on git.hanzo.ai — they were among the few
that had been corrected to authenticate to ghcr.io with GHCR_USER/GHCR_TOKEN
rather than secrets.GITHUB_TOKEN, which on this forge is a git.hanzo.ai token
that ghcr.io answers "denied: denied".

What was wrong was the location. .github/workflows is read by both the forge
and GitHub, and the luxfi GitHub org has zero registered runners, so every one
of these also sat queued on GitHub until it was cancelled. One build, two
schedulers, one of which can never finish. Moving to .hanzo/workflows leaves
exactly one.

Runner label normalised to lux-build-linux-amd64. lux-build, lux-build-amd64
and [self-hosted, linux, amd64] are three names the git-runner answers to for
the same pool; one name for one thing.

Co-authored-by: Hanzo Dev <[email protected]>
2026-08-06 17:48:00 -07:00
zeekayandHanzo Dev cf2dec04aa fix(ci): set GOPRIVATE in ci.yml, the last place still missing it
`go mod download` failed with a checksum mismatch on hanzoai/[email protected].5.
Same cause already documented in the Dockerfile and release.yml -- commerce went
private AFTER it was notarised, so the public proxy still serves the pre-privatisation
zip while go.sum records the direct hash the tag points at today.

Both other places were fixed; ci.yml was missed, so it kept failing alone.

go.sum is CORRECT and must not be "fixed" to the proxy value -- sum.golang.org
holds the proxy hash, so a go.sum-vs-sumdb audit points the wrong way for this
module. Measured with GOPRIVATE set: `go mod download` exits 0 against the
committed go.sum.

Co-authored-by: Hanzo Dev <[email protected]>
2026-08-02 03:27:41 -07:00
zeekayandHanzo Dev c2ca958a5f build: fetch private hanzoai modules direct, with a mounted credential
`go mod download` failed inside the image build:

    verifying github.com/hanzoai/[email protected].5: checksum mismatch
    SECURITY ERROR

Three faults compounded. github.com/hanzoai/* is private, but the Dockerfile
said "No GOPRIVATE -- public modules resolve via the immutable public proxy",
which stopped being true when that repo went private; Go therefore asked the
proxy, which still serves the pre-privatisation zip. The credential arrived as
`ARG GITHUB_TOKEN` and the workflow passed neither build-args nor secrets, so it
expanded to `https://@github.com/` and authenticated nothing. And an ARG is
recorded in image history, so had a token ever been supplied that way it would
have shipped inside the published image.

Now: GOPRIVATE names hanzoai only, and the token arrives as a mounted secret
scoped to the one RUN -- the same `--mount=type=secret,id=gh_token` shape used
across the estate. luxfi/* stays public on purpose, so the proxy keeps it pinned
to bytes the checksum database signed.

go.sum is untouched: it holds the direct hash, which is the correct one here.

Co-authored-by: Hanzo Dev <[email protected]>
2026-08-01 19:16:14 -07:00
zeekayandHanzo Dev 01ae0a70b6 ci: fetch private hanzoai modules direct, and release with a real GitHub token
The release died in goreleaser`s `go mod tidy` hook:

    verifying github.com/hanzoai/[email protected].5: checksum mismatch
      downloaded: h1:RS6pjCKOAjigi…
      go.sum:     h1:DjGiR7NSOZYD+…
    SECURITY ERROR

github.com/hanzoai/* is private, but nothing here set GOPRIVATE, so Go resolved
it through the public proxy -- which still serves the bits from before the repo
went private, a different zip than the tag points at today. go.sum records the
direct hash, so the two could never agree.

⚠️ go.sum is the CORRECT side and is left untouched. I checked the other way
first: with GOPRIVATE set, `go mod download` exits 0 against the committed
go.sum. sum.golang.org holds the proxy value, so an audit of go.sum against the
checksum database points the wrong way for a module that went private AFTER it
was notarised. The polarity has to be measured per module, not assumed -- the
opposite case (go.sum wrong, sumdb right) was true for luxfi/pq in dex an hour
ago.

A direct fetch of a private module needs a real credential, so GHCR_TOKEN is
wired in as a git url rewrite. It is also now what goreleaser publishes with:
`secrets.GITHUB_TOKEN` is git.hanzo.ai`s token on a self-hosted pool and
github.com has never seen it.

luxfi/* is deliberately not in GOPRIVATE -- it is public, and resolving it
through the proxy is what keeps it pinned to bytes the checksum database signed.

Co-authored-by: Hanzo Dev <[email protected]>
2026-08-01 18:47:18 -07:00
zeekayandHanzo Dev d034c38050 fix(auth): reject non-canonical base64 in bearer tokens
Go's base64 decoders are lenient in two independent ways, and both JWT
verifiers used them raw, so one credential validated under several spellings.

The unused low bits of the final quantum are ignored: an HMAC-SHA256
signature is 32 bytes in 43 base64url characters, leaving 2 slack bits and
so 4 accepted strings per admin token; an RSA-2048 signature is 256 bytes in
342 characters, leaving 4 slack bits and so 16 per IAM token. Separately,
\r and \n are skipped wherever they appear, which Encoding.Strict() does not
close -- so Strict alone would not have been a fix.

Every string-keyed structure over a credential silently splits under that: a
denylist misses the spellings it was not handed, an audit log reads one actor
as several, a rate limiter multiplies the budget by the class size.

pkg/token holds the one canonical encoding. Decode round-trips rather than
enumerating failure modes -- a segment is canonical exactly when it is the
encoding of the bytes it decodes to -- and rejects rather than normalizes.
token.ID is the hash to key revocation, audit and rate limiting on, so the
identity is never the submitted string.

Both verifiers now decode every segment through it. JWKS n/e stays lenient on
purpose: public key material from the trusted IAM endpoint, where every
spelling is the same key.

Also in pkg/admin: sign returns raw MAC bytes instead of text, dropping an
encode-then-decode round trip in the verify path; the header is now decoded
and required to declare HS256, closing alg confusion under future refactor;
and a comment referencing a long-deleted hashPassword is gone.

Tests: exactly one spelling authenticates on both paths; revocation keyed on
token.ID holds across all four; CR/LF, padding, standard alphabet and empty
segments rejected. TestValidateToken_TamperedSignature substituted a fixed
'X' that was a no-op whenever the token already ended in one -- a 1-in-64
flake that this bug made a 1-in-16 one -- now derived from the token.

LLM.md: admin auth section corrected (bcrypt cost 12, not SHA-256+salt; no
ADMIN_* env vars exist; middleware strips those headers rather than setting
them; package is not mounted).

Co-authored-by: Hanzo Dev <[email protected]>
2026-08-01 18:13:16 -07:00
zeekayandHanzo Dev c9f78a7f10 ci: ghcr login uses the credential luxfi actually provisions
Every job in this file runs on a lux self-hosted pool, so GitHub is not the
executor -- but the login presented `secrets.GITHUB_TOKEN`, which git.hanzo.ai
mints and ghcr.io has never seen:

    Error response from daemon: Get "https://ghcr.io/v2/": denied: denied

The login step failed and every job after it skipped, so the image was never
published. The pair is GitHub`s own idiom and is correct there; it is simply
not a credential anywhere else.

orgs/luxfi provisions exactly GHCR_USER + GHCR_TOKEN. Named directly rather
than through a `||` chain: a missing org secret evaluates to "", so a fallback
silently swaps credentials and the log never says which one was used -- that is
what made the same failure in luxfi/trader read as a registry problem.

Co-authored-by: Hanzo Dev <[email protected]>
2026-08-01 16:41:06 -07:00
zeekayandHanzo Dev b681bbd0e3 build: bump Go builder images to 1.26.5, set GOTOOLCHAIN=auto
Currency bump: golang builder images -> 1.26.5 (latest stable).

Adds ENV GOTOOLCHAIN=auto to each Go builder stage. The official golang
images set GOTOOLCHAIN=local, so a builder image older than the module's
go directive is a hard mid-build failure. The pin stays the hermetic fast
path; auto is the safety net for the next go.mod bump.

No go.mod go directive was changed. Builder images move; directives stay.

Co-authored-by: Hanzo Dev <[email protected]>
2026-08-01 16:25:21 -07:00
zeekayandHanzo Dev 53f764d698 chore(aml): drop optional external-Jube provider — luxfi/aml is native
Remove the optional github.com/luxfi/compliance/pkg/jube provider from the
broker compliance layer. AML screening keeps working via the OFAC SDN list
and the ScamSniffer database; the native github.com/luxfi/aml engine (amld)
is the deeper risk-scoring backend. Removed the jube import, the jubeClient
field/option, and every jube-guarded block; docs updated to reflect the
native engine.

Co-authored-by: Hanzo Dev <[email protected]>
2026-07-17 12:16:00 -07:00
zeekay b0f5f68db1 ci: route linux/amd64 jobs to lux-build-amd64 ARC scale set 2026-07-10 21:54:09 -07:00
zeekayandHanzo Dev 2bcd1c2d54 fix(go.sum): refresh re-tagged dependency checksums
Co-authored-by: Hanzo Dev <[email protected]>
2026-06-30 14:18:34 -07:00
zeekay 8ada1f7fd3 docs(brand): add hero banner 2026-06-28 20:36:04 -07:00
zeekay 6e28e2f529 chore(brand): dynamic hero banner 2026-06-28 20:36:03 -07:00
zeekay 7275d47c8d build: drop GOPRIVATE — public modules use immutable proxy+sumdb
The Dockerfile set GOPRIVATE=luxfi/*,hanzoai/* which routes module
fetches 'direct' (git) and bypasses sum.golang.org. luxfi/hanzoai Go
modules are all PUBLIC; after a tag was force-republished, the git tag
content diverged from the immutable proxy/sumdb hash — so 'go mod
download' fetched bits that no longer matched the canonical, sumdb-correct
go.sum, failing image builds with SECURITY ERROR checksum mismatch.

Dropping GOPRIVATE resolves these modules through the default public
proxy + sumdb, yielding the canonical immutable hash that matches go.sum
and that a re-publish can no longer break. GITHUB_TOKEN insteadOf stays
for the direct fallback. go.sum re-tidied against the public sumdb is
byte-identical (already canonical); only the Dockerfile changes.
2026-06-28 14:30:40 -07:00
zeekay c015c30416 docs: KMS naming in architecture 2026-06-24 11:01:14 -07:00
zeekay 2cad3b5a92 ci: route to canonical native arcd labels [self-hosted, linux, <arch>]
Replaces non-canonical scale-set / org-prefixed labels with the
existing labels every arcd host registers with. Matches evo for
amd64 and spark for arm64. No new labels added.
2026-06-10 22:34:14 -07:00
zeekay 0c9952d0b9 deps: bump Go to 1.26.4 across go.mod, Dockerfiles, GH Actions
Workspace-wide sync. luxfi/node already shipped on 1.26.4 in v1.30.6
(commit 121aca1fa9); this is the cross-repo catch-up.
2026-06-10 22:34:14 -07:00
zeekay ef49ef1e8c go: 1.26.3 → 1.26.4 (security: crypto/x509, mime, net/textproto) 2026-06-06 22:04:29 -07:00
zeekay 42b659524d WIP: session checkpoint 2026-06-05 (codec rip in flight) 2026-06-05 15:33:52 -07:00
zeekay d95bbdfc03 fix: gofmt -s across repo (CI format check) 2026-06-02 11:37:59 -07:00
zeekay 25ae9559e5 feat(provider/northcapital): LoadFromKMS implemented
Replace the ErrNotImplemented stub on Config.LoadFromKMS with a real
implementation against a narrow KMSGetter interface. Adapter has zero
direct dependency on luxfi/kms; the canonical luxfi/kms.GetWith
satisfies KMSGetter via a 1-line wrapper at brokerd startup.

Signature: (ctx, KMSGetter, env) error. env is "sandbox" or "prod".

KMS path scheme (per integration brief §2):
  shared/northcapital/<env>/clientID         (required)
  shared/northcapital/<env>/developerAPIKey  (required)
  shared/northcapital/<env>/webhookAuthKey   (required)
  shared/northcapital/<env>/webhookDecryptionKey  (optional)

Error sentinels:
  ErrKMSKeyMissing  — required key absent / empty value
  ErrKMSDecryption  — transport / decryption failure

Tests: 13 cases, all pass under -race. Conformance:
  - sandbox + prod happy paths populate every field
  - exact KMS path shape (shared/northcapital/<env>/<field>)
  - missing required key → ErrKMSKeyMissing (each of 3 required)
  - empty value treated as missing
  - decryption error → ErrKMSDecryption
  - transport error → ErrKMSDecryption
  - transport error on optional key still fatal
  - missing optional key tolerated; preserves caller-supplied value
  - nil KMSGetter rejected
  - empty / unknown env rejected

Source-of-design: Public-Spec
Source-ref: legal/partnerships/northcapital/
  NorthCapital_Lux_Integration_Brief.md §2; github.com/luxfi/kms
  public Go surface (kms.Get / kms.GetWith)
Clean-Team-Member: dev-agent (P0 NCPS LoadFromKMS completion)
Signed-off-by: dev <[email protected]>
2026-05-26 18:15:19 -07:00
zeekay 7b09f3d3d0 feat(pretrade): G-37 synchronous pre-trade compliance gate
New pkg/pretrade implementing Gap G-37 (Stage 6.5): synchronous gate
at order-entry that fetches state from cap-table, transfer, AML, KYC,
and on-chain ERC-3643 compliance providers and decides allow / deny /
escalate. Each predicate is a composable internal function; the
Decision carries every actionable reason in a single round.

Wired into the broker order path via Wrap[Req, Resp] generic
middleware so existing router signatures stay untouched (additive,
forwards-only). GateError exposes the Decision to callers that surface
required actions to the investor.

Audit is delegated through AuditSink; canonical AuditLogSink bridges
to broker/pkg/audit. PII (ClientIP) is masked to /24 / /48 in the
audit row.

Test coverage: happy path, every hard-deny predicate (kyc_not_verified,
kyc_stale, not_accredited, accreditation_stale_{income,third_party},
suitability_{missing,stale}, aml_not_clean, open_sar,
ofac_screen_stale, lockup_active, rule144_holding_period,
country_restricted, max_holders, transfer_restricted,
chain_compliance_refused), every escalate predicate
(suitability_ambiguous, kyc_near_expiry, accreditation_near_expiry),
upstream-provider error propagation, input validation, max-holder
sell-side exemption, max-holder existing-holder exemption, PII
redaction. 41 tests passing under -race.

Source-of-design: Lux-Prior-IP
Source-ref: f7a8d7a
Clean-Team-Member: dev-agent (P0 G-37 implementation)
Signed-off-by: dev <[email protected]>
2026-05-26 18:11:50 -07:00
zeekay ad493e2310 ci(docker): single-arch amd64 on lux-build runner (per-arch pools not online yet) 2026-05-24 06:33:28 -07:00
zeekay a044bf8600 ci(docker): inline native multi-arch buildx (drop broken cross-org reusable)
Builds on luxfi-org ARC runners per-arch (lux-build-linux-amd64,
lux-build-linux-arm64), pushes by-digest, then assembles a multi-arch
manifest tagged with the semver release tag plus :latest. No QEMU, no
cross-org workflow_call.
2026-05-24 06:03:42 -07:00
zeekay a7ebfca4fd ci: remove cross-org workflow-sanity caller (rejects lux-build-* labels but cross-org docker-build reusable mandates them — contradiction) 2026-05-24 01:42:35 -07:00
zeekay adf445b0c9 ci: fix runner-deploy + ci.yml runner (use lux-build-linux-amd64 — lux-deploy pool not deployed) 2026-05-24 01:37:57 -07:00
zeekay d64edf75d4 ci: install syft to user-writable path; drop cross-org secrets:inherit 2026-05-24 01:28:57 -07:00
zeekay 9b673e4d09 merge: ci/canonical-docker-build (release matrix + docs) 2026-05-24 01:13:09 -07:00
zeekay 57a6aaa9ac docs: generic integration patterns 2026-05-24 01:12:17 -07:00
zeekay c3ad56cda8 ci: native six-platform release matrix + multi-arch container
goreleaser builds the six-platform binary matrix (linux/darwin/windows ×
amd64/arm64) with SBOMs and checksums on `v*` tag push. docker.yml is
pinned to the lux-build-linux-{amd64,arm64} ARC runners (native, no
QEMU) and delegates to the canonical hanzoai/.github reusable workflow.
release.yml triggers on `v*` tags and runs on the lux-build self-hosted
scale-set.
2026-05-24 01:10:33 -07:00
zeekayandGitHub e9ccaea03b chore: bump Go toolchain to 1.26.3 (#3)
Pin Go version to 1.26.3 across go.mod, CI workflows, and Dockerfiles
for canonical alignment with the rest of the luxfi/* stack.
2026-05-16 16:42:22 -07:00
zeekayandGitHub cd472c25cb chore: add LICENSE (Lux Ecosystem License v1.2) + LICENSING.md pointer (#2)
* chore: add Lux Ecosystem License v1.2

* docs: add LICENSING.md pointer to canonical IP/licensing strategy
2026-05-16 10:45:27 -07:00
zeekay 53e80aaa11 go.mod: bump go directive to 1.26.3 (security advisory) 2026-05-12 21:28:28 -07:00
zeekayandGitHub f13f72439b ci: migrate to canonical docker-build reusable (#1)
- Add docker.yml using hanzoai/.github/.github/workflows/docker-build.yml@main
- Add workflow-sanity.yml to enforce canonical CI contract
- Remove bespoke docker build steps from existing workflows

Refs: hanzoai/.github canonical Docker CI contract.
2026-04-23 19:12:18 -07:00
zeekay 5c02d9b42d ci: migrate to canonical docker-build reusable
- Add docker.yml using hanzoai/.github/.github/workflows/docker-build.yml@main
- Add workflow-sanity.yml to enforce canonical CI contract
- Remove bespoke docker build steps from existing workflows

Refs: hanzoai/.github canonical Docker CI contract.
2026-04-23 19:09:31 -07:00
zeekay cebba77350 chore: optimize .dockerignore + add .gcloudignore for minimal build context 2026-04-17 13:47:45 -07:00
zeekay e42f801472 feat: full FI asset field parsing — description, ticker, coupon, maturity 2026-04-17 10:07:17 -07:00
zeekay 6981a9a08d fix: generic test emails 2026-04-16 22:08:09 -07:00
zeekay 97ba736ff0 unify FI assets into ListAssets — one endpoint for all asset classes
ListAssets now fetches Alpaca FI treasuries + corporates alongside
equities and crypto. Maps CUSIP as symbol, bond_status as tradable
filter. Corporates 403 (not subscribed) silently skipped.
ATS registerMarketsFromBroker picks them up automatically.
2026-04-16 21:07:02 -07:00
zeekay 582c834a39 fix: add missing go.sum entries for luxfi/compliance and luxfi/zap
go mod tidy was not run after replace directives were removed.
CI failed because go.sum lacked entries for luxfi/compliance v0.2.2,
luxfi/zap v0.2.0, and their transitive dependencies.
2026-04-14 03:16:24 -07:00
zeekay a27676c2af feat(alpaca_omnisub): JWT P-256 auth + pluggable httpauth package
- New pkg/provider/httpauth/ with Authenticator interface and 3 impls:
  * Basic (legacy HTTP-Basic for Kraken, old Alpaca, etc.)
  * ClientSecret (OAuth2 client_secret_post, e.g. Alpaca's Client Secret tier)
  * JWT (RFC 7523 private_key_jwt, e.g. Alpaca's JWT P-256 tier)
- alpaca_omnisub: Config gains ClientID + PrivateKeyPEM; do() unifies
  auth via Authenticator — one codepath covers Basic OR JWT Bearer.
- Alpaca quirk captured: omit `kid` from JWT header (authx rejects it with
  invalid_request even though RFC 7523 permits).
- Token URL auto-selects authx.sandbox vs authx prod by base URL.
- Token cache with 60s safety refresh window; 15-min Alpaca TTL.
- Verified live against both devnet (CKVH...) and testnet (CKZR...)
  client credentials: token exchange 200, live /v1/clock, /v1/accounts,
  and real equity orders placed under Bearer auth.
2026-04-13 15:16:08 -07:00
zeekay e52be5f2a9 provider: scaffold btgpactual, ig, questrade, saxo, stake, zerodha adapters 2026-04-13 13:49:03 -07:00
zeekay 86afa4ba31 provider: alpaca_omnisub — Alpaca Broker OmniSub (omnibus + sub-accounts)
Sibling to alpaca provider. Runs in parallel with standalone Alpaca using
independent ALPACA_OMNISUB_* env vars. Full Provider interface plus
per-sub positions, omnibus-routed transfers, batch journals + reverse-batch
for trade reversals, omnibus snapshot for reconciliation. 19 tests.
2026-04-13 13:49:03 -07:00
zeekay f8d7400eac feat(broker): adapter stubs for sdx, adgm, securrency, cssf, euronext, xetra
Six new venue adapters for CH, AE, EU jurisdictions. Each implements
provider.Provider with all methods returning ErrNotImplemented — enough
to register, dispatch, and show up in superadmin.

- sdx: SIX Digital Exchange (Switzerland, FINMA)
- adgm: ADGM RegS venues (Abu Dhabi, FSRA)
- securrency: DFSA-licensed digital securities (Dubai DIFC)
- cssf: Luxembourg DLT securities rail (CSSF)
- euronext: Pan-EU exchange (Paris, Amsterdam, Brussels, Lisbon, Dublin, Oslo)
- xetra: Deutsche Boerse / Xetra (Germany, BaFin)
2026-04-13 12:26:09 -07:00
zeekay cad59ba58a feat(types): asset_class constants for ch_*, ae_*, eu_* (MiCA taxonomy)
Extends AssetClass vocabulary with 17 new constants:
- CH: ch_equity, ch_dlt_security, ch_crypto, ch_fi (FINMA)
- AE: ae_equity, ae_crypto, ae_fi, ae_tokenized_security, ae_funds (SCA/DFSA/VARA/FSRA)
- EU: eu_equity, eu_mifid_fi, eu_ucits, eu_aif, eu_art, eu_emt, eu_cas, eu_dlt_security (MiCA)

AllAssetClasses() and ValidAssetClass() updated, existing tests pass.
2026-04-13 12:25:57 -07:00
zeekay 5ddcedb4da refactor(lx): native ZAP schemas — no JSON on the wire
Replace JSON-in-ZAP envelopes with typed ZAP structs at fixed offsets.
Every request and response has a documented schema in schema.go:

  Requests       Responses
  ──────────     ───────────
  SymbolReq      SnapshotResp       (72 bytes fixed)
  SymbolsReq     AssetResp          (64 bytes fixed)
  ClassReq       OrderResp         (128 bytes fixed)
  BarsReq        EmptyResp           (8 bytes fixed)
  AccountReq     ErrorResp          (16 bytes fixed)
  AccountOrderReq
  CreateOrderReq

Flags header now carries opcode (upper byte) + status (lower byte, with
StatusOK=0, StatusErr=1). RPCError type surfaces server-side errors
transparently.

Strings use ZAP text fields (4-byte offset + 4-byte length, zero-copy
read). Floats use IEEE-754 binary64. Timestamps are int64 Unix nanos.
Multi-symbol requests pack symbols as CSV in a single text field.

12 tests passing — schema round-trips for every type plus provider-level
ZAP roundtrips for GetSnapshot, GetAsset, GetOrder, CreateOrder,
CancelOrder, and error propagation.
2026-04-13 10:51:35 -07:00
zeekay 573911488e feat(provider): add lx DEX provider with native ZAP transport — works for Lux + <tenant>
Single broker provider for the on-chain DEX precompile (luxfi/dex). Works
against any chain shipping the DEX precompile: Lux mainnet, Lux subnets,
<tenant> L1. Uses luxfi/zap (zero-copy binary RPC) instead of HTTP/JSON-RPC
or gRPC.

Wire format follows the ledger replication pattern: opcode in flags upper
8 bits, JSON payload in field 0. Opcodes cover assets, snapshots, bars,
quotes, trades, orderbook, and full order lifecycle (create/get/list/cancel).

Smart order router auto-registers when LX_DEX_ADDR is set or
ENVIRONMENT=local. No env var → no registration → router transparently
falls back to external venues.

Tests cover real ZAP roundtrips (TCP listener + client, opcode dispatch,
JSON-in-ZAP envelope encoding/decoding). 5 tests passing.
2026-04-13 10:27:52 -07:00
zeekay 3075bd3702 deps: upgrade luxfi/compliance v0.2.1 -> v0.2.2 (KeyHash json:"-") 2026-04-08 04:56:22 -07:00
zeekay 93d175600a security: require JWT aud, add key_hash migration column, remove stale gRPC comment
- checkAudience now rejects tokens without aud when BROKER_JWT_AUDIENCE is set
- credentials migration adds key_hash column (fixes SaveCredential on fresh PG)
- Remove stale "shared between REST and gRPC" comment from main.go
2026-04-07 22:24:44 -07:00
zeekay 6ad0541cd1 security: remove gRPC (use ZAP), fix TWAP race, harden Alpaca provider
- Remove pkg/grpc/ entirely (replaced by ZAP binary protocol)
- Remove proto/broker.proto
- Clean garbled gRPC remnants from cmd/brokerd/main.go
- Fix TWAP scheduler data race: Get() returns snapshot copy under lock,
  pass config by value to run() goroutine
- Remove google.golang.org/grpc from go.mod
2026-04-07 22:20:04 -07:00
zeekay f7a8d7a9a2 security: bcrypt admin auth, JWKS per-kid cache, gRPC reflection dev-only, audit bounded+rand IDs, credential hashing 2026-04-07 20:14:39 -07:00
zeekay 788166713a security: fix IDOR, SSRF, HMAC replay, JWT iss/aud, SOR injection
CRITICAL:
- Account ownership middleware on all /accounts/{provider}/{accountId}/* routes
- Deposit/withdraw: verify account belongs to authenticated user

HIGH:
- Webhook SSRF: HTTPS required, reject private IPs, loopback, link-local, internal hosts
- HMAC replay: reject signatures with |now - timestamp| > 5 minutes
- JWT: validate iss matches IAM endpoint, aud contains expected audience
- SOR: build accounts map server-side from resolver, ignore client input

All 43 packages pass. Zero failures.
2026-04-07 19:52:17 -07:00
zeekay 962842fdeb feat: taskqueue client + 20 webhook tests + prefix filtering + org isolation 2026-04-07 15:50:36 -07:00
zeekay a99d4b42e8 fix: webhook retry test — assert 2+ attempts (timing-safe) 2026-04-07 15:28:23 -07:00
zeekay e57d388cfd feat: webhook infrastructure — 16 BD events, HMAC delivery, routes, store 2026-04-07 15:10:25 -07:00
zeekay 3972925c18 feat: auto-register Kraken and Gemini in public-data mode for local dev
When ENVIRONMENT=local or ENABLE_PUBLIC_DATA is set, register Kraken
and Gemini without API keys. Their public endpoints provide real-time
crypto prices without authentication.
2026-04-06 20:35:37 -07:00
zeekay bf09f371b2 fix: red team findings — 5 critical + 3 high from Alpaca sign-off review
C1: queryCountryInfos endpoint typo (-1 suffix removed)
C2: ValidateTIF wired into CreateOrder (was dead code)
C3: GetFixedIncomeQuote returns honest error (Moment ToS required)
C4: Agreement signing uses real client IP (was 0.0.0.0)
C5: Overnight trading uses America/New_York timezone (was UTC-4)
H1: Background asset sync goroutine (1-hour ticker)
H2: ACATS disclosure API endpoint
H3: Crypto $200K aggregate limit on split plans
2026-04-06 15:53:47 -07:00
zeekay 986666f8b1 feat: complete Alpaca sign-off — ACATS, Fixed Income, Crypto, Omnibus
ACATS (7/7 items):
- ValidateACATSAccount/Assets, CreateACATSTransfer, GetACATSTransfer
- ListACATSTransfers, CancelACATSTransfer, ListACATSActivities
- GetACATSRejection, ACATSDisclosure constant
- 13 tests with subtests covering all validation paths

Fixed Income (11/11 items):
- ListCorporateBonds, ListTreasuryBonds (/v1/assets/fixed_income/*)
- CreateFixedIncomeOrder (market-only, TIF=day, decimal validation)
- GetFixedIncomeActivities (maturity filter), GetFixedIncomePositions
- CloseFixedIncomePosition, CancelFixedIncomeOrder
- 11 tests including 6 validation error subtests

Remaining Crypto (5/5 items):
- AddCryptoAgreement PATCH for existing accounts
- ListCryptoAssets with min_order_size/price_increment
- NonMarginableBuyingPower in Portfolio
- ListCryptoWallets, GetCryptoWallet stubs
- GetCountryInfo for regional license checks

Remaining Omnibus (5/5 items):
- ValidateTIF with per-asset-class TIF maps
- GetSubaccountConfirms for trade confirmations
- trading_blocked flag in SSE account events
- fractional_eh_enabled already in Asset struct

All 38 Alpaca provider tests pass. Full broker build clean.
2026-04-06 14:03:54 -07:00
zeekay 72facc0488 feat: overnight attributes, decimal validation, crypto order limits
Asset struct: add overnight_tradable, overnight_halted,
fractional_eh_enabled, min_order_size, price_increment,
min_trade_increment — parsed from Alpaca assets API.

CreateOrder: add pre-flight validation:
- Notional max 2 decimal places, qty max 9 decimal places
- Crypto: TIF must be gtc or ioc (rejects day)
- Crypto: max $200k notional, min $1 buy notional
- Crypto: min qty 0.000000002

CreateOrderRequest: add AssetClass field for asset-class-aware
validation without symbol heuristics.

Addresses Alpaca Tech Sign Off items across all 5 checklists.
2026-04-06 13:47:28 -07:00
zeekay b9a2b50d41 fix: wallet-screen route path (remove /compliance prefix) 2026-04-03 17:39:31 -07:00
zeekay 3091a6199a fix: bump compliance to v0.2.1 (AlpacaAccountID) 2026-04-03 17:13:59 -07:00
zeekay 1ed243014c feat: auto-provision Alpaca account on application approval 2026-04-03 16:15:58 -07:00
zeekay e771f3b1ed feat: ScamSniffer scam database integration, 153 addresses, 6h auto-refresh 2026-04-03 16:10:06 -07:00
zeekay a43e175760 feat: OFAC wallet screening + crypto KYT endpoint
- POST /compliance/wallet-screen: checks addresses against 47 OFAC SDN
  sanctioned wallets (Tornado Cash, Lazarus Group, Garantex, etc.)
- Falls through to Jube for real-time risk scoring
- Self-service accessible (any org)
- 11 new tests covering OFAC hits, case insensitivity, chains, directions
2026-04-03 16:00:04 -07:00
zeekay 8fc466fd0a feat: add Unregister to provider Registry (runtime provider removal) 2026-04-03 15:29:50 -07:00
zeekay d4fb39fa11 add applications table migration (016, 017) 2026-04-03 15:04:45 -07:00
zeekay 2957c9e773 refactor: org-based RBAC, drop isAdmin/roles claims
Org IS the role:
- built-in org = full admin access to all compliance modules
- Any other org = self-service only (sessions, applications, kyc)

Removed: RequirePermission, HasRole, X-User-Roles, X-User-IsAdmin
Added: requireOrgAccess (checks X-Org-Id against adminOrg)
Added: localnet environment config
2026-04-03 14:57:22 -07:00
zeekay b6191d040e fix: built-in org users get X-User-IsAdmin (Casdoor JWT lacks isAdmin claim) 2026-04-03 14:48:47 -07:00
zeekay 872c13d867 split self-service vs admin routes: sessions/applications open, admin behind built-in org 2026-04-03 14:27:39 -07:00
zeekay 454b735593 feat: compliance restricted to built-in org, RequireOrg middleware
- auth.RequireOrg() middleware rejects requests from wrong org
- Compliance routes require built-in org (COMPLIANCE_ADMIN_ORG env var)
- SuperAdmin users (built-in org) manage compliance for all customer orgs
- Customer data lives in liquidity org, admin access via built-in org
- Tests updated to use built-in org
2026-04-03 14:22:51 -07:00
zeekay 1a99305992 allow self-service onboarding for authenticated users without roles 2026-04-03 14:19:44 -07:00
zeekay b219edd2c9 security: fix all critical/high findings from red team review
- Strip 9 identity headers at start of every request (prevents injection via direct pod access)
- Remove X-Account-Id fast path and fallback headers from frontend handlers (fixes IDOR)
- Add gRPC auth interceptor with IAM JWKS validation
- Fix RBAC role splitting (comma-separated X-User-Roles)
- Move webhook endpoint outside auth middleware
- Fatal on empty SSN_HMAC_KEY in production
- Remove dead admin store code
- Remove duplicate CORS handler from compliance router
- Fix writeErr JSON injection (use json.Marshal)
- Propagate isAdmin + roles claims from JWT
2026-04-03 14:13:17 -07:00
zeekay f29020297f feat: add IAM JWKS validation to auth middleware
Middleware now validates Bearer tokens against IAM JWKS endpoint
(IAM_ENDPOINT/.well-known/jwks). Extracts sub, owner, email claims
and sets X-User-Id, X-Org-Id, X-User-Email headers.

Also accepts gateway-propagated headers (X-User-Id) for deployments
behind Hanzo Gateway. One auth path: IAM only.
2026-04-03 13:56:14 -07:00
zeekay 0c02dd3424 fix: compliance auth checks X-User-IsAdmin from IAM JWT (no separate admin store) 2026-04-03 13:53:58 -07:00
zeekay 4df5223100 refactor: remove internal auth, use IAM via gateway only
All authentication now goes through Hanzo Gateway → IAM JWKS.
The broker reads identity from gateway-propagated headers
(X-User-Id, X-Org-Id, X-User-Email, X-User-Roles) and never
validates JWTs or API keys internally.

- Remove auth.Store, auth.APIKey, API key middleware
- Remove admin.Middleware from compliance router
- Remove /auth/login and /auth/verify endpoints
- RBAC reads X-User-Roles header from gateway
- Reviewer identity from X-User-Id header
- /healthz remains unauthenticated
- Rename super_admin → superadmin
2026-04-03 13:40:51 -07:00
zeekay 1d940ce212 chore: symlink AGENTS.md and CLAUDE.md to LLM.md
Canonical project context lives in LLM.md. Symlinks ensure
agentic coding tools (Claude Code, Cursor, etc.) find context
automatically regardless of which filename they look for.
2026-04-01 14:08:13 -07:00
zeekay 5e5edb227a chore: update dependencies 2026-04-01 13:59:38 -07:00
zeekay a710df9da2 ci: use <tenant>/.github shared workflow (GKE runners) 2026-04-01 13:36:46 -07:00
zeekay a644e17f16 ci: native multi-arch Docker builds (amd64 + arm64, no QEMU) 2026-04-01 13:33:29 -07:00
zeekay 49d964fdef fix: require explicit role for trade permission (SEC Rule 301(b)(5))
Default grant for authenticated IAM users now only covers "read" (browsing
market data). Trade permission requires explicit role assignment to prevent
pre-KYC users from placing orders on the ATS.
2026-03-31 20:49:27 -07:00
zeekay 445d25465a fix: accept X-User-Id header and X-Account-Id fast path for per-user account scoping
resolveUserID now checks X-User-Id first (set by ATS injectAuthHeaders)
before falling back to X-Gateway-User-Id and X-Hanzo-User-Id.

resolveUserAccounts short-circuits on X-Account-Id + optional
X-Account-Provider headers, skipping the ListAccounts call when the
caller already knows the sub-account.
2026-03-31 16:16:36 -07:00
zeekay a1ad0aa1b0 fix: compliance init for CI, alpaca crypto snapshot test mock 2026-03-31 12:55:08 -07:00
zeekay 43d96a28e9 fix: bump compliance to v0.2.0, simplify CI 2026-03-30 20:39:35 -07:00
zeekay 596ddcdd1b fix: update compliance module checksum 2026-03-30 20:29:21 -07:00
zeekay 4f436df93a ci: add GitHub Actions — :main/:test/:dev image tags 2026-03-30 20:23:56 -07:00
zeekay 27ad8b7090 security: role-based permissions for IAM users in RequirePermission
IAM-authenticated users no longer get implicit admin access. RequirePermission
now checks X-User-Roles header for the required permission. Default IAM users
without explicit roles get read + trade only. Admin endpoints require explicit
admin role in JWT claims.

Adds hasRole() helper for comma-separated role string parsing.
2026-03-30 20:11:32 -07:00
zeekay 32ee53d6f5 fix: remove hardcoded WL origins — CORS from CORS_ALLOWED_ORIGINS env var 2026-03-30 18:43:13 -07:00
zeekay c01bbb0eab fix(ibkr): global exchange support — TSX, LSE, HKEX via contract search
resolveConid now uses POST /iserver/secdef/search for proper
multi-exchange symbol resolution. Supports TSX symbols (U.UN,
SPUT.TO), LSE (VOD.L), HKEX, etc. SMART routing auto-selects
best execution venue.
2026-03-30 18:28:17 -07:00
zeekay 0bb03de5bf feat: add Apex provider + futures/FX/bonds/margin interfaces
- FuturesProvider, FXProvider, FixedIncomeProvider, MarginProvider interfaces
- Apex Clearing provider (self-clearing, 1-4 leg options, futures, margin)
- 20 new types: FuturesContract, FXPair, Bond, AccountMargin, etc.
- Registered in envconfig (APEX_API_KEY, APEX_API_SECRET, APEX_SANDBOX)
- 17 providers total (Alpaca, Apex, IBKR, Tradier, CurrencyCloud, LMAX, etc.)
2026-03-30 16:11:00 -07:00
zeekay 5de9cbe333 feat: add options trading — Alpaca Level 3 multi-leg support
- OptionsProvider interface (7 methods): chain, expirations, quote,
  single-leg orders, multi-leg orders, exercise, positions
- Alpaca implementation: order_class "mleg", ratio_qty, position_intent
- Fix 3 critical bugs: mleg class, ratio_qty field, position_intent
- Add DoNotExercise + SetOptionsApprovalLevel endpoints
- HTTP handlers: 10 new routes under /v1/options/*
- Security: OCC symbol validation, input sanitization, audit logging
- Tests: 16 provider + handler tests passing
2026-03-30 15:25:47 -07:00
zeekay 760d8e710e fix: default ListOrders to status=all (Alpaca defaults to open only) 2025-12-24 22:00:00 -07:00
zeekay b9afd116a8 fix: set Account.ID from provider ID in Alpaca toUnified
Account.ID was empty causing SmartOrder to call
/v1/trading/accounts//orders (double slash, 404).
2025-12-24 21:46:11 -07:00
zeekay a798507a84 debug: log alpaca 4xx responses with URL 2025-12-24 21:37:48 -07:00
zeekay dcdde1d895 fix: auto-resolve account in SmartOrder when accounts map is empty
When the caller doesn't provide explicit provider→account mappings,
SmartOrder now auto-discovers the first available account for the
best provider. Fixes "no account configured" errors from ATS.
2025-12-24 21:15:47 -07:00
zeekay 8723d2fc14 feat: add Hyperliquid perpetuals DEX provider
Read-only integration with Hyperliquid API:
- Market data: snapshots, bars, trades, quotes via /info endpoint
- Account state: clearinghouse positions and margin
- Open orders query
- Order execution stubbed (needs wallet signing)
2025-11-15 14:23:41 -08:00
zeekay f1fd941892 fix: clean up dead code in auth middleware, fix RequirePermission bug
- Remove unreachable ATS proxy X-User-Id block (already handled above)
- Fix RequirePermission: was checking X-User-Id twice instead of
  X-User-Id || X-IAM-User-Id
- Change error message to "authentication required" for consistency
2025-12-24 19:09:01 -07:00
zeekay 03c7446a38 revert: restore Dockerfile + cloudbuild — broker is a deployed BD service 2025-12-24 18:45:12 -07:00