mirror of
https://github.com/Comfy-Org/comfy-typescript-sdk.git
synced 2026-11-04 23:47:27 +00:00
[PR #100] [MERGED] test: gate the Router run route and default host against the vendored spec #103
Labels
No labels
audit:ai-readiness
audit:community
audit:github
audit:health-scoring
audit:policy
audit:release
audit:supply-chain
bug
cursor-review-pin
effort:medium
effort:small
effort:trivial
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-typescript-sdk#103
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-typescript-sdk/pull/100
Author: @mattmillerai
Created: 8/27/2026
Status: ✅ Merged
Merged: 8/27/2026
Merged by: @mattmillerai
Base:
main← Head:matt/be-9936-router-spec-contract📝 Commits (1)
6c23f26test: gate the Router run route and default host against the vendored spec📊 Changes
9 files changed (+492 additions, -21 deletions)
View changed files
📝
.github/workflows/ci.yml(+1 -1)📝
AGENTS.md(+23 -6)📝
CHANGELOG.md(+10 -0)📝
README.md(+2 -0)📝
scripts/check-spec-drift.mjs(+100 -9)➕
scripts/router-route-contract.mjs(+189 -0)📝
spec/README.md(+6 -1)📝
src/sdk/models.ts(+55 -4)➕
src/sdk/router-spec-contract.test.ts(+106 -0)📄 Description
ELI-5
comfy.models.runknows two things by heart: the path it posts to (/v1/models/{provider}/{model}) and the host it posts to (https://api.comfy.org). This repo also carries a vendored copy of the Comfy Router contract that declares both — but nothing read the route out of it, so if a sync PR ever moved the route, CI would stay green andrunwould quietly start 404ing. This PR makes the two agree by force: the path becomes a named constant, and two checks now compare it (and the host) against the vendored spec. Nothing about the request changes.What changed
src/sdk/models.ts— the inline path becomesexport const RUN_ROUTE_TEMPLATE = "/v1/models/{provider}/{model}", filled by a module-privatefillRoute(template, values)that runsencodeURIComponentper segment, exactly as the interpolated string did. An unknown placeholder throws rather than shipping a literal{...}in a URL.ModelIdbecame atypeso it carries the implicit index signaturefillRouteneeds (no cast).src/sdk/router-spec-contract.test.ts(new) — compares the live constants againstspec/router-openapi.yaml: the path declared forpost.operationId: runRouterModelvsRUN_ROUTE_TEMPLATE,servers[0].urlvsCOMFY_ROUTER_BASE_URL, and the path parameters (providerthenmodel, order included,$refs resolved) vs the template's placeholders. Two further cases drive a real request through the stub server so the constant is proved to be the onerunUrlactually uses, and that per-segment percent-encoding survived the extraction.scripts/check-spec-drift.mjs— makes the same comparisons insidepnpm check:spec-drift, so the drift job reddens by name rather than only the unit suite. That job is plain Node with no TypeScript loader, so it reads the two constants out of their source text. Its two checks (codegen diff, Router route) now both run and both report on every invocation, so one failure cannot hide the other.scripts/router-route-contract.mjs(new) — the shared spec reader, mirroring howscripts/python-surface.mjsis shared with the parity test. Every extractor in it throws rather than returning nothing: a drift check that silently reads nothing reports agreement, which is the one failure mode it must not have.comfy.models.runin the README naming the file and the two constants to update; a CHANGELOG entry under Unreleased → Changed (marked no-behaviour-change);spec/README.mdandAGENTS.md§1–2 updated, since both stated that the error-bucket table was the only thing coupled to this contract and thatcheck-spec-drift.mjsread nothing from it. The CI step name changed fromCheck generated code is in sync with spec/openapi.yamltoCheck the code coupled to spec/ is in sync with it, since it now covers both specs (AGENTS.mdquoted the old name and was updated with it).Acceptance, exercised
Both halves of the stated criterion were run, in both directions, on the real vendored spec.
Green on the unmodified tree:
pnpm test406 passed / 23 files;pnpm check:spec-driftOK: src/low/generated is in sync with spec/openapi.yaml+OK: the Router route matches spec/router-openapi.yaml;pnpm lint,pnpm format:check,pnpm typecheck,pnpm build,node scripts/check-public-repo-hygiene.mjs,pnpm check:sdk-parity(network) all clean.Red when
spec/router-openapi.yaml'srunRouterModelpath is temporarily edited to/v2/models/{provider}/{model}— the spec was restored afterwards andgit statusonspec/is clean:pnpm test→ 1 failed / 23 files,AssertionError: the vendored Router contract moved the runRouterModel path — update RUN_ROUTE_TEMPLATE in src/sdk/models.ts to match it (comfy.models.run 404s until you do).pnpm check:spec-drift→ exit 1,RUN_ROUTE_TEMPLATE (src/sdk/models.ts) is "/v1/models/{provider}/{model}", but spec/router-openapi.yaml declares "/v2/models/{provider}/{model}" for post.operationId: runRouterModel.The other two gates were exercised the same way and each fires alone: editing
servers[0].urlnamesCOMFY_ROUTER_BASE_URL, and swapping the two$refd path parameters namesRUN_ROUTE_TEMPLATEand prints the order it expected.Sweep
The vendored Router contract declares 4 operations. This SDK calls exactly 1 of them (
runRouterModel) and this PR pins that one. The other 3 —listRouterModels,getRouterModel,getRouterModelInputSchema— have no call site in this repo, so there is no constant to drift and nothing to pin; if the SDK grows one, it needs the same treatment. A grep acrosssrc/,test/andscripts/finds 1 production spelling of the route (nowRUN_ROUTE_TEMPLATE) and 0 other call sites; the remaining hits are prose, a test's expected path, and the stub server's header comment. The stub answers any path, so it is not coupled to the route.Judgment calls
RUN_ROUTE_TEMPLATEis exported fromsrc/sdk/models.tsbut deliberately NOT re-exported fromsrc/sdk/index.ts. It is the anchor for a drift gate, not a knob a caller turns — the route is the SDK's business andcomfy.config({ baseUrl })is how a caller retargets the host. Publishing it would make an internal coupling point semver-relevant and would put a TypeScript-only name onto the cross-SDK surface. The package's public surface is therefore unchanged, the parity check needed no allowlist entry, andpnpm check:sdk-parityis green. Say the word if you'd rather it were public.check:spec-driftruns under plain Node in CI with no TypeScript loader and no build ahead of it. The regex requires theexport const X = "…";form on one line and throws with a message naming the constant if it does not find it, so a future reformat fails loudly rather than silently passing. The unit test imports the real values, which is the half that proves the runtime value.scripts/check-spec-drift.mjs; that file had none. The existing reader lives insrc/sdk/router-spec-coverage.test.ts. I used theyamldev dependency the same way it does — the alternative the plan named.throw(fillRouteon an unknown placeholder) is unreachable from any caller:RUN_ROUTE_TEMPLATEdeclares exactly{provider}and{model}, andModelIdis typechecked to carry exactly those two keys. I verified empirically rather than by assertion that the capability it could conceivably block still works:comfy.models.runcompletes end to end against the stub server in the pre-existing suite and in two new cases that read the request path off the wire (/v1/models/fal-ai/flux-pro, and/v1/models/fal%20ai/flux%23profor the encoding). The new CI gate only blocks a spec sync that moves the route without updating the constant, and it is green against today's real vendored spec.Residual
Not fixed here, and worth its own tracking:
spec/router-openapi.yaml, that sync PR will be red untilRUN_ROUTE_TEMPLATEinsrc/sdk/models.tsis updated in the same PR. That is the intended outcome, but it does mean the sync is no longer a merge-on-green change for this repo, and whoever handles it needs to know that. The README,spec/README.mdandAGENTS.md§2 all name the constant so the failure is self-servicing.push-v2-spec-to-sdks.yml, itstypescript-sdk-routerrow) lives in a different repository and is not present in this tree, so I could not read or run it. I have therefore not verified that a real sync PR rewritesspec/router-openapi.yamlin the shape this gate assumes — a change to thepathskey and/orservers[0].url. If a sync instead, say, adds a/v2path alongside the/v1one and leavesrunRouterModelon the old key, this gate stays green and the SDK keeps calling the old route. Worth confirming against that workflow.servers[0]only. The host comparison reads the first server entry. A contract that grows a second entry, or reorders them, is not covered.Provenance
pnpm test406 passed / 0 failed (23 files);pnpm check:spec-driftboth checks OK, and exit 1 namingRUN_ROUTE_TEMPLATEunder a simulated/v2move;pnpm lint,pnpm format:check,pnpm typecheck,pnpm buildclean;node scripts/check-public-repo-hygiene.mjsOK;pnpm check:sdk-paritymatches the Python SDK'smain.🔄 This issue represents a GitHub Pull Request. It cannot be merged through Gitea due to API limitations.