Behavior, Ownership, and Dependency Contracts
Define what clients may observe
For each public operation, record input domain, result, allowed errors, state transition, mutation/alias policy, ordering, cost model and non-goals. The lab contract below is the source of truth for its exercises. It intentionally uses a small sequential model; it is not a production financial system.
| API | Required behavior |
|---|---|
| Money(minor,currency), plus(other) | Integer minor units excluding bool, three uppercase ASCII letters, immutable public values; plus requires the same Money type and currency; invalid input raises ValueError. Signed values allowed. No currency conversion or registry validation. |
| BalanceAccount, LedgerAccount | Construct from nonnegative Money; separate instances. balance() returns Money; history() returns a stable tuple of signed integer changes after opening. |
| deposit(amount) | Nonnegative same-currency Money; returns None. A positive deposit appends one positive event. |
| withdraw(amount) | Same valid domain; returns True and records a negative event if affordable, otherwise False with all state unchanged. |
| Zero and invalid operations | Zero is accepted with no event; invalid type, negative amount or currency mismatch raises ValueError without state change. |
| statement(reader,formatter) | Calls balance() once and formatter once; returns formatter result. Reader requires no mutation API; collaborator exceptions propagate. |
| quote(grams,rate) | Positive integer grams excluding bool; reject before calling rate otherwise. Invoke rate once; require nonnegative Money result. Invalid result raises ValueError; provider failures propagate. |
| render_report(rows) | Finite one-pass iterable of two-item list/tuple rows (str name, nonnegative USD Money). Preserve order, escape customer text, return the exact HTML format in the kit README. Reject invalid rows with ValueError; propagate iterator errors. |
| write_report(rows,sink) | Render fully before one write; return None. Input/validation failure produces no write. Sink failures may leave partial external output and propagate. |
Representation and history
Opening minor units + sum(history) = current balance at every public boundary. Snapshots exclude the opening amount and remain stable after later operations. The cached and recomputed account variants have the same public contract, but different costs: deriving a balance from h entries is O(h); a cached balance read is O(1). Immutable tuple append can itself cost O(h). Do not claim equal performance from equal outputs. Costs here count fixed-width-sized operations conceptually; Python integer bit costs grow with value size.
Limits and review
No concurrency, persistence, process-crash recovery, account transfer atomicity, external I/O transactions, exchange or rounding guarantee is provided. Ordinary Python API immutability does not resist hostile reflection. Callers supply conforming collaborators and finite iterables. Record intended contract changes separately from structural refactors. Tests inspect public behavior, not required class shapes.
For a design decision, name observed pressure, candidate boundary, alternative, evidence, cost and a revisit trigger. Smells are investigation signals; a count of methods, comments or implementations does not determine the answer.