Patterns
Reusable decisions extracted from real engagements: the trade-offs we chose and why, so the next system doesn't start from zero.
Auto-apply a match only when it's mutually unique — everything else goes to a person
When matching two lists — bank statement lines to ledger entries — apply a match automatically only if exactly one candidate exists on each side within tolerance (exact amount, ±3 days). Any ambiguity becomes a suggestion for a person, and an unmatched line gets a one-click fix. Fewer auto-matches, but none of them are guesses.
Compute "what's blocking this" on every read, and observe before you enforce
To show why a workflow step is blocked and by what, compute the blocker fresh on every read from the step's declared predecessor and live signals — never store it. Ship it read-only first: surface the signal everywhere the step appears, enforce nothing, and flag steps marked done while their predecessor isn't. Enforcement comes after the signal is trusted.
One computed result, several renderers — never recalculate a report per format
A report shown on screen, returned as JSON and exported as PDF should come from one computation, with each format only rendering its result. Register each report once with the exact function that computes it; on-screen table, API and PDF all call that function. Across 23 reports and documents, the three formats cannot disagree.
Keep statutory rates in one human-approved registry that deployments pull from — don't scrape them
GST, TDS, PF, ESI, income-tax and professional-tax rates change a few times a year and must be right. Keep them in one central registry where a person enters and approves each change, and have every deployment pull approved changes daily through the same validation as a hand-entered rate. No scraping of government sites.
Let one deliberate human correction teach the system — but only for narrowly trusted error shapes
When imported data carries typos — TINNER for THINNER, PALTE for PLATE — suggest a correction only for two trusted shapes, one-character edits and adjacent swaps, and remember the answer after a single human confirmation. A looser "edit distance ≤ 2" rule matched PALTE to VALVE as readily as to PLATE, and was rejected.
Compute a balance from source transactions at read time, don't store a running total
An outstanding balance — partial deliveries, payroll advances — can be tracked as a running total updated on each transaction, or computed fresh from source records every time. Compute it at read time instead of storing it separately, trading recomputation cost for avoiding drift between the stored total and the transactions that actually produced it.
Fail-closed doesn't have to mean fail-invisible — pick the blocking mechanism deliberately
A security-critical control needs default-deny, but the naive way to enforce "blocked" — disabling the underlying service entirely — can also destroy the ability to detect the thing being blocked. Default to blocked, but enforce it through a mechanism that blocks use without blocking detection, so a blocked resource can still be found and requested.
Classify failures before retrying — transient errors retry, real rejections wait for a person
A voucher push can fail because the target system is briefly unreachable, or because the voucher is genuinely invalid — two different problems needing different responses. Classify the failure first: transient errors retry automatically, structural ones are held with the reason attached for a person to fix.
Optional integrations should degrade the specific feature, not the app
Optional integrations shouldn't be a hard requirement at startup — check each one's config where it's actually used, and decide what happens per feature. Here that means three different behaviors for three integrations: a full local fallback, a silent skip, and a hard reject. The trade-off is holding three separate rules in mind instead of one simple story.
AI drafts, a person confirms — never auto-post extracted data
AI-based document extraction is fast but not perfect — even a good system leaves some share of documents needing correction. Post nothing automatically: every extraction sits in a review state until a person approves it. The cost is a manual step on every document; the alternative is trusting unreviewed numbers with real money.
Route through a local agent, not a direct API, when the target system is local-only
When the system you need to integrate with only exposes a local interface — like TallyPrime's local-only XML gateway — route through a small agent running on the same machine instead of building toward a cloud API that doesn't exist. The cost is lag: minutes to a day, depending on how fast that data actually needs to move.
After writing to an external system, verify against it — don't trust your own write
After writing to an external system of record, don't trust your own write as fact — read back from the authoritative system and update your own copy to match what's actually there. The external system stays the real source of truth; your copy is a cache of it, verified after every write.
Flag deviations from a known reference rate, don't manually audit every charge
Incoming billed amounts can't all be manually audited line by line. Compare each charge against a known reference rate and automatically flag deviations, instead of trusting every incoming number or requiring full manual review. Catches overcharges without a person checking every line — only as good as how current the reference data stays.
Queue the original request, replay it through the same handler on approval
Some actions need approval before taking effect, but duplicating business logic for "pending" vs. "approved" versions of the same action doubles the surface area for bugs. Queue non-approved actions as the original request payload, and on approval, replay it through the exact same handler real-time requests use — one code path instead of two.
Scope financial visibility by role at the query layer, not just the UI
Don't rely on hiding UI elements to protect financial data from the wrong role — enforce the boundary where the data is fetched. Staff cash visibility is filtered to their own entries at the query level; bank data is rejected outright for staff before any query runs. The trade-off: it's a per-endpoint discipline, not one central gate.
Zero-config first run — seed default logins, force rotation through the UI
A new client instance needs to be usable immediately, not blocked on manual account provisioning. Auto-create the schema and seed default logins on first run, then force rotation through a UI visible to everyone, rather than leaving default credentials to quietly persist. Zero-friction setup, with a real but narrow exposure window.
Re-derive a sequence counter from the data itself before trusting it
A sequential ID scheme backed by a mutable counter table can desync from manual edits or partial writes. Before incrementing, re-derive the counter as the max of its stored value and the highest numeric suffix already in the table — it heals itself from drift instead of needing a manual fix, at the cost of an extra scan on each allocation.
Make a machine credential structurally unable to pass as a human one
A system with both human users and machine/service clients needs a leaked machine credential to be unable to impersonate a human. Machine identity is carried in a token claim that can only come from the token itself, never a request body, and the human-session check explicitly excludes that credential type — structurally impossible, not just policy.
Mark inferred rules unconfirmed, and keep them read-only until a person confirms them
A system can infer relationships from existing data — implicit orderings baked into config — that look plausible but were never confirmed by a domain expert. Mark such inferences explicitly unconfirmed and keep any automation that would act on them read-only until a domain expert actually confirms them, rather than letting the inference stand in.