mirror of
https://github.com/Comfy-Org/comfy-python-sdk.git
synced 2026-11-04 23:37:47 +00:00
[PR #134] [MERGED] feat: gate the router exception docstrings with a per-bucket digest of the spec's meaning #135
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#135
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/134
Author: @mattmillerai
Created: 9/6/2026
Status: ✅ Merged
Merged: 9/14/2026
Merged by: @mattmillerai
Base:
main← Head:matt/be-9891-router-meaning-digest📝 Commits (2)
b1df022feat: gate the router exception docstrings with a per-bucket digest of the spec's meaning792b069fix: address bot review comments📊 Changes
5 files changed (+290 additions, -49 deletions)
View changed files
📝
AGENTS.md(+8 -3)📝
scripts/check_drift.py(+140 -37)📝
spec/README.md(+4 -4)📝
src/comfy_sdk/router_exceptions.py(+60 -4)📝
tests/test_router_spec_contract.py(+78 -1)📄 Description
ELI-5
The vendored Router contract writes a sentence of
meaningprose for each of the 15 error buckets, and eachRouterErrorsubclass rewords that sentence into its docstring. The drift check only compared the bucket names and their order — so a contract sync that rewrote a bucket's retry guidance and nothing else passed CI clean, leaving the SDK docstring quietly describing prose that no longer exists. Now every class carries a short fingerprint of the exactmeaningits docstring was written against; when the prose moves, CI goes red naming that bucket and printing the new fingerprint to paste back once you have re-read the docstring.What changed
src/comfy_sdk/router_exceptions.py— a module-level_meaning_digest(meaning)helper (first 12 hex of sha256 of the whitespace-normalized prose), and a_spec_meaning_digestclass attribute on each of the 15RouterErrorsubclasses, sitting right undererror_typeso the marker is next to the docstring it blesses. Every value was computed from the currently vendoredspec/router-openapi.yaml. The attribute is deliberately absent from theRouterErrorbase class: a subclass that forgets it has to fail the check viagetattr(cls, "_spec_meaning_digest", None)rather than silently inherit a blessing for prose nobody read.scripts/check_drift.py—_declared_router_error_types()now returns validated{value, tier, meaning}entries instead of a bare value list, in the same actionable-ValueErrorstyle already used there (tiermust berequest/transport,meaninga non-empty string)._check_router_error_types()keeps its existing values-and-order comparison byte-for-byte identical in behavior (it just derives the flat value list from the entries) and then runs a digest pass on top.tests/test_router_spec_contract.py— a parametrized test asserting the same digest equality with the same guidance in its message, plus a test that everyrequest-tier entry precedes everytransport-tier one. That ordering is the assumption that lets the flat order comparison stand in for a tier check; nothing was asserting it.src/comfy_sdk/router_exceptions.py(counts) — the hardcoded bucket counts are gone from the two section comments and theROUTER_EXCEPTIONSdocstring ("the six … then the nine …"), so a sync cannot falsify them.spec/README.mdandAGENTS.md— both said a changedmeaningwas the one thing no check caught. Both now describe the read marker and the three-step sync (spec, class table, re-bless).The design constraint, stated explicitly
The digest hashes the spec's
meaning. It is never compared against the docstring, and it must not be "fixed" into one: these docstrings deliberately reword the prose into reST, so equality is impossible by design. The digest means "this docstring was written against this version of the meaning" — a question a checker can answer, where "does the docstring say the same thing" is not.Verification of the failure path
Beyond the suite passing, I confirmed the new gate actually fires and actually clears, by temporarily mutating the tree and reverting each time (the vendored spec is unchanged in this diff):
meaning— flipped one word innot_enabled's prose (outage→incident).check_drift.pyfailed withnot_enabled: NotEnabled is blessed against 'bc789c2d6efb', and the spec'smeaninghashes to 'c6ef19909d2d'plus the paste-back line;pytestfailed ontest_every_class_is_blessed_against_the_spec_s_current_meaning[not_enabled]with the same digest.Forbidden's_spec_meaning_digest. Both went red withForbidden carries no _spec_meaning_digestand the digest to add. The message branches on this case on purpose: telling someone their prose "changed" when they have simply not blessed a newly added bucket yet sends them diffing a spec that did not move.meaningas a folded YAML block scalar with identical words. All 54 contract tests stayed green, which is the point of normalizing with" ".join(meaning.split()): a re-wrap should not demand a re-read that has nothing to read.rate_limitedtotier: requestso a request-tier entry followed transport-tier ones; only the new tier-order test failed.Residual
x-comfy-error-typesmeaningentries — the whole corpus this change was scoped to. The same staleness risk exists for the rest of the contract's prose that the SDK reproduces in docstrings and comments:spec/router-openapi.yamlcarries 63 otherdescription:fields, none of which has a read marker, andspec/openapi.yamlis generated so its descriptions ride along in_generated.pyand are covered by the existing byte-for-byte codegen gate instead. Extending the marker to the 63 hand-reworded router descriptions is a separate, larger question (there is no existing class-per-description table to hang a marker on) and is not attempted here.Provenance
uv run --extra dev pytest: 734 passed, 4 skipped;uv run --extra dev pytest tests/test_router_spec_contract.py tests/test_router_exceptions.py: 162 passed;ruff check .: all checks passed;ruff format --check .: 51 files already formatted;mypy src: no issues in 19 source files;python scripts/check_drift.py(codegen extra): all three checks OK. Failure path exercised in the four scenarios above and reverted.import hashlibis a module-level import rather than a local one inside_meaning_digestas the request's sample code wrote it — behaviorally identical, and it matches the file's existing import style. No other deviations; every step was implemented as specified.Summary by CodeRabbit
Bug Fixes
Documentation
🔄 This issue represents a GitHub Pull Request. It cannot be merged through Gitea due to API limitations.