Skip to content

Release 4.3.0 - #319

Merged
admin-sbneto merged 28 commits into
masterfrom
develop
Aug 4, 2026
Merged

Release 4.3.0#319
admin-sbneto merged 28 commits into
masterfrom
develop

Conversation

@sbneto

@sbneto sbneto commented Aug 4, 2026

Copy link
Copy Markdown
Contributor

TL;DR — changelog for 4.3.0

  • Downloads of a withheld known-good binary now raise the typed KnownGoodWithheldException, carrying the server's machine-readable refusal code and the flagging sources, on both the sync and async clients.
  • Download failure messages render dict- and string-shaped server error payloads as readable text instead of one character per line.
  • A refusal payload with a missing or malformed sources list is surfaced explicitly instead of being silently dropped.
  • The hash existence probe no longer reports a catalogued-but-never-scanned hash as a scanned record, and its 404 arm is covered on both transports.
  • Known-good refusal semantics are documented on the methods that raise them, including what the server's two-predicate model means for consumers.
  • The refusal and probe contracts are pinned against the live server in the test suite, with quieter default output.

Requires

  • polyswarm-api #318 merged into develop first (the 4.3.0 version bump this release ships).

sbneto and others added 27 commits July 27, 2026 21:39
Removes per-test verbose lines so the suite prints one progress char per test plus
the end-of-run summary; failures still print full tracebacks, and -ra adds a
short-summary recap of failures/errors/skips. Keeps live streaming (-s).
Dropping -v (prior commit) was necessary but not sufficient: pytest enables live
logging when log_cli is true OR the log_cli_level option is set, and conftest was
unconditionally setting that option — which force-enables live logging, and live
logging prints every test's nodeid on its own line (verbose-style) regardless of
-v. That was ~130 [gwN] PASSED noise lines on a green 8-way run streamed into the
harness log.

- pyproject: log_cli = false (was true).
- conftest: set only log_level (captured, shown under a failing test) from
  TESTS_LOG_LEVEL by default; set the log_cli_level option only when
  TESTS_LOG_CLI=1 is passed. An explicit --log-cli-level is still honored.

Green run -> dots + summary; a failing test still shows its traceback + captured
logs. Opt into live streaming with TESTS_LOG_CLI=1. Documented in specs/04-testing.
A download refused because the sample is a known-good binary now carries a stable
machine-readable code in the error envelope, so a caller can tell it apart from a
deleted, expired or never-uploaded artifact instead of matching prose.

`KnownGoodWithheldException` subclasses `NotFoundException` and is raised from the
shared 404 arm when the payload's code says so, exposing the flagging feeds as
`.sources`. Subclassing is the point: the status is unchanged and every existing
`except NotFoundException` handler keeps catching it, which the downstream
contract requires. Every other 404 is unaffected.

Also documents the server's `require_scan` semantics: a known-good hash counts as
present, because it is a decided terminal record — the platform will never scan it
and never stores its binary. The 200-vs-204 contract is unchanged.
The server renamed the withheld-binary cause from KNOWN_GOOD_WITHHELD to
KNOWN_GOOD — every member of that closed set is a cause of a refusal, so the
suffix restated what the set already implies. The 404 arm has to match the value
the server actually sends, or a withheld-binary refusal quietly degrades to a
plain NotFoundException.

The exception class keeps its name: it describes the outcome a caller catches, not
the cause code it was dispatched from.
test: quieter pytest output for the live e2e run
…streaming refusal

_bad_status_message assumed `errors` was a list, so it joined over it directly.
Iterating a dict yields its keys, so any non-404 status carrying the new
dict-shaped envelope rendered as "code\nknown_good\nsources" — every value
silently dropped from the message the caller sees. The specs now call the list
shape legacy and the dict shape the way forward, so this change is what makes the
dict expected; both shapes are handled.

The exception exists for a refused download, which flows through the streaming
arm rather than parse_response, and no test drove that path — the coverage was
pure-unit against a body this branch wrote itself. There is now a test that
refuses a download at the transport and asserts the typed exception with its
sources.
….sources

The streaming-refusal test added last round was an async-only respx body, which
the testing spec's harness invariant forbids — and the consequence was that the
sync transport's refusal arm had no coverage at all. It now runs on the
parametrised harness so one body covers both, and the spec paragraph no longer
sanctions a parallel async-only body. The harness moved to its own module rather
than being imported from a collected test file.

