# effects — if you are changing X, open these One job: turn "I am about to change X" into a short list of cards to read first. This file is an **index only**. It holds no facts — if it disagrees with a card, the card is right and this file is stale. ## Inputs - Reference: `../objects/_index.md`, `../processes/CONTEXT.md` - Reference: `../CONTEXT.md` — read the traps before any change to money or rosters ## Read first, always Four things are true of this codebase and wrong in most people's mental model. All four are in `../CONTEXT.md`: 1. A student's classroom has **two** rival sources of truth. 2. Money is integer cents — except `Portfolio#cash_balance`, which returns dollars. 3. Cash is never stored; the balance includes **pending** orders. 4. The Alpha Vantage key is read two different ways with two different fallbacks. ## By what you are changing | If you are changing… | Open | Then check | |---|---|---| | **anything with a balance** | [portfolio](../objects/money/portfolio.md), [portfolio-transaction](../objects/money/portfolio-transaction.md) | every `_cents` vs dollars boundary; pending-order subtraction | | **order placement or execution** | [order](../objects/trading/order.md), [place-and-execute-order](../processes/place-and-execute-order.md) | model validations *and* `ExecuteOrder` — they duplicate each other | | **the trading fee** | [portfolio-transaction](../objects/money/portfolio-transaction.md), [order](../objects/trading/order.md) | fee is per user **per sweep**, and `Portfolio` anticipates exactly one | | **holdings or share counts** | [portfolio-stock](../objects/trading/portfolio-stock.md), [portfolio-position](../objects/trading/portfolio-position.md) | lots are append-only; sells are negative rows | | **stock prices or the API** | [stock](../objects/trading/stock.md), [refresh-market-data](../processes/refresh-market-data.md) | the two key lookups; the six auto-overwritten columns | | **payout amounts** | [grade-entry](../objects/gradebook/grade-entry.md), [finalize-gradebook-earnings](../processes/finalize-gradebook-earnings.md) | constants are code, not config; `GRADE_OPTIONS` order is load-bearing | | **gradebook workflow or status** | [grade-book](../objects/gradebook/grade-book.md) | the `completed?` guard is the only double-pay protection; finalize is **admin-only** | | **rosters or enrollment** | [classroom-enrollment](../objects/org/classroom-enrollment.md), [classroom](../objects/org/classroom.md), [student](../objects/identity/student.md) | both roster paths, every time | | **the school-year skeleton** | [school-year](../objects/org/school-year.md), [quarter](../objects/org/quarter.md), [year](../objects/org/year.md) | the two auto-create cascades; the `"YYYY - YYYY"` string format | | **login, roles, or permissions** | [user](../objects/identity/user.md), [authenticate-authorize](../processes/authenticate-authorize.md) | username-not-email; `/admin` bypasses Pundit; `verify_authorized` is off | | **a scheduled job** | [processes/CONTEXT.md](../processes/CONTEXT.md), `config/recurring.yml` | jobs bypass authorization entirely | | **charts or history** | [portfolio-snapshot](../objects/trading/portfolio-snapshot.md), [snapshot-portfolio-worth](../processes/snapshot-portfolio-worth.md) | history is unrecoverable if a month is missed | | **creating students in bulk** | [import-students](../processes/import-students.md), [student](../objects/identity/student.md) | `classroom_id` drives the enrollment callback | | **announcements** | [announcement](../objects/announcement.md) | `body` column is dead; content is Action Text | ## Changes with a wider blast radius than they look | Change | Why it spreads | |---|---| | `Portfolio#cash_balance` return unit | every caller converts by hand; there is no shared money type | | `GradeEntry::GRADE_OPTIONS` order | improvement bonuses compare array indices | | `users.classroom_id` | still joined by `Order.for_teacher`, `Classroom#students`, and redirects | | `PortfolioTransaction` enum values | integer-backed; renumbering rewrites the meaning of existing rows | | `Year#name` format | SQL ordering and quarter navigation both parse the string | | adding a `before_action` to `ApplicationController` | runs on `/admin` too — `Admin::BaseController` inherits it | ## Changes that are safer than they look | Change | Why it is contained | |---|---| | editing a gradebook after finalize | deposits carry no link back; nothing recomputes | | archiving a stock | sells still work; only buying is blocked | | discarding a user | the ledger is untouched and still sums | | deleting snapshots | charts break, balances do not | | editing `app/controllers/schools_controller.rb` | it is a **ghost** — no route reaches it | ## Human check After a change, re-read the **Does not hit** line of every card you opened. That line is the one most likely to have gone stale, and a wrong "does not hit" is more expensive than a missing card.