Common Business Rules
Project: Stack Trading Date Created: 2026-07-22 Version: v1.25 Last Updated: 2026-11-09
This document defines common business rules that repeat across multiple screens or UCs. Rules that appear only in a single flow/UC belong in that UC's SRS doc, not here.
CR-01: Numeric Fields
Applies to all numeric input fields (integer only). Does NOT apply to phone numbers, zip/postal codes, or identification numbers — those follow their own format rules specified in the UC.
1.1 Character Validation — Real-Time Block
| Criterion | Rule | Error Message |
|---|---|---|
| Valid chars | Allowed: digits0–9. On invalid keystroke → Block — character does not appear in the field, no message shown | — |
| Paste — invalid format | Reject entire string — nothing is inserted. Show red inline error below field immediately | "Invalid number format" |
1.2 Length Limit
| Criterion | Rule | Error Message |
|---|---|---|
| Max length | Max15 digits. Typing the 16th digit → Block, no message | — |
| Paste exceeding limit | Reject entire string, no auto-truncate. Show red inline error immediately | "[Field name] must not exceed [max] characters" |
1.3 Auto-format (no error shown)
| Criterion | Rule |
|---|---|
| Leading zero | Auto-remove when user types a second digit. E.g.09 → 9 |
| Leading/trailing spaces | Keep while typing; auto-trim on out-click |
1.4 Placeholder & Required
| Criterion | Rule | Error Message |
|---|---|---|
| Placeholder | Display*"Enter [field's label]"* when field is empty | — |
| Required field — empty | On out-click → show red inline error below field + red border. Error auto-clears when user types a valid character | "Please enter [field's label]" |
CR-02: Text / Textarea / Search Fields
Applies to all Textbox, Textarea, and Search inputs.
2.1 Whitespace Auto-Trim
| Criterion | Rule |
|---|---|
| Leading/trailing spaces | Keep while typing (no UX disruption). Auto-trim on out-click |
| Spaces-only input | Treated as empty — applies required-field rule if applicable |
2.2 Default Length Limits
| Field Type | Limit | On Limit Reached |
|---|---|---|
| Textbox | 255 chars | Do NOT block while typing. On out-click → red inline error below field + red border |
| Textarea | 500 chars | Do NOT block while typing. On out-click → red inline error below field + red border |
| Search box | 255 chars | Block — character 256 cannot be entered, no message shown |
| Promo code input | 100 chars | Block — character 101 cannot be entered, no message shown |
2.3 Placeholder
| Field Type | Placeholder |
|---|---|
| Textbox / Textarea | "Enter [field's label]" — specify exact label, e.g. "Enter email" |
| Search box | "Search" |
2.4 Required Field & Errors
Required timing is IDENTICAL for all field types: "On out-click"
| Criterion | Rule | Error Message |
|---|---|---|
| Required — empty | On out-click → red inline error below field + red border. Error auto-clears when user types valid input | "Please enter [field's label]" |
| Over max — Textbox/Textarea | On out-click → red inline error below field + red border. Error auto-clears when input is trimmed to valid length | "[Field name] must not exceed [max] characters" |
| Under min | On out-click → red inline error below field + red border. Error auto-clears when input meets minimum length | "[Field name] must be at least [min] characters" |
2.5 Search Behavior
| Criterion | Rule |
|---|---|
| Real-time search | Filter and display results as user types each character — no Enter required |
| Case sensitivity | Case-insensitive |
CR-03: Dropdown
Applies to all Dropdown / Combobox components.
| Criterion | Rule | Error Message |
|---|---|---|
| Default state | Show*"Select [field's label]"* placeholder. No option pre-selected unless UC specifies a default | — |
Searchable[CHR-99] | Every dropdown renders a search/filter input inside the open option list — user can type to narrow the visible options by substring match (case-insensitive). Applies regardless of option count. | — |
| Long option text | Truncate with... in the field. Hover → tooltip shows full text | — |
| After selection | Close dropdown; display selected value in field. No✕ clear icon | — |
| Re-open dropdown | Currently selected option is highlighted (checked/bold) in the list | — |
| Required — empty | On out-click → red inline error below field + red border. Error auto-clears when user selects a valid option | "Please select [field's label]" |
CR-04: Filter Dropdown — Default "All"
Applies to filter controls on list screens. Does NOT apply to form dropdowns.
| Criterion | Rule |
|---|---|
| "All" option | Every filter dropdown MUST have an"All" option at the top of the list |
| Default value | On first load →"All" is selected; all records are displayed |
| Results | Update immediately on filter change — no confirmation needed |
| Distinction | Form dropdown: placeholder"Select [field's label]", no "All" option — see CR-03 |
CR-05: Unsaved Data Navigation Warning
Applies to screens with data-entry forms.
5.1 Trigger Conditions
| Condition | Description |
|---|---|
| Form is dirty | User has changed at least one field since the form loaded |
| Navigation away | User clicks [Cancel], switches tab, uses breadcrumb, or any method to leave the current screen |
| No trigger | Form has not changed → navigate normally, no popup |
5.2 Warning Popup Content
Ref: CF-01
CR-06: List Pagination
| Criterion | Rule |
|---|---|
| Default page size | 10 records per page |
| Page size options | 10, 20, 50, 100. Change → reload list, return to page 1 |
| [Previous] button | Disabled on page 1 |
| [Next] button | Disabled on the last page |
| Info display | "Showing [start]–[end] of [total] records" |
CR-07: File Upload
Applies to screens with upload features. Screen applicability defined in the UC. File count, size limit, and format MUST be taken from UC. Do NOT hardcode defaults.
7.1 Upload Limits
| Criterion | Value | Source |
|---|---|---|
| Maximum file count | [From UC] | UC must specify |
| Allowed formats | [From UC] | UC must list exact extensions |
| Max size per file | [From UC] | UC must specify (e.g., 5 MB, 10 MB) |
7.2 Error Messages
| Violation | Error Message |
|---|---|
| Wrong format | "File [filename] is not an accepted format. Accepted formats: [list from UC]" |
| Exceeds file count limit | "Maximum [N] files allowed" |
| File exceeds size limit | "Please select a file smaller than [N] MB" |
| Server error during upload | Error toast : "Failed to upload file. Please try again later" |
7.3 Upload Area States
| Screen Mode | State |
|---|---|
| Create / Edit | Enabled — user can add or remove files |
| View Detail | Disabled (read-only) — show file list only |
CR-08: Timestamp Standard — UTC / ISO 8601 / Millisecond Precision
Applies to all date/time values across the entire stack: API request and response payloads, database columns, log entries, and webhook parameters.
8.1 Required Standard
| Attribute | Specification |
|---|---|
| Timezone | UTC only. Local timezones must never be stored or returned. |
| Format | ISO 8601 —YYYY-MM-DDTHH:mm:ss.sssZ |
| Precision | Milliseconds (.sss) — required so logs can measure sub-50ms operations |
| DB column type | TIMESTAMP WITH TIME ZONE storing UTC |
| Scope | API request payloads, API responses, database columns, log entries, Zapier webhook parameters |
8.2 Exception
Issuing platforms and brokers (e.g. CME exchange session rollover) manage localized market session schedules independently. Stack Trading's timestamp standard applies only to Stack Trading's own stack and does not override exchange-side time configurations.
8.3 Affected Fields (Non-Exhaustive)
Fields confirmed in source documents that must conform to this rule:
| Field / Column | Location |
|---|---|
timestamp_utc | API:POST /execute-checkout input payload |
expiration_date | Evaluation lifecycle (DB + API response) |
created_at, updated_at | All DB tables (audit metadata) |
start_date timestamp_utc | Evaluation start tracking |
Session_Open_UTC, Official_Close_UTC | Market session DB columns (Zapier tables) |
Halt_Start_UTC, Halt_End_UTC | Market session halt window columns |
Last_Ingested_UTC | Data ingestion audit checkpoint |
next_state_timestamp | /system/market-status response — "ISO8601" format |
CR-09: Special Field Types
Applies to Email and Phone Number input fields across the system. Rules here override CR-02 defaults where specified.
Note: No phone number field has been identified in customer supplies to date. This rule is defined preemptively for future UCs. If a UC introduces a phone field, reference §9.2 and specify any UC-specific constraints inline.
9.1 Email Field
| Criterion | Rule |
|---|---|
| Placeholder | "Enter email" |
| Max length | 100 characters (overrides CR-02 default of 255) |
| Required characters | Must contain@ and at least one . in the domain part |
| Allowed characters | Letters (a–z, A–Z), digits (0–9),. (dot), _ (underscore), - (hyphen) |
| Prohibited characters | Spaces, accented characters, and special characters (e.g.!, #, $, ,) — blocked on paste; flagged on out-click |
| Format | [local-part]@[domain].[tld] — e.g. user@gmail.com |
| Auto-trim | Leading and trailing spaces automatically removed on out-click |
| Validation timing | On out-click (not real-time) |
Validation errors:
| Situation | Timing | Error Message |
|---|---|---|
| Required field — empty | Out-click | "Please enter email" |
| Invalid format | Out-click | "Please enter a valid email address" |
| Exceeds 100 characters | Out-click | "Email must not exceed 100 characters" |
9.2 Phone Number Field
| Criterion | Rule |
|---|---|
| Placeholder | "Enter phone number" |
| Input structure | Two-part field:[Country Code selector] (prefix, left) and [Number input] (right) |
| Country Code default | +1 (United States) on first load |
| Country Code selector | Scroll-only — no search input; user browses the country list by scrolling |
| Country Code — valid characters | Digits (0–9) only, no+, no spaces, no special characters. Regex: ^[0-9]{1,3}$. |
| Phone Number — valid characters | Digits (0–9) only, no+, no spaces, no special characters. Letters and special characters are blocked in real-time. Regex: ^[0-9]{1,20}$. Required. |
| Auto-trim | Leading zeros in the number input are automatically removed on out-click |
| Length | Country Code: 1–3 digits. Phone Number: 1–20 digits.[CHR-106] |
| Submitted / stored format | The Country Code and number are concatenated into a single string (e.g.14155552671) — no separator, no spaces. This combined value is what's sent in the API payload (phone_number) and stored in the DB. The UI keeps the two-part input for entry, but the value leaving the form must always carry the country code. |
| Validation timing | On out-click and on submit |
Validation errors:
| Situation | Timing | Error Message |
|---|---|---|
| Required field — empty | Out-click / Submit | "Please enter phone number" |
Invalid length / format (phone number, fails^[0-9]{1,20}$) | Out-click / Submit | "Please enter a valid phone number" |
Invalid country code (fails^[0-9]{1,3}$) | Out-click / Submit | "Please select a valid country code" |
CR-10: Tooltip Interaction
| Environment | Trigger | Dismiss |
|---|---|---|
| Desktop | Hover over tooltip icon | Auto-dismiss on cursor leave |
| Mobile | Tap tooltip icon | Tap anywhere outside the tooltip |
- Tooltip content is static text only — no interactive elements (links, buttons) inside.
- Tooltip does NOT block the flow or require user acknowledgement.
CR-11: Numeric / Monetary Value Display
Applies to all numeric and monetary fields displayed across the app — price, fee, discount, tax, total, balance, equity, PnL, and any other financial or numeric value shown on a screen.
| Criterion | Rule |
|---|---|
| Format | Display with max 2 decimal places. Example:499.00, 11.94, 510.94. |
| Discount display | Discount line items are displayed as a negative dollar amount:−$XX.XX (en-dash + dollar sign + value to 2 decimal places). Example: −$25.00. The value is the absolute discount_amount float returned by the server — never a negative float. |
| Hover | Every value always shows a tooltip on hover with thefull value. Ref: CR-10 for tooltip interaction behavior |
| Thousands separator | Use comma, for thousands grouping (e.g. 1,250.00). |
CR-12: UTM Attribution Capture — Global, Root Layout
Source: Adrian Stack (client, Slack, 2026-07-30). Supersedes any UC-local UTM capture rule that scopes parsing to a single page/step — see CR-20260730-002.
Applies site-wide — every public page of the Marketing website, the Checkout App, and the Partner Application page. This is a cross-cutting rule, not specific to any single UC; individual UCs reference this rule instead of re-defining UTM capture logic.
12.1 Rationale
Users enter the site from unpredictable URLs (an ad may link to a blog post, an affiliate may link to the homepage, organic search may land anywhere). Because users navigate across multiple pages before converting, the system cannot rely on UTM parameters remaining in the active URL bar at the moment of conversion. Two attribution-critical paid-ad flows depend on this rule:
- Associate Track landing page(s) → Checkout.
- Partner landing page → Partner Application webform.
12.2 Global Root Execution
| Criterion | Rule |
|---|---|
| Where | The UTM parsing script sits in theroot layout component — fires on every single page load and every route change, across the entire frontend (Marketing website + Checkout App). Not isolated to specific pages. |
| When | On page initialization (mount) and on every client-side route change. |
12.3 Persistent Storage — localStorage
| Criterion | Rule |
|---|---|
| Storage mechanism | localStorage (not sessionStorage, not in-memory state only) — so attribution survives a closed browser and a returning user on a later visit. |
| UTM parameters present in URL | Writeutm_source, utm_medium, utm_campaign, utm_term, utm_content to localStorage. If old UTM values already exist in storage, overwrite them — this is deliberate last-touch attribution, not first-seen-wins. |
| No UTM parameters in URL | Donothing. Do NOT clear localStorage. Do NOT overwrite existing values with null. Leave existing localStorage UTM values intact. |
12.4 Payload Injection
At the moment of submission for any of the three conversion points below, the frontend component reads the current UTM values from localStorage and injects them into the outbound API payload:
| Conversion point | UC reference |
|---|---|
| Partner Application submit | Partner Application form (CR-20260726-005) |
| Join Waitlist | UC_2.1.3 — direct ActiveCampaign + Klaviyo calls |
| Step 5 of Checkout | UC_2.6.1 / UC_2.6.2 POST /capture-lead, and POST /execute-checkout at Step 6 |
This guarantees acquisition source data is preserved and transmitted to Stack Trading's database, Klaviyo, and ActiveCampaign, even if the user clicks around the site, leaves, and returns later via a clean URL (no UTM params).
CR-13: Rounding to 2 Decimal Places
Applies to all backend-calculated numeric/monetary values that feed into threshold comparisons or are displayed — e.g. Daily_Loss_Limit, Net_P&L, and similar computed values.
| Criterion | Rule |
|---|---|
| Algorithm | Round to nearest hundredth (2 decimal places), round-half-up. Use the code language's native rounding function (e.g.Math.round, toFixed) — no need to hand-code a carry-chain. |
| Examples | 624.99375 → 624.99. 624.99875 → 625.00. |
| Ref | Display formatting once rounded →CR-11. |
CR-14: WebSocket Reconnection & Snapshot Resiliency (Shared)
Applies to every Dashboard widget/table fed by a WebSocket event (e.g. POSITION_METRICS, PERFORMANCE_UPDATE, and equivalent event channels across other Dashboard UCs).
| Criterion | Rule |
|---|---|
| Trigger | Cloudflare edge routing can drop idle or long-running WebSocket connections; separately, the underlying market/exchange can go into an off-hours/closed state where no new events are emitted at all. |
| Backend snapshot persistence | The Backend maintains and holds the latest data snapshot for each real-time widget/table (not merely re-broadcasting on push), so that on a fresh page load or a page reload (F5), the Frontend can restore the widget/table from that snapshot immediately — instead of rendering it empty while waiting for the next WebSocket event. |
| Reconnection behavior | Frontend implements exponential backoff and automatic reconnection to prevent UI freezing or silent data failures. |
| Data retention while reconnecting | Widgets/tables retain their last-known snapshot values rather than clearing to empty while the WebSocket connection is temporarily unavailable or disconnected. |
| Market-closed behavior | If the market is closed (no new events being emitted because there is no trading activity), the widget/table continues displaying the latest available snapshot data until new data becomes available — it does not reset to empty or zero merely because no real-time events are currently arriving. |
| Page reload (F5) behavior | On page load/reload, the Frontend renders from the Backend's latest snapshot first, then applies subsequent WebSocket events on top of it. The widget/table is never shown empty purely because no new WebSocket event has arrived yet since load. |
| Ref | Source: RFQ_ Website and Dashboard Implementation V7.pdf (original reconnection-resiliency rule); Source: User instruction (BA session), 2026-08-31 (extends the rule to backend snapshot persistence, market-closed behavior, and page-reload/F5 behavior). |
(Moved here from UC_4.2.1 §BR_4.2.1.4, which was scoped to a single widget but referenced by multiple sibling and external UCs — see Changelog. Extended 2026-08-31 to cover snapshot persistence, market-closed, and F5/reload behavior, per the same rationale — this is a shared cross-cutting concern, not specific to any one widget.)
CR-15: Reference
CR-15.1: Email
Support@stacktrading.com Marketing@stacktrading.com Compliance@stacktrading.com Sales@stacktrading.com no-reply@stacktrading.com
CR-15.2: Slack
Production Channels
#ops-treasury ID: C0BRCCMN1MG Receives Hourly Solvency Reports, Automated Treasury Sweep confirmations, and the Daily EOD Firm Health Report.
#ops-risk ID: C0BREC78LGH (Replacing #risk-ops): Receives High-Impact News Event summaries, Overnight Margin and Swap Monitor alerts, and TRIM_TO_COMPLIANCE gateway rejection Severity 1 alerts.
#ops-dev ID: C0BRCDB8U3U (Replacing #dev_ops): Receives the Configuration Database Backup success or failure notifications.
#ops-marketing ID: C0BQX3AV679 (Replacing #marketing): Receives fulfillment notifications, such as a Level 10 trader claiming a Tom James Suit or Goodwill Merch inventory depletion alerts.
#ops-onboarding ID: C0BRC4UE15F Receives E-Verify failures from Rippling's native Flow 6 onboarding.
#ops-general ID: C0BR8LRC5MK (The Catch-All): Designated for Veriff manual resets, FCM institution denial alerts, Dispute/Chargeback handlers, Pay raise logic confirmations, Pod Vesting completions, MID capacity warnings, and Aggregate Exhaustion Severity 1 alerts.
#ops-security ID: C0BQX3L74B1 (The Restricted Channel): A designated secure channel for receiving "High Volume Pod Reassignment" alerts to monitor internal administrators for abuse.
Development/Test Channels
#test-ops-onboarding ID: C0BQX1MDX47 Receives Background Check flags from E-Verify failures.
#test-ops-treasury ID: C0BRCBMGTC2 Receives Hourly Solvency Reports, Automated Treasury Sweep confirmations, and the Daily EOD Firm Health Report.
#test-ops-risk ID: C0BRAMTDYMQ Receives High-Impact News Event summaries, Overnight Margin and Swap Monitor alerts, and TRIM_TO_COMPLIANCE gateway rejection Severity 1 alerts.
#test-ops-general ID: C0BR6UXRR8D Designated for Veriff manual resets, FCM institution denial alerts, Dispute/Chargeback handlers, Pay raise logic confirmations, Pod Vesting completions, MID capacity warnings, and Aggregate Exhaustion Severity 1 alerts.
CR-16: Net PnL Display — Full Precision (Whole Dollars), No Abbreviation
Applies to any live Net PnL dollar-value display across the Dashboard (e.g. PnL Gauge center value, Daily Loss Limit Bar's dollar/remaining-amount label) — not to other monetary fields, which remain governed by CR-11.
| Criterion | Rule |
|---|---|
| Format | Display the exact dollar amount using comma thousands separators, cents omitted (e.g.$1,875, not $1,875.00). |
| Abbreviation | Never abbreviate (1.8K, 1M, etc.), regardless of magnitude. |
| Relationship to CR-11 | Deliberate exception toCR-11's 2-decimal-place default, for this field only — CR-11's thousands-separator and hover-tooltip (full value) rules still apply. |
| Ref | Source: Client description, relayed by user, BA session, 2026-08-31 (originally authored as UC_4.8.1 BR_4.8.1.6 point 1); Source: BA/user instruction, 2026-09-15 (extracted into this shared CR, and extended to the Daily Loss Limit Bar's dollar-value display, UC_4.7.5/UC_4.12.5 — no client source describes that widget's dollar label, added on explicit BA/user instruction). |
(Moved here from UC_4.8.1 §BR_4.8.1.6 point 1 — originally a one-off exception to CR-11 scoped to a single widget, but already reused verbatim by UC_4.12.2 and now also applied to UC_4.7.5/UC_4.12.5. Follows the same generalization pattern as CR-14. See Changelog.)
CR-17: Soft Breach / Hard Breach — Widget Data Display Behavior (Shared)
Applies to every real-time Overview-screen widget covered by UC_4.2.1–4.2.5 (Position Monitor Table, The Pulse, Equity Curve, Profit Factor, Win Rate) and UC_4.8.1 (PnL Gauge). This CR governs data display behavior only — i.e. what the widget shows and whether it keeps updating. It does not govern breach trigger/lock/flatten mechanics (UC_4.10.1 Soft Breach, UC_4.10.2 Hard Breach) or Pulse Score's scoring-eligibility of flatten/cancel orders (Ref: UC_4.2.2 §6 Exceptional Flow), both of which are unchanged by this CR.
| Criterion | Soft Breach (Ref: UC_4.10.1) | Hard Breach (Ref: UC_4.10.2) |
|---|---|---|
| While breach is active | Positions/orders force-closed by the Soft Breach flatten still push their normal WebSocket event (POSITION_METRICS close, PERFORMANCE_UPDATE, etc.) the instant they are flattened/cancelled — the affected widget(s) update immediately, same as any trader-initiated close/cancel. Widgets are not frozen during the soft-lock window itself. | Once the Atomic Kill Switch's flatten/cancel step completes andstatus is written to Failed, the backend stops emitting further real-time updates for that account — no more POSITION_METRICS/PERFORMANCE_UPDATE/equivalent events fire. Each widget's last-received values freeze in place (Ref: CR-14 retention-of-last-snapshot behavior, now permanent rather than temporary). |
| Data capture (snapshot-based widgets, e.g. Equity Curve) | The underlying snapshot/history capture (e.g. the Equity Curve's 30-minute cron, Ref:UC_4.2.3 BR_4.2.3.4) pauses for the duration the account is under an active Soft Breach lock — no new snapshot is captured between the breach firing and reset_time auto-unlock. This supersedes the prior (2026-09-15) wording that snapshotting continued uninterrupted through Soft Breach. | Data capture stops entirely once the account reachesstatus = Failed — same as the "while breach is active" row above; there is no further capture to resume, since the account does not return to active trading. |
On Soft Breach auto-unlock (reset_time) | Widgets keep showing the data accumulated before/during the lock window — nothing is cleared. Once the trader resumes trading after unlock, new activity displaysalongside (appended after) that pre-existing data, in the same session — consistent with Soft Breach carrying no archive and not being a failure (Ref: _QNA_INDEX.md, "Soft Breach KHÔNG phải failure và KHÔNG có archive"). Paused snapshot capture (row above) resumes on the next scheduled 30-minute tick after unlock. | Not applicable here — Hard Breach has no auto-unlock; see "On Reset" row below. |
| On Reset (Hard Breach only) | — | Widgets donot reset to default automatically at the moment of breach or at archive_date firing on their own. The reset-to-Day-0/default display only occurs once a Reset/Re-Buy purchase completes successfully and a brand-new sub-account is provisioned and attached to the user (Ref: UC_4.10.2 §5.3 Reset Flow) — at that point every widget is simply reading a fresh account with no trading history yet, which is why it displays the same Day-0/empty-state values as a newly created account, not because any widget performed a "clear" operation on the old account's data. This matches the confirmed rule that the D=0/default dashboard state "KHÔNG BAO GIỜ reset khi failure, chỉ reset sau khi thanh toán Reset/Re-Buy thành công" (Ref: _QNA_INDEX.md). |
| Ref | Source: User instruction (BA session), 2026-09-19, relaying client requirement on Soft/Hard Breach data display; cross-checked againstUC_4.10.1, UC_4.10.2, and _QNA_INDEX.md (Soft Breach no-archive/not-a-failure; D=0 reset-only-after-successful-purchase). |
(New shared rule, 2026-09-19 — consolidates the Soft/Hard Breach data-display behavior that otherwise would be repeated near-identically across all 6 widgets in scope. Per-widget sections in UC_4.2.1–4.2.5 and UC_4.8.1 cross-reference this CR and add only what is genuinely widget-specific, e.g. the Equity Curve's snapshot-cadence interaction (Ref: UC_4.2.3 BR_4.2.3.4) or the PnL Gauge's existing Breached dial state (Ref: UC_4.8.1 BR_4.8.1.5).)
CR-18: Inbound Webhook — No-Op Returns 200 OK and Drops the Payload (Shared)
Applies to every Middleware endpoint or webhook receiver called by Zapier, Typeform, Klaviyo or the Discord bot flows.
| Criterion | Rule |
|---|---|
| When it applies | The call arrives but its precondition is not met, or it would repeat an outcome that is already applied (e.g. the user already has the target state). |
| Response | Return200 OK. Do not return an error status. |
| Payload | Drop it — no data is written and no downstream action runs. |
| Calling flow | The calling Zap / flow is not failed or crashed, and no retry loop is triggered. |
| Extra side actions | Any additional action on the drop (e.g. a Slack alert to Operations) is defined in the owning UC, not here. |
Known instances: UC_8.1.1 (Discord skip-if-unlinked) · UC_8.2.8 BR_8.2.8.7 (duplicate pod confirmation click) · Pod System Flow 10 / 10.1A (duplicate Style form submission, discord_user_id IS NULL, missing pod channel, Trading Plan submitted while on waitlist).
CR-19: Platform Provisioning & Third-Party Charting Platform Rules (Shared)
Applies to every UC touching trader platform provisioning (Checkout Step 3 — Select Platform, Zapier Flow 1 Step 3 POST /provision-sim-user), the license-validation endpoint, and Payout UCs that compute Sierra Chart reimbursement. This is a cross-cutting rule — individual UCs reference it instead of re-defining platform provisioning sequencing or the license API contract.
19.1 Provisioning Sequence — Rithmic-First (The Foundation)
| Criterion | Rule |
|---|---|
| Step order | For all of TradeSea, Quantower, ATAS, MotiveWave, and Sierra Chart, the middlewareMUST first provision the Rithmic trading account, because Rithmic is the actual execution and data engine. Once Rithmic credentials are created, the middleware evaluates platform_selection and executes the corresponding platform-specific flow (§19.2–§19.5). |
19.2 [CHR-117] Sierra Chart — BYOL Disclaimer & Usage-Based Reimbursement
Affected UCs: UC_2.7-2.8, UC_4.15.2, UC_5.1, UC_7.3, UC_8.1.1.
1. Frontend Disclaimer. On the Checkout page, Step 3 (Selecting a platform): if Sierra Chart is selected, display this exact copy:
"Sierra Chart operates on a Bring-Your-Own-License (BYOL) model. You must purchase your own package. Stack Trading will reimburse your platform costs ($35/mo) directly to your payout account upon successfully passing your evaluation. Once funded, you will be reimbursed at the end of every Live Level. Note: To qualify for the reimbursement, our system must verify that Sierra Chart was used as your primary execution terminal for the majority of your active trading days."
2. Tracking. For every user, track platform_selection == 'Sierra Chart'.
| Condition | Result |
|---|---|
User status ever changes toFailed (hard breach on SIM env) | Donot refund. |
3. Reimbursement calculation. When a user moves from SIM to Live, and for every Live level progression, or when a user gets a hard breach on Live:
(1) Calculate payout ratio:
Payout_Ratio = Sierra_Chart_Days / Total_Active_Trading_Days| Variable | Definition |
|---|---|
Sierra_Chart_Days | [CHR-117] The number of distinct days in the current level where the user placed at least 1 order on Rithmic that returned AppName = Sierra Chart. On Rithmic, AppName is only ever returned at the point an order is placed — there is no concept of reading AppName from a held position or a bare login with no order. If the user placed orders on 2 platforms the same day, including Sierra, that day still counts as a Sierra_Chart_Day as long as at least 1 of that day's orders returned AppName = Sierra. |
Total_Active_Trading_Days | [CHR-117] The number of distinct days in the current level where the user placed at least 1 order on Rithmic, on ANY platform (not just Sierra). |
Superseded wording (pre-2026-10-30): the prior draft defined
Total_Active_Trading_Daysas "a day the trader logged into the platform and established a connection to the server" (general login/connection, not order-gated) and left ambiguous whether held-position days without new orders should count towardSierra_Chart_Days. The client explicitly rejected both: do not track held positions or general Rithmic logins — gate both numerator and denominator on "placed at least 1 order," full stop.
(2) If Payout_Ratio >= 0.5: calculate the final amount:
Final_Amount = Ceiling(Days in Level / 30) * $35.00Push the calculated amount to the Dots API (Contractors and International traders) or Rippling API (W-2s - US traders from level 6 to above):
| Trigger | Action | Impacted UC_ID |
|---|---|---|
| Level progression | Total_Payout = Level_Performance_Bonus + refundable_amount (from market data) + final_amount (Sierra Chart). Update the SQL payouts table record for this promotion event with amount = Total_Payout. This ensures that when Flow 5 picks up this payout ID, it processes the full amount (Bonus + Refund) automatically. | UC_8.1.1 (promotion trigger) |
| Hard breach | Push the calculated amount to the Dots API or Rippling API. | UC_4.15.2 (hard breach trigger) |
| SIM to Live | Push the calculated amount to the Dots API or Rippling API. | UC_5.1 (go-live trigger) |
Example: 10 days / 30 = 0.33 → Ceiling = 1 → (1 × $35 = $35).
45 days / 30 = 1.5 → Ceiling = 2 → (2 × $35 = $70).
[CHR-117 — client confirmation, 2026-10-30] (2a) Reaching two levels within the same calendar month — paid twice, NOT blocked. If a user reaches Level 3 on, say, 1/10 and Level 4 on 30/10, each level progression independently triggers its own Reimbursement calculation (Step (1)–(2) above) scoped to that level's own Days_in_Level/Sierra_Chart_Days/Total_Active_Trading_Days window — this produces 2 separate Final_Amount payouts in the same calendar month. This is confirmed intentional, not a bug — do not add logic to detect or block a second same-month payout, and do not collapse the two Final_Amount calculations into one. The client explicitly accepted paying 2x in this scenario as currently specified.
⚠️ Conditional cap — not yet in scope, requires a separate CR. The client separately noted they are open to capping total Sierra Chart reimbursement at $35/month (i.e. suppressing/reducing the second same-month payout so the user never nets more than $35 in any calendar month), but said this would need its own formal Change Request before being implemented. Until that CR is raised and approved, the uncapped 2x-payout behavior above is the confirmed behavior — BA should not build a cap speculatively.
(3) If Payout_Ratio < 0.5: trigger the Sierra_Reimbursement_Denied email via AWS SES:
"Our connection logs indicate that Sierra Chart was not your primary execution terminal during this period (Used [Sierra_Chart_Days] out of [Total_Active_Trading_Days] active days). As a result, the platform reimbursement has been declined."
Out-of-scope: Stack Trading does not need to verify if users have paid for Sierra Chart or not. Stack Trading does not need to charge the user a Sierra Chart fee (reimbursement only) — users go directly to sierrachart.com to purchase their own license, and Stack Trading is not involved in that transaction.
19.3 [CHR-129] Quantower — License Validation Endpoint POST /api/license
Affected UC: UC_2.7-2.8.
The flow is inverted — Stack Trading hosts an endpoint for Quantower. When the user opens Quantower and tries to connect, Quantower pings Stack Trading's endpoint to ask "Is this user authorized?" The middleware checks the database, and if platform_selection == 'Quantower', returns an approval response. (Quantower API spec: api.quantower.com.)
/api/license
Request:
{ "username": "User@emaildomain.com", "provider": "Quantower" }Response:
{ "allowed": true }| Criterion | Rule |
|---|---|
| Data source | Stack Trading's user table — contains the trader's chosen charting platform. |
| Authentication | Headerx-api-key. Keys must authenticate or validate (Auth0, or another secure method). |
| Key management | Keys must be managed by Stack Trading — rotated, with a way to rotate the key. Prefer a static key provided to vendors (not a short-lived token). |
| Key scope | Keep this simple, data-driven, and dynamic — the schema{ username, provider } → { allowed: true/false } must not be scoped so narrowly that it only works for Quantower; it must also work for other vendors (e.g. Sierra Chart, which the client intends to propose this same endpoint for).API Keys should be given scope. Meaning if we create the API key for Quantower and they attempt to query for ATAS we can return 403 Forbidden |
| Email matching | Case-insensitive. |
| Provider matching | Case-insensitive. Theprovider field allows any provider input. |
| No match | Do not return more data than given. If the email or the provider does not match the query, the return isfalse. |
| HTTP status — wrong key | 401. |
| HTTP status — valid key, wrong scope | 403 — reserved for proper auth but lack of permissions. |
| Rate limiting | Limit calls/minute allowed to a reasonable amount (15 calls per 10 seconds) |
| IP restriction | Blacklist model (not whitelist). Infrastructure-level enforcement — not application level — so it can be updated and maintained without requiring a deployment. List starts empty — all IPs are allowed by default. Ops manually adds any abusive IP to the blacklist to block it. |
19.4 [CHR-130] MotiveWave + ATAS — B2B License Provisioning
Affected UC: UC_2.7-2.8.
The middleware makes a separate, subsequent API call directly to the ATAS or MotiveWave B2B API to automatically generate a charting license/account for the user. The dashboard then displays both the Rithmic credentials and the newly generated platform license, IF applicable.
Impact on UC_2.8.3 / UC_2.8.4
This B2B license call always runs as step 2, after Rithmic account creation (step 1), in parallel with Auth0 account creation:
Create Rithmic account
→ then, in parallel: Auth0 account creation + 3rd-party platform (ATAS/MotiveWave) license call
- Auth0 fails OR Rithmic fails → HALT (existing behavior, unchanged — Ref: UC_2.8.3 §5 / UC_2.8.4 §6)
- Platform license call fails → PROCEED normally (does NOT halt, does NOT block checkout/login)3rd-party platform account creation failure never stops provisioning:
- The existing 2-way gate in
UC_2.8.3 §4 step 6/UC_2.8.4 §5 step 4(Auth0 + SIM/Rithmic) is unchanged — it stays the only hard gate on the "Setting up your trading floor" interstitial. The B2B license call (CHR-130) is explicitly not added as a third blocking condition. - If the ATAS/MotiveWave B2B API call fails: retry 3 times, then stop retrying and treat as failed. The user still completes checkout and still reaches login/Dashboard normally with Rithmic credentials — Rithmic account is not rolled back.
- The trader is notified by email that the issue has been escalated to Ops. Ops is notified via Slack with full account-creation details for the specific platform, so an Ops member can create it manually and email the trader once done.
Slack alert — Platform License Provisioning Failed (Ops)
| Field | Value |
|---|---|
| Trigger | ATAS/MotiveWave B2B license API call fails after 3 retry attempts |
| Channel | #ops-general — C0BR8LRC5MK (Prod) · #test-ops-general — C0BR6UXRR8D (Test) |
| Message | See the two message templates below (one per platform). |
| Additional action | Ops creates the account manually on the vendor's portal, then emails the trader once complete (no system-automated completion email for this step — manual) |
Slack message template — MotiveWave:
⚠️ Platform License Creation Failed: Trader [Trader_ID] / [Trader_Email] — Platform: MotiveWave.
Rithmic account created successfully; MotiveWave license call failed after 3 attempts. Manual account creation required.
Details:
• Partner: [Partner_Code]
• Partner password: [Partner_Pwd]
• Email: [Trader_Email]
• First name: [First_Name]
• Last name: [Last_Name]
• Lease period (months): [Lease_Period_Months]
• Service: RithmicSlack message template — ATAS:
⚠️ Platform License Creation Failed: Trader [Trader_ID] / [Trader_Email] — Platform: ATAS.
Rithmic account created successfully; ATAS license call failed after 3 attempts. Manual account creation required.
Details:
• Login (email): [Trader_Email]
• Password: [Auto_Generated_Password] (also emailed to trader separately)
• Stack Trading account ID: [Stack_Trading_User_ID]
• Region (country, full name): [Trader_Country]
• Land (country, full name — same as Region): [Trader_Country]
• Autorenewal: 1Email — SES-48 (new)
| Field | Value |
|---|---|
| Email ID | SES-48 |
| Template Key | Platform_License_Provisioning_Failed |
| Trigger | Same as the Slack alert above — ATAS/MotiveWave B2B license call fails after 3 retries. Fired to the trader at the same time as the Ops Slack alert |
| Recipient | Trader whose platform_selection is ATAS or MotiveWave and whose license call failed |
| UC Reference | UC_2.7-2.8 (CHR-130) |
Full Email:
Subject: Action Needed on Your [platform_selection] License — Our Team Is On It
Header: We Hit a Snag Setting Up Your [platform_selection] License, [First Name].
Body:
We weren't able to automatically generate your [platform_selection] charting license during setup.
Our Operations team has already been notified and is setting up your [platform_selection] license manually. You'll receive a follow-up email with your license details as soon as it's ready.
No further action is needed on your end right now.
If you have any questions in the meantime, contact support@stacktrading.com.
Footer: Stack Trading | support@stacktrading.com
19.5 TradeSea — Guide Only
There is no custom API integration required. The only thing needed at this time is the guide.
CR-20: is_default Payment Card Write Scope
Applies to every flow that takes a payment from the trader through a Change Payment Method / Change Payment Card modal or a 1-Click/Secure Checkout purchase — i.e. Market Data subscription billing (UC_4.17.1) and one-off transactions: Reset, Re-Buy, Time Extension (UC_4.10.3, UC_4.10.4, UC_4.15.3). This CR governs when is_default is written.
20.2 Market Data Change Payment Method modal — always writes is_default
| Trigger | is_default write |
|---|---|
Trader selects or adds a card in the Market Data Change Payment Method modal, then confirms (Done) | Set is_default = true on that card. Ref: UC_4.17.1 §5.6 step 30 |
Trader selects Alternative Payment Method in the same modal, then confirms | Clear is_default = null on every saved card. Ref: UC_4.17.1 §5.9 |
20.3 Reset / Re-Buy / Time Extension
| Criterion | Case | Rule |
|---|---|---|
| Scope | — | Clicking Change Payment Method / using 1-Click / Secure Checkout Fallback during a Reset, Re-Buy, or Time Extension purchase. This is a one-off transaction for that specific purchase only. |
| Case A — no card ever saved before | Trader has never saved any card at all (no vaulted card exists — e.g. they originally checked out with APM and this is the first card they've ever added) and pays this one-off purchase with a credit card | The newly added card is saved as is_default = true |
Case B — any card already saved, regardless of current is_default | Trader already has at least one saved card — whether one of them is is_default = true, or all of them are is_default = null because none was ever marked default | Do not write to is_default at all, no matter which payment method is used to pay. A newly added card, if any, is saved normally but is never marked is_default. The existing is_default state is left exactly as it was before the purchase. |
CR-21: Admin BPS Operations — General Rules (Shared)
Applies to every Admin BPS endpoint (Business/Platform Support endpoints called by Stack Trading's internal Ops/Admin team via the internal BPS UI at bps-admin.stacktrading.com.
21.1 Common payload shape & required fields
Every Admin BPS POST endpoint that modifies a trader account carries this common envelope.
{
"user_id": "usr_uuid_12345678",
"admin_id": "admin@stacktrading.com",
"reason": "Trader platform preference change request",
"transcript_url": "https://stacktrading.freshdesk.com/agent/tickets/99897"
}| Field | Required | Source / Rule |
|---|---|---|
user_id | Yes | Target trader's ID. |
admin_id | Yes | Passed explicitly in the payload by the BPS UI, or extracted server-side from the Admin's validated Auth0 JWT (Admin's Auth0 ID). |
reason | Yes | Free-text justification, typed by the Admin in the BPS UI (e.g. "Trader platform preference change request"). Not a dropdown/enum — Admin writes it themselves. |
transcript_url | Yes | Direct URL link to the existing Freshdesk ticket or Discord/Slack support thread that authorized this action — pasted by the Admin, not auto-generated (see §21.1.2). |
The middleware must reject the request with HTTP 400 Bad Request if reason or transcript_url is missing or fails validation below — regardless of which specific Admin BPS endpoint is called.
21.1.1 reason field validation
- Mandatory text field, validated at the DTO Validation layer (reject with
HTTP 400if missing or invalid). - Must contain at least 3 non-whitespace characters after trimming.
- Maximum 1,000 characters.
- Reject strings that contain only whitespace.
21.1.2 transcript_url field validation & no auto-ticket creation
- Validated at the DTO layer (
HTTP 400on failure): must be a validhttps://URL, maximum 2,048 characters. - Not restricted to
freshdesk.com— Admins may also paste Discord or Slack thread URLs. Any validhttps://URL is accepted.
21.2 Admin identity & role source — Auth0 JWT, not database
- Do NOT add admin role columns (
admin_LV1/admin_LV2/system_adminor similar) to the trader-facingUserstable. - Per
[CHR-52], Stack Trading employees authenticate via Google Workspace SSO in Auth0, which injects their role (Level_1,Level_2, orAdmin) into the JWT custom claims. - The middleware must validate the Admin's role directly from the Admin's Auth0 JWT token on every Admin BPS call.
admin_idlogged anywhere (payload,BPS_Audit_Log, etc.) is the Admin's Auth0 ID, extracted from their validated Auth0 JWT.
21.4 Ops tooling — no dedicated Admin BPS Console UI in scope
- The Admin BPS Console UI itself is out of scope for SotaTek's delivery. Stack Trading's own team (Adam) has already built the internal BPS UI at bps-admin.stacktrading.com and is waiting on the backend endpoints to connect to.
- SotaTek's scope for this endpoint family is limited to: secured REST endpoints (validating payload per §21.1, Admin role per §21.2) + Swagger/Postman documentation for Stack Trading to wire up on their side.
21.5 Support Action Audit Trail — immutable BPS_Audit_Log
Per the V7 Prop Tech BPS specification: all administrative overrides executed via a Support Screen must be written to an immutable PostgreSQL audit table. The record must include: Admin ID, User ID, Action Type, Timestamp, Reason, and Transcript URL.
Admin ID— see §21.2 (Auth0 ID from the Admin's validated JWT).User ID— the target trader'suser_id.Action Type— not an input parameter sent by the Admin. Each BPS endpoint automatically writes its own action name — a standardVARCHARstring, not a database enum — intoBPS_Audit_Logwhen called. Full list of Action Type values:ADMIN_PLATFORM_REASSIGNMENTADMIN_POD_REASSIGNMENTADMIN_HARD_RESETFORCE_SESSION_KICKREDUCE_PROFIT_TARGETINJECT_GOODWILL_EQUITYOVERRIDE_TIME_EXTENSIONSEND_GOODWILL_MERCHISSUE_REFUNDRESEND_RTRADER_INSTRUCTIONSRESEND_WELCOMETRIPLE_A_CRYPTO_RESCUEREVOKE_POD_LEADERSHIPTERMINATE_POD_LEADERSHIPRESET_VERIFF_KYCOVERRIDE_KYC_EXCEPTIONAPPLY_PAYOUT_HOLDESCALATE_TO_L2IMPERSONATE_USERPROVISION_IN_HOUSE_TRADERMANUAL_PROMOTION
Timestamp— per CR-08 (UTC / ISO 8601).Reason— see §21.1.1.Transcript URL— see §21.1.2.
21.6 Action Type ↔ UC_ID mapping
Maps each Action Type to its owning UC (Stage 10 — Admin / BPS Operations).
| Action Type | UC_ID |
|---|---|
ADMIN_PLATFORM_REASSIGNMENT | UC_10.4.4 (Admin Platform Reassignment) |
ADMIN_POD_REASSIGNMENT | UC_10.3.1 (Admin Pod Reassignment) |
ADMIN_HARD_RESET | UC_10.4.1 (Admin Hard Reset) |
FORCE_SESSION_KICK | UC_10.4.2 (Force Session Kick) |
REDUCE_PROFIT_TARGET | UC_10.2.1 (Reduce Profit Target) |
INJECT_GOODWILL_EQUITY | UC_10.2.2 (Inject Goodwill Equity) |
OVERRIDE_TIME_EXTENSION | UC_10.2.3 (Override Time Extension) |
SEND_GOODWILL_MERCH | UC_10.1.8 (Send Goodwill Merch) |
ISSUE_REFUND | UC_10.4.3 (Issue Full Refund — Account Kill) |
RESEND_RTRADER_INSTRUCTIONS | UC_10.2.5 (Resend R|Trader Instructions) |
RESEND_WELCOME | UC_10.2.9 (Resend Welcome / Claim Email) |
TRIPLE_A_CRYPTO_RESCUE | UC_10.2.6 (Triple-A Crypto Rescue) |
REVOKE_POD_LEADERSHIP | UC_10.3.2 (Revoke Pod Leadership) |
TERMINATE_POD_LEADERSHIP | UC_10.3.3 (Terminate Pod Leadership) |
RESET_VERIFF_KYC | UC_10.2.7 (Reset Veriff KYC) |
OVERRIDE_KYC_EXCEPTION | UC_10.4.7 (Manual KYC Exception Override) |
APPLY_PAYOUT_HOLD | UC_10.4.6 (Manual Payout Hold) |
ESCALATE_TO_L2 | UC_10.2.8 (Escalate to L2) |
IMPERSONATE_USER | UC_10.5 (Admin Impersonation) |
PROVISION_IN_HOUSE_TRADER | ❌ — not identified in the current UC_10.x list. Day 2 item |
MANUAL_PROMOTION | ❌ — not identified in the current UC_10.x list. Day 2 item |
CR-22: Reset Time & Market Closed Definition — Futures vs. Forex (Shared)
22.1 Reset Time — Definition & Anchor per Asset Class
reset_time (roll-over) is the fixed daily clock boundary at which a Soft Breach soft-lock auto-unlocks and the Daily P&L counter resets.
| Criterion | Futures | Forex |
|---|---|---|
| Anchor | Fixed daily wall-clock literal — 18:00 ET (18:00 EST in winter / 18:00 EDT in summer), every day. | Fixed local wall-clock instant — 17:00 ET (17:00 EST in winter / 17:00 EDT in summer), Monday–Friday. |
| DST (EST/EDT) resolution mechanism | Source-native — resolved upstream by the CME Reference Data API itself when it publishes that day's Official_Close_UTC/Session_Open_UTC; no application-side DST logic. | Timezone-library-native — backend resolves the America/New_York IANA zone's local 17:00 every day (e.g. Intl/date-fns-tz/luxon); the zone's own EST/EDT transition dates yield the correct UTC instant automatically, no hardcoded offset or manual transition-date table. |
Full mechanism detail (applies identically everywhere this UTC instant is consumed — UC_6.1.1, UC_6.1.2, UC_6.1.3, UC_4.1.3): UC_4.1.3 BR_4.1.3.5. Every downstream UC consumes the already-resolved UTC instant — none re-implements EST/EDT offset math itself.
22.2 Market Closed Definition — Futures (CME Cronjob-Driven) vs. Forex (Fixed Weekly Window)
"Market closed" is asset-class-specific and is never a locally-hardcoded assumption
| Criterion | Futures | Forex |
|---|---|---|
| Authoritative source | Must be driven by the CME Reference Data Ingestion cronjob (UC_6.1.1) — a single daily cron @ 01:00 UTC that pulls CME's published trading calendar (GET /v3/tradingHours, GET /v3/productDetails) per whitelisted symbol and writes Session_Open_UTC/Official_Close_UTC/Halt_Start_UTC/Halt_End_UTC into CME_Instrument_Details | Fixed weekly window, not exchange-driven. Confirmed close edge: Friday 17:00 ET — same institutional rollover instant as the daily reset_time (§22.1) |
CR-23: Pod Status Lifecycle — Mentee & Pod Leader (Shared)
Single reference for every pod status of a mentee and of a Pod Leader: when a status is entered, what it blocks, what it keeps, and what moves it to another status. Owning UCs: UC_8.2.1 (Flow 10), UC_8.2.8 (Flow 11), UC_8.2.9 – UC_8.2.13 (Flow 15A–15C, 17), UC_4.14.2 (My POD card), UC_4.17.1 (Flow 12), UC_10.3.2 / UC_10.3.3 (BPS Revoke / Terminate). When a UC and this CR differ, the owning UC's latest version wins and this CR must be updated.
23.1 Mentee — pod_status
pod_statusis stored in its own column, separate from the trading account stateusers.status, so the two never overwrite each other.- Only LIVE Level 1–6 traders can be mentees. SIM / Evaluation never join a pod. From Level 7 a trader is never a mentee again.
- A trader counts as in the waitlist when
pod_statusisWaitlist,Inactive_HoldorPending_Pod_Confirmation. Join_Dateis the date the trader entered the waitlist (queue order, FIFO). It is notpod_join_date(the date the trader joined a pod). Entering the waitlist does not changepod_join_date.
| Status | Entered when | Blocked from | Keeps | Moves to another status when |
|---|---|---|---|---|
null (in no pod) | Never joined: live onboarding completed, Pod_Mentorship_Invite email receivedLeft: account Terminated (LIVE) or Failed (SIM) after Hard Breach or Resign (Flow 7) · reached Level 7 · rejected when clicking the confirmation link | ❌ No leader, no Discord pod channel, no Trading Plan deadline❌ A trader who left is removed from the Discord channel; pod_leader_id and pending SLA deadlines are cleared | ✅ A trader who never joined sees [Join a POD]✅ A Level 7 trader who accepts the leader role gets their own pod | Submits the Style form, a leader has a spot → AssignedSubmits the Style form, no spot → WaitlistTerminated / Failed or Level 7+: stays null, cannot join again |
Assigned | Flow 10 direct match (same Asset_Class required, same Style preferred, then the pod with the fewest mentees) · clicks the confirmation link within 48h · returns from Inactive_Hold and the matching finds a leader · Flow 15A re-matches to a new leader | ❌ Cannot change or leave the pod by themselves (Support → Ops POST /admin-pod-reassignment, 30-day cooldown)❌ Cannot resubmit the Style form❌ Trading Plan is submitted once only❌ Discord not linked → My POD card stays on Link Discord to Unlock | ✅ Leader, members, Discord channel, Pod Member role✅ Trading Plan analysed by AI and sent to the leader✅ On a pod change, the existing Trading Plan is reused | 30 days without trading → Inactive_HoldLeader Suspended / Terminated and no other leader found → WaitlistTerminated / Failed or reaches Level 7 → nullOps reassigns the pod → stays Assigned |
Waitlist | Submits the form with no spot · loses the leader and Flow 15A finds no match · Inactive_Hold trader who was in a pod reactivates with no spot · clicks the link but the leader is no longer Active and no other match → end of the queue (Join_Date = time of entering the waitlist)Inactive_Hold trader who was waiting reactivates → keeps the original Join_Date | ❌ No leader, no pod channel❌ Cannot resubmit the Style form❌ A submitted Trading Plan gets no AI analysis, no SLA, no webhook to a leader❌ No queue position or estimated wait shown | ✅ Queue position (FIFO by Join_Date), no expiry✅ Trading Plan PDF kept on S3 and reused once assigned | Flow 11 invites → Pending_Pod_Confirmation30 days without trading → Inactive_HoldTerminated / Failed or reaches Level 7 → null |
Pending_Pod_Confirmation | A spot opens (mentee leaves, new leader, Ops raises Max_Capacity, leader Suspended → Active) and Flow 11 selects the trader | ❌ Link is single-use and expires after 48h❌ Not invited again; cannot resubmit the form❌ Dashboard shows no invitation, button or countdown | ✅ The spot is held for the trader (Current_Count already increased) | Clicks within 48h → AssignedClicks, leader no longer Active, no other match → Waitlist (end of the queue)Clicks but is Terminated / Failed or Level 7+ → nullNo click within 48h, or caught by Flow 12 before clicking → Waitlist_Ghost |
Waitlist_Ghost | No click within 48h · caught by Flow 12 (30 days without trading) while waiting to click | ❌ Removed from the queue, loses the old Join_Date | ✅ Treated as never joined: My POD card shows [Join a POD] again | Resubmits the form, a spot exists → AssignedResubmits the form, no spot → Waitlist (end of the queue) |
Inactive_Hold | 30 days without placing an order (Flow 12 Zombie Protocol): market data paused; a trader in a pod releases the spot | ❌ Removed from the pod member list❌ Skipped when Flow 11 selects a trader❌ Cannot resubmit the Style form | ✅ A waiting trader keeps the waitlist record and Join_Date✅ SLA deadlines are paused, not lost | Reactivates market data, was in a pod, matching finds a leader → Assigned (no priority for the old pod)Reactivates, was in a pod, no spot → Waitlist (end of the queue)Reactivates, was waiting → Waitlist (previous position) |
Not a pod status: a trading account Locked for DTO / Hiatus (Flow 13). A waiting trader is skipped by Flow 11 but keeps Join_Date. An Assigned trader stays in the pod. SLA deadlines are paused.
23.2 Pod Leader — Pod_Leader_Registry.Status
- Flow 10 and Flow 11 match mentees only to a leader whose
StatusisActive.
| Status | Entered when | Blocked from | Keeps | Moves to another status when |
|---|---|---|---|---|
Active | Opts in via Flow 15B · or Founding Mentor created via Flow 1.5 | Nothing | ✅ Receives mentees (Flow 10 / 11, while Current_Count < Max_Capacity)✅ Carry accrues and vests monthly (Flow 17)✅ Discord role and channel pod-[leader_name]✅ Founding Mentor has a Workspace account | DTO / Hiatus leave (Flow 13) → On_LeaveDrawdown Defense (Flow 4B) or BPS Admin sets it manually → SuspendedAccount fails or resigns (Flow 7) or BPS Terminate → TerminatedBPS Revoke → Permanently_Ineligible |
On_Leave | Leader takes DTO / Hiatus leave via Flow 13 | ❌ Receives no new mentees (Flow 10 matches Active only)❌ SLA timers paused❌ No Acting Cover; mentees are not re-matched | ✅ Keeps pod, mentees, Pod Leader role, pod channel, Workspace (still receives the Wednesday AI Brief)✅ Carry still accrues and vests✅ POD_LEADER_TERMINATED / Flow 15C is blocked during the leave | Flow 13 Return Protocol → Active |
Suspended | Flow 4B Drawdown Defense when is_pod_leader = True: account locked for 48h, then set to Suspended · or BPS Admin sets it manually (UC_8.2.9 Trigger A) | ❌ Pod dispersed; mentees re-matched via Flow 15A❌ During the 60-day PIP (stagnation_deadline = Today + 60d): no mentees, no opt-in, no pod activity❌ Carry frozen | ✅ Carry is not lost; kept until the leader returns to Active | Passes the PIP and opts in again via Flow 15B → Active (stagnation_deadline = Today + 90d)Fails the PIP → Terminated (carry lost)BPS Revoke → Permanently_Ineligible |
Terminated | Flow 7 Condition B: the leader's account fails or resigns · or BPS Terminate (UC_10.3.3, only while Active) → Flow 15C cascade | ❌ Loses the Pod Leader role (Discord and DB); pod channel archived❌ Mentees re-matched or sent to the waitlist❌ Pending_Carry_Balance = 0 (carry lost)❌ Founding Mentor's Workspace @stacktrading.com is suspended | ✅ BPS Terminate only: SIM / LIVE account unchanged; the leader left the role in good standing and can be invited again | BPS Terminate only: Flow 4 invites again, opts in via Flow 15B → Active |
Permanently_Ineligible | BPS Revoke (UC_10.3.2, Level 2 Admin / System Admin only) while the leader is Active or Suspended (Flow 15 Trigger C) | ❌ Loses everything as in Terminated (role, channel, mentees, carry = 0)❌ Permanent blacklist: Flow 4 does not invite, Flow 10 does not match, Flow 15B refuses opt-in | ✅ SIM / LIVE account unchanged | None (final state) |
Glossary
| Term | Definition |
|---|---|
| Trader | Participant in the evaluation program; if targets are met, receives real capital allocation |
| PnL (Profit and Loss) | Trader's profit or loss metric |
| Equity | Current net worth = Starting Balance + Floating P&L. Distinct from Balance (settled) |
| Drawdown | Capital decline — % loss from the highest point ever reached. Primary risk metric |
| Daily Loss Limit | Maximum permitted loss in one trading day. Breach → Soft_Breach (trading suspended until day reset) |
| Max Drawdown / Level Stop | Maximum cumulative loss limit. Breach → account permanently closed (Hard Breach) |
| Profit Target | Required profit level for a trader to pass the evaluation |
| Soft Breach | Minor violation (Daily Loss Limit hit). Trading temporarily suspended; account remains open |
| Hard Breach | Severe violation (Max Drawdown hit). All positions liquidated; account permanently locked |
| Flatten / Liquidate | Close all open positions, returning account to a "flat" (no open orders) state |
| Trim (Partial Position Reduction) | Close a portion of a position (not all) to reduce risk |
| TRIM_TO_COMPLIANCE | Automated algorithm: when risk threshold is exceeded, iteratively close largest positions until portfolio is within safe limits |
| Atomic Kill Switch | Emergency shutdown — 4 sequential steps: (1) lock new orders, (2) close all positions, (3) cancel pending orders, (4) update DB → status "Failed" |
| Margin | Minimum capital required to hold positions overnight |
| DMA (Direct Market Access) | Order routing mechanism that sends orders directly to the real exchange without intermediaries |
| FCM (Futures Commission Merchant) | CFTC-licensed broker for Futures contracts. Ironbeam is Stack Trading's FCM |
| Omnibus Account | Master account held under the company name at the exchange, containing individual trader sub-accounts |
| KYC (Know Your Customer) | Mandatory identity verification process before live capital allocation, for AML/fraud prevention |
| W-8BEN / W-9 | US tax forms: W-9 for US citizens, W-8BEN for non-US persons |
| Carry | Commission earned by a Pod Leader from the profits of their Pod members |
| Pod | Trading group — one leader plus multiple members sharing a trading style |
| WORM (Write Once, Read Many) | Immutable storage mechanism: once written, data cannot be modified or deleted |
| Immutable Ledger | Immutable on-chain record — data written to Polygon PoS blockchain; no one can alter payment history |
| W-2 Employee | Official US employee with a fixed-term contract, salary, benefits, and paid time off |
| International Contractor | Non-US collaborator; receives compensation via Dots (not US payroll) |
| Rippling | HR and payroll management software — automates employee onboarding, tax calculation, and salary disbursement |
| DTO (Days Time Off) | Paid Time Off |
| Evaluation Package | Trader evaluation tier — 3 options: Associate Track (EVAL_L1 / $650 / $25k notional), Accelerated Associate Track (EVAL_L2 / $1,250 / $50k notional), Advanced Associate Track (EVAL_L5 / $4,500 / $150k notional) |
| Founder Cohort | "Founding 500" program — first 500 registrants purchase at a permanently locked price (no market increases) |
| Reset Token | Token allowing a failed trader to reset their Sim account to Day 1, keeping their tier. Cheaper than a new purchase |
| Re-Buy | When a trader fails on a Live account (Level 1+), they must purchase a new Challenge to restart from Level 0. Price = Challenge Price × (1 − Retry_Discount) |
| Defense Protocol | Protection for Level 9+ traders who hit Level Stop — instead of immediate termination, they enter a 48-hour cooling-off period, then trade again with reduced parameters (capital ×0.25, target 6%, stop 5%) |
| Flow A–J | Checkout geo-routing system by country: A=USA/default, B=UK/AU, C=EU/EEA, D=Quebec (French UI), E=UAE, F=Sanctioned (block 403), G=India, H=Philippines, I=Brazil, J=South Africa |
| NMI | Primary payment gateway — processes Credit Card, Apple Pay, Google Pay via Collect.js iframe (PCI compliant) |
| Rithmic | Futures gateway — Level 0 uses Paper Trading Gateway (sim); Level 1+ uses Live FCM Gateway (Ironbeam) |
| TraderEvolution | Primary Forex platform — provides Admin API for account management and equity monitoring |
| YourBourse Bridge | FIX routing bridge — routes orders from TraderEvolution/MT5 to Forex.com and Interactive Brokers |
| Zapier | Automation bus — orchestrates 40+ operational flows (provisioning, promotion, termination, billing, payout, etc.) |
| The Desk Manager / GEKKO | AI commentary engine (Gemini 1.5 Flash 8B) — plays the role of Gordon Gekko, auto-commenting on trader actions across 15 event types. Rate limit: 10 critiques/hour/user |
| Zapier Tables A–J | Global configuration variables stored in Zapier: Table C (global vars), Table D (email templates), Pod_Leader_Registry — native PostgreSQL table, not a live Zapier Table (name retained for cross-reference to the original RFQ "Table E" section only), Table F (Defense Matrix), Table G (Discord roles), Table H (Perks Registry), Table I (Platform Registry), Table J (Pricing) |
| UTC | Coordinated Universal Time — standard for all system timestamps, expiration metrics, and database entries. See CR-08 |
| Nuvei | Payment gateway for LatAm (Brazil, Colombia, Mexico) — replaces EBANX per CR-20260720-002 |
| Everflow | Affiliate/velocity tracking platform — replaces Impact.com per CR-20260720-003 |
| TradeSea | Trading platform — replaces NinjaTrader per CR-20260720-001 |
| Freshdesk | Support ticketing system — replaces Zendesk per CR-20260720-004 |
Changelog
| Date | Version | Updated item | Before | After | Notes |
|---|---|---|---|---|---|
| 2026-07-26 | v1.1 | Initial document | — | CR-01–CR-11 + Glossary | Init version. |
| 2026-07-30 | v1.2 | New CR-12 — UTM Attribution Capture | No global cross-cutting UTM rule existed; UTM capture logic was defined locally inside UC_2.1.1 (BR_2.1.1.4) as session-state + first-seen-wins, scoped to the checkout page only | Added CR-12: global root-layout execution (site-wide, not checkout-only) +localStorage persistence + last-touch (overwrite-on-presence) attribution + payload injection at Partner Application / Waitlist / Checkout Step 5. Supersedes BR_2.1.1.4's storage/attribution mechanism. | CR-20260730-002; Adrian Stack (Slack, 2026-07-30) |
| 2026-08-06 | v1.3 | CR-09 §9.2 — Phone Number Field: Input structure | Two-part field: [Country flag + Dial code] (prefix, left) and [Number input] (right) | Two-part field: [Dial code selector] (prefix, left) and [Number input] (right) — Removed unsourced "Country flag" component; no source confirms flag UI element. | |
| 2026-08-08 | v1.4 | New CR-13 — Rounding to 2 Decimal Places | No shared rounding rule existed; UC_4.10.1 referenced CR-11 (display format) for its rounding logic | Added CR-13: round-half-up to nearest hundredth using the code language's native rounding function, applies to computed values likeDaily_Loss_Limit/Net_P&L. UC_4.10.1's BR_4.10.1.1 now cites CR-13 for the rounding algorithm (CR-11 remains for display formatting only) | STAGE2-031; QN-4.10.1-02/04 |
| 2026-08-10 | v1.5 | New CR-14 — WebSocket Reconnection Resiliency (Shared) | Rule lived only as UC_4.2.1 §BR_4.2.1.4, a single-widget BR referenced by 4 sibling UCs (4.2.2/4.2.3/4.2.4/4.2.5) and 4 external UCs (4.3/4.4/4.8.1/4.8.2) | Moved to CR-14 (generalized to any WS-fed Dashboard widget); UC_4.2.1's BR_4.2.1.4 removed, all 8 cross-reference points repointed tocommon_rules.md#cr-14-websocket-reconnection-resiliency-shared | User request — UC_4.2.1-4.2.5 batch edit |
| 2026-08-20 | v1.6 | CR-02 §2.2 — Default Length Limits: added Promo code input row | Table only listed Textbox (255), Textarea (500), Search box (255) — Promo code input's 100-char block limit existed only locally in UC_2.7-2.8 §5 Screen Description Row 1, not in the shared table | Added row: Promo code input — 100 chars, Block (character 101 cannot be entered, no message shown), matching UC_2.7-2.8's existing field-level spec | User request |
| 2026-08-31 | v1.7 | CR-14 renamed "WebSocket Reconnection Resiliency" → "WebSocket Reconnection & Snapshot Resiliency"; added Backend snapshot persistence / Market-closed behavior / Page reload (F5) behavior rows | CR-14 only covered mid-session reconnection (widget retains last-known values while reconnecting) — did not cover backend snapshot persistence for reload, market-closed state, or F5 behavior | Extended to: (1) Backend maintains latest snapshot per widget so reload restores immediately instead of rendering empty; (2) market-closed → keep showing latest snapshot until new data arrives; (3) F5/reload → render from snapshot first, then apply subsequent WS events on top | User instruction (BA session) — applies to all UCs referencing CR-14 (UC_4.2.1-4.2.5, UC_4.3, UC_4.4, UC_4.5.1, UC_4.5.2, UC_4.6.1-4.6.4, UC_4.8.1, UC_4.8.2, UC_4.12.1, UC_4.12.2, UC_4.12.4, UC_4.12.6, UC_4.13) |
| 2026-08-25 | v1.7 | New CR-15 — Reference (Email & Slack) | Did not exist | Added CR-15 with two sub-sections: CR-15.1 (5 operational email addresses) and CR-15.2 (Slack channel registry — 7 production channels + 4 development/test channels, each with channel ID and description) | User request |
| 2026-08-26 | v1.8 | CR-09 §9.2 — Phone Number Field: added "Submitted / stored format" row | Table only specified the two-part UI input (dial code selector + number input) and E.164 length (6–15 digits excluding dial code); no rule stated whether the dial code must be included in the value sent to the API / stored in DB — downstream UCs (e.g.UC_4.6.2_v1.md, IN-10) already assumed a combined E.164 string but CR-09 itself never said so | Added row: dial code + number are concatenated into a single E.164 string (e.g.+14155552671, no separator) before being sent in the phone_number payload / stored in DB | User request |
| 2026-09-15 | v1.9 | New CR-16 — Net PnL Display, Full Precision (Whole Dollars), No Abbreviation | Rule lived only as UC_4.8.1 §BR_4.8.1.6 point 1 (framed as a one-off CR-11 exception for the PnL Gauge center value), reused verbatim by UC_4.12.2 via cross-reference; UC_4.7.5/UC_4.12.5 (Daily Loss Limit Bar) had no dollar-value display rule at all — pure %-fill progress bars | Added CR-16 (exact dollar amount, comma separators, cents omitted, never abbreviated — deliberate exception to CR-11's 2-decimal rule). UC_4.8.1 BR_4.8.1.6 point 1 and UC_4.12.2 BR_4.12.2.2 repointed to cite CR-16. Extended to UC_4.7.5/UC_4.12.5: new BR added to each introducing a Net PnL/remaining-limit dollar-value label, citing CR-16 — this UI element has no client source; added on explicit user instruction | User request — see UC_4.8.1, UC_4.12.2, UC_4.7.5, UC_4.12.5 Changelogs |
| 2026-09-19 | v1.10 | New CR-17 — Soft Breach / Hard Breach Widget Data Display Behavior (Shared) | No shared rule existed for how the 6 Overview-screen real-time widgets (Position Monitor Table, Pulse, Equity Curve, Profit Factor, Win Rate, PnL Gauge) display data during/after a breach — UC_4.10.1/UC_4.10.2 define breach trigger/lock/flatten mechanics only, not per-widget display behavior. UC_4.2.3 BR_4.2.3.4 (2026-09-15) additionally stated the Equity Curve's 30-min snapshot cron continued uninterrupted through Soft Breach, which conflicts with the new instruction below | Added CR-17: Soft Breach — flatten/cancel still pushes immediate WS updates (widgets not frozen during the soft-lock window); snapshot-based widgets (Equity Curve) pause capture while the lock is active, resuming on the next tick afterreset_time auto-unlock; old data persists through unlock, new trading appends alongside it, consistent with Soft Breach having no archive/not being a failure. Hard Breach — WS updates stop once flatten/cancel completes and status = Failed; widgets freeze at last-received values; reset-to-Day-0 display only happens once a Reset/Re-Buy purchase provisions a genuinely new sub-account, never automatically. Supersedes the 2026-09-15 BR_4.2.3.4 wording that Soft Breach did not pause snapshotting — user confirmed override, 2026-09-19. UC_4.2.1–4.2.5 and UC_4.8.1 repointed to cite this CR | User instruction (BA session), 2026-09-19, relaying client requirement; conflict with prior BR_4.2.3.4 flagged and resolved via explicit user override — see UC_4.2.1-4.2.5, UC_4.8.1 Changelogs |
| 2026-10-09 | v1.11 | New CR-18 — Inbound Webhook No-Op: 200 OK + Drop Payload (Shared) | Did not exist — the "return 200 OK and drop" behavior was restated separately in UC_8.1.1 and across Pod System flows (QnAPOD-JOIN-16, POD-JOIN-20, POD-JOIN-21, POD-PLAN-09) | Single shared rule; UCs reference CR-18 | BA approved in UC_8.2.8 Init Flow (QnA_init_docs P-01) |
| 2026-10-12 | v1.11 | CR-18 — Known instances: UC_8.2.8 example | UC_8.2.8 BR_8.2.8.5 — confirmation click that fails re-validation | UC_8.2.8 BR_8.2.8.7 — duplicate pod confirmation click | Client round 3 (POD-WAIT-19): an invalid click is now rejected with a message and releases the seat — no longer a silent drop. Only the duplicate-click case stays under CR-18 (POD-WAIT-21) |
| 2026-10-13 | v1.12 | CR-03 — new "Searchable" criterion[CHR-99] | Dropdown rule had no search/filter requirement — only default state, long-text truncation, after-selection, re-open, and required-empty behavior | Added: every dropdown renders a search/filter input inside the open option list, letting the user narrow visible options by case-insensitive substring match, regardless of option count | Client supplement CHR-99 — "All Dropdown fields must allow to search." Applies globally (CR-03 is cross-cutting); no per-UC changes needed since every UC's dropdown fields already cite CR-03. |
| 2026-10-14 | v1.14 | New CR-19 — Platform Provisioning & Third-Party Charting Platform Rules (Shared) | No shared rule existed for platform provisioning sequencing — Zapier Flow 1 Step 3 described Rithmic-gateway platforms (TradeSea/Quantower/ATAS/MotiveWave/Sierra) as one flat IF-branch with no Rithmic-first ordering; no spec existed anywhere for an inbound license-validation API, or for Sierra Chart's BYOL disclaimer + usage-based reimbursement | Added CR-19: (1) Rithmic account provisioning always runs first for TradeSea/Quantower/ATAS/MotiveWave/Sierra Chart, platform-specific provisioning always runs after; (2)[CHR-117] Sierra Chart BYOL disclaimer + Payout_Ratio = Sierra_Chart_Days / Total_Active_Trading_Days reimbursement formula ($35/mo × Ceiling(Days_in_Level/30) if ratio ≥ 0.5, else Sierra_Reimbursement_Denied email); (3) [CHR-129] Quantower flow inverted — Stack Trading hosts POST /api/license, a vendor-agnostic {username, provider} → {allowed} endpoint secured by a static per-vendor x-api-key (scoped to provider, infra-level IP whitelist, 401 wrong key / 403 wrong scope); (4) [CHR-130] ATAS/MotiveWave get a separate B2B API call post-Rithmic; (5) TradeSea stays guide-only | CHR-117, CHR-129, CHR-130 |
| 2026-10-14 | v1.15 | CR-19 §19.2 — "SIM to Live" reimbursement trigger: UC_ID corrected UC_5.8 → UC_5.1; UC_5.8 dropped from "Affected UCs" list | Trigger table's "SIM to Live" row named UC_5.8 (go-live trigger); "Affected UCs" line included UC_5.8 | Platform entitlement (Rithmic + any platform-specific call) only ever happens once, at checkout (UC_2.7-2.8 / Flow 1 Step 3) — Flow 3H (UC_5.8) is Live account provisioning only and does not re-touch platform entitlement, so it has no CR-19 impact. The actual "SIM to Live" transition this trigger refers to is owned by UC_5.1 (Flow 3A). Corrected the trigger table row to UC_5.1 (go-live trigger) and removed UC_5.8 from Affected UCs | User correction, 2026-10-14 — UC_5.8 edit made earlier this session was reverted by user for the same reason |
| 2026-10-17 | v1.16 | Glossary — "Zapier Tables A–J" row — Table E renamed | Table E listed as "Table E (Pod Leader Registry)" alongside the other Zapier Tables, implying it is a live Zapier Table | Renamed to Pod_Leader_Registry — native PostgreSQL table, not a live Zapier Table; name retained only for cross-reference to the original RFQ "Table E" section | Client confirmation 2026-10-17 — PostgreSQL-only build instruction for Pod_Leader_Registry, restating POD-JOIN-02 |
| 2026-10-30 | v1.17 | CR-19 §19.2 [CHR-117] Sierra Chart — client correction on Sierra_Chart_Days/Total_Active_Trading_Days definitions + confirmation on same-month double payout | Total_Active_Trading_Days was defined as "a day the trader logged into the platform and established a connection to the server" (general login/connection); left ambiguous whether held-position days without new orders count toward Sierra_Chart_Days; no rule addressed what happens when a user reaches 2 Levels in the same calendar month | Both variables now explicitly order-gated: Sierra_Chart_Days and Total_Active_Trading_Days each count only distinct days in the current level where the user placed at least 1 order on Rithmic (numerator additionally requires that order to return AppName = Sierra Chart) — held positions and logins-without-an-order are excluded from BOTH sides, so holding a position for multiple days never hurts (or helps) the ratio. Added new point (2a): reaching 2 Levels in the same calendar month triggers 2 independent reimbursement calculations and is confirmed intentional (2x payout, not blocked) — a $35/month cap was raised as a possible future change but explicitly requires its own CR before being built. | Client correction/confirmation relayed by user (BA session), 2026-10-30 — recommend opening a formal CR entry (e.g. CR-20261030-00X) in References/CR/_CR_INDEX.md for CR-20261014-001/CHR-117 since this is a correction to already-drafted CR-19 text, not a first-draft clarification |
| 2026-10-30 | v1.18 | New CR-20 — is_default Payment Card Write Scope (Shared) | No shared rule existed for when is_default gets written. UC_4.17.1 (Market Data), UC_4.10.3/UC_4.10.4 (SIM Reset/Re-Buy/Time Extension), and UC_4.15.3 (LIVE Reset/Re-Buy/Time Extension) all read is_default as a Gate 3 1-Click condition, but none specified the write-side behavior for one-off purchases — risk of a one-off APM/Crypto payment silently clearing the trader's recurring Market Data default, or a one-off card payment silently hijacking it. | Added CR-20: Market Data's Change Payment Method modal always writes is_default (selecting a card → true; selecting APM → cleared) since that modal's whole purpose is managing the recurring default. Reset/Re-Buy/Time Extension (one-off transactions): Case A — trader has never saved any card at all and pays this one-off with a credit card → that card is saved as the new is_default, since there is no existing saved-card state to disturb. Case B — trader already has at least one saved card, regardless of whether is_default is currently true on one of them or null on all of them → paying with anything (saved card, APM, Crypto, or a new card) never writes is_default. UC_4.17.1, UC_4.10.3, UC_4.10.4, UC_4.15.3 should cross-reference this CR instead of re-stating is_default write logic locally. | User instruction (BA session), 2026-10-30 |
| 2026-11-02 | v1.19 | New CR-21 — Admin BPS Operations General Rules (Shared) | No shared rule existed for the common payload shape, field validation, admin identity source, or notification behavior across the Admin BPS endpoint family (Admin Platform Reassignment, Admin Hard Reset, Issue Refund, etc.) — each endpoint would otherwise redefine reason/transcript_url validation and admin-role sourcing independently | Added CR-21: (1) common payload envelope {user_id, admin_id, reason, transcript_url}, all 4 mandatory, HTTP 400 on missing/invalid; (2) reason — DTO-layer validation, ≥3 non-whitespace chars trimmed, max 1,000 chars, reject whitespace-only; (3) transcript_url — DTO-layer validation, valid https:// URL, max 2,048 chars, not restricted to freshdesk.com, and BPS endpoints do NOT auto-create a Freshdesk ticket (only Escalate to L2 calls the Freshdesk API) — Admin manually pastes an existing ticket/thread link; (4) Admin role/identity sourced from Auth0 JWT custom claims ([CHR-52]) — no admin role column on the trader Users table, admin_id = Auth0 ID/@stacktrading.com email; (5) notifications — standard Slack audit alert to #ops-general (routine, not SEV-1), trader email optional/Day-2; (6) Admin BPS Console UI itself is out of scope — Stack Trading's own bps-admin.stacktrading.com UI consumes SotaTek's REST endpoints + Swagger/Postman docs | QnA relayed by user (BA session), 2026-11-02 — see References/QnA from clients/09_ADMIN_BPS_OPERATIONS/QnA_ADMIN_BPS_OPERATIONS.md (ADMIN-002, ADMIN-003, ADMIN-004, ADMIN-010 to ADMIN-012, ADMIN-015) |
| 2026-11-03 | v1.20 | New CR-22 — Reset Time & Market Closed Definition — Futures vs. Forex (Shared) | No shared rule existed — reset_time and "market closed" were each defined piecemeal inside UC_6.1.1 (CME cron/Dynamic Timekeeper), UC_4.1.3 BR_4.1.3.5 (DST handling), UC_4.10.1/UC_4.15.1 (Soft Breach unlock timing), and MT5_Plugin_Business_Logic_v1.md, with no single cross-reference point and no explicit statement that Futures "market closed" must be driven by the CME Reference Data Ingestion cronjob rather than a hardcoded assumption | Added CR-22: §22.1 reset_time — Futures anchors to the dynamic Session_Open_UTC written daily by the CME cron (Ref: UC_6.1.1/CHR-76), Forex anchors to a fixed America/New_York 17:00 local wall-clock instant (21:00 UTC EDT / 22:00 UTC EST), Mon–Fri only, with Friday breaches holding the soft lock through the weekend until Monday's reset. §22.2 Market closed — Futures must be derived from CME_Instrument_Details as populated by the CME cronjob (Official_Close_UTC→next Session_Open_UTC, with Halt_Start_UTC/Halt_End_UTC as a distinct intraday maintenance state), falling back to the 14-day rolling backup on CME API failure, never a hardcoded local-hours assumption; Forex closed = fixed Friday 17:00 ET → Sunday 17:00 ET weekly window, no exchange-style cronjob needed. Flagged (not fixed) a known doc gap: UC_4.10.1/UC_4.15.1 still describe Futures reset_time as a fixed literal ("17:00 CST") rather than the dynamic Session_Open_UTC anchor — tracked via QnA_init_docs.md A-18 / CHR-76 | User instruction (BA session), 2026-11-03 — consolidates UC_6.1.1, UC_4.1.3 BR_4.1.3.5, UC_4.10.1, UC_4.15.1, MT5_Plugin_Business_Logic_v1.md; sourced from RFQ_ Stack Trading Prop Tech V7.pdf §3.3 / §Risk Enforcement Endpoints / §DAILY_LIMIT_BREACH and CR-20260827-001/CHR-76 |
| 2026-11-03 | v1.21 | CR-22 §22.2 — Forex "market closed" table: Sunday reopen edge | Table stated "Friday 17:00 ET to Sunday 17:00 ET" as one confirmed fixed window, with the Sunday reopen instant presented at the same confidence level as the Friday close instant | Split the two edges by confidence: Friday 17:00 ET close is confirmed (same rollover instant as reset_time, §22.1); Sunday 17:00 ET reopen is flagged ⚠️ as the symmetric/inferred value only — no RFQ/QnA source states the Sunday reopen time explicitly (source only confirms Friday close + generic "24/5 Market Hours", v7_full.txt §SLA Requirement). BA should get explicit client confirmation of the Sunday reopen instant | BA self-review during authoring (scope/accuracy check against _SOURCE_MANIFEST.yaml protocol) — no source passage found for the Sunday reopen instant despite exhaustive search of v7_full.txt, QnA STAGE2 series, and all CME/Forex-related CRs |
| 2026-11-05 | v1.23 | CR-22 §22.1 — corrected the Anchor values and added explicit DST (EST/EDT) wording | §22.1 table stated Futures anchor = 17:00 ET and Forex anchor = 16:00 ET, with no DST breakdown | Corrected to: Futures = 18:00 ET (18:00 EST in winter / 18:00 EDT in summer); Forex = 17:00 ET (17:00 EST in winter / 17:00 EDT in summer), Monday–Friday. This is the single authoritative value for reset_time across all UCs — UC docs should cite this section by reference rather than restating a clock value inline. | Client-confirmed instruction, 2026-11-05: "Forex: Resets lúc 17:00 ET (17:00 EST vào mùa đông / 17:00 EDT vào mùa hè). Futures: Resets lúc 18:00 ET (18:00 EST vào mùa đông / 18:00 EDT vào mùa hè)." Supersedes v1.22's 17:00 ET (Futures) / 16:00 ET (Forex) values. |
| 2026-11-03 | v1.22 | CR-22 §22.1 — Futures reset_time anchor, correction | §22.1 stated Futures reset_time = dynamic Session_Open_UTC (sourced from the CME cron), and flagged UC_4.10.1/UC_4.15.1's fixed "17:00 CST" wording as a known doc gap needing future correction | Reverted to fixed wall-clock literal. User correction: reset_time is a fixed daily clock (17:00 CST for Futures, confirmed in v7_full.txt §DAILY_LIMIT_BREACH and already correctly stated in UC_4.10.1/UC_4.15.1), not a value derived from CME_Instrument_Details/Session_Open_UTC. That CME-cron-driven mechanism remains correct for §22.2's market-closed determination and for the Dynamic Timekeeper's own T-3min/T-2min/T-30sec/Halt milestones (UC_6.1.1) — it was mistakenly over-applied to reset_time in the prior draft. Removed the "known doc gap" flag against UC_4.10.1/UC_4.15.1 — those UCs needed no correction. | User correction (BA session), 2026-11-03 — "mục reset time check lại nhé reset time là lấy theo giờ cố định mà" |
| 2026-11-05 | v1.24 | #ops-onboarding channel description | "Receives Background Check flags from Checkr and E-Verify failures." | Removed the Checkr mention — Rippling's native Flow 6 onboarding now handles the background check entirely, so no Checkr flag is ever emitted to this channel. Channel now only receives E-Verify failures from Rippling's native onboarding. | Source: QnA_Level_Promotion.md §4 Round 1, Path A Step 3 Q&A, client-confirmed 2026-09-22 — custom Checkr integration was descoped from Flow 6; this stale mention had not been propagated into the Slack channel description. User instruction, 2026-11-05. |
| 2026-11-09 | v1.25 | New CR-23 — Pod Status Lifecycle, Mentee & Pod Leader (Shared) | Mentee pod statuses spread across UC_8.2.1, UC_8.2.8, UC_4.14.2, UC_4.17.1; Pod Leader statuses spread across UC_8.2.9 – UC_8.2.13, UC_10.3.2, UC_10.3.3 | Added CR-23: §23.1 mentee pod_status (null, Assigned, Waitlist, Pending_Pod_Confirmation, Waitlist_Ghost, Inactive_Hold), §23.2 Pod Leader Status (Active, On_Leave, Suspended, Terminated, Permanently_Ineligible) — entered when, blocked from, keeps, moves to | BA request 2026-11-09. Includes BA decisions UC_8.2.8 N-05 / N-06 / N-07 (2026-11-09). Terminated → re-invite applies to BPS Terminate only |