`.sources` was passed through from the wire unvalidated while the downstream
contract promises a list of feed names, so a bare string or the feed-dict shape
would have survived and made iteration surprising. It is normalised at the
boundary: a list of strings stays, a bare string becomes one element, anything
else becomes empty.
The suite asserted the error envelope only against bodies it fabricated
itself, which asserts what we think the server sends. This PR's own history
is the failure mode: the fabricated body said KNOWN_GOOD_WITHHELD while the
server said KNOWN_GOOD, and nothing in the suite noticed. Both known-good
lifecycle tests (sync + async) now drive the real endpoint:

- download() raises KnownGoodWithheldException and .sources names the
  flagging feeds, re-checked after a second feed extends the entry
- exists(require_scan=True) is True — semantics documented in four places
  with no coverage until now

Cassettes re-recorded against a live stack; the recordings confirm the
server's shape is a list of feed-name strings.

Also harden _normalise_sources: extract feed['tool'] from dict entries
instead of dropping them. That is the other shape the platform uses for
this concept (the instance-level known_good field), and silently emptying
.sources would take the exception's whole added value with it.
…r per line

Review follow-ups:

- The dict branch added earlier reasoned that iterating a mapping yields only
  its keys; the same applies to a bare string, which iterates as characters.
  An `"errors": "some prose"` envelope rendered one letter per line. Only
  list/tuple payloads now get the line-per-entry treatment.
- _client_harness: last_request_url read calls[0] while last_request_body
  read calls[-1]. Invisible in a single-request test, silently wrong in a
  multi-request one — and this is now the shared harness every respx module
  imports, not one module's private helper.
- Trim the specs/05 paragraph to the contract and let _normalise_sources'
  docstring carry the per-shape rationale; drop a test comment that narrated
  review history rather than the risk.
Review follow-ups.

- _normalise_sources' docstring argues that a silent discard is the failure
  mode it exists to prevent, then its fall-through did exactly that for every
  unrecognised shape. It now logs the discard at debug level.
- Both live-e2e lifecycle tests asserted the post-delete 404 with
  pytest.raises(NotFoundException), which the subclass also satisfies — so the
  one place the real server returns a plain miss never checked that it stayed
  the base class. No re-record needed; the cassettes already hold the
  interaction.
- specs/01 and specs/02 said the mapping-shaped errors render applies on every
  status. True of what the server sends, not of what the caller sees:
  _bad_status_message has one call site, so the 404/422/429 arms build their
  messages from _result alone. Narrowed to the RequestException arm, with the
  payload's reachability at exc.request.errors stated separately.
… respx tier

Review follow-up, and a spec contradiction this branch introduced.

download_refusal_test.py's docstring and the specs/04 paragraph that
legitimised it both claimed the streaming refusal was "reachable by neither a
pure-unit parse test nor a cassette-backed endpoint test". A later commit on
this same branch made that false: both known-good lifecycle tests now drive
download() against the live server on both transports, which runs
_execute_download's 404 arm end to end. The only assertion unique to the respx
module was that the refusal writes nothing to the destination, so that moved
into the two live bodies and the module is gone. Keeping it would have left a
tier whose stated justification reads as licence to reach for respx on any
transport arm.

specs/04 now says what actually covers that arm, and when respx is still the
right tool (a connection reset, a retry ladder, a truncated body).

Also, on prose: specs/05 committed the published surface to accepting the
feed-dict sources shape the envelope has never sent. The defensive branch stays
(it costs three lines and prevents a silent empty), but the contract is narrowed
to the one promise consumers can rely on — always a list of strings — with the
per-shape reasoning left in the helper's docstring. Trimmed that docstring and
dropped the "Regression:" label from a comment describing a defect that only
existed between commits on this branch.
…ontradicted

Review follow-ups, both about statements that were wrong rather than code.

- The new os.listdir assertion was justified with "the streaming path opens its
  destination before the response is read". It does not: _execute_download
  checks the status before calling open_destination. The assertion is still
  worth keeping — it pins that the ordering never inverts, since an empty file
  is indistinguishable from a successful download — so it now says that.
- specs/04's decision tree still listed "an SDK transport arm" among the
  scenarios respx is for, one sentence before the new section saying it is not.
  That parenthetical was the licence the section exists to remove.
- specs/05's Versioning table had no row for a new exception class, so the
  release PR would have had to infer the bump. Added it, with the reasoning that
  makes it minor rather than major (the subclass preserves every existing
  `except NotFoundException`).
… the server

artifact-index reverted the change that made its hash existence probe answer
"found" for a known-good sha256 (PR #1919, commits 5c9268a8 / 0b90f54c). Those
status codes are a frozen contract, documented there in
specs/09-hash-search-head-contract.md: 200 = found, 204 = not found, anything
else = the request was wrong. A KnownGood row is a fact about the sample, not a
searchable record of an artifact, so widening the probe to include it moved a
caller's case from 204 to 200 — which this SDK reads as a bare status code with
no error channel, i.e. a wrong boolean and nothing else.

This side documented and asserted the widened behaviour, which is what turned
artifact-index's e2e job red: the live suite asserted
exists(..., require_scan=True) is True against a server that now correctly
answers 204.

Corrected in all four places (both exists() docstrings, specs/02, specs/03) and
in both lifecycle tests, which now assert what the server actually does for a
hash catalogued through the CRUD:

  plain          -> True   (create_known_good builds a searchable reference
                            instance, so a real record exists)
  require_scan   -> False  (nothing was ever scanned for it)

Cassettes re-recorded against a live stack running the reverted server, so they
now carry the first HEAD interactions in test/vcr/ — 200 for the plain probe and
204 for require_scan. That matters beyond this fix: every other assertion about
this endpoint in the suite is respx-mocked, so it pins the SDK's own mapping and
would stay green through a server-side flip. That is exactly how the 4.0
exists() inversion survived to ship in 4.0.0 and 4.1.0. These two tests would
not.
Review follow-ups.

- The exists() docstring said a non-2xx never raises "because the probe carries
  no result parser". Wrong cause, and it generalises to a falsehood:
  parse_response maps non-2xx to an exception whether or not a parser is set —
  test_500_raises_even_without_parser pins exactly that. What actually happens is
  the HEAD short-circuit, which sets the status as the result and returns before
  the mapping. exists_hash has both properties, so the conclusion held while the
  reason did not.
- Cover _bad_status_message's third arm: a 500 whose errors is a bare string
  renders on one line. Both specs promise it; the dict and list arms had tests
  and this one did not.
- specs/04 described _client_harness as what "every respx module imports" (two
  modules use respx directly) and credited the `_` prefix for keeping it
  uncollected (it is not matching *_test.py / test_*.py that does).
…mock

The probe's 200/204 arms were covered only by a respx mock, which asserts this
SDK's mapping and not the server's behaviour — so a server-side flip left the
suite green. That is exactly how the 4.0 exists() inversion shipped in 4.0.0 and
4.1.0, and artifact-index's specs/09 named the gap.

test_hash_existence_probe_against_the_real_server (+ async twin) now provisions
its own resources the way every other e2e test here does — two deterministic
EICAR variants, one submitted and scanned, one nothing ever submits — and
asserts:

  never submitted   -> False   (204), plain and require_scan
  submitted+scanned -> True    (200), plain and require_scan

Deriving the absent case from its own uid rather than probing the sha about to be
submitted is what keeps the test re-runnable against a reused stack. Cassettes
recorded live, so test/vcr/ now carries HEAD interactions for both codes and both
run VCR-off against the stack in every e2e job.

The mock keeps exactly one arm — 404 -> False — because a well-formed probe never
produces a 404 (artifact-index reserves it for the request being wrong), and its
docstring says so rather than implying e2e could not cover the rest.

Also from review:
- exists()' note leaked a test name into public help() output and said "anything
  else means the request was wrong", which reads against specs/03 (404 also maps
  to absent). Rewritten.
- _normalise_sources logged its discards at DEBUG while the docstring and
  specs/05 promise a discard is never silent. A library under an app configured
  at INFO would show nothing. WARNING matches the promise.
- specs/04 claimed every respx module imports ClientTestCase (one does) and
  invariant 5's only exemption was live/VCR tests, which left the direct-respx
  bodies in the two client modules non-compliant by its own text. Invariant 5
  now names the single-transport exemption, and the harness docstring agrees.
…ndex

Review follow-ups on the tests added last commit.

- test_known_good_lifecycle asserted exists() unpolled immediately after
  known_good_create, in the same PR whose sibling test explains that the search
  row is written by an async task. known_good_create returns an
  artifact_instance_id, so it goes through that same write — the assertion was a
  flake under TESTS_VCR=off by this PR's own reasoning.
- The probe test polled the BROAD form and then asserted the NARROW one.
  require_scan filters on the scan state of the row the plain form only needs to
  exist, so it can become true no earlier — polling the loose condition and
  asserting the strict one is the wrong way round. Both now poll the strict form
  and assert the captured value, which also keeps each test's request count at or
  below what its cassette recorded, so no re-record was needed.
