mirror of
https://github.com/Comfy-Org/comfy-python-sdk.git
synced 2026-11-04 23:37:47 +00:00
[PR #182] [MERGED] test(router): pin credits_used to the header the synced contract now declares #182
Labels
No labels
audit:ai-readiness
audit:community
audit:github
audit:health-scoring
audit:policy
audit:release
audit:supply-chain
effort:medium
effort:small
effort:small
effort:trivial
priority:nice-to-have
priority:nice-to-have
priority:recommended
pull-request
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference
Comfy-Org/comfy-python-sdk#182
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
📋 Pull Request Information
Original PR: https://github.com/Comfy-Org/comfy-python-sdk/pull/182
Author: @mattmillerai
Created: 9/22/2026
Status: ✅ Merged
Merged: 10/9/2026
Merged by: @mattmillerai
Base:
main← Head:matt/be-16678-pin-credits-used-header-to-the-synced-contract📝 Commits (6)
da3caeetest(router): pin credits_used to the header the synced contract now declaresbd37a9ctest(router): verify the _NON_HEADER_FIELDS exemption instead of trusting itbbe527atest(router): document the declared-name half of the contract pin78e6290Merge origin/main into the credits_used contract-pin branch1c8fd7dtest(router): keep exemption probes independent of the lift classificationacea5fbtest(router): note the unprobed-declared-header gap in the exemption test docstring📊 Changes
1 file changed (+168 additions, -29 deletions)
View changed files
📝
tests/test_router_spec_contract.py(+168 -29)📄 Description
ELI-5
The SDK reads a few facts about a model run out of the HTTP response headers Comfy Router sends back — which provider actually served it, whether it was an idempotency replay, and what it cost. A test file pins each of those header names against the vendored copy of Router's own contract, so a typo can't silently make a field read as "absent" forever.
credits_usedwas the one field that wasn't pinned. When it was added, the vendored contract didn't declareX-Comfy-Credits-Usedyet, so it shipped behind a deliberate tripwire test that asserted the header was still undeclared — a reminder that would go red the moment a spec sync declared it, with instructions to reconcile. The sync that declared it merged 35 seconds before the lift did. So the tripwire was stale on arrival, andmainwent red. #145 has since replaced the tripwire onmainwith a standalone declared-name test forcredits_used, which turnedmaingreen again. This PR does the full reconciliation the tripwire asked for: it pinscredits_usedlike every other lift.What changed
Test-only. No production code is touched.
credits_usedmoves into_CONTRACT_HEADER_LIFTS, so it is now checked from both ends like every other lift: the vendored contract declares the name, and_run_resultdemonstrably reads that name._CONTRACT_HEADER_LIFTSand get it pinned like the rest"), and with the header now declared it asserted a falsehood. On currentmain, #145 had already replaced it with a standalonetest_credits_used_header_is_declared_by_the_contract. That test exists only because the old shared"x"probe could not exercisecredits_used. Mergingmainin, this branch keeps its parametrized pins instead:test_every_lifted_header_is_declared_by_the_contract[credits_used-X-Comfy-Credits-Used]makes the same declared-name assertion, and the per-field probe adds the read side.credits_usedimmediately surfaced a second latent gap:test_the_lift_actually_reads_the_declared_nameset every header to the literal"x", but"x"is not a finite decimal, so_credits_usednormalises it toNone— a completely correct lift looked like it was reading some other name. Each header's probe is now the spec's own declaredexample, rendered as the wire string (X-Comfy-Router-Fallback-Provideris the one header declaring no example). The probes sit in their own header-keyed_HEADER_PROBEStable, which_CONTRACT_HEADER_LIFTSand the exemption test both read (see the second review round)._CONTRACT_HEADER_LIFTS, which means a field that simply isn't listed is checked by nothing at all — which is precisely howcredits_usedslipped through.test_every_header_derived_field_is_pinned_against_the_contractcloses the list from the other end: everyRouterRunResultfield must be either listed with the header it is lifted from, or named in_NON_HEADER_FIELDSas deliberately not a lift.Review round
Still test-only. Four findings from the automated review panel, all on the pinning block above:
_NON_HEADER_FIELDSwas an unverified escape hatch (the one Medium). Listing a field there exempts it from both pins, and nothing checked the exemption was honest — which made it the convenient way to land exactly the shape this PR exists to close, since a field lifted from a header the spec has not declared yet failstest_every_lifted_header_is_declared_by_the_contractif it is filed truthfully.test_an_exempt_field_is_really_unmoved_by_the_headers_it_skipsnow tests the claim: hand_run_resultevery header that could move a field — the ones pinned here, plus every other name the contract declares on the200— and an exempt field must read the same as it does against no headers at all.httpx.Headers, not a plain dict — which is what production hands_run_result. A dict's.getis case-sensitive;httpx.Headersis not. A spec sync that only re-cased a declared name would have failed these tests even though the SDK still reads it correctly, and the only way back to green would have been a no-op edit to the source spelling.dropped_params' probe was a comma-free simplification of an example whose single entry contains a comma — precisely the property its parser exists to preserve. Probes are now copied from the spec verbatim (request_id's was an invented id too), so the comment's claim that each is "a value the contract itself would send" is literal rather than aspirational.test_every_lifted_header_is_declared_by_the_contract), which is also what clears CodeRabbit's docstring-coverage check on the touched functions. It records that this is the "Router actually sends this name" half of the pair — the halfcredits_usedshipped without. No assertion changed.Each of the first two was mutation-checked the same way as the table below: misfiling
request_idinto_NON_HEADER_FIELDSfails the new exemption test, naming it in both collections fails the new disjointness assert, and re-casing a declared header tox-comfy-request-idnow stays green where it previously went red.Review round 2
_CONTRACT_HEADER_LIFTS. Misfilingcredits_usedinto_NON_HEADER_FIELDStherefore also dropped its decimal probe, so the header got the generic"probe"fallback._credits_usedrejects that value, so the field readNoneboth with and without the header, and the misfiling passed. The probes now live in a header-keyed_HEADER_PROBEStable, separate from the lift classification. Mutation-checked: movingcredits_usedinto_NON_HEADER_FIELDSnow failstest_an_exempt_field_is_really_unmoved_by_the_headers_it_skips.Review round 3
_HEADER_PROBESentry. That header gets the generic"probe"fallback. If the field's parser rejects that value, the field reads the same with and without the header, so the test cannot see the misfiling. The docstring now states this limit. The change is to the docstring only.Why the contract now declares it
spec/router-openapi.yamldefinesX-Comfy-Credits-UsedasRouterCreditsUsedHeader,type: string,required: false. That matches what the field already documents and what it already does — it is carried as the wire string and never coerced to a number — so no production change follows from the sync.Verification
Mutation-tested, because a test that only passes proves very little here. Each of these fails a distinct test, and each was confirmed by actually making the edit and running the suite:
credits_usedfrom_CONTRACT_HEADER_LIFTStest_every_header_derived_field_is_pinned_against_the_contractX-Comfy-Idempotent-Replayedtest_the_lift_actually_reads_the_declared_name[replayed-...]X-Comfy-Creditstest_the_lift_actually_reads_the_declared_name[credits_used-...]The second row is the regression that made
replayedpermanentlyFalseagainst a real deployment. It was already covered by unit tests; it is now also pinned against the contract itself, which is the check that would have caught it without anyone thinking to write a test for that specific name.Residual
Not fixed by this PR, and worth a human's attention:
main.credits_usedbeing surfaced fromX-Comfy-Credits-Used, andreplayedbeing read fromIdempotent-Replayedrather than theX-Comfy--prefixed spelling, both landed in #166 (merged 2026-09-20), along with tests covering the"0"-vs-Nonedistinction, the absent-header case, the replay regression, and both the sync and asyncrun_detailedpaths. Nothing in that set needed redoing; this PR only closes the contract-pinning gap that #166's own tripwire flagged and that the spec sync then tripped.mainwas red from 2026-09-20 until #145 replaced the stale tripwire. #177 (spec sync) and #166 merged 35 seconds apart, and neither PR's CI run could see the other's change — a semantic conflict that branch-level CI structurally cannot catch. Worth considering whether the merge queue or a required up-to-date-with-base check should cover this class; that is a repo-infrastructure decision, not something to settle in this PR.v0.4.0, which predates #166; both thecredits_usedaddition and thereplayedfix sit under## [Unreleased]in the changelog. Anyone installingcomfy-sdkfrom PyPI today still gets aRouterRunResultwith nocredits_usedand a permanently-Falsereplayed. Cutting a release is a human call and is out of scope here.X-Committed-Spend-*trio is still not surfaced. The contract declaresX-Committed-Spend-Current,-Limitand-Remainingon the same200; the SDK lifts none of them. That is a different quantity fromcredits_used(in-flight commitment, not the price of this run) and is deliberately out of scope. Note the new completeness guard does not force the issue — it only checks that fields which exist are pinned, so it will stay green while these remain unsurfaced.Idempotent-Replayed: true, the header "restates what the original run cost". The SDK's docstring never encoded the stricter claim, so there is nothing to correct in code — but if any sibling SDK or doc page encoded it, it needs revisiting. I did not audit for that.Unexercised artifacts
model_provider=fal|wavespeed|higgsfield|runware) were not exercised against production: a real run is a billable, state-changing call, and I hold no credentials for it. Everything here is verified against the vendored contract and the repo's fake-server fixtures. The end-to-end claim that production stamps these headers as the contract says rests on the contract and on the operator's own prod run, not on anything I ran.Provenance
acea5fb(the round-3 docstring commit):ruff check .passed,ruff format --check .passed (60 files),mypy srcfound no issues (22 files),tests/test_router_spec_contract.pypassed (81 tests), andscripts/check_public_repo_hygiene.pypassed.1c8fd7d, the last commit that changed test code: the fullpytestrun passed (1138 passed, 9 skipped).scripts/check_drift.pypassed after the merge withmain.mainby #166 (see## Residual).mainkept this branch's side oftests/test_router_spec_contract.py. That replaces #145's standalonecredits_useddeclared-name test.Summary by CodeRabbit
🔄 This issue represents a GitHub Pull Request. It cannot be merged through Gitea due to API limitations.