From 01e27f4a55e603c46e4491706d7639f31af18932 Mon Sep 17 00:00:00 2001 From: Ion Alpha Date: Tue, 11 Aug 2026 18:36:10 +0000 Subject: [PATCH 1/3] feat(skill): add the external-integration skill to the bundled pack Calling a service someone else runs, from fetching the current documentation for the version you are on to deciding whether repeating a failed request is safe. The measured basis for the first half: over eight Python libraries and eleven models, code generated against an API that changed after training ran in the target environment 43% of the time, and 66% with the current page in the prompt, so a citation is the largest single improvement available and still leaves a third wrong. The finishing move is one recorded response. The second half is the part no published skill covers: translating payloads at the adapter so an additive change upstream is not an outage, classifying every failure with "do not retry" as the default, treating retry safety as a property of the request rather than of the error, bounding retries to one layer and a budget, stating what callers get while the service is down, and naming the hosts and credential the integration may use. Vendor specifics stay out by rule, with the reason written into the body: providers now publish and maintain their own instructions for agents, so a section here about a named API would be a changelog we sign up to maintain. references/failures.md carries the per-transport catalogue: what each failure looks like on the wire, which class it maps to, and what is known about the write afterwards. Retrieval rows cover the seven objectives it must be offered for, and four rows now assert it is not offered for a neighbour's objective. Signed-off-by: Ion Alpha --- .../skills/external-integration/SKILL.md | 224 ++++++++++++++++++ .../references/failures.md | 83 +++++++ skill/bundled/skills/retrieval.txt | 18 +- 3 files changed, 320 insertions(+), 5 deletions(-) create mode 100644 skill/bundled/skills/external-integration/SKILL.md create mode 100644 skill/bundled/skills/external-integration/references/failures.md diff --git a/skill/bundled/skills/external-integration/SKILL.md b/skill/bundled/skills/external-integration/SKILL.md new file mode 100644 index 0000000..39e77cb --- /dev/null +++ b/skill/bundled/skills/external-integration/SKILL.md @@ -0,0 +1,224 @@ +--- +name: external-integration +description: Use when your code calls something you do not run: a remote API you did not write, a vendor SDK, a payment, mail, storage or model provider, or a webhook one of them sends you. Covers reading their current documentation for the version you are actually on instead of writing the call from memory, proving it with one real response before building on it, translating their payloads into your own types so their next release is not your rewrite, turning a failure that comes back into a reaction your code can act on, deciding whether repeating a request is safe, how often and in which layer to retry, what your callers get while they are unavailable, and which hosts and credentials the integration may use. +metadata: + flynnhq.com/title: External integration + flynnhq.com/tags: '["integration","http","reliability","retries","errors"]' +--- + +# External integration + +## Read the current document, then make one real call + +An integration written from memory is a set of guesses about someone else's system, +and the guesses are wrong at a rate somebody has measured. Over eight Python libraries +and eleven models, code generated for an API that changed after the models were +trained ran in the target environment 43% of the time. With the current documentation +page in the prompt, that rose to 66%. + +Read both halves of that. Fetching the page is the largest single improvement +available, and it still leaves a third of the code broken. Among the calls that stayed +wrong with the page in context, one in six was the call that page marked deprecated. + +So a citation is not a verification, and the finishing move is a response. Before the +client, the types, or the tests exist, send one request to the real service or its +sandbox and keep what came back: the status, the headers you are going to depend on, +the body. A curl, a scratch script, one test against the sandbox. Everything written +after that is written against something observed; everything written before it is a +hypothesis with a URL attached. + +## The version is part of the API, and memory averages the versions + +Find the version twice, because there are two of them and they move separately. The +SDK version is in the dependency file and the lockfile. The API version is whatever +the vendor pins it with: a header, a segment in the path, a date, or a default sitting +on the account. Set it explicitly in the code. A version that lives on the account is +one someone else can change on a Tuesday without touching your repository. + +Then fetch the page for that version, not the one search returns, and read the +deprecation notes twice. When the vendor publishes their own instructions for agents +alongside their documentation, fetch those first: they are maintained by the people +who make the changes. + +## Their shapes stop at one file + +The response arrives as their shape and gets translated into yours at the adapter, +which is the one file allowed to mention their field names. What matters is how the +translation behaves when their shape moves, and it will, because your integration +sits on the far side of a release you do not schedule. + +- Ignore fields you do not know. A parser that rejects an unrecognised field turns + their additive change, the safest one they can make, into your outage. +- Treat every enumerated value as open. They will add a status, and the day they do, + the code either maps it to a named unrecognised case that a human can see or it + panics on a string. +- Keep units and precision as documented. Money in minor units stays in minor units, + timestamps keep their offset, identifiers stay strings even when they look numeric. +- A field documented as optional will be missing eventually. Decide now what your + type does about it rather than finding out through a nil. + +Translate the parts your own logic reads, and let the rest go unread. Handing their +object to code that makes a decision is how their next release becomes your rewrite. + +## Every failure gets a class, and the default is do not retry + +The adapter is the only place that saw everything: the transport error, the status, +the headers, the body. Classify there, and hand the rest of the program a reaction +rather than an error to interpret. Anything deeper that tries to work out whether a +failure was transient is doing it from less information than the code that received it. + +The classes are reactions, not causes. Worth waiting and trying again. Will fail the +same way at three in the morning. Needs a person. Was cancelled by us. In this runtime +they are `fault.Class` values, and an error that reaches `Classify` without one is +treated as terminal, which is the default worth copying anywhere: retry is opted into +per failure and never inherited. An unclassified error that gets retried is a bug that +only shows up as load. + +Two failures get miscategorised more than any others. A rate limit is transient and +carries a schedule, so it is the one failure that tells you exactly how long to wait. +An authorisation failure is terminal and permanent until a human acts: a token missing +a scope will not gain one by being asked again, and retrying it just spends the +account's quota on a guaranteed refusal. + +`references/failures.md` catalogues what each failure looks like on the wire, per +transport, and which class it maps to. + +## What you parsed may not have come from them + +Between your code and their service sit load balancers, proxies, gateways, and +whatever your own platform puts in front of you. Each answers in its own shape. An +HTML error page decoded as their JSON becomes "unknown error" in your logs, and the +outage reads as a bug in your parser. + +Branch on the transport status first and treat the body as detail. The problem details +format used by many APIs says as much about itself: generic HTTP software knows +nothing of a status carried inside a body, so the two can disagree, and the transport +status is the one the network acted on. Where an API instead reports failure inside a +200, that is part of its contract and belongs in the adapter with everything else. + +When a body will not decode, log its content type and its first line before discarding +it. That one line is what tells the next person the response was an HTML challenge +page from a firewall. + +## Retry safety is a property of the request, not of the failure + +The failure tells you whether waiting could help. It says nothing about whether +repeating is safe, and those are separate questions that get answered as one. + +A read is safe. A write is safe only when the service gives you something to make it +safe: an idempotency key, a conditional update against a version or an entity tag, or +a natural key it deduplicates on. Where keys are offered, generate one when the work +is decided rather than when the request is sent, so every attempt carries the same key, +and change it when the parameters change: services typically replay a stored response +for a repeated key and reject the same key sent with different parameters. Retention is +finite, often around a day, so a key is not a permanent record of what you did. + +The case worth designing for is the one with no answer. A request that timed out with +no response leaves you not knowing whether it happened. There are three honest moves: +repeat it under the same idempotency key, read back to see whether it landed, or fail +the operation and report the uncertainty. Guessing is not one of them. + +A retry also needs a body you can send again. A consumed stream, a one-shot reader, or +a signature computed over a nonce is a request that can be sent once, and the code +should refuse to replay it rather than sending something subtly different. + +## Retry in one layer, on a budget + +Retries are the mechanism by which a service having a bad minute has a bad hour, so +they get limits, and the limits are the ones operators converged on. + +- Three attempts for a request, then let the failure out. A fourth attempt rarely + succeeds and always adds load. +- One layer retries: the one immediately above the failure. If the client, the service + calling it, and the gateway above that all retry three times, one user request became + twenty-seven. +- Cap retries as a share of traffic, not just per request. A ceiling near one retry per + ten requests keeps a widespread failure from turning every client into a load + generator. +- Wait with full jitter, sleeping a random duration up to the backoff rather than the + backoff itself. Measured against plain exponential backoff, it cut both total client + work and time to completion, because unjittered clients retry in step. +- Honour a rate limit's stated delay, and cap how long it may hold you. A server, or + something impersonating one, can otherwise park your run for a day. +- Retries live inside the caller's deadline. A budget that outlives the request it is + serving is spending someone else's patience. + +## Say what happens while it is down + +Every integration behaves somehow when the service is unavailable, whether or not +anyone chose the behaviour. Choose it, and write it where the adapter is defined: +fail the operation with a classified error, serve a stored copy along with its age, +queue the work with a stated way of draining it, or drop to a lesser feature and say +so. Each is defensible. Discovering which one you shipped during an incident is not. + +Set two timeouts, one to connect and one for the whole call, and make both shorter +than the deadline of whatever is waiting on you. A call with no deadline is a worker +holding a slot until the outage ends. + +## Name what it can reach and what it may carry + +The set of hosts an integration talks to is knowable, small, and almost never written +down. Write it down and let something enforce it, because the enforcement answers a +question that is otherwise a guess: what can this integration actually reach. + +Two things follow from having that list. A URL that arrives in someone else's payload, +or in a webhook, cannot become a request to an address of their choosing, which is the +whole of server-side request forgery. The check belongs on the address the connection +resolved to rather than on the name, or a name that resolves to an internal address +walks straight through it. This runtime's outbound gate is default-deny and checks at +connect for that reason, and the shared transport is built on it, so an integration +starts with public addresses only and the private, loopback and cloud metadata ranges +refused. + +The credential rides on the same list. Attach it only for hosts you named, so a +redirect or an attacker-supplied URL cannot carry your token somewhere else, and give +the integration the smallest role that does its job. Resolve it by name at the moment +of the call, so the secret itself never sits in a spec, a log, or a prompt. + +## The vendor's specifics are the vendor's to maintain + +Their error codes, their pagination, their auth dance, and their retry semantics change +on their schedule. Write those into your repository and you have adopted their +changelog for as long as the file exists, which is how integration guidance rots into +confident instructions for a version nobody runs. + +The division is clean. Theirs: what the endpoint is called, what it returns, what the +codes mean, which parameters exist. Fetch it, or install the instructions they publish +for agents, and cite the page with its version. Yours: which operations you use, what +you translate them into, which class each failure maps to, whether repeating is safe, +and what happens while they are down. That part is worth writing down carefully, +because no vendor will ever write it for you. + +This skill follows its own rule and names no provider. A section here about a +particular API would be out of date on their next release and would be teaching a +changelog rather than a craft. + +## Leave the evidence beside the adapter + +The next person to touch this call has the same problem you started with, and can be +saved most of it by three lines in the adapter's doc comment: the documentation URL +with its version, the date it was read, and what a real response looked like. + +Keep that response as a dated fixture the tests read. It is what lets the next failure +be diagnosed in one step, by telling apart the two explanations that look identical +from the logs: their shape changed, or we broke it. Run the calls that touch the real +sandbox on a schedule rather than on every commit, so a vendor's change is discovered +by a red build on a quiet morning rather than by a customer. + +## Refusals + +- No call written from memory. The current page for the version you are on is fetched, + or the code is marked unverified where it sits. +- No client built out before one real response has been received and recorded. +- No vendor type passed to code that decides something, and no parser that fails on a + field it does not recognise. +- No error crossing the adapter unclassified, and nothing retried because an error was + unrecognised. +- No repeat of a write without an idempotency key, a conditional update, or a stated + reason it is safe. +- No retry loop without an attempt ceiling, jitter, and a deadline it lives inside, and + none in a layer that already has one below it. +- No integration shipped without a stated behaviour for the service being down, and no + outbound call to a host nobody listed. +- No section, file, or comment in your repository that restates a vendor's API where + their own documentation would have been fetched. diff --git a/skill/bundled/skills/external-integration/references/failures.md b/skill/bundled/skills/external-integration/references/failures.md new file mode 100644 index 0000000..143f555 --- /dev/null +++ b/skill/bundled/skills/external-integration/references/failures.md @@ -0,0 +1,83 @@ +# What the failure looks like, and what it means + +The adapter has to turn everything that can go wrong into a small set of reactions. +This is the mechanical half: what each failure looks like on the wire, which class it +belongs in, and what you know about the request afterwards. + +## Three questions, in this order + +1. **Did the request reach them?** A connection refused and a 500 are both failures, + and only one of them ran your work. +2. **Can waiting help?** This is the class. It is a property of the failure. +3. **Is repeating safe?** This is a property of the request, and the answer does not + change because the failure looked transient. + +A failure you cannot answer question one about is the interesting case, and it has its +own table below. + +## HTTP + +| What came back | Class | Usual meaning | +|---|---|---| +| Connection refused, DNS failure, TLS handshake failure | Transient, but suspect configuration | Nothing ran. A failure that is instant and total on every host is usually yours: a wrong name, an expired certificate, a blocked egress. | +| Connection reset or timeout before the response | Unknown outcome | May have run. See the table below before retrying. | +| 400, 422 | Terminal | Your payload. Repeating sends the same payload. | +| 401 | Terminal, needs a person | Credential absent, expired, or wrong. Refresh once if the flow has a refresh step, then stop. | +| 403 | Terminal, needs a person | Authenticated and not permitted. A missing scope does not appear by retrying. | +| 404 | Terminal, with one exception | Read the vendor's consistency note: some services return it briefly after a create. | +| 405, 406, 415 | Terminal | Method, accept header, or content type wrong. A version mismatch often lands here. | +| 409 | Terminal, resolvable | A conflict with current state. Re-read, decide, and send a new request rather than the same one. | +| 413, 414 | Terminal | Too large. Split the work, do not repeat it. | +| 429 | Transient, scheduled | Wait what the header says, capped. The only failure that tells you how long. | +| 500, 502, 503, 504 | Transient | Theirs. 502 and 504 usually come from something in front of them, so the body is often not their error shape. | +| 501 | Terminal | Not implemented in this version. | + +## Other transports + +| Transport | Where the classification lives | +|---|---| +| gRPC | The status code carries it: `UNAVAILABLE`, `DEADLINE_EXCEEDED` and `RESOURCE_EXHAUSTED` are transient, `INVALID_ARGUMENT`, `PERMISSION_DENIED` and `FAILED_PRECONDITION` are terminal, and `ABORTED` means retry the whole operation rather than the call. | +| A vendor SDK | The exception hierarchy, which usually distinguishes throttling and service errors from client errors. Read it once and map the types; do not match on message text, which they reword without notice. | +| A message queue | Delivery is at least once, so the consumer is the idempotent one. Failure means the message returns, and the counter that matters is the redelivery count that sends it to the dead letter queue. | +| A webhook you receive | Your response is their retry signal. Return success once the event is stored, not once it is processed, and process from your own store. A 500 because your handler was slow buys you the same event again in a minute. | +| A database or cache behind the vendor | Their outage arrives as latency before it arrives as an error. A timeout you set is the only thing that turns it into a failure you can classify. | + +## What you know after the failure + +The column that decides whether repeating is safe. + +| Failure | Did the write land? | What to do | +|---|---|---| +| Connection refused, DNS failure | No | Safe to repeat. | +| Timeout before the request finished sending | Probably not | Safe to repeat under a key. | +| Timeout after sending, no response | Unknown | Repeat under the same key, read back, or fail and say the outcome is unknown. | +| Connection reset while reading the response | Unknown, and it probably succeeded | Same three options. This is the case people assume is a failure. | +| 5xx from the service itself | Unknown | Their documentation is the only authority on whether a 500 means it did not happen. | +| 502 or 504 from a proxy | Unknown | The request may have reached the service and the answer may have been lost on the way back. | +| 429 | No | Safe to repeat after the stated wait. | +| Any 4xx other than 429 | No | Do not repeat the same request. | + +## Failures that get classified wrong + +- **Matching on message text.** Vendors reword messages in patch releases. Every rule + that survives is written against a status, a code, or an exception type. +- **A blanket retry around the whole call.** It catches the parse error in your own + code and sends the request again, three times, at every layer. +- **Treating a timeout as a failure.** It is an unknown, and the difference decides + whether a customer is charged twice. +- **Treating 429 as an error to log.** It is a schedule. If it appears at all, either + the concurrency is wrong or the rate limit is worth asking about. +- **Retrying a cancelled context.** The caller has already gone. Spending three + attempts on their behalf is load with no reader. +- **One class for the whole integration.** The failure that says the payload was wrong + and the failure that says the service is down do not deserve the same reaction, and + collapsing them is what produces a retry loop against a permanently bad request. + +## What to record when it fails + +One line, and it holds the four things a person will want at three in the morning: +the operation, the status or code, the class you assigned, and the attempt number. Add +the content type and first line of a body that would not decode, and the request +identifier the vendor returned, since it is the only thing their support can act on. +Never log the credential, and never log a whole response body by default: it carries +somebody's data and it is the field most likely to be a customer's. diff --git a/skill/bundled/skills/retrieval.txt b/skill/bundled/skills/retrieval.txt index 58c17b9..5972162 100644 --- a/skill/bundled/skills/retrieval.txt +++ b/skill/bundled/skills/retrieval.txt @@ -32,11 +32,11 @@ the tests are failing after my change and I cannot see why | systematic-debugging | production is throwing errors and I cannot reproduce it locally | systematic-debugging | -this test passes sometimes and fails other times | systematic-debugging | +this test passes sometimes and fails other times | systematic-debugging | external-integration the nightly job has started running past its window | systematic-debugging | a customer reported that discounts are being applied at the wrong rate | systematic-debugging | -where should this new code go | structural-boundaries | +where should this new code go | structural-boundaries | external-integration should this be a new package or a change to an existing one | structural-boundaries | this package imports something it should not import | structural-boundaries | how do I keep the database out of the code that holds the rules | structural-boundaries | @@ -56,7 +56,7 @@ can I add this field without breaking the clients | contract-design | I need to change the signature of an exported function | contract-design | design the API for this new service | contract-design | what should this endpoint return when it fails | contract-design | -is this a breaking change | contract-design | +is this a breaking change | contract-design | external-integration we need to version this API | contract-design | the client crashes when the server sends a field it does not know about | contract-design | @@ -68,10 +68,18 @@ this library has not been updated in three years | dependencies | can we use this package with its licence | dependencies | should I pin this version or use a range | dependencies | -this function takes eight parameters and I keep adding more | interface-depth | +this function takes eight parameters and I keep adding more | interface-depth | external-integration every caller of this has to do the same three steps first | interface-depth | -this class has twelve methods and hardly does anything | interface-depth | +this class has twelve methods and hardly does anything | interface-depth | external-integration make this thing easier to call | interface-depth | this wrapper just forwards to the thing underneath it | interface-depth | my test only passes if I mock everything the module calls | interface-depth | should I add an option for this one caller | interface-depth | + +write a client for the payment provider's API | external-integration | +this call to the vendor fails every so often in production | external-integration | +should I retry this request when it times out | external-integration | +the API we call started returning 429 and everything fell over | external-integration | +handle the webhook this service sends us | external-integration | +what happens to our checkout if their service is down | external-integration | +I am not sure this SDK method still exists in the version we use | external-integration | From ccd3a22b81393c2984c9b711412ae8e39e51bd9b Mon Sep 17 00:00:00 2001 From: Ion Alpha Date: Tue, 11 Aug 2026 18:38:10 +0000 Subject: [PATCH 2/3] chore(skill): take the retrieval table from main before re-applying this skill's rows The domain-language rows landed on main while this branch was open. Reset the table to main's copy so the merge is clean, then re-add this skill's rows on top of it in the next commit. Signed-off-by: Ion Alpha --- skill/bundled/skills/retrieval.txt | 30 +++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/skill/bundled/skills/retrieval.txt b/skill/bundled/skills/retrieval.txt index 5972162..5f51846 100644 --- a/skill/bundled/skills/retrieval.txt +++ b/skill/bundled/skills/retrieval.txt @@ -32,12 +32,12 @@ the tests are failing after my change and I cannot see why | systematic-debugging | production is throwing errors and I cannot reproduce it locally | systematic-debugging | -this test passes sometimes and fails other times | systematic-debugging | external-integration +this test passes sometimes and fails other times | systematic-debugging | the nightly job has started running past its window | systematic-debugging | a customer reported that discounts are being applied at the wrong rate | systematic-debugging | -where should this new code go | structural-boundaries | external-integration -should this be a new package or a change to an existing one | structural-boundaries | +where should this new code go | structural-boundaries | +should this be a new package or a change to an existing one | structural-boundaries | domain-language this package imports something it should not import | structural-boundaries | how do I keep the database out of the code that holds the rules | structural-boundaries | should I put an interface here or call the thing directly | structural-boundaries | @@ -53,10 +53,10 @@ clean up the leftovers from the last change | deletion | how do I retire an endpoint that other people still call | deletion | can I add this field without breaking the clients | contract-design | -I need to change the signature of an exported function | contract-design | +I need to change the signature of an exported function | contract-design | domain-language design the API for this new service | contract-design | what should this endpoint return when it fails | contract-design | -is this a breaking change | contract-design | external-integration +is this a breaking change | contract-design | we need to version this API | contract-design | the client crashes when the server sends a field it does not know about | contract-design | @@ -68,18 +68,18 @@ this library has not been updated in three years | dependencies | can we use this package with its licence | dependencies | should I pin this version or use a range | dependencies | -this function takes eight parameters and I keep adding more | interface-depth | external-integration +what should I call this thing | domain-language | +we have three different words for the same concept | domain-language | +this field means two different things depending on who set it | domain-language | +write down the terms this project uses before I start building | domain-language | +the code calls it a client and the invoice calls it a customer | domain-language | +rename this function, it does not do what it says | domain-language | +what are the entities in this domain and how do they relate | domain-language | + +this function takes eight parameters and I keep adding more | interface-depth | every caller of this has to do the same three steps first | interface-depth | -this class has twelve methods and hardly does anything | interface-depth | external-integration +this class has twelve methods and hardly does anything | interface-depth | make this thing easier to call | interface-depth | this wrapper just forwards to the thing underneath it | interface-depth | my test only passes if I mock everything the module calls | interface-depth | should I add an option for this one caller | interface-depth | - -write a client for the payment provider's API | external-integration | -this call to the vendor fails every so often in production | external-integration | -should I retry this request when it times out | external-integration | -the API we call started returning 429 and everything fell over | external-integration | -handle the webhook this service sends us | external-integration | -what happens to our checkout if their service is down | external-integration | -I am not sure this SDK method still exists in the version we use | external-integration | From f603fd6f137319b370da172c3520e99aa820ef05 Mon Sep 17 00:00:00 2001 From: Ion Alpha Date: Tue, 11 Aug 2026 18:40:13 +0000 Subject: [PATCH 3/3] feat(skill): re-apply the external-integration retrieval rows over main's table Seven rows for the objectives this skill must be offered for, and five rows that assert it is not offered for a neighbour's objective. Two descriptions moved to keep the ranking honest now that the pack holds eight skills and offers five. This skill's opening clause no longer says "calls something", which was scoring on "make this thing easier to call" and displacing the skill that objective belongs to. interface-depth's description now says that making something easier to call is what depth means, which is its own subject stated in the words a person types, and it wins that row back on the match rather than on the tie-break. Signed-off-by: Ion Alpha --- .../skills/external-integration/SKILL.md | 2 +- skill/bundled/skills/interface-depth/SKILL.md | 2 +- skill/bundled/skills/retrieval.txt | 18 +++++++++++++----- 3 files changed, 15 insertions(+), 7 deletions(-) diff --git a/skill/bundled/skills/external-integration/SKILL.md b/skill/bundled/skills/external-integration/SKILL.md index 39e77cb..67cbe06 100644 --- a/skill/bundled/skills/external-integration/SKILL.md +++ b/skill/bundled/skills/external-integration/SKILL.md @@ -1,6 +1,6 @@ --- name: external-integration -description: Use when your code calls something you do not run: a remote API you did not write, a vendor SDK, a payment, mail, storage or model provider, or a webhook one of them sends you. Covers reading their current documentation for the version you are actually on instead of writing the call from memory, proving it with one real response before building on it, translating their payloads into your own types so their next release is not your rewrite, turning a failure that comes back into a reaction your code can act on, deciding whether repeating a request is safe, how often and in which layer to retry, what your callers get while they are unavailable, and which hosts and credentials the integration may use. +description: Use when your code depends on a system you do not run: a remote API you did not write, a vendor SDK, a payment, mail, storage or model provider, or a webhook one of them sends you. Covers reading their current documentation for the version you are actually on instead of writing the call from memory, proving it with one real response before building on it, translating their payloads into your own types so their next release is not your rewrite, turning a failure that comes back into a reaction your code can act on, deciding whether repeating a request is safe, how often and in which layer to retry, what your callers get while they are unavailable, and which hosts and credentials the integration may use. metadata: flynnhq.com/title: External integration flynnhq.com/tags: '["integration","http","reliability","retries","errors"]' diff --git a/skill/bundled/skills/interface-depth/SKILL.md b/skill/bundled/skills/interface-depth/SKILL.md index d84b527..6304df4 100644 --- a/skill/bundled/skills/interface-depth/SKILL.md +++ b/skill/bundled/skills/interface-depth/SKILL.md @@ -1,6 +1,6 @@ --- name: interface-depth -description: Use when a surface is awkward to use or keeps growing: a function with too many parameters, a type whose callers all repeat the same three steps, a class of twelve methods that does very little, a wrapper that forwards, an option added because one caller needed it, or a module whose tests pass only by standing in for what it calls. Depth is counted at the call site. The interface is every fact a caller must hold to make the call correctly, not the number of methods, and a module gets deeper when one of those facts is removed from every call site at once. Deepening buys less re-reading rather than better odds of getting the change right, so it is worth doing on a surface that is used often and not on a leaf that is called once. +description: Use when a surface is awkward to use or keeps growing: a function with too many parameters, a type whose callers all repeat the same three steps, a class of twelve methods that does very little, a wrapper that forwards, an option added because one caller needed it, or a module whose tests pass only by standing in for what it calls. Making something easier to call is what depth means here, and depth is counted at the call site. The interface is every fact a caller must hold to make the call correctly, not the number of methods, and a module gets deeper when one of those facts is removed from every call site at once. Deepening buys less re-reading rather than better odds of getting the change right, so it is worth doing on a surface that is used often and not on a leaf that is called once. metadata: flynnhq.com/title: Interface depth flynnhq.com/tags: '["design","interfaces","modularity","testing","refactoring"]' diff --git a/skill/bundled/skills/retrieval.txt b/skill/bundled/skills/retrieval.txt index 5f51846..ca60e5d 100644 --- a/skill/bundled/skills/retrieval.txt +++ b/skill/bundled/skills/retrieval.txt @@ -32,11 +32,11 @@ the tests are failing after my change and I cannot see why | systematic-debugging | production is throwing errors and I cannot reproduce it locally | systematic-debugging | -this test passes sometimes and fails other times | systematic-debugging | +this test passes sometimes and fails other times | systematic-debugging | external-integration the nightly job has started running past its window | systematic-debugging | a customer reported that discounts are being applied at the wrong rate | systematic-debugging | -where should this new code go | structural-boundaries | +where should this new code go | structural-boundaries | external-integration should this be a new package or a change to an existing one | structural-boundaries | domain-language this package imports something it should not import | structural-boundaries | how do I keep the database out of the code that holds the rules | structural-boundaries | @@ -56,7 +56,7 @@ can I add this field without breaking the clients | contract-design | I need to change the signature of an exported function | contract-design | domain-language design the API for this new service | contract-design | what should this endpoint return when it fails | contract-design | -is this a breaking change | contract-design | +is this a breaking change | contract-design | external-integration we need to version this API | contract-design | the client crashes when the server sends a field it does not know about | contract-design | @@ -76,10 +76,18 @@ the code calls it a client and the invoice calls it a customer | domain-language rename this function, it does not do what it says | domain-language | what are the entities in this domain and how do they relate | domain-language | -this function takes eight parameters and I keep adding more | interface-depth | +this function takes eight parameters and I keep adding more | interface-depth | external-integration every caller of this has to do the same three steps first | interface-depth | -this class has twelve methods and hardly does anything | interface-depth | +this class has twelve methods and hardly does anything | interface-depth | external-integration make this thing easier to call | interface-depth | this wrapper just forwards to the thing underneath it | interface-depth | my test only passes if I mock everything the module calls | interface-depth | should I add an option for this one caller | interface-depth | + +write a client for the payment provider's API | external-integration | +this call to the vendor fails every so often in production | external-integration | +should I retry this request when it times out | external-integration | +the API we call started returning 429 and everything fell over | external-integration | +handle the webhook this service sends us | external-integration | +what happens to our checkout if their service is down | external-integration | +I am not sure this SDK method still exists in the version we use | external-integration |