- Registered `existence_probe` in _LONG_POLE_FRAGMENTS: both tests submit, wait
  for a settle and then tolerate index lag, but neither substring-matched, so on
  the live xdist run they would have backfilled the tail — the straggler pattern
  that list exists to prevent.
- specs/04's amended invariant 5 claimed every direct-respx body states why it is
  not on the harness. Most predate the harness and say nothing, so the claim is
  now forward-looking, and the count is honest (~27, not "a handful").
- specs/05 relates KnownGoodWithheldException.sources to
  ArtifactInstance.known_good_sources: same concept, different normalisation (the
  latter sorted and deduped), so a consumer does not assume parity.
Both were recorded before the body was restructured to poll the strict
require_scan form, so they carried the old order (false, false, true) — the
exact 'poll the broad form, assert the narrow one' race the comment above them
argues against. Replay passed only because VCR matches by request identity, not
order, and left the extra interaction unplayed. Recorded fresh against a live
stack: 2 plain + 2 require_scan, matching what the body now runs.
All the docstring work landed on exists(), which never raises it. Every
download* method can, and the endpoint catalogue said only 'closes the handle'
— so a consumer reading either had no pointer to the new exception. Adds the
:raises: (with the NotFoundException-subclass note, .sources, and the
nothing-is-written guarantee) and the catalogue rows, including that only a
dropped file can be withheld out of the sandbox-artifact route.
The mapping is transport-independent and the sync client reaches it through
the same generated `int(result) == 200`, but the test sat in the async module
— in the PR that extracted the harness so one body could cover both. Moved
onto ClientTestCase and added the require_scan form plus the 5xx case, which
is worth pinning as a recorded decision: with no error channel on a HEAD, a
server error also collapses to False, i.e. a fabricated negative.
The sandbox tests dispatched 'triage' with vm_slug='win10-build-15063' at 8 call
sites. That is not a valid triage VM: the server rewrites any slug containing
'win10' to 'windows11-21h2-x64' for backward compatibility and logs an ERROR each
time it does. Every e2e run therefore carried 8 server-side ERROR lines that were
entirely self-inflicted.

Send the real slug instead. 'triage' offers windows11-21h2-x64 (plus
ubuntu-22.04-amd64 and android-11-x64); the win10-style slug belongs to a
different provider and does not apply here.

Cassettes are deliberately NOT touched. vm_slug travels in the POST body and the
VCR matcher is [method, scheme, host, port, path, query], so replay is unaffected
— and hand-editing cassettes is against the recording convention. The old recorded
bodies simply get refreshed the next time these cassettes are re-recorded
delete-driven against a live stack. Verified: 13 sandbox/sample tests pass on
replay.
test: use the current triage VM slug, not the deprecated win10 one
artifact-index reworked known-good to its authoritative model (its specs/05,
DN-8425): the refusal now fires on the CURRENT understanding — a catalogue entry
for the sha256 AND that entry's extension passing an executable allow-list —
evaluated live per request. Two consumer-visible consequences, now in the
downstream contract: a refused download can start working again with no action
(entry deleted, or the policy narrows), and `ArtifactInstance.state` can carry
the new `NOT_STORED` value for a submission the server declined as known-good
whose hash is no longer currently known-good.

No code change: `state` is a plain string the SDK does not enumerate, the 404
`errors.code` strings are unchanged, and KnownGoodWithheldException keeps its
NotFoundException base. The lifecycle test already catalogues with an eligible
filename ('kg-sample.exe'), so its assertions hold unchanged.
Review fixes, plus one drift the review missed:

- Drop the internal ticket reference from the downstream-contract spec — this
  repo is public, and the artifact-index specs/05 pointer alone carries it.
- specs/03 claimed every download* method can raise KnownGoodWithheldException.
  download_archive cannot: it fetches the caller-supplied pre-signed object-store
  URL from the stream() feed (Authorization suppressed), so an error there is the
  store's own XML body — no coded JSON envelope — and surfaces through the
  generic arms. The code had this right (it is the one download* method with no
  :raises docstring); the spec drifted. Scoped the claim to the four
  artifact-index-served downloads and gave the archive row its own honest note.
- The download_sandbox_artifact row still described the retired sample-vs-evidence
  exemption; the server-side model gates every sandbox artifact by its own sha256.
- Renamed test_a_server_error_is_not_reported_as_absent_silently — its body pins
  the opposite (a 5xx does collapse to a fabricated negative, as a recorded
  decision), so anyone grepping test names read the inverse of the guarantee.
… withheld signal

- download_sandbox_artifact's docstring (both mirrors) still described the
  retired server-side sample-vs-evidence exemption, telling callers not to
  handle a refusal they will get; specs/03 already had it right. The gate
  applies to every sandbox artifact by its own sha256.
- specs/02 and specs/05 called state == KNOWN_GOOD "the signal that bytes are
  withheld" in general. Qualified: it signals the *typed refusal*; a NOT_STORED
  instance has no bytes either but 404s plainly, because nothing is being
  withheld by policy any more. A consumer branching on state to predict
  availability would have gotten NOT_STORED wrong.
- resources.py: rejoined a comment fragment the rewrite orphaned.
The shared :raises block said 'the status is checked before the file is
opened' — copy-pasted from the folder-destination methods. download_to_handle
never opens anything: the handle is the caller's and arrives open. The
guarantee holds (the status check precedes any write); only the justification
was wrong, on the one method where a reader most wants it restated.
feat: typed exception for a withheld known-good binary
@claude claude Bot mentioned this pull request Aug 4, 2026
@claude

claude Bot commented Aug 4, 2026

Copy link
Copy Markdown

Reviewed against AGENTS.md + specs/. The substantive work is clean: KnownGoodWithheldException correctly subclasses NotFoundException (invariant 3 preserved), the 404 arm lives in the shared _raise_for_status so both parse_response and _execute_download reach it identically, the status check genuinely precedes open_destination (so the "nothing is written" docstring claim holds), _bad_status_message's three-arm render matches its single call site as specs/01/02 now state, the sync mirror matches the canonical async source exactly, and specs/00-05 + 99 were all updated in step — including the new bump-table rows. Cassettes for the refusal and the probe are real recorded exchanges, not hand-built. Two things need action.

1. The version bump is not in this PR — merging as-is ships 4.3.0 code with no release.

pyproject.toml version, [tool.bumpversion] current_version, and src/polyswarm_api/__init__.py:2 are all still 4.2.0 on this head. #318 (release: bump version to 4.3.0) is still open against develop.

AGENTS.md §Gitflow: "PyPI release happens automatically when pyproject.toml's version changes on master." specs/05-downstream-contract.md §Release flow step 3: "The version bump goes in this PR (or as a prep commit on develop just before the merge)."

So merging #319 first puts the whole 4.3.0 surface on master under a 4.2.0 version string, fires no release-pypi, and leaves master with published code that no PyPI version corresponds to. The PR body already names the dependency — this is just a note that it is load-bearing: merge #318 into develop and let it land here before merging this PR. (4.2.04.3.0 is the right size, per the new "new exception class that subclasses an existing one → minor" row.)

2. Sandbox cassettes were not re-recorded after the triage VM-slug change.

9d8eb677 swapped win10-build-15063windows11-21h2-x64 in five test bodies (client_scan_test.py + async_client_test.py), but the eight cassettes that record that POST body still carry the deprecated slug:

  • test/vcr/test_sample.vcr:243"vm_slug":"win10-build-15063"
  • test/vcr/test_sandboxtask_submit.vcr:243
  • test/vcr/test_sandboxtask_latest.vcr:243
  • test/vcr/test_sandboxtask_list.vcr:243
  • test/vcr/test_async_sample.vcr:243
  • test/vcr/test_async_sandboxtask_submit.vcr:243
  • test/vcr/test_async_sandboxtask_latest.vcr:243
  • test/vcr/test_async_sandboxtask_list.vcr:199

vm_slug travels in the JSON body (SandboxTask.createBaseJsonResource.create), and the matcher is [method, scheme, host, port, path, query] — body excluded — so replay stays green while the committed cassette describes a request the SDK no longer sends. That inverts what the cassette is for: the whole point of the slug change is that the live stack no longer accepts the old VM, and replay is now structurally unable to tell you whether the new one is right either. specs/99-open-questions.md already states the rule for this case — "re-record cassettes where the request shape differs."

Fix is the documented loop against a fresh stack: delete the eight cassettes above, confirm live with TESTS_VCR=off pytest test/client_scan_test.py test/async_client_test.py -k "sample or sandboxtask", then re-run the same selection without TESTS_VCR=off to re-record.

Minor while you are there: the TL;DR changelog does not mention this slug fix (#317), which is the one behavioural change in the release that is not about known-good or the probe.

Notes, no action needed

  • The respx exists 200/204 arms deleted from async_client_test.py are genuinely re-covered live in both new probe tests, and specs/04's new §"The transport arm" argues the deletion rather than leaving it implicit — good. exists_probe_mapping_test.py correctly keeps only the arms the stack cannot produce (404, 5xx) and states why in its docstring, per invariant 5 as amended.
  • _MockBoundary.last_request_url moving calls[0]calls[-1] during the harness extraction is a real latent-bug fix, not a refactor artifact; the one existing caller is single-request, so behaviour is unchanged.
  • KnownGoodWithheldException is reachable via polyswarm_api.exceptions (public surface per invariant 1) without a top-level __init__ re-export, matching every sibling exception.

mjbradford89
mjbradford89 previously approved these changes Aug 4, 2026
release: bump version to 4.3.0
@claude

claude Bot commented Aug 4, 2026

Copy link
Copy Markdown

Release review — reads clean against AGENTS.md and specs/. Details verified, then four minor items.

Verified

  • Gitflow + bump: develop → master, title Release 4.3.0, pyproject.toml + __init__.py both at 4.3.0 via the dedicated release PR on develop. Matches the documented release step, and the new-subclass-of-an-existing-exception → minor row that specs/05 adds in the same PR.
  • Correctness of the 404 arm: _raise_for_status reads request.errors only after _extract_json_body populates it, so both entry points (buffered parse_response and the streaming _execute_download, which aread()s first) reach the same mapping. errors.get(code) == KNOWN_GOOD matches the cassette envelope (test_known_good_lifecycle.vcr:67,273).
  • The "nothing is written" docstring claim is accurate — aio/session.py:146-149 raises before open_destination at :165, and both live tests assert the empty out_dir.
  • _bad_status_message: single call site (core.py:374), which is exactly what specs/01 now says. The dict / list / str three-arm render is pinned in core_test.py for all three.
  • .sources normalisation is at the exception boundary, always list[str], drops are logged — matches the specs/05 contract, including the explicit non-parity note vs ArtifactInstance.known_good_sources.
  • Harness extraction: _client_harness.py matches neither python_files glob, test/__init__.py exists so from test._client_harness import … follows the existing _e2e_helpers convention, and the calls[0] → calls[-1] fix on last_request_url is a genuine latent bug fix (no current multi-request user, but correct now that the harness is shared).
  • Test-tier placement is right per specs/04 invariant 1: 200/204 live on both transports, 404/5xx on the shared harness with the exemption argued in the module docstring, and the deleted test_async_exists_maps_200_true_204_and_404_false is fully replaced (both transports now, not just async).

Items

  1. Stale cassette request bodies from the triage slug change (low). 9d8eb677 switched the dispatch to windows11-21h2-x64, but the sandbox cassettes were not re-recorded — e.g. test/vcr/test_sandboxtask_submit.vcr:243 still records "vm_slug":"win10-build-15063". Replay stays green only because the VCR matcher is [method, scheme, host, port, path, query] and ignores bodies. Harmless today (nothing asserts the slug) but the cassettes no longer describe the request the test makes, so the first assertion on task.json[config][vm_slug] would read the old value. Worth a delete-and-re-record sweep of the eight *sandboxtask* / *sample* cassettes.

  2. Ticket IDs in commit messages landing on master (convention). AGENTS.md: "Dont reference ticket IDs or internal project codes in commit messages, PR titles, or PR descriptions. This repo is public." The merge commits c601ee2a (polyswarm/dn-8391-e2e-coverage) and f7917241 (polyswarm/dn-8425-known-good-status-signal) carry them into public history via this release. Already on develop so its after the fact; flagging so the branch-naming habit doesnt keep reproducing it.

  3. PR body ## Requires is stale and self-referential (nit). It points at polyswarm-api#318 in this same repo, and release: bump version to 4.3.0 #318 is the head of this PR (84c7f591). Nothing to wait on — drop the section.

  4. TL;DR bullet 2 overstates what shipped (nit). "Download failure messages render dict- and string-shaped server error payloads as readable text" — _bad_status_message feeds only the generic RequestException arm, so a KNOWN_GOOD 404s message is still request._result alone and is unaffected by that fix. specs/01 and specs/02 both say this correctly; only the changelog wording implies otherwise.

None of these block the release.

@sbneto
sbneto requested a review from mjbradford89 August 4, 2026 19:17
@admin-sbneto
admin-sbneto merged commit e330409 into master Aug 4, 2026
3 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

3 participants