This document describes the architecture as currently implemented in this repository, not an idealized target state. This file is intended for technical reviewers; see
README.mdfor product framing and demo-first flow.
Fortexa is a policy-controlled payment firewall for agent-triggered actions on Stellar.
It inserts a decision layer between agent intent and transaction submission.
Core intent:
- authenticate users via wallet-bound session
- evaluate requested actions against policy + risk heuristics
- allow signed XDR submission only through authenticated operator flows
- preserve audit evidence for traceability
- Wallet-address-based login via public-key allowlists (
/api/auth/login) - Role-based route protection (
operator,viewer) - Policy + security-based decisioning
- Signed XDR submission to Stellar Testnet
- Audit/history persistence with DB-first fallback
- Basic health/metrics endpoints for ops visibility
- Server-side signing
- Private-key custody
- Mainnet-first transaction flow
- Distributed, strongly consistent security state by default
flowchart LR
UI[Next.js App UI\n/pages + client components] --> API[Next.js API Routes]
API --> AUTH[Auth + Session]
API --> DEC[Decision Engine]
API --> POL[Policy Store]
API --> AUD[Audit Store]
API --> STELLAR[Stellar Client]
API --> OBS[Metrics + Logs]
POL --> DB[(Postgres if available)]
AUD --> DB
AUTH --> DB
POL --> FILE[JSON fallback\nlocal .fortexa / Vercel /tmp/fortexa]
AUD --> FILE
AUTH --> FILE
STELLAR --> HZN[Horizon Testnet]
API --> GROQ[Groq API /agent/plan]
flowchart TB
subgraph Untrusted[Untrusted Zone]
Browser[Browser + User Input]
WalletExt[Wallet Extension\n(Freighter etc.)]
end
subgraph AppBoundary[Fortexa Application Boundary]
WebUI[Next.js UI]
ApiRoutes[API Routes + Validation]
Authz[Session + Role Authorization]
Decisioning[Policy + Risk Decisioning]
Storage[DB/File Persistence]
Observability[Metrics + Logs]
end
subgraph External[External Systems]
Horizon[Stellar Horizon Testnet]
Groq[Groq API]
end
Browser --> WebUI
WebUI --> ApiRoutes
ApiRoutes --> Authz
ApiRoutes --> Decisioning
ApiRoutes --> Storage
ApiRoutes --> Observability
WebUI <-- signed XDR --> WalletExt
ApiRoutes --> Horizon
ApiRoutes --> Groq
src/app/api/auth/*: wallet login, session issue/refresh/logout/session lookup.src/lib/auth/session.ts: signed cookie session token (fortexa_session).src/lib/auth/require-auth.ts: role-based auth guard for protected APIs.src/app/api/decision/route.ts: evaluates action, applies optional human-approval override, appends audit record.src/lib/decision/engine.ts: combines policy checks + risk findings into decision outcome.src/lib/policy/engine.ts: deterministic policy rules (caps, tools, domains, hours, thresholds).src/lib/security/analyzer.ts: heuristic risk findings + risk score.src/app/api/stellar/build-payment/route.ts: builds unsigned TESTNET payment XDR.src/app/api/stellar/submit-signed/route.ts: submits signed XDR, returns tx hash + explorer link.src/lib/stellar/client.ts: Horizon calls, XDR construction, XDR submission.src/lib/storage/*-store.ts: policy/audit/user-wallet persistence with DB fallback.src/lib/storage/db.ts: optional Postgres connector + migration bootstrap + graceful fallback.src/app/api/metrics/route.ts+src/lib/observability/metrics.ts: JSON and Prometheus metrics.
- Client posts wallet
publicKeyto/api/auth/login. - Input validation enforces Stellar public key format.
- Role resolves via
FORTEXA_OPERATOR_WALLETS/FORTEXA_VIEWER_WALLETS. - Lockout + rate-limit checks apply.
- Session token is issued in
fortexa_sessioncookie. - Wallet mapping is upserted for that
userId.
Current implementation note: this login path is allowlist + session-token based; it is not a cryptographic challenge-signature authentication flow.
Honest note: if both allowlists are empty, current behavior allows any valid wallet as operator (developer-friendly, not production-safe).
sequenceDiagram
autonumber
participant U as User/Browser
participant API as /api/auth/login
participant RL as Rate Limit + Lockout
participant AUTH as Session Signer
participant UW as user-wallet-store
U->>API: POST { publicKey }
API->>RL: consumeRateLimit + isLoginLocked
alt Limited or locked
RL-->>API: deny
API-->>U: 429/423 + headers
else Allowed
API->>API: validate Stellar key + resolve role
alt Unauthorized wallet
API->>RL: register failure
API-->>U: 401
else Authorized wallet
API->>UW: upsertUserWallet(userId, publicKey)
API->>AUTH: createSessionToken(userId, role)
API-->>U: 200 + fortexa_session cookie
end
end
- Authenticated operator calls
/api/decision. - Request is schema-validated.
- Policy is loaded (
policy-store) and current usage is loaded (audit-store). - Decision engine computes
BLOCK | REQUIRE_APPROVAL | WARN | APPROVE. - Optional
approvedByHumancan elevateREQUIRE_APPROVALtoAPPROVE. - Usage is consumed only for
APPROVE/WARN. - Audit entry is persisted.
- Build unsigned transaction via
/api/stellar/build-payment. - User signs in external wallet (e.g., Freighter).
- Signed XDR is sent to
/api/stellar/submit-signed. - API validates input and auth (
operator), submits to Horizon Testnet. - Response includes tx hash, ledger, and testnet explorer URL.
Honest note: no server-held secret signs transactions.
sequenceDiagram
autonumber
participant U as Operator UI
participant BP as /api/stellar/build-payment
participant W as Wallet Extension
participant SS as /api/stellar/submit-signed
participant H as Horizon Testnet
U->>BP: POST payment request
BP-->>U: unsigned XDR
U->>W: request signature(unsigned XDR)
W-->>U: signed XDR
U->>SS: POST signedXdr
SS->>H: submitTransaction
alt Horizon success
H-->>SS: hash, ledger, result_xdr
SS-->>U: 200 + explorerUrl
else Horizon failure
H-->>SS: error + result_codes
SS-->>U: 500 + parsed tx/op codes
end
/api/stellar/setup currently acts as a session-wallet bootstrap/sync helper.
It does not enable arbitrary manual wallet linking; it syncs the wallet already present in session context.
policy-store: policy config + version historyaudit-store: per-user audit records + daily usage countersuser-wallet-store: user-to-wallet mapping
erDiagram
FORTEXA_POLICY_STATE ||--o{ FORTEXA_POLICY_HISTORY : versions
FORTEXA_USAGE ||--o{ FORTEXA_AUDIT_ENTRIES : per_user
FORTEXA_POLICY_STATE {
int id PK
int version
timestamptz updated_at
jsonb policy
}
FORTEXA_POLICY_HISTORY {
int version PK
timestamptz updated_at
text updated_by
jsonb policy
}
FORTEXA_USAGE {
text user_id PK
numeric spent_xlm
int tool_calls
timestamptz last_updated
}
FORTEXA_AUDIT_ENTRIES {
uuid id PK
text user_id
timestamptz timestamp
jsonb payload
}
FORTEXA_USER_WALLETS {
text user_id PK
text public_key
text source
text provider
timestamptz updated_at
}
- If
DATABASE_URLis set and DB is reachable: uses Postgres tables. - On DB unavailability or operation failure: falls back to local JSON files.
- local/dev default:
.fortexa/ - Vercel default:
/tmp/fortexa/ - optional override:
FORTEXA_STORE_DIR
- local/dev default:
This fallback is intentional for local resilience, but introduces consistency tradeoffs in multi-instance deployments.
- Migration definitions:
src/lib/storage/migrations.ts - Runtime bootstrap:
src/lib/storage/db.ts - Script:
npm run db:migrate
- Browser/UI is untrusted input surface.
- API routes enforce schema validation + auth.
- Session cookie is signed; authz is checked per protected route.
- Stellar signature material remains client-side in external wallet.
- Horizon/Groq are external dependencies.
- Per-route rate limiting (
src/lib/security/rate-limit.ts) - Login lockout on repeated failures (
src/lib/auth/login-lockout.ts) - Optional shared file-backed state (
FORTEXA_SHARED_STATE_PATH) for cross-process limiter/lockout state - Role gating (
requireAuth) for sensitive APIs
- No Redis-backed distributed rate limiter by default
- No HSM/KMS signing flow (intentionally)
- No advanced fraud intelligence feed integration
stateDiagram-v2
[*] --> Evaluate
Evaluate --> BLOCK: policy/risk hard fail
Evaluate --> REQUIRE_APPROVAL: threshold crossed
Evaluate --> WARN: soft concerns
Evaluate --> APPROVE: clean pass
REQUIRE_APPROVAL --> APPROVE: approvedByHuman=true
REQUIRE_APPROVAL --> REQUIRE_APPROVAL: no manual approval
WARN --> Audited
APPROVE --> Audited
BLOCK --> Audited
REQUIRE_APPROVAL --> Audited
Audited --> [*]
- Structured request-aware responses/log context via
jsonWithRequestContextand logger utilities /api/healthfor health checks/api/metricsfor JSON snapshot/api/metrics?format=prometheusfor scrape-compatible format- Ops UI consumes these APIs for dashboarding
- DB down or flaky: storage layer logs warning and falls back to file store.
- Horizon rejection: submit endpoint returns enriched error with result codes when available.
- Auth failures: role/auth errors short-circuit protected routes.
- Rate limit exceeded: API returns
429with rate-limit headers. - Invalid session wallet state: setup/balance flows return validation errors or require resync.
- Deployment model is still effectively single-node oriented unless external shared state is configured.
- Shared security state is file-backed, not distributed by default.
- Decision risk scoring is heuristic and policy-driven, not ML threat-intel driven.
- Transaction flow is testnet-centric (
Networks.TESTNET, testnet explorer links). - End-to-end automated coverage for full decision-to-payment lifecycle is still limited.
Near-term, high-impact improvements:
- move limiter/lockout shared state to Redis for true multi-instance consistency
- add deeper post-submit reconciliation and payment state tracking
- expand full-path integration/e2e coverage across decision + signing + submission + audit
If this file and README.md disagree, treat this file as implementation-level detail and update both together in the same PR.