Order Safety Notes
FlintTrade is personal-use, self-hosted software. It is not a broker, an investment adviser, a managed strategy product, a compliance product, or a service that accepts funds or account access from other people. Nothing in this repository is financial, investment, tax, legal, or regulatory advice.
This page describes the technical safety controls FlintTrade applies when an operator enables an order-capable integration. Broker, exchange, tax, and regulatory responsibilities remain with the operator and their broker.
Order-Gating Model
Placement. Every order FlintTrade submits goes through admission
when it's placed. The HTTP routes that submit an order are
POST /api/v1/orders/place, POST /api/v1/orders/<broker>/place,
POST /api/v1/positions/exit-all, and POST /api/v1/orders/bracket
when the body has exactly one stop-loss or one target. Each bracket leg
is admitted, then placed through SafetySystem, gate_order, and
BrokerRouter. Practice on that route is HTTP 403 practice_unsupported.
GTT, a broker-held variety, a stop-loss and a target together, and a
trailing stop are refused before that admission. Operator and automate
place (core /orders/place, strategy dispatch, and webhook place) share
the admission below. place-smart, open-position, and close-position
are not mounted. The Practice sandbox has no place or square-off route.
Settings → Practice does not place.
- The mode guard runs first. Example data stays HTTP 403
mode_blocked(Orders are not available for Example. Switch to Practice or Live to trade.) and does not enterLaya.admit. The/tradeOrder Pad records a sample fill on the client for example data (Example order placed, id startingSAMPLE-). This guard is the server path. Laya.admitthen admits or refuses the proposal. A refusal (laya_denied) or a quantity clamp (laya_clamp) stops before SafetySystem on Live and before the native sandbox on Practice. Neither quantity is placed.- An allowed Live place runs
SafetySystem.check_orderin runtime order L5 → L4 → L1 → L2 → L3 (kill switch, daily P&L, field validation, position limits, portfolio risk). gate_orderthen mints theSafetyContextbound to that order and the selector-bound principal. It does not re-run the layer checks.gate_orderremains the only mint after an allowed Live place.BrokerRouter.place_orderre-HMACs, matches fields, applies the account ACL, and consumes the one-shot gate.
An allowed Practice place is admitted, then goes to the native sandbox. It
does not enter SafetySystem, gate_order, or BrokerRouter. When the
order names a security id, the quantity must be a positive multiple of
that contract's lot from the broker instrument master. If the master has
no lot, the sandbox refuses the order and does not fill it:
Not placed. The lot size for NIFTY 24500 CE isn't in the instrument master, so this order can't be sized.
An expired future is named as the desk shows it, for example
NIFTY-OCT2026-FUT. The routed place route is Live only and uses the same
Live admission. Modify and cancel are not this admission. POST /api/v1/orders/cancel-all only
cancels. A body with "variety": "gtt" is HTTP 422 gtt_unsupported
before Laya, SafetySystem, and any broker call, on place, routed place,
exit-all, and a bracket. The message is Not placed. GTT orders aren't supported right now.
No submit route reaches a broker forever or super-order endpoint. The
Kotak Neo adapter refuses a gtt place. POST /api/v1/orders/forever returns
HTTP 501 Orders are placed through /api/v1/orders/place. and does not
call a broker. Practice and Live both refuse that variety before the
sandbox or a broker. The Order Pad GTT option stays visible and disabled,
with the tooltip GTT orders aren't supported right now.
Regular modify and cancel:
- Ordinary modify and cancel are blocked while Layer 5 is latched. A cancellation can remove a protective exit, so it is not exempt.
- A risk-increasing modify also runs
SafetySystem.check_orderon the proposed order. A proven no-increase modify and a cancel skip that chain. gate_ordermints theSafetyContextover the canonical fingerprint (_opismodifyorcancel).BrokerRouter.modify_order/cancel_orderre-verify and consume the gate.
Extended verbs (forever modify/cancel, super-order, conditional
trigger modify/cancel, convert, exit-all, reducing, multi, cancel-all,
smart-cancel). Exit-all is a submit route: the server records a
reduce-only proof for each open contract before exit_all_positions.
Forever, basket, split, and trigger place are not submit routes.
- Risk-increasing legs still run
SafetySystem.check_orderwhere the route admits exposure. gate_broker_writemints theSafetyContext(it delegates togate_order, which remains the sole mint). The verb must be listed inGATED_WRITE_VERBSandpayload["_op"]must equal the verb.BrokerRouter.execute_gatedre-verifies and dispatches.
New integrations must preserve this split. Do not invent a third path, and
do not send an extended verb through gate_order alone.
Laya admission
An operator or automate proposal is admitted or refused as a typed verdict
with allow, reason, and limits. Laya does not place an order and does
not mint the one-shot write ticket. gate_order remains the only mint.
Chat may suggest and explain only: bring your own API key, use managed
Ollama, or point Custom (OpenAI-compatible) at another local runtime.
Chat is not an admission source, and Chat downtime does not close Live.
Operator and automate place run the mode guard, then Laya.admit.
Live place then runs SafetySystem L1–L5, gate_order, and
BrokerRouter. Laya does not replace those layers, and gate_order
remains the only mint after an allowed Live place. Practice place is
admitted before the sandbox and does not enter SafetySystem, gate_order,
or BrokerRouter. Example data stays mode_blocked before admit. A refusal
(laya_denied) or a quantity clamp (laya_clamp) stops before
SafetySystem on Live and before the sandbox on Practice. A clamp names
the reduced quantity. Neither size is placed. Order Pad and Quick Trade
require the operator to place that reduced quantity. An automate clamp is
a dispatcher error and does not place the reduced quantity on its own.
Chat is not an admission source. Modify, cancel, and cancel-all are not
admitted as place. Forever place, basket, split, and conditional-trigger
place do not submit. A Live bracket with exactly one stop-loss or one
target does submit. A GTT body is refused on every submit route
before Laya admission and SafetySystem. No submit route reaches a broker
forever or super-order endpoint. The Kotak Neo adapter refuses a gtt place.
Admission order is fixed. Down is checked first. The existing hard rules
then run unchanged (source, mode, symbol, side, quantity, price, trigger).
Only after those rules pass does Laya ask typed A/B questions about free
text: whether a note states a reason, whether it shows tilt or revenge, and
whether the stated plan contradicts the order side. Expiry, quantity, price,
and symbol are not sent to the model. Thresholds sit on option probabilities
in the versioned laya_policy.toml. There is no confidence field in the
gate. The model can deny or clamp. It cannot raise a quantity or overturn a
rule refusal. An unreachable host, a timeout, a malformed response, or a
revision or digest mismatch is Down. Down refuses Practice as well as Live.
Down copy is the same in every mode: "Laya is Down. New orders are paused until it's Ready. You can still close positions." A non-exit order is HTTP 403. That refusal carries no quantity ceiling. A Practice
refusal never says Live. When Laya is Ready
or Degraded and a Live place is refused only because Live is not qualified,
the reason is "Laya isn't qualified for Live yet. Practice orders are
available." An uncertain answer, including an empty note, clamps in
Practice and denies in Live. It is not a hard reject. The Practice server
reason is "Laya is uncertain. Quantity stays inside the tighter limit."
The Live server reason is "Laya is uncertain. Live stays closed." On a
denial, Order Pad and Quick Trade show that server reason inside one
alert (role="alert"), the only live region. The reason line is named
"Laya decision" and is not its own status. The desk
does not auto-place. A clamp is only when the requested quantity is
greater than the allowed one. Order Pad and Quick Trade show "Not placed.
Laya allows up to N." with Place N and Cancel. Place N sends that
quantity. On Order Pad, "Review Practice order" then shows the placed
quantity. Place 1 on "Not placed. Laya allows up to 1." places, because
that request is already at the allowed quantity.
Chip reason codes are not_started (Not started), stopped (Stopped),
port_in_use (Port <n> in use), still_loading (Still loading),
downloading (Downloading the model · X of Y GB), download_failed
(Can't download the model), unreachable (Unreachable), unverified
(Can't verify the model), wrong_revision (Wrong model version),
key_rejected (Can't reach Laya), and key_missing (The Laya API key file is missing.). A health check does not replace key_missing with Not started. <n> is the sidecar port. For
not_started, stopped, port_in_use, still_loading, and
unreachable, the tooltip is the label followed by
. Next: python -m flinttrade_core.laya_runtime start. downloading
has no tooltip and no Next line, and it is not Still loading. The
download_failed tooltip is "Check your connection, then Start Laya again."
downloading and download_failed use the status word Down. Orders are
refused with "Laya is Down. New orders are paused until it's Ready. You can still close positions."
The unverified tooltip is "The installed model couldn't be checked
against the pinned version. Restart Laya. If it keeps happening, reinstall
it." That code applies when this start did not download. A failed download,
including one over an older unverified snapshot, is download_failed
("Can't download the model"). The wrong_revision tooltip is "Laya is running a different model
than FlintTrade expects." That code is only a real mismatch: a complete
download whose files do not match the pin, a snapshot already on disk
that this start is not replacing, or a running sidecar that reports
another revision or digest. A dropped connection, a partial download, or
a failed download or swap, including one that puts the previous
checkpoint back, is not this code. The
key_rejected tooltip is "Laya restarted with a new key. Reconnecting…"
The chip stays Down and orders are refused. When a place is refused
because Laya cannot be reached, or because it rejects the key, the chip
updates on that same order: Unreachable, or Can't reach Laya. The
refusal text stays "Laya is Down. New orders are paused until it's Ready. You can still close positions."
Every chip-Down refusal reads that sentence.
identity_absent is not a chip code. When the chip is Ready and a single
decision carries no proof, the refusal code is laya_unverified and the
refusal reads "Not placed. Laya's decision couldn't be verified. Try again."
On each sidecar start FlintTrade hashes model.safetensors and every
file in [checkpoint.manifest] before launch. The runtime record holds
the sha256, pid, and start token, and the inode, size, and modification
time of the weights file and of each pinned file
(<workspace>/runtime/laya/verification.json, with the token and pid
also in run.json). Every stop deletes that record, as does a start
that fails after it was written. A record from an earlier run is
rejected. A decision without revision or sha256 is checked against
that record for both admitted and clamped orders. The decision log is
<workspace>/runtime/laya/decisions.jsonl. It records proof=decision
or proof=runtime. An admitted Practice place with an empty note skips
the model and writes one line, effect=clamp with failure=note_absent
and no proof, including when the quantity already fits. When this run's
record stood in, each model allow keeps its own effect=allow
proof=runtime line. There is no dedupe. A model decision with no proof is refused with "Not placed.
Laya's decision couldn't be verified. Try again." A health document that
omits the digest is Ready when that record matches the pin. If the
record cannot be checked, the chip reason is unverified. Stopping the
sidecar records Down before an in-flight probe can publish Ready. Desk
place surfaces go through this admission. Laya is not Ready by default.
The pins live in
packages/services/engine/src/flinttrade_engine/laya_policy.toml.
[checkpoint] names revision beside sha256, and the weights file model.safetensors.
[checkpoint.manifest] pins these files by sha256: rl_agent_config.json,
encoder/config.json, tokenizer/tokenizer_config.json, and
tokenizer/tokenizer.json. FlintTrade hashes each of them before launch.
When the files are already on disk and this start is not replacing them,
a missing pinned file, a shard index (model.safetensors.index.json),
or any extra weights file or other file the launcher could read shows
Can't verify the model (unverified) and the sidecar does not start. A
changed byte in a snapshot that this start is not replacing shows Wrong
model version (wrong_revision) and the sidecar does not start. A changed
byte in the runtime checkpoint starts the download below; the sidecar does
not start on that tree. Laya does not reach Ready in these cases. start
downloads the commit in [checkpoint] revision, not the model repository's
default branch, into <workspace>/runtime/laya/staging when the weights
file or a manifest file is not on disk, and when the runtime checkpoint
is on disk but its hashes are not the pin. The download does not start
the sidecar. That download sets HF_HOME to
<workspace>/runtime/laya/hf-home and HF_HUB_DISABLE_XET=1, so transfer
logs stay out of the shared cache. The model is about 2.37 GB, and that
size is reported once. While it runs, including a pin change, the chip is
Downloading the model · X of Y GB (for example Downloading the model · 1.2 of 3.4 GB) and the status word is Down. There
is no Updating label. When no checkpoint is already there, a full match
renames staging onto <workspace>/runtime/laya/checkpoint. When a
checkpoint is already there, the current copy stays in place until the
new files match. On a full match that checkpoint is renamed aside to
checkpoint.old-<random> in the same runtime directory, staging is
renamed onto checkpoint, then the old copy is deleted. Hub access stays
off for that launch. If that second rename fails, the old checkpoint is
renamed back and the chip is download_failed ("Can't download the
model"), not wrong_revision. A complete download whose files do not
match the pin is wrong_revision, staging is deleted, and the checkpoint
already on disk stays. An extra loadable file in a complete download is
unverified. A dropped connection, a partial or missing file, or a read
error is also download_failed. The sidecar does not start on files that
do not match the pin. If the download does not finish, the chip is download_failed, not
wrong_revision and not unverified, whatever older snapshot is on
disk. unverified stays when this start did not download. A snapshot
already on disk is wrong_revision only when this start did not
download. Those failures delete the staging directory and leave the shared
model cache alone. Leftover staging directories and checkpoint.old-*
copies are removed at the start of start once a checkpoint is in place,
with no chip change and no message. If checkpoint is missing and one or
more checkpoint.old-* copies remain, the last checkpoint.old-* name
is restored onto checkpoint and any other aside copies are removed. If that restore
fails, the aside copy stays where it is and that cleanup is skipped. A
copy that was restored is then checked against the pin. If it does not
match, the sidecar does not start on it; the pinned download runs
instead, and a failed download leaves download_failed with that copy
still on disk.
A verified boot
sets LAYA_WEIGHTS_PATH to that hashed weights file. A model already in
the standard Hugging Face cache is accepted. When that file is the cache
symlink (snapshots/<revision>/model.safetensors into blobs/), the
launch path is the snapshot file, not the blob. A blob path is still
refused. The boot runs offline
(HF_HUB_OFFLINE=1, TRANSFORMERS_OFFLINE=1). It does not pass a repo
id or a revision. When the sidecar health document leaves the revision
empty, FlintTrade fills the pinned revision from the verified manifest, so
the chip leaves Still loading. The launch log line is
laya weights path=<path> sha256=<digest>. The recorded inode, size,
and modification time are rechecked, without hashing again, when Laya
reports Ready and on each watch tick, about every 1.5 seconds. If one
changes, the chip shows Can't verify the model and the log line is
laya weights path=<path> changed=<field>, where <field> is inode,
size, mtime, or a comma-separated list of those. The same watch
reads the pid file (runtime/laya/sidecar.pid), the key file
(runtime/laya/api.key), and the runtime record, so a command-line
stop or start, or a key rotation, is reconciled by that watch. The desk
polls GET /api/v1/ping every 1.5 seconds. That ping reconciles the
pid, the key, and the runtime record the same way an order does, so the
chip and the order gate read the same state. A stop or a start shows on
the chip by the next 1.5-second check. After Start Laya, until the ping confirms
the new state, the chip says Checking in the neutral colour and the
popover says Checking Laya…. It does not show a stale Ready during that
wait. An admitted place while the chip is not Ready or Degraded also
shows Checking until the next ping. A confirmed first load still says Still loading. A place refused
with exactly "Laya is Down. New orders are paused until it's Ready. You can still close positions." sets
the chip to Down on that response. The refusal line stays that sentence.
New orders stay paused. A reduce-only close is unchanged.
When decision status is Down, the desk opens incident class laya ("Laya is
Down. New orders are paused until it's Ready. You can still close positions.").
That class closes a new Live place on the shared
client place path. The server decides reduce-only. A client flag is ignored.
A close qualifies inside either place route when it reduces the same contract without changing its sign. Its opposite-side quantity cannot exceed the open quantity minus pending exits. On Live, available position and broker-order books must establish that bound; this desk’s pending exits also count. Missing, malformed or contradictory books cannot establish a reduce-only close or known flatness. There is no unreadable-book fallback.
Laya records a qualifying close with proof kind reduce_only and does not deny or clamp it. Down and Degraded do not block that verified close. Live still runs SafetySystem after the record. A second exit on the same exact broker account and contract, while this desk’s earlier exit remains unfilled, returns HTTP 409 exit_pending: "Not placed. An exit for <symbol> is already pending. Wait for it to fill, or cancel it and try again."
Practice uses that code on the Practice book. On Live, readable broker evidence and this desk’s retained reservations determine the refusal. If a retained exit exists but broker orders are unavailable, the response is HTTP 409 exit_orders_unreadable: "Not placed. Broker orders for <symbol> are unavailable. Reconcile them before another exit." The response’s message and reason contain the same text. The label is the symbol, or this contract when the symbol is empty.
The Positions row shows Exit pending for the unfilled-exit case. A position whose sign flips after the broker book has
loaded keeps that row, tagged Unexpected, and the book shows
Position changed after your broker's orders loaded. You're now <long or short> <quantity> <symbol>. Close it if that wasn't intended.
until dismissed.
Anything that would flip or add to a position takes the full admit.
POST /api/v1/positions/exit-all requires a server-side reducing proof before requesting exits. Unavailable position evidence returns HTTP 503; ambiguous evidence returns HTTP 409. Neither response sends a square-off. An accepted exit-all or reducing place means requested, not filled or flat: check broker positions and orders for the outcome.
POST /api/v1/orders/cancel-all requests cancellation only. Cancellation acknowledgement does not establish terminal cancellation or closed exposure. Layer 5 also requests exits after resting-order cancellation; those emergency paths remain reachable while Laya is Down. Only independently confirmed reducing execution may be described as closed.
Broker may stay Connected or Connected (read). Laya starts Down.
GET /health records Ready, Degraded, or Down from the opt-in sidecar when
one is registered. The desk polls GET /api/v1/ping every 1.5 seconds. That ping reconciles the watched pid, key, and runtime record the same way an order does, then
publishes Live-facing laya, sidecar laya_practice,
laya_live_qualified, laya_reason, and laya_port. It does not invent Ready. The Laya chip label follows
the current mode, so Practice shows the sidecar and does not read Down while
Practice orders are being admitted. During the first load the chip says
Still loading. "Not qualified for Live" is the chip tooltip and the popover
line when the sidecar is up and Live is not qualified. A base checkpoint is not
qualified for Live, so Live stays Down until a qualification record exists
for the exact model revision, weight digest, and policy version
(EvidenceUseScope.LIVE_DECISION). Practice can be Ready or Degraded from
the same probe. Degraded does not open that class and does not mute Live.
Degraded enforces the tighter quantity ceiling and the desk says so. Down
does not add a second deny under a Live control that is already muted.
Other Live write verbs still reach SafetySystem without this admission.
Other Practice verbs go straight to the sandbox. The sidecar install is in
DEVELOPER_GUIDE.md.
Operator steps are in Start Laya.
| Concern | Automate risk note | SafetySystem | Ticket guards | Laya |
|---|---|---|---|---|
| Allow or deny | allowed and reason | L1–L5 pass or fail | refusal string | verdict allow and reason |
| Size | position_qty | L1 quantity, L2 limits | lot multiple | limits.max_quantity |
| Price | stop and target | L1 price band | limit and trigger present | price and trigger present |
| Mode | not modelled | mode guard | Example data refused | Example data stays mode_blocked before admit; Down refuses the proposal |
| Book, margin, Greeks, daily loss, kill | daily loss is agent config | L2–L5 | not present | not owned |
The automate risk note is not this admission. Lot size, the price band, margin, Greeks, daily loss, and the kill switch stay where they are.
Unknown Broker Outcomes
If adapter entry occurred but FlintTrade could not persist or receive the
broker acknowledgement, the lifecycle ledger records OUTCOME_UNKNOWN and
blocks later normal writes. A clean broker snapshot never clears that state:
the order may have filled and disappeared or may use an identifier FlintTrade
did not receive.
Recovery is an operator decision in the Reconciliation widget. The operator must inspect the broker, use an authenticated PIN-unlocked Live session, own the exact broker/account selector, and type the decision, selector and attempt ID exactly. Before accepting a new decision, FlintTrade forces reconciliation for only that selector and requires the resulting snapshot generation to be durably adopted. Finalisation revalidates the same evidence under the outcome lease so a newer conflicting snapshot cannot be ignored. Snapshot timestamps are monotonic: older snapshots and same-time snapshots with different content fail closed. Every broker-observed material order identity is retained as an immutable, generation-scoped observation, so a later changed or empty snapshot cannot erase earlier matching evidence. The public diff is cryptographically bound to recursively frozen private broker/local snapshots and rebuilt before persistence and again before adoption. Malformed, mutated, or mismatched reconciliation reports are rejected rather than interpreted as clean.
For applied placements, every supplied broker ID must have been first observed
after the attempt was invoked and must match all material persisted identity
fields. Basket IDs are bound to explicit child indexes rather than inferred
from list order; a partial decision must partition every child into applied and
not-applied sets. Modify and cancel decisions use operation-specific state
evidence. A cancel-pending state, a mixed modify state, missing broker fields,
an UNKNOWN material marker, or an operation for which the ledger did not
persist enough evidence remains blocked. Broker normalisers do not substitute
zero for an omitted numeric order field; an explicit zero remains valid and an
omitted or malformed value remains unavailable.
FlintTrade first persists the immutable decision as PENDING_AUDIT, fsyncs a
hash-chained audit event and independently verifies its receipt. The ledger
then commits CONFIRMED_APPLIED, CONFIRMED_NOT_APPLIED, or
CONFIRMED_PARTIAL together with the local order-state update, but retains a
durable PENDING_ROUTER_CLEAR record until the current router removes only
that attempt's fault. Completion requires a verifiable receipt bound to the
resolution, attempt, selector, and exact current-router generation; a boolean,
missing, stale, or mismatched proof fails closed. A PENDING_AUDIT retry must
obtain a newer exact-selector
generation; the ledger archives the prior pending revision and creates a new
resolution ID. A PENDING_ROUTER_CLEAR retry resumes the already committed
decision without another broker read. Other
unknown attempts and unrelated critical ledger health, including conservatively
migrated legacy faults, continue to block normal writes. Recovery performs no
broker mutation and emergency reducing writes remain on the gated path.
The terminal rejects malformed or contradictory status, report, success and structured-error envelopes before changing cached state or showing a healthy result. Attempt identity, canonical outcome, status, booleans and authorised remaining-outcome counts are runtime-validated. Authentication transitions retire the entire QueryClient generation, so callbacks from an older principal can finish only against the retired client and cannot repopulate the next principal's cache.
If task cancellation or another BaseException crosses a broker-write boundary
after adapter invocation, the attempt is durably marked OUTCOME_UNKNOWN before
the exception is re-raised. Audit appends repair and fsync a torn tail in the
newest plain chain segment, including across a date rollover, before extending
the chain; archive compression uses a same-directory,
fsynced atomic replacement so a crash preserves either the source or a complete
archive.
Safety Layers
| Layer | Purpose | Examples |
|---|---|---|
| L5 Kill switch | Stop order-capable workflows, cancel open orders, and request position flattening where supported. | Explicit UI button, API endpoint, or Telegram command. Checked first. |
| L4 Daily P&L | Pause or hard-stop subsequent new orders when daily loss thresholds are hit. | Default pause at 3 % and hard stop at 15 %; no broker-side cancel or flatten. |
| L1 Order validation | Reject malformed or disallowed orders before routing. | Symbol, exchange, side, quantity, order type, price, and market-session checks. |
| L2 Position limits | Prevent local workflows from exceeding configured exposure. | Default max five open positions and 60 % margin-use guard. |
| L3 Portfolio risk | Cap book-level Greek exposure. | Net delta and net vega guards. |
Kill Switch
Kill switch triggers:
- Telegram bot:
/killcommand when the operator enables the integration. - Terminal UI: the Kill Switch control on the
/tradeworkspace (Live mode only), and Activate / Reset on/automate→ Settings. - API:
POST /api/v1/safety/kill-switchto latch Layer 5 (JSON body{ "reason": "…" }).DELETE /api/v1/safety/kill-switchresets it after emergency actions complete. From the Vite dev proxy the same routes are/ft-api/api/v1/safety/kill-switch. There is no/activatesuffix.
The percentage-based Layer 4 daily-loss thresholds do not activate this kill switch. They latch new-order admission until manually reset. Automatic account-level flattening, when enabled, is owned separately by the authoritative rupee MTM circuit breaker.
The kill switch is a local software control. It should be paired with broker-side limits, broker-side position checks, and manual review of live-mode settings.
Audit Logging
FlintTrade writes local audit events for order and safety activity. The audit
directory is resolved by audit_log_dir(): AUDIT_LOG_DIR (direct directory),
then <storage.archive>/audit when workspace.json sets storage.archive,
then archive/audit/ inside the workspace directory
(FLINTTRADE_WORKSPACE_DIR, then FLINTTRADE_HOME, then the OS default):
| Platform | Default audit archive |
|---|---|
| Linux | ~/.flinttrade/archive/audit/ |
| macOS | ~/Library/Application Support/flinttrade/archive/audit/ |
| Windows | %APPDATA%\flinttrade\archive\audit\ |
| Override | AUDIT_LOG_DIR, then <storage.archive>/audit, then the workspace archive/audit/ path above |
| Event | Typical fields |
|---|---|
ORDER_PLACED | Strategy or source, symbol, exchange, side, quantity, order type, timestamp. |
ORDER_MODIFIED | Original fields, modified fields, reason, timestamp. |
ORDER_CANCELLED | Source, symbol, reason, timestamp. |
SAFETY_CHECK | Layer, verdict, reason, and order summary. |
LOGIN / LOGOUT | Session start or end, broker label, timestamp. |
KILL_SWITCH_ACTIVATED | Trigger, local P&L snapshot, affected positions or orders where available. |
Audit retention is a local configuration choice. These logs are useful for debugging and personal records; they are not a legal or regulatory attestation.
Rate Limits
| Category | Default local limit | Enforced by |
|---|---|---|
| Smart orders | 2 per second | Same split: proxy 429 plus client-side throttle. |
Limits apply across configured exchanges for the running FlintTrade instance. Operators should also configure any limits available in their broker dashboard.
Sessions And Credentials
-
Broker login, OAuth, TOTP, and exchange access remain broker-side concerns.
-
Native-adapter broker credentials live in the encrypted gateway vault.
-
The account transaction foundation retains unknown authentication outcomes rather than retrying them. Its vault and workspace evidence must remain coherent before a read generation can be exposed. Redacted terminal audit events have durable pending delivery and stable IDs; audit export never repeats authentication or order work. Production native account HTTP mutations remain
503, and native HTTP reads remain409until their separate cutovers land. -
Secrets should be file-backed under your platform workspace directory (
~/.flinttrade/on Linux,~/Library/Application Support/flinttrade/on macOS,%APPDATA%\flinttrade\on Windows; overridden byFLINTTRADE_WORKSPACE_DIR, thenFLINTTRADE_HOME). Telegram and LLM credentials live as hardened files under<workspace>/secrets/with asecret://reference inworkspace.json. Do not commit credentials or personal network details.
Market Metadata
FlintTrade includes market-hours, expiry, fee, and cost-model metadata so local screens, calculators, and backtests can behave consistently. Treat this metadata as software configuration, not advice or a guarantee that an order is suitable, permitted, or profitable.
Operator Checklist
Before enabling live-mode order routing:
- Review the source code for the order path you plan to use.
- Run in sandbox mode first and inspect the audit log output.
- Configure broker-side safeguards such as order limits, account limits, and manual approval controls where available.
- Keep broker credentials, API keys, and personal network details out of Git.
- Confirm that every enabled automation still routes through
gate_orderorgate_broker_write. - Treat FlintTrade as local software for your own account, not as an investment service for others.
FlintTrade — Supported Versions
What FlintTrade is known to work with at runtime. These projects are > NOT bundled with FlintTrade — you install them separately. This page > tells you which versions are safe to i
Checks and continuous integration
Use the affected gate while editing and the exhaustive gate before pushing. Both use the cross-platform task runner: