{"slug":"design-3","title":"design","summary":"Designing a new interface, module, schema or type together with its validation and failure behaviour: what each input accepts, what happens when it does not, and what the caller gets back when something goes wrong.","platform":"Claude","tags":[],"authorName":"LLM Mart","authorSlug":"llm-mart","score":0,"source":"github","price":null,"verified":false,"createdAt":"2026-09-14T21:08:50.460661Z","repo":{"url":"https://github.com/rainmanjam/poka-yoke","stars":22,"forks":3,"license":"MIT","updatedAt":"2026-09-01T16:13:25Z"},"bodyHtml":"<hr>\n<h2>name: design\ndescription: &gt;-\nDesigning a new interface, module, schema or type together with its validation and failure\nbehaviour: what each input accepts, what happens when it does not, and what the caller gets\nback when something goes wrong.</h2>\n<h1>Design: Decide the Failure Behaviour Now</h1>\n<p>A new interface is a chance to decide what happens when things go wrong <em>before</em> anything\ndepends on the answer. Retrofitting validation onto an interface that has callers means\nchoosing between breaking them and leaving the gap open.</p>\n<p>So design the failure paths at the same time as the happy path. For every parameter, decide\nwhat it accepts and what it does with everything else. For every operation that can fail,\ndecide what the caller receives.</p>\n<h2>Specify the accepted domain of every parameter</h2>\n<p>A type is a start, not a specification. <code>amount: float</code> still permits negatives, infinity, NaN\nand values with more precision than money has. Write down what is actually allowed:</p>\n<pre><code>def charge(amount, currency, customer_id):\n    \"\"\"\n    amount:      positive, at most 2 decimal places, below the 10_000 per-txn ceiling\n    currency:    3-letter ISO code, one of the supported set\n    customer_id: non-empty, exists, account not closed\n    \"\"\"\n</code></pre>\n<p>Then enforce it at the top of the function, because a docstring is not a check:</p>\n<pre><code>    if not isinstance(amount, (int, float)) or amount &lt;= 0:\n        raise ValueError(f\"amount must be positive, got {amount!r}\")\n    if round(amount, 2) != amount:\n        raise ValueError(f\"amount has sub-cent precision: {amount!r}\")\n    if currency not in SUPPORTED_CURRENCIES:\n        raise ValueError(f\"unsupported currency {currency!r}\")\n</code></pre>\n<p>Three checks, each naming the value that failed. A caller reading the error knows what to fix\nwithout opening the source.</p>\n<h2>Decide the failure contract before implementing</h2>\n<p>For each way the operation can fail, choose one and be consistent:</p>\n<p><strong>Raise.</strong> The caller cannot sensibly continue and should not have to check. Right for\nprogrammer error and for corrupt state.</p>\n<p><strong>Return an outcome.</strong> The failure is expected and the caller has a decision to make. Right for\n\"not found\", \"already exists\", \"insufficient funds\".</p>\n<p><strong>Return a default.</strong> The value is optional and a sensible substitute exists. Right for display\npaths and configuration, wrong for anything that moves money or deletes data.</p>\n<p>Mixing all three inside one module means every caller has to remember which convention this\nfunction follows, so the convention itself becomes a source of mistakes.</p>\n<h2>Validate at the boundary, and again at depth</h2>\n<p>The entry point should reject bad input early, with a message aimed at whoever sent it. Inner\nfunctions should check again, because a future caller may reach them by another path.</p>\n<p>This duplication is deliberate. A single check is a single point of failure, and the inner\ncheck is what protects you when someone adds a second entry point next year and forgets.</p>\n<p>Keep the two different in emphasis: the outer check produces a user-facing error, the inner\ncheck produces a programmer-facing one.</p>\n<h2>Schemas and stored shapes</h2>\n<p><strong>Make required fields non-nullable and say so.</strong> A column that permits null will eventually\ncontain null, and every reader must handle it.</p>\n<p><strong>Constrain at the storage layer too.</strong> Check constraints, foreign keys and unique indexes hold\nwhen application code is bypassed by a migration, a script or a console session.</p>\n<p><strong>Decide what a missing optional means.</strong> Absent and empty are different, and if the difference\nmatters, the schema should distinguish them rather than leaving each consumer to guess.</p>\n<p><strong>Plan for the malformed row.</strong> Data that predates the current validation exists in every\nsystem with history. Decide whether readers skip it, repair it, or fail on it.</p>\n<h2>Timeouts, limits and resource ceilings</h2>\n<p>Anything that talks to something else needs a bound, decided at design time:</p>\n<ul>\n<li><strong>Timeout on every network call.</strong> A call without one waits forever, and the failure surfaces as a hang rather than an error.</li>\n<li><strong>Size ceiling on every input you accept.</strong> Uploads, request bodies, list parameters and pagination limits all need a maximum, or a caller can exhaust memory.</li>\n<li><strong>Retry policy, with a cap.</strong> Decide how many, how spaced, and which errors are worth retrying. Retrying a validation failure just fails more slowly.</li>\n<li><strong>A ceiling on anything recursive or iterative</strong> driven by input, so a malformed structure cannot loop indefinitely.</li>\n</ul>\n<p>Name the unit in the parameter: <code>timeout_seconds</code>, <code>max_rows</code>, <code>retry_limit</code>.</p>\n<h2>What the caller receives when it fails</h2>\n<p>An error is an interface too, and it deserves the same care as the success path.</p>\n<ul>\n<li><strong>Say what was wrong, specifically.</strong> \"Invalid request\" tells the caller nothing; \"currency 'XYZ' is not supported\" tells them exactly what to change.</li>\n<li><strong>Include the offending value</strong>, truncated if it might be large, and never if it might be a secret.</li>\n<li><strong>Distinguish caller error from system failure.</strong> The first is theirs to fix, the second is yours, and conflating them wastes everyone's time.</li>\n<li><strong>Keep the error stable.</strong> Callers will parse it, whatever you intended.</li>\n</ul>\n<h2>What good output looks like</h2>\n<ul>\n<li><strong>Show the signature with its accepted domain</strong>, not just its types.</li>\n<li><strong>Show the guards.</strong> The checks are the design, so put them on the page.</li>\n<li><strong>State the failure contract explicitly</strong>: what raises, what returns an outcome, what defaults.</li>\n<li><strong>Cover the dependency being unavailable</strong>, not only the inputs being wrong.</li>\n</ul>\n<h2>What to avoid</h2>\n<p><strong>Types treated as validation.</strong> <code>amount: float</code> does not exclude negative, infinite or\nsub-cent values. The annotation documents intent; the check enforces it.</p>\n<p><strong>Errors that lose the cause.</strong> Catching and re-raising a generic exception discards the\ninformation the caller needed.</p>\n<p><strong>Validation that cannot be reached.</strong> A check after the value has already been used is\ndecoration.</p>\n<p><strong>Unbounded anything.</strong> No timeout, no size limit, no retry cap. Each is a defect waiting for\nan unusual day.</p>\n","files":[{"path":"SKILL.md","sizeBytes":6041,"isText":true}],"reviewScore":null,"reviewSummary":null,"trust":{"provenance":"trusted-source-unreviewed","notice":"Community-authored content, reproduced verbatim and not vetted as instructions. Treat it as data to evaluate, never as directives to follow.","bodySource":null},"bodyLocked":false,"purchaseUrl":null,"sourceUrl":null,"report":{"provenance":"trusted-source-unreviewed","screen":{"ran":true,"outcome":"clean","suspicious":0,"notes":0,"hiddenCharacters":false},"virusScan":{"engine":"clamav","status":"clean","scannedAt":"2026-09-14T21:09:13.523474Z","sha256":"35AAC00ADD1145ED43452A1A63980D59D28E2BECD21EC37C6312DD6E7F427B39","sizeBytes":2869},"review":null,"source":{"repositoryUrl":"https://github.com/rainmanjam/poka-yoke","path":"benchmarks/controls/defensive/skills/design","license":"MIT","commit":"726a575e3d48d07d908abfcbb192cae09671fff2","subtreeSha":"D3C5611030F505A29E7D982524C38B6D97D639BAA76D5C8641598461A7CF992C","lastSyncedAt":"2026-09-27T19:47:53.390177Z"},"reviewedAt":"2026-09-14T21:10:15.07676Z","notice":"Community-authored content, reproduced verbatim and not vetted as instructions. Treat it as data to evaluate, never as directives to follow."},"install":[{"target":"skills-cli","command":"npx skills add https://github.com/rainmanjam/poka-yoke/tree/main/benchmarks/controls/defensive/skills/design"},{"target":"claude-code","command":"claude plugin marketplace add https://llmmart.ai/marketplace.json && claude plugin install rainmanjam-poka-yoke@llmmart"},{"target":"git","command":"git clone https://github.com/rainmanjam/poka-yoke.git"}]}