skilldrop-cli 0.16.31 → 0.16.33
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/skilldrop.js +133 -18
- package/contracts/skill.schema.json +3 -3
- package/package.json +1 -1
- package/packs/converters/skills/file-to-markdown/SKILL.md +8 -4
- package/packs/converters/skills/file-to-markdown/manifest.json +11 -2
- package/packs/converters/skills/file-to-markdown/scripts/to_markdown.py +118 -8
- package/packs/converters/skills/md-to-docx/examples/q3-support-review.md +7 -0
- package/packs/converters/skills/md-to-xlsx/examples/vendor-review.md +6 -1
- package/packs/converters/skills/mermaid-render/manifest.json +11 -1
- package/packs/dev-team/skills/codebase-tour/evals/files/3/.github/workflows/ci.yml +2 -2
- package/packs/dev-team/skills/codebase-tour/evals/files/3/go.mod +2 -2
- package/packs/dev-team/skills/dependency-upgrade-review/SKILL.md +52 -34
- package/packs/dev-team/skills/dependency-upgrade-review/evals/evals.json +33 -0
- package/packs/dev-team/skills/dependency-upgrade-review/evals/files/6/.github/workflows/ci.yml +39 -0
- package/packs/dev-team/skills/dependency-upgrade-review/evals/files/6/.github/workflows/integration.yml +20 -0
- package/packs/dev-team/skills/dependency-upgrade-review/evals/files/6/PR.md +39 -0
- package/packs/dev-team/skills/dependency-upgrade-review/evals/files/6/go.mod +17 -0
- package/packs/dev-team/skills/dependency-upgrade-review/evals/files/6/internal/notify/client.go +35 -0
- package/packs/dev-team/skills/dependency-upgrade-review/evals/files/6/internal/payments/client.go +66 -0
- package/packs/dev-team/skills/dependency-upgrade-review/evals/files/6/internal/payments/client_integration_test.go +38 -0
- package/packs/dev-team/skills/dependency-upgrade-review/evals/files/6/internal/payments/client_test.go +53 -0
- package/packs/dev-team/skills/dependency-upgrade-review/evals/files/6/internal/payments/settle.go +30 -0
- package/packs/dev-team/skills/dependency-upgrade-review/evals/files/6/pr.diff +42 -0
- package/packs/dev-team/skills/dependency-upgrade-review/evals/files/6/vendor-notes/httpkit-CHANGELOG.md +43 -0
- package/packs/dev-team/skills/dependency-upgrade-review/evals/files/7/.github/workflows/ci.yml +67 -0
- package/packs/dev-team/skills/dependency-upgrade-review/evals/files/7/.lintwright.json +7 -0
- package/packs/dev-team/skills/dependency-upgrade-review/evals/files/7/.nvmrc +1 -0
- package/packs/dev-team/skills/dependency-upgrade-review/evals/files/7/PR.md +110 -0
- package/packs/dev-team/skills/dependency-upgrade-review/evals/files/7/package.json +25 -0
- package/packs/dev-team/skills/dependency-upgrade-review/evals/files/7/pr.diff +146 -0
- package/packs/dev-team/skills/dependency-upgrade-review/evals/files/7/src/due-date.ts +16 -0
- package/packs/dev-team/skills/dependency-upgrade-review/evals/files/7/src/totals.ts +11 -0
- package/packs/dev-team/skills/dependency-upgrade-review/evals/files/7/tests/due-date.test.ts +17 -0
- package/packs/dev-team/skills/dependency-upgrade-review/evals/files/7/tests/totals.test.ts +12 -0
- package/packs/dev-team/skills/dependency-upgrade-review/examples/ledgerline-tidyparse.md +23 -14
- package/packs/dev-team/skills/dependency-upgrade-review/manifest.json +17 -5
- package/packs/dev-team/skills/dependency-upgrade-review/reference.md +36 -1
- package/packs/dev-team/skills/dependency-upgrade-review/scripts/upgrade_diff.py +22 -4
- package/packs/dev-team/skills/sonar-review/manifest.json +12 -0
- package/packs/infra-as-code/skills/terraform-module/manifest.json +11 -0
- package/packs/infra-as-code/skills/terraform-plan-review/manifest.json +8 -1
- package/packs/solution-architect/skills/figma-diagrams/manifest.json +12 -1
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: dependency-upgrade-review
|
|
3
|
-
description: Give a merge verdict on a dependency-upgrade pull request (Dependabot, Renovate or a hand-made bump) across npm, pip, Go, Cargo and Maven lockfiles and GitHub Actions pins. Reads the release notes for every skipped version, lists what the lockfile changes beyond the PR title (transitive bumps, new and removed packages, install scripts, registry changes), checks
|
|
3
|
+
description: Give a merge verdict on a dependency-upgrade pull request (Dependabot, Renovate or a hand-made bump) across npm, pip, Go, Cargo and Maven lockfiles and GitHub Actions pins. Reads the release notes for every skipped version, lists what the lockfile changes beyond the PR title (transitive bumps, new and removed packages, install scripts, registry changes), checks each SHA pin against its release tag and against the PR's other pins, checks raised runtime floors against the images and runners that install it, says whether CI ran the changed code, traces what each caller of a changed API now does and writes the rollback. The reply leads with MERGE, MERGE AFTER CHECKS or DO NOT MERGE and the reasons that decide it. Use when the user says "review this Dependabot PR", "is this Renovate bump safe to merge", "can I merge this dependency upgrade", "check this lockfile diff", or "is this action SHA pin right".
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
# dependency-upgrade-review
|
|
7
7
|
|
|
8
|
-
A dependency bump is someone else's code entering the repo under a one-line title. This skill reads it the way a careful maintainer does: every release between the old and new version, every line of the lockfile, every pin, and the tests that would have caught a break. The verdict follows from those checks, not from a green badge. Reviewing the repo's own code changes is `pre-merge-review`; writing the notes for a release of this project is `release-notes`; planning a framework migration across many PRs is `migration-plan`.
|
|
8
|
+
A dependency bump is someone else's code entering the repo under a one-line title. This skill reads it the way a careful maintainer does: every release between the old and new version, every line of the lockfile, every pin, every place the code installs and runs, and the tests that would have caught a break. The verdict follows from those checks, not from a green badge. The reply is short: the verdict and what decides it come first, and the checks that found nothing take one line. Reviewing the repo's own code changes is `pre-merge-review`; writing the notes for a release of this project is `release-notes`; planning a framework migration across many PRs is `migration-plan`.
|
|
9
9
|
|
|
10
10
|
## How to respond
|
|
11
11
|
|
|
@@ -15,11 +15,13 @@ A dependency bump is someone else's code entering the repo under a one-line titl
|
|
|
15
15
|
- **Use only what you were given.** Don't add facts, names, numbers, incidents, history, steps or sections the input doesn't contain. What you need and don't have is left out of the artifact and listed once at the end under "To confirm".
|
|
16
16
|
- **Deliver from what you have.** When the request gives you something to work on, state your assumptions in a line and produce the result. When it gives you nothing to work on, ask for it in one or two plain sentences and say what you will do once you have it.
|
|
17
17
|
- **Write for someone who has never heard of this skill.** No skill names, no paths or scripts from this folder, no internal terms, and nothing about how the run was set up. A next step is one plain sentence at the end that describes the work.
|
|
18
|
-
- **For this skill:** A check nobody ran is an open check, written as the command that runs it. It is never reported as passed, and "CI is green" closes only the checks CI actually ran.
|
|
18
|
+
- **For this skill:** A check nobody ran is an open check, written as the command that runs it. It is never reported as passed, and "CI is green" closes only the checks CI actually ran. Say what was not checked and the command that checks it; never narrate the session (what was blocked, denied or unavailable).
|
|
19
19
|
|
|
20
|
-
1
|
|
20
|
+
Steps 1 to 10 are the review. Step 11 is the reply, and most of what the review turns up stays out of it.
|
|
21
21
|
|
|
22
|
-
|
|
22
|
+
1. **Get the diff, the notes and the code, asking once.** Three inputs: the PR diff covering manifests, lockfiles and workflow files (`gh pr diff 212` or `git diff origin/main...HEAD`); the release notes or changelog for every version between old and new; the repo, or at least the files that import the package and the CI workflow files. Also read what decides where the code installs and runs: the Dockerfile or base image, every workflow that installs dependencies (release, deploy and scheduled ones too, not only the PR's), `.nvmrc` / `.python-version` / `engines`, and any note in the PR body of the runtime the lockfile was generated on. When the PR has more than one commit, note who made each; a hand-made commit on a bot's PR is read first. Ask for what is missing in one message. With network access, fetch missing notes; without it, each missing release becomes an open check.
|
|
23
|
+
|
|
24
|
+
2. **Run the diff script first.** It lists every changed dependency with old → new, semver class, direct or transitive, and flags added and removed packages, install hooks, native-code hints, registry changes, licence changes, runtime requirement changes, pins, and one tag pinned to different commits, then prints a floor verdict.
|
|
23
25
|
```bash
|
|
24
26
|
# Claude Code
|
|
25
27
|
python3 "${CLAUDE_SKILL_DIR}/scripts/upgrade_diff.py" pr.diff --title "Bump quillmark from 3.4.1 to 4.1.0"
|
|
@@ -28,50 +30,65 @@ A dependency bump is someone else's code entering the repo under a one-line titl
|
|
|
28
30
|
```
|
|
29
31
|
It reads stdin with `-` (`gh pr diff 212 | python3 scripts/upgrade_diff.py -`). If it reports version lines out of context, re-run on `git diff -U12`. If no script can run, read every lockfile hunk by hand with the per-ecosystem notes in [`reference.md`](reference.md).
|
|
30
32
|
|
|
31
|
-
3. **List everything the lockfile changes beyond the title.** The title names one package; the lockfile is the truth.
|
|
33
|
+
3. **List everything the lockfile changes beyond the title.** The title names one package; the lockfile is the truth. Find each of: transitive bumps (with class), packages added, packages removed, new install scripts (`hasInstallScript: true`, a `preinstall` / `install` / `postinstall` / `prepare` hook, pnpm `requiresBuild: true`), new native or binary dependencies (`prebuild-install`, `node-gyp`, a Cargo `-sys` crate, wheels for only some platforms), a changed `resolved` / `source` / index URL, a raised `engines` or `python-versions`, and a maintainer or publisher change when the input shows one. ✅ *"Also changes: `entity-codec` 2.1.0 → 3.0.2 (major, licence MIT → MPL-2.0), adds `quill-native-highlight` 1.0.0 with an install script, removes `html-escape-lite`."* ❌ *"Bumps quillmark to 4.1.0."*
|
|
32
34
|
|
|
33
35
|
4. **Class each jump.** Major, minor or patch from the version numbers. Below 1.0 the minor is the breaking number: 0.8.3 → 0.9.0 is a breaking jump, and below 0.1 so is the patch. A Go module path gaining `/v2` is a new module, not a bump. A prerelease target (`-rc.1`, `-beta`) or a downgrade is never a plain MERGE. A moving major tag (`@v2` → `@v3`) spans every v2.x after the one in use plus v3.0.0 to the current v3.
|
|
34
36
|
|
|
35
|
-
5. **Read every release between old and new, not only the target.** For each direct dependency,
|
|
37
|
+
5. **Read every release between old and new, not only the target.** For each direct dependency, read every release after the old version up to and including the new one, and pull five things from each: breaking changes, dropped runtime versions, deprecations, security fixes, licence changes. Transitive majors, 0.x breaking jumps and new packages get the same reading; transitive patch and minor bumps are listed, not read. Name each release whose notes are missing. Copy version numbers exactly as the notes give them: "deprecated since 2.2, warns from 2.5.0" is two facts. Note any migration aid an intermediate release offers (an opt-in flag, a compatibility mode); it is the staged path. A security fix is a reason to take the upgrade; when the full jump is blocked, name the smallest version that carries the fix as the alternative.
|
|
38
|
+
|
|
39
|
+
6. **Search for every changed API, then trace each caller.** For each removed or renamed function, changed signature, changed default, changed return value and changed error, search and report `file:line` for each hit, or "no hits". Give the search (`git grep -n -E "renderInline|setOptions|render\(" -- '*.js' '*.ts'`); with no repo in hand, give that command as an open check and claim neither hits nor no hits. Then read the calling function and say what it does under the new behaviour, and the fix in this repo's code:
|
|
40
|
+
- A return value that became an exception skips every `if result is None` / `== -1` branch and escapes the retry or fallback built on it.
|
|
41
|
+
- A tuple or dict that became an object breaks the unpacking or the key access.
|
|
42
|
+
- A changed default hits every call that doesn't pass the option.
|
|
43
|
+
- An import inside a function fails only when that function runs, never at import time or test collection.
|
|
44
|
+
✅ *"`load_profile` (`app/cache.py:31`) falls back to the database when `cache.get()` returns `None`; from 5.0.0 `get()` raises `CacheMiss`, so every miss escapes as an error and the fallback never runs. Catch `CacheMiss` there, or move to 4.6 first with `strict_miss=True`."* ❌ *"`get()` behaviour changed in 5.0.0; check usages."*
|
|
36
45
|
|
|
37
|
-
|
|
46
|
+
7. **Check every runtime floor against where the code runs.** A raised minimum (Python `>=3.10`, Node `engines >=20`, a Go directive, an MSRV, wheels built only for some interpreter versions, an action needing a newer runner) is compared with the Dockerfile `FROM`, each workflow's `setup-*` version and matrix, `.nvmrc` / `.python-version`, self-hosted runner versions, and the runtime the lockfile was generated on. CI and the lock tool running a newer runtime than production is how a green PR breaks the image build. ✅ *"`deploy/Dockerfile:1` builds on `node:18-alpine` and 6.0 needs Node >=20; CI runs Node 22 (`ci.yml:14`), so the PR passed and the image build will fail at `npm ci`."*
|
|
38
47
|
|
|
39
|
-
|
|
48
|
+
8. **Verify every pin the PR changes; an unverified SHA is an open check.** A SHA-pinned action (or any dependency pinned to a commit) must be the commit its release tag points to:
|
|
40
49
|
```bash
|
|
41
50
|
git ls-remote https://github.com/<owner>/<repo> refs/tags/<tag> 'refs/tags/<tag>^{}'
|
|
42
51
|
```
|
|
43
|
-
For an annotated tag the `^{}` line is the commit;
|
|
52
|
+
For an annotated tag the `^{}` line is the commit; for a lightweight tag the single line is. Not run means unverified, never "looks right": a SHA from a fork also resolves under the upstream path. Compare the PR's pins with each other: one action and tag pinned to two different SHAs means at most one is the tag's commit, and the suspect is the one changed by hand or in the job with secrets. A SHA with no version comment is an unknown release. A tag-only pin (`@v3`) is weaker than a SHA, because the tag can be moved after review. Say what the job hands the action: a `secrets.*` token, `id-token: write` or `contents: write` raises the stakes. Pins the PR does not change are not part of this review.
|
|
44
53
|
|
|
45
|
-
|
|
54
|
+
9. **Say whether CI ran the changed code.** A green check proves what it ran and nothing else. For each changed package or action, name the workflow job and test that exercise it, or say nothing does. The usual gaps: a workflow triggered only by a tag push, a release, a schedule or `workflow_dispatch`; a file no test imports (a step 6 hit with no test); a mock that returns the old behaviour (a fake client that never times out tests the old contract); a PR job that installs differently from the release job (`npm ci --ignore-scripts` on the PR, plain `npm ci` in release means a new install script first runs in the job holding the secrets). ✅ *"`ci / test` runs `test/note.test.js`, which renders Markdown with no raw HTML, so the `sanitize` change is untested; nothing imports `src/render/comment.js`."* ❌ *"All checks pass."*
|
|
46
55
|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
- **DO NOT MERGE** when any holds: a removed or changed API is still used and the PR does not fix it; a new install script or native binary download nobody has confirmed is expected; a dependency source or registry moved to an unexplained host; a SHA that does not match its tag; a pin weakened (SHA to tag, tag to branch); a licence the project cannot ship; a target version an advisory names as vulnerable; a red CI check.
|
|
51
|
-
- **MERGE AFTER CHECKS** when none of those holds but any of these does: an unverified SHA; a major or 0.x breaking jump; a release whose notes were not read; changed code no CI job runs; a deprecated API still used; a new package or transitive major; a licence change that needs sign-off; a moved tag-only pin; no repo to search. Each check is a command or a concrete action.
|
|
56
|
+
10. **Write the rollback, then derive the verdict.** Rollback: `git revert` of the merge commit, or the pin-back for the ecosystem (`npm install quillmark@3.4.1`, `poetry add tidyparse@2.2.0`, `go get <module>@v0.8.3 && go mod tidy`, the old `uses:` line and SHA) with the lockfile regenerated, plus what a revert does not undo (a lockfile format migration, data or cache the new version wrote, an artifact already published or deployed, a security fix that rolling back reopens; then pin back to the first fixed version). Per-ecosystem commands are in [`reference.md`](reference.md). Then exactly one verdict per PR:
|
|
57
|
+
- **DO NOT MERGE** when any holds: a removed or changed API is still used and the PR does not fix it; a runtime floor above what production or the release job runs; a new install script or native binary download nobody has confirmed is expected; a dependency source moved to an unexplained host; a SHA that does not match its tag, or two SHAs for one tag; a pin weakened (SHA to tag, tag to branch); a licence the project cannot ship; a target version an advisory names as vulnerable; a red CI check.
|
|
58
|
+
- **MERGE AFTER CHECKS** when none of those holds but any of these does: an unverified SHA; a major or 0.x breaking jump; a release whose notes were not read; changed code no CI job runs; a deprecated API still used; a new package or transitive major; a licence change that needs sign-off; a moved tag-only pin; no repo to search.
|
|
52
59
|
- **MERGE** only when every check above is closed, with the evidence in the reply.
|
|
53
|
-
The script's floor is a minimum
|
|
54
|
-
|
|
55
|
-
11. **Reply
|
|
60
|
+
The script's floor is a minimum; raise it on what steps 5 to 9 find. Lower DO NOT MERGE only when the user confirms the install script or new source is expected, and say so.
|
|
61
|
+
|
|
62
|
+
11. **Reply short, verdict first, in one message.** Only what changes the decision or the fix goes in.
|
|
63
|
+
- **First line:** the verdict and the one to three reasons that decide it, in plain words. With two PRs, one line each. ✅ *"DO NOT MERGE. 6.0 needs Node 20 and the production image runs 18, and two call sites break on removed APIs."* ❌ *"Here is my review of the quillmark upgrade."*
|
|
64
|
+
- **🟥 Blockers, numbered, the one that breaks first on top.** Each: what breaks, `file:line`, why the green check missed it, and the fix in this repo, in two to four lines. When an intermediate release offers a migration aid, one line after the blockers gives the staged path. The count in the first line matches the list.
|
|
65
|
+
- **🟧 Before merge:** each open check as the command or action that closes it. An unverified SHA is written as the command and the SHA it must equal.
|
|
66
|
+
- **Also in the lockfile:** one line naming every package added, removed or moved beyond the title, with install scripts and source changes called out. Not a table.
|
|
67
|
+
- **Rollback:** one or two lines, the command and what a revert leaves behind.
|
|
68
|
+
- **To confirm** only for what only the user can answer (is this install script expected?); omit it when empty.
|
|
69
|
+
- **Last line:** one sentence offering the next piece of work (the code fixes, the pin change).
|
|
70
|
+
No tables, no recited changelog, no pins or files the PR does not touch, no empty sections. A clean MERGE fits in about 120 words; a blocked PR with three blockers in about 300.
|
|
56
71
|
|
|
57
72
|
**Non-interactive runs** (subagent, CI, headless): with no diff, emit `BLOCKED: need the PR diff covering the manifests, lockfiles and workflow files (gh pr diff <number>)`. Missing release notes, repo or CI files do not block: each becomes a named open check, the verdict is at best MERGE AFTER CHECKS, and the gap is stated first as `[assumption] notes for <versions> not read` or `[assumption] CI coverage unknown, treated as none`.
|
|
58
73
|
|
|
59
74
|
## Useful references in this skill
|
|
60
75
|
|
|
61
|
-
- [`reference.md`](reference.md): reading each ecosystem's lockfile diff, where install scripts and
|
|
76
|
+
- [`reference.md`](reference.md): reading each ecosystem's lockfile diff, where install scripts, sources and runtime floors show up, how to list the releases between two versions, SHA verification details, tracing behaviour changes, and per-ecosystem rollback commands with what a revert leaves behind
|
|
62
77
|
- [`scripts/upgrade_diff.py`](scripts/upgrade_diff.py): stdlib parser for a unified diff of manifests, lockfiles and workflow files; prints changes, flags, pin checks and a floor verdict (exit 2 on bad input)
|
|
63
|
-
- [`examples/ledgerline-tidyparse.md`](examples/ledgerline-tidyparse.md): worked example, a Poetry patch bump
|
|
78
|
+
- [`examples/ledgerline-tidyparse.md`](examples/ledgerline-tidyparse.md): worked example, a Poetry patch bump that earns MERGE, and a blocked reply in the same shape
|
|
64
79
|
|
|
65
80
|
## Quality bar
|
|
66
81
|
|
|
67
|
-
- **
|
|
68
|
-
- **Every release between old and new is accounted for:** read, with its breaking changes, deprecations, security fixes and licence changes
|
|
69
|
-
- **Everything the lockfile changes beyond the title is listed
|
|
70
|
-
- **Every changed API has a search result:**
|
|
71
|
-
- **Every
|
|
72
|
-
- **
|
|
82
|
+
- **The first line is the verdict and the reasons that decide it,** one verdict per PR, following from the checks. Any unverified SHA, added install script, or removed API still in use rules out MERGE.
|
|
83
|
+
- **Every release between old and new is accounted for:** read, with its breaking changes, runtime drops, deprecations, security fixes and licence changes, or named as unread.
|
|
84
|
+
- **Everything the lockfile changes beyond the title is listed,** in one line: transitive bumps, added and removed packages, install scripts, native code, source and runtime changes.
|
|
85
|
+
- **Every changed API has a search result and every hit is traced:** what the caller does now and the fix, or the search command when there is no repo.
|
|
86
|
+
- **Every raised runtime floor is compared** with the base image, the CI runtime, the runners and the lockfile tool's runtime.
|
|
87
|
+
- **Every changed SHA pin is verified or given as the `git ls-remote` command,** and the PR's pins are compared with each other.
|
|
88
|
+
- **CI coverage is named per changed package:** the job and test that exercise it, or "nothing does", and how each workflow installs.
|
|
73
89
|
- **A rollback with the exact command** and what a revert does not undo.
|
|
74
|
-
- **
|
|
90
|
+
- **Nothing reads as checked that was not run,** and nothing describes the session's tools or network.
|
|
91
|
+
- **No invented versions, advisories, APIs, files or test names;** version numbers are copied from the notes.
|
|
75
92
|
|
|
76
93
|
## When to use this skill
|
|
77
94
|
|
|
@@ -91,11 +108,12 @@ A dependency bump is someone else's code entering the repo under a one-line titl
|
|
|
91
108
|
|
|
92
109
|
## Anti-patterns to avoid
|
|
93
110
|
|
|
111
|
+
- ❌ **The report reply.** A package table, a rollback essay and an empty "To confirm" around a verdict the reader has to dig for. Verdict and reasons first; the rest in a line each.
|
|
94
112
|
- ❌ **Reading only the target version's notes.** The breaking change usually lives in a release the PR skipped.
|
|
95
|
-
- ❌ **Trusting the title.** "Bump X from 1 to 2" says nothing about the
|
|
96
|
-
- ❌ **
|
|
97
|
-
- ❌ **
|
|
98
|
-
- ❌ **
|
|
99
|
-
- ❌ **
|
|
100
|
-
- ❌ **
|
|
113
|
+
- ❌ **Trusting the title.** "Bump X from 1 to 2" says nothing about the transitive packages that moved with it.
|
|
114
|
+
- ❌ **Finding the change but not the caller.** "`fetch()` now raises" is half a finding; "so the retry loop never retries" is the finding.
|
|
115
|
+
- ❌ **Trusting CI's runtime.** CI on Python 3.11 says nothing about an image built on 3.9.
|
|
116
|
+
- ❌ **Calling a SHA pin fine because it is a SHA.** A SHA proves nothing until it matches the release tag, and two SHAs for one tag cannot both match.
|
|
117
|
+
- ❌ **"CI is green, ship it."** Release workflows, publish steps, lazy imports and mocked paths never ran on the PR.
|
|
118
|
+
- ❌ **Explaining the session.** "The network was blocked, so I couldn't check" is about the run, not the PR; give the command.
|
|
101
119
|
- ❌ **Hedged verdicts.** "Probably safe" is not a verdict; pick one of the three and give the reason.
|
|
@@ -12,6 +12,7 @@
|
|
|
12
12
|
"Says the green CI does not cover the change: the only test renders Markdown with no raw HTML, and no test imports src/render/comment.js",
|
|
13
13
|
"Accounts for the skipped releases, not only 4.1.0: names the 3.5.0 link-title security fix and the setOptions() deprecation that src/render/note.js still uses",
|
|
14
14
|
"Gives a rollback that pins quillmark back and regenerates package-lock.json (or reverts the PR), and names taking 3.5.x as the way to get the security fix without the breaking changes",
|
|
15
|
+
"Opens with the verdict and the decisive reasons (the removed renderInline() call and the sanitize default) in plain words before any list, keeps the lockfile changes to a line or a short list rather than a table, and does not describe the session's network or tool access",
|
|
15
16
|
"Does not invent versions, advisory IDs, APIs, files or tests that are not in the supplied input"
|
|
16
17
|
]
|
|
17
18
|
},
|
|
@@ -26,6 +27,7 @@
|
|
|
26
27
|
"Reports that release.yml still passes the deprecated password input (release.yml line 22) and that verify-metadata now defaults to true, so the next release can fail on an invalid long description",
|
|
27
28
|
"Classes the jumps: checkout-lite v3.1.0 to v3.2.0 as minor, pypi-publish v2 to v3 as major",
|
|
28
29
|
"Gives a rollback (revert the PR or restore the old uses: lines) and notes a revert cannot undo a release already published to PyPI with v3",
|
|
30
|
+
"Does not report either SHA or the tag move as verified, and does not explain the gap by describing the session's network or tool access; it gives the command and the SHA it must equal instead",
|
|
29
31
|
"Does not invent action versions, SHAs, inputs or workflow steps that are not in the supplied files"
|
|
30
32
|
]
|
|
31
33
|
},
|
|
@@ -40,6 +42,7 @@
|
|
|
40
42
|
"Flags github.com/lanternhq/clock v1.2.0 as a new indirect module the PR title does not mention",
|
|
41
43
|
"Says the passing CI only covers this if a test exercises the burst-0 Wait path, and lists that as a check rather than assuming it",
|
|
42
44
|
"Gives a rollback command such as go get github.com/lanternhq/ratelimit@v0.8.3 followed by go mod tidy, or reverting the PR",
|
|
45
|
+
"Stays short, as the user asked for a quick verdict: the verdict and its reasons come first, and there is no package table and no empty section",
|
|
43
46
|
"Does not invent file names, call sites, tests or release versions that are not in the prompt"
|
|
44
47
|
]
|
|
45
48
|
},
|
|
@@ -72,6 +75,36 @@
|
|
|
72
75
|
"Explains that the green checks never ran the changed code. ci.yml runs on pull_request with `npm ci --ignore-scripts` and never runs scripts/release.mjs. release.yml runs plain `npm ci`, so notary-telemetry's install script would first execute in the production environment, with RELEASE_SIGNING_KEY and NPM_TOKEN in the job and contents: write / id-token: write permissions. Mentions NOTARY_TELEMETRY=0 and/or keeping 1.8.x as mitigations.",
|
|
73
76
|
"Gives a rollback for each PR: revert #412 to restore the c8aee2ae521bdb1ef1b97761673ebbe92c953b0f (v3.2.0) pins; revert #413, or pin release-notary back to 1.8.2 and regenerate package-lock.json. Does not invent versions, flags or APIs that are not in the files, and does not claim to have run npm install, the workflows, or the release script."
|
|
74
77
|
]
|
|
78
|
+
},
|
|
79
|
+
{
|
|
80
|
+
"id": 6,
|
|
81
|
+
"held_out": "2026-10-20",
|
|
82
|
+
"prompt": "Dependabot opened PR #412 on our payments-gateway service bumping github.com/fernwire/httpkit from 1.8.1 to 1.8.4. It's only a patch bump and every check is green, so I'd like to merge it before the release cut this afternoon. Can I merge this dependency upgrade? The PR body is in PR.md, the diff is pr.diff, and I pasted the upstream changelog into vendor-notes/httpkit-CHANGELOG.md. The repo source is checked out here.",
|
|
83
|
+
"assertions": [
|
|
84
|
+
"Picks out the v1.8.3 entry in vendor-notes/httpkit-CHANGELOG.md (a version the Dependabot release notes in PR.md leave out): NewClient no longer applies a default 30-second Timeout, and the zero value now means no client-level timeout. It treats this as the key finding even though the bump looks like a patch.",
|
|
85
|
+
"Points to internal/payments/client.go NewClient, which calls httpkit.NewClient with only WithBaseURL and WithRetries and whose comment says it relies on the 30s default (INC-2291), and to internal/payments/settle.go, which calls Charge with context.Background() and no deadline. So after the upgrade a hung gateway can block the settlement batch indefinitely. It also notes that internal/notify/client.go sets WithTimeout(5*time.Second) explicitly and is unaffected.",
|
|
86
|
+
"Explains that the green CI does not test this change: client_test.go injects fakeDoer, so the real httpkit client is never exercised, and ci.yml runs `go test -race -short ./...` without `-tags integration`, so client_integration_test.go (TestChargeHungGatewayTimesOut) did not run on this PR. It runs only nightly or on manual dispatch via integration.yml.",
|
|
87
|
+
"Gives a verdict of DO NOT MERGE as-is, or MERGE AFTER CHECKS on the specific condition that payments NewClient gains an explicit httpkit.WithTimeout(30*time.Second) (or a per-call context deadline in SettleBatch) and the integration test passes. It is never plain MERGE.",
|
|
88
|
+
"Lists the go.mod/go.sum changes the PR title does not mention: indirect github.com/fernwire/backoff v0.3.0 -> v0.4.0 and a newly added indirect github.com/fernwire/otelshim v0.1.0. It ties them to the v1.8.2 (backoff, full-jitter) and v1.8.4 (WithTracer) changelog entries rather than calling them unexplained.",
|
|
89
|
+
"Gives a concrete rollback: revert the merge commit, or `go get github.com/fernwire/httpkit@v1.8.1 && go mod tidy`, which also returns backoff to v0.3.0 and drops otelshim. It notes that adding the explicit WithTimeout is safe to keep on either version.",
|
|
90
|
+
"Does not claim to have run tests or verified the go.sum hashes or upstream tags. It gives the network or execution steps as commands to run (for example `go mod verify`, `go test -tags integration -run TestChargeHungGatewayTimesOut ./internal/payments/`, or triggering integration.yml via workflow_dispatch).",
|
|
91
|
+
"Invents no httpkit versions, options or behaviours beyond those in the files (for example no made-up v1.8.5, no claimed retry-on-POST change, no options other than WithBaseURL, WithRetries, WithTimeout and WithTracer)."
|
|
92
|
+
]
|
|
93
|
+
},
|
|
94
|
+
{
|
|
95
|
+
"id": 7,
|
|
96
|
+
"held_out": "2026-10-20",
|
|
97
|
+
"prompt": "Renovate grouped this week's dev dependency updates into PR #88 for our invoice-widgets package: five npm packages in one go (details in PR.md, diff in pr.diff). After last quarter's incident with a grouped bump I'm nervous about these. Is this Renovate bump safe to merge, or should I split it up? Repo files are here.",
|
|
98
|
+
"assertions": [
|
|
99
|
+
"Ends with a verdict of MERGE. It does not downgrade to MERGE AFTER CHECKS or DO NOT MERGE over problems the files do not contain (for example an install script, a registry change, a maintainer change, or a breaking change that affects this repo), and it does not insist on splitting the PR without a concrete reason from the files.",
|
|
100
|
+
"Checks bundlecrate 6.1.0's new Node >=18.18 minimum against the repo and finds it harmless: .nvmrc is 20.11.1, package.json engines is node >=20, and ci.yml uses node-version-file: .nvmrc.",
|
|
101
|
+
"Checks the repo's uses of the changed APIs and finds nothing that needs action. vellum-test 2.8.0 deprecates vt.legacyTimers(), but tests/due-date.test.ts uses only vt.fakeTimers/vt.restoreTimers. lintwright 4.3.0's new no-floating-promise rule is only in lintwright:strict, while .lintwright.json extends lintwright:recommended.",
|
|
102
|
+
"Lists the lockfile changes beyond the five headline bumps: @bundlecrate/resolver-core 1.0.0 added, bundlecrate-legacy-resolver 2.3.4 removed, and the transitive picolabel 0.9.1 -> 0.9.2. It ties each to a changelog entry (bundlecrate 6.1.0 resolver extraction, lintwright 4.3.0 picolabel update).",
|
|
103
|
+
"States that no lockfile entry in pr.diff has \"hasInstallScript\" and that every resolved URL is on https://registry.npmjs.org/, which matches .npmrc. So there are no new install-time scripts and no registry switch.",
|
|
104
|
+
"Says CI did exercise the changed code, mapping the packages to jobs in .github/workflows/ci.yml: lintwright -> lint, prettyquill -> format, tsforge-types -> typecheck, vellum-test -> test, bundlecrate -> build-and-smoke (which runs test:dist against the built bundle). It says the pass/fail status comes from PR.md and was not observed directly.",
|
|
105
|
+
"Gives a rollback: revert the merge commit (restoring package.json and package-lock.json together) and run `npm ci`. It notes these are all devDependencies, so only the bundlecrate-built dist/ could reach users, and that build is covered by the smoke step.",
|
|
106
|
+
"Does not claim to have verified integrity hashes, provenance or upstream tags offline. It invents no versions, rules or APIs not in the files, and frames anything network-dependent (for example `npm audit signatures`, `npm ci` locally) as an optional command, not a blocker."
|
|
107
|
+
]
|
|
75
108
|
}
|
|
76
109
|
]
|
|
77
110
|
}
|
package/packs/dev-team/skills/dependency-upgrade-review/evals/files/6/.github/workflows/ci.yml
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
name: ci
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
pull_request:
|
|
5
|
+
push:
|
|
6
|
+
branches: [main]
|
|
7
|
+
|
|
8
|
+
permissions:
|
|
9
|
+
contents: read
|
|
10
|
+
|
|
11
|
+
jobs:
|
|
12
|
+
lint:
|
|
13
|
+
runs-on: ubuntu-latest
|
|
14
|
+
steps:
|
|
15
|
+
- uses: actions/checkout@3d1f0a9c7e5b2846f0c1a7d9e3b5f8c2a6d4e017 # v4.2.2
|
|
16
|
+
- uses: actions/setup-go@8e2c4a6f0b1d3957a2c8e4f6b0d1a3c5e7f9b204 # v5.1.0
|
|
17
|
+
with:
|
|
18
|
+
go-version: "1.23"
|
|
19
|
+
- run: go vet ./...
|
|
20
|
+
|
|
21
|
+
test:
|
|
22
|
+
runs-on: ubuntu-latest
|
|
23
|
+
steps:
|
|
24
|
+
- uses: actions/checkout@3d1f0a9c7e5b2846f0c1a7d9e3b5f8c2a6d4e017 # v4.2.2
|
|
25
|
+
- uses: actions/setup-go@8e2c4a6f0b1d3957a2c8e4f6b0d1a3c5e7f9b204 # v5.1.0
|
|
26
|
+
with:
|
|
27
|
+
go-version: "1.23"
|
|
28
|
+
# Unit tests only. Integration tests (-tags integration) run nightly in
|
|
29
|
+
# integration.yml because they need a long timeout.
|
|
30
|
+
- run: go test -race -short ./...
|
|
31
|
+
|
|
32
|
+
build:
|
|
33
|
+
runs-on: ubuntu-latest
|
|
34
|
+
steps:
|
|
35
|
+
- uses: actions/checkout@3d1f0a9c7e5b2846f0c1a7d9e3b5f8c2a6d4e017 # v4.2.2
|
|
36
|
+
- uses: actions/setup-go@8e2c4a6f0b1d3957a2c8e4f6b0d1a3c5e7f9b204 # v5.1.0
|
|
37
|
+
with:
|
|
38
|
+
go-version: "1.23"
|
|
39
|
+
- run: go build ./...
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
name: integration
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
schedule:
|
|
5
|
+
- cron: "30 2 * * *"
|
|
6
|
+
workflow_dispatch:
|
|
7
|
+
|
|
8
|
+
permissions:
|
|
9
|
+
contents: read
|
|
10
|
+
|
|
11
|
+
jobs:
|
|
12
|
+
integration:
|
|
13
|
+
runs-on: ubuntu-latest
|
|
14
|
+
timeout-minutes: 20
|
|
15
|
+
steps:
|
|
16
|
+
- uses: actions/checkout@3d1f0a9c7e5b2846f0c1a7d9e3b5f8c2a6d4e017 # v4.2.2
|
|
17
|
+
- uses: actions/setup-go@8e2c4a6f0b1d3957a2c8e4f6b0d1a3c5e7f9b204 # v5.1.0
|
|
18
|
+
with:
|
|
19
|
+
go-version: "1.23"
|
|
20
|
+
- run: go test -tags integration -timeout 10m ./...
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
# Bump github.com/fernwire/httpkit from 1.8.1 to 1.8.4 #412
|
|
2
|
+
|
|
3
|
+
**dependabot** wants to merge 1 commit into `main` from `dependabot/go_modules/github.com/fernwire/httpkit-1.8.4`
|
|
4
|
+
|
|
5
|
+
Bumps [github.com/fernwire/httpkit](https://github.com/fernwire/httpkit) from 1.8.1 to 1.8.4.
|
|
6
|
+
|
|
7
|
+
<details>
|
|
8
|
+
<summary>Release notes</summary>
|
|
9
|
+
<p><em>Sourced from <a href="https://github.com/fernwire/httpkit/releases">github.com/fernwire/httpkit's releases</a>.</em></p>
|
|
10
|
+
<blockquote>
|
|
11
|
+
<h2>v1.8.4</h2>
|
|
12
|
+
<ul>
|
|
13
|
+
<li>Add optional OpenTelemetry hooks via <code>WithTracer</code> (backed by the new <code>fernwire/otelshim</code> module). No behaviour change unless the option is passed.</li>
|
|
14
|
+
<li>Fix a data race in connection-pool metrics (#240).</li>
|
|
15
|
+
</ul>
|
|
16
|
+
</blockquote>
|
|
17
|
+
</details>
|
|
18
|
+
<details>
|
|
19
|
+
<summary>Commits</summary>
|
|
20
|
+
<ul>
|
|
21
|
+
<li><code>9c1e4b7</code> release v1.8.4</li>
|
|
22
|
+
<li><code>3fa02d1</code> pool: fix metrics race (#240)</li>
|
|
23
|
+
<li><code>b81d6e0</code> add WithTracer option (#238)</li>
|
|
24
|
+
<li>Additional commits viewable in <a href="https://github.com/fernwire/httpkit/compare/v1.8.1...v1.8.4">compare view</a></li>
|
|
25
|
+
</ul>
|
|
26
|
+
</details>
|
|
27
|
+
<br />
|
|
28
|
+
|
|
29
|
+
Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself.
|
|
30
|
+
|
|
31
|
+
---
|
|
32
|
+
|
|
33
|
+
### Checks
|
|
34
|
+
|
|
35
|
+
- ci / lint — passed (41s)
|
|
36
|
+
- ci / test — passed (1m 52s)
|
|
37
|
+
- ci / build — passed (38s)
|
|
38
|
+
|
|
39
|
+
All checks have passed.
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
module example.internal/payments-gateway
|
|
2
|
+
|
|
3
|
+
go 1.23
|
|
4
|
+
|
|
5
|
+
require (
|
|
6
|
+
github.com/fernwire/httpkit v1.8.4
|
|
7
|
+
github.com/quillstack/zlogx v0.11.2
|
|
8
|
+
github.com/stretchr/testify v1.9.0
|
|
9
|
+
)
|
|
10
|
+
|
|
11
|
+
require (
|
|
12
|
+
github.com/davecgh/go-spew v1.1.1 // indirect
|
|
13
|
+
github.com/fernwire/backoff v0.4.0 // indirect
|
|
14
|
+
github.com/fernwire/otelshim v0.1.0 // indirect
|
|
15
|
+
github.com/pmezard/go-difflib v1.0.0 // indirect
|
|
16
|
+
gopkg.in/yaml.v3 v3.0.1 // indirect
|
|
17
|
+
)
|
package/packs/dev-team/skills/dependency-upgrade-review/evals/files/6/internal/notify/client.go
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
// Package notify sends merchant webhooks.
|
|
2
|
+
package notify
|
|
3
|
+
|
|
4
|
+
import (
|
|
5
|
+
"bytes"
|
|
6
|
+
"context"
|
|
7
|
+
"net/http"
|
|
8
|
+
"time"
|
|
9
|
+
|
|
10
|
+
"github.com/fernwire/httpkit"
|
|
11
|
+
)
|
|
12
|
+
|
|
13
|
+
type Sender struct {
|
|
14
|
+
hc *httpkit.Client
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
func NewSender() *Sender {
|
|
18
|
+
return &Sender{hc: httpkit.NewClient(
|
|
19
|
+
httpkit.WithTimeout(5*time.Second),
|
|
20
|
+
httpkit.WithRetries(3),
|
|
21
|
+
)}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
func (s *Sender) Send(ctx context.Context, url string, payload []byte) error {
|
|
25
|
+
req, err := http.NewRequest(http.MethodPost, url, bytes.NewReader(payload))
|
|
26
|
+
if err != nil {
|
|
27
|
+
return err
|
|
28
|
+
}
|
|
29
|
+
req.Header.Set("Content-Type", "application/json")
|
|
30
|
+
resp, err := s.hc.Do(ctx, req)
|
|
31
|
+
if err != nil {
|
|
32
|
+
return err
|
|
33
|
+
}
|
|
34
|
+
return resp.Body.Close()
|
|
35
|
+
}
|
package/packs/dev-team/skills/dependency-upgrade-review/evals/files/6/internal/payments/client.go
ADDED
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
// Package payments talks to the upstream card-processing gateway.
|
|
2
|
+
package payments
|
|
3
|
+
|
|
4
|
+
import (
|
|
5
|
+
"context"
|
|
6
|
+
"encoding/json"
|
|
7
|
+
"fmt"
|
|
8
|
+
"net/http"
|
|
9
|
+
"strings"
|
|
10
|
+
|
|
11
|
+
"github.com/fernwire/httpkit"
|
|
12
|
+
)
|
|
13
|
+
|
|
14
|
+
// doer is the subset of *httpkit.Client we use. Tests substitute a fake.
|
|
15
|
+
type doer interface {
|
|
16
|
+
Do(ctx context.Context, req *http.Request) (*http.Response, error)
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
// Client calls the gateway's /v2/charges API.
|
|
20
|
+
type Client struct {
|
|
21
|
+
http doer
|
|
22
|
+
baseURL string
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
// NewClient builds a gateway client.
|
|
26
|
+
//
|
|
27
|
+
// We deliberately do not set a timeout here: httpkit's default 30s client
|
|
28
|
+
// timeout is what protects the settlement worker from a hung gateway
|
|
29
|
+
// connection (see INC-2291).
|
|
30
|
+
func NewClient(baseURL string) *Client {
|
|
31
|
+
hc := httpkit.NewClient(
|
|
32
|
+
httpkit.WithBaseURL(baseURL),
|
|
33
|
+
httpkit.WithRetries(2),
|
|
34
|
+
)
|
|
35
|
+
return &Client{http: hc, baseURL: baseURL}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
type ChargeResult struct {
|
|
39
|
+
ID string `json:"id"`
|
|
40
|
+
Status string `json:"status"`
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// Charge submits a capture for a previously authorised payment.
|
|
44
|
+
func (c *Client) Charge(ctx context.Context, authID string, amountMinor int64, idemKey string) (*ChargeResult, error) {
|
|
45
|
+
body := fmt.Sprintf(`{"authorization":%q,"amount":%d}`, authID, amountMinor)
|
|
46
|
+
req, err := http.NewRequest(http.MethodPost, c.baseURL+"/v2/charges", strings.NewReader(body))
|
|
47
|
+
if err != nil {
|
|
48
|
+
return nil, err
|
|
49
|
+
}
|
|
50
|
+
req.Header.Set("Content-Type", "application/json")
|
|
51
|
+
req.Header.Set("Idempotency-Key", idemKey)
|
|
52
|
+
|
|
53
|
+
resp, err := c.http.Do(ctx, req)
|
|
54
|
+
if err != nil {
|
|
55
|
+
return nil, fmt.Errorf("gateway charge: %w", err)
|
|
56
|
+
}
|
|
57
|
+
defer resp.Body.Close()
|
|
58
|
+
if resp.StatusCode >= 300 {
|
|
59
|
+
return nil, fmt.Errorf("gateway charge: status %d", resp.StatusCode)
|
|
60
|
+
}
|
|
61
|
+
var out ChargeResult
|
|
62
|
+
if err := json.NewDecoder(resp.Body).Decode(&out); err != nil {
|
|
63
|
+
return nil, fmt.Errorf("gateway charge: decode: %w", err)
|
|
64
|
+
}
|
|
65
|
+
return &out, nil
|
|
66
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
//go:build integration
|
|
2
|
+
|
|
3
|
+
package payments
|
|
4
|
+
|
|
5
|
+
import (
|
|
6
|
+
"context"
|
|
7
|
+
"net/http"
|
|
8
|
+
"net/http/httptest"
|
|
9
|
+
"testing"
|
|
10
|
+
"time"
|
|
11
|
+
|
|
12
|
+
"github.com/stretchr/testify/require"
|
|
13
|
+
)
|
|
14
|
+
|
|
15
|
+
// TestChargeHungGatewayTimesOut guards INC-2291: a gateway that accepts the
|
|
16
|
+
// connection and never responds must not block the settlement worker forever.
|
|
17
|
+
func TestChargeHungGatewayTimesOut(t *testing.T) {
|
|
18
|
+
block := make(chan struct{})
|
|
19
|
+
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
20
|
+
<-block
|
|
21
|
+
}))
|
|
22
|
+
defer srv.Close()
|
|
23
|
+
defer close(block)
|
|
24
|
+
|
|
25
|
+
c := NewClient(srv.URL)
|
|
26
|
+
done := make(chan error, 1)
|
|
27
|
+
go func() {
|
|
28
|
+
_, err := c.Charge(context.Background(), "auth_1", 100, "idem-hang")
|
|
29
|
+
done <- err
|
|
30
|
+
}()
|
|
31
|
+
|
|
32
|
+
select {
|
|
33
|
+
case err := <-done:
|
|
34
|
+
require.Error(t, err)
|
|
35
|
+
case <-time.After(2 * time.Minute):
|
|
36
|
+
t.Fatal("Charge did not time out against a hung gateway")
|
|
37
|
+
}
|
|
38
|
+
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
package payments
|
|
2
|
+
|
|
3
|
+
import (
|
|
4
|
+
"context"
|
|
5
|
+
"errors"
|
|
6
|
+
"io"
|
|
7
|
+
"net/http"
|
|
8
|
+
"strings"
|
|
9
|
+
"testing"
|
|
10
|
+
|
|
11
|
+
"github.com/stretchr/testify/require"
|
|
12
|
+
)
|
|
13
|
+
|
|
14
|
+
type fakeDoer struct {
|
|
15
|
+
status int
|
|
16
|
+
body string
|
|
17
|
+
err error
|
|
18
|
+
got *http.Request
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
func (f *fakeDoer) Do(_ context.Context, req *http.Request) (*http.Response, error) {
|
|
22
|
+
f.got = req
|
|
23
|
+
if f.err != nil {
|
|
24
|
+
return nil, f.err
|
|
25
|
+
}
|
|
26
|
+
return &http.Response{
|
|
27
|
+
StatusCode: f.status,
|
|
28
|
+
Body: io.NopCloser(strings.NewReader(f.body)),
|
|
29
|
+
}, nil
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
func TestChargeOK(t *testing.T) {
|
|
33
|
+
fd := &fakeDoer{status: 201, body: `{"id":"ch_1","status":"captured"}`}
|
|
34
|
+
c := &Client{http: fd, baseURL: "https://gw.test"}
|
|
35
|
+
res, err := c.Charge(context.Background(), "auth_1", 1250, "idem-1")
|
|
36
|
+
require.NoError(t, err)
|
|
37
|
+
require.Equal(t, "ch_1", res.ID)
|
|
38
|
+
require.Equal(t, "idem-1", fd.got.Header.Get("Idempotency-Key"))
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
func TestChargeGatewayError(t *testing.T) {
|
|
42
|
+
fd := &fakeDoer{err: errors.New("boom")}
|
|
43
|
+
c := &Client{http: fd, baseURL: "https://gw.test"}
|
|
44
|
+
_, err := c.Charge(context.Background(), "auth_1", 1250, "idem-1")
|
|
45
|
+
require.Error(t, err)
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
func TestChargeNon2xx(t *testing.T) {
|
|
49
|
+
fd := &fakeDoer{status: 502, body: ``}
|
|
50
|
+
c := &Client{http: fd, baseURL: "https://gw.test"}
|
|
51
|
+
_, err := c.Charge(context.Background(), "auth_1", 1250, "idem-1")
|
|
52
|
+
require.ErrorContains(t, err, "status 502")
|
|
53
|
+
}
|
package/packs/dev-team/skills/dependency-upgrade-review/evals/files/6/internal/payments/settle.go
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
package payments
|
|
2
|
+
|
|
3
|
+
import (
|
|
4
|
+
"context"
|
|
5
|
+
|
|
6
|
+
"github.com/quillstack/zlogx"
|
|
7
|
+
)
|
|
8
|
+
|
|
9
|
+
// Pending is a capture waiting to be settled.
|
|
10
|
+
type Pending struct {
|
|
11
|
+
AuthID string
|
|
12
|
+
AmountMinor int64
|
|
13
|
+
IdemKey string
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
// SettleBatch is run by the nightly settlement worker. It processes captures
|
|
17
|
+
// one at a time, in order; a single stuck call blocks the rest of the batch.
|
|
18
|
+
func SettleBatch(c *Client, batch []Pending, log *zlogx.Logger) (settled int) {
|
|
19
|
+
for _, p := range batch {
|
|
20
|
+
// No per-call deadline: we rely on the client's own timeout.
|
|
21
|
+
res, err := c.Charge(context.Background(), p.AuthID, p.AmountMinor, p.IdemKey)
|
|
22
|
+
if err != nil {
|
|
23
|
+
log.Warn("settle failed", "auth", p.AuthID, "err", err)
|
|
24
|
+
continue
|
|
25
|
+
}
|
|
26
|
+
log.Info("settled", "auth", p.AuthID, "charge", res.ID)
|
|
27
|
+
settled++
|
|
28
|
+
}
|
|
29
|
+
return settled
|
|
30
|
+
}
|