chore(ci): auto-generate SDK code for standard new spec operations - #71
Merged
Merged
Conversation
Teaches the api-sync patcher a new operation-insert change kind. Today, any spec operation with no corresponding SDK method is always needs-human, even for ordinary CRUD endpoints that fit the SDK's existing conventions. Adds a state-based operation-coverage check (reconcile_operations): resource modules are discovered directly from their own call-site f-strings (verb + path template), so coverage never needs a hand-curated map. An uncovered operation is classified STANDARD (JSON in/out, request and response resolve to a plain object or scalar/ref array, and the path matches exactly one existing resource module by prefix) or NON-STANDARD, with a precise reason (unsupported content type, no/ambiguous resource match, oneOf/anyOf, unmappable property type). STANDARD operations get a generated sync/async method pair, TypedDicts synthesized from the spec schema, and spec-map entries for any newly referenced named schemas. --check now fails on a pending operation-insert (it did not check operation coverage before). Applied operation-inserts bump minor, so the existing api-sync workflow's bump-to-prefix mapping (minor -> feat) covers them automatically, no workflow change needed. A pre-existing, previously invisible gap (the RFI endpoints have no resource module at all) is now caught by this check; recorded as a new unmodeled.json kind=operation entry rather than silently patched over. Claude-Session: https://claude.ai/code/session_01F1stiNzuNtJXoXtiW9ZCbs
Collaborator
✅ Snyk checks have passed. No issues have been found so far.
💻 Catch issues earlier using the plugins for VS Code, JetBrains IDEs, Visual Studio, and Eclipse. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What changed
The api-sync patcher (
.api-sync/sync.py) previously classified every spec operation with no corresponding SDK method asneeds-human, even for plain CRUD endpoints that fit the SDK's existing conventions exactly. This PR adds a newoperation-insertchange kind that auto-generates the method(s), TypedDicts, and spec-map entries for those, and narrowsneeds-humanto operations that genuinely need a person.Operation coverage is derived, not hand-curated
A new
reconcile_operationscheck walks every operation in the current spec (state-based, like the existingreconcile_enums/reconcile_types, not a diff against the old snapshot) and asks: does any resource module already issue this exact (HTTP verb, path template) request? Coverage is read directly off each resource module's own call-site f-strings (discover_resource_modules), so it never needs a hand-maintained map the way spec-map.json's schema/enum entries do.STANDARD vs NON-STANDARD boundary
An uncovered operation is STANDARD (auto-generated) only if all of these hold:
.../partner-fees, or the "item" prefix up to the last path param, e.g..../webhook-endpoints/{id}, which is what lets a new sub-action like.../{id}/secretattach to the same resource as a siblingdelete(id)). Zero matches or more than one is NON-STANDARD.application/jsononly (no multipart, binary, streaming, etc).$ref.oneOf/anyOfanywhere is NON-STANDARD.SCALAR_TYPE_MAP,$ref'd properties only if spec-map.json already maps that schema 1:1, enum-constrained properties only if spec-map.json already maps that exact(schema, property)to a Literal. Anything else (an inline enum with no mapped Literal, a$refwith no existing map entry, a nested inline object) is NON-STANDARD.get/get_<suffix>/list/create/update/delete, derived from the HTTP verb and path shape) must not already exist on the target resource class.Every NON-STANDARD case gets a precise reason string, e.g.
"unsupported content type(s): ['multipart/form-data']","no existing resource matches this path's prefix","schema uses oneOf, which is unsupported".Generated code is text-spliced in the same style as the existing enum/property appliers, then run through
ruff format+ruff check --fix --select=Iso it lands already canonical (no hand-tuned line wrapping needed).Bump and CI workflow
operation-insertapplies now count toward aminorbump, same as enum additions. Theapi-sync.ymlworkflow already mapsbump == "minor"to afeat:commit/PR prefix ({"minor": "feat", "patch": "fix"}.get(bump, "chore")), so no workflow change was needed for a real future operation-insert to land asfeat:.--checknow covers operations--checkpreviously never looked at operation coverage at all (only--apply's old-vs-new path-key diff did, and only for brand new path keys). It now fails on any pending operation-insert or NON-STANDARD operation gap, state-based like every other reconcile check. This surfaced a real, previously invisible pre-existing gap -- the RFI endpoints (GET/POST .../rfi) have no resource module at all -- which is now recorded as akind=operationentry inunmodeled.json(new kind, same "honest ledger with a reason and owner" pattern as the existing kinds) rather than silently generated or silently ignored.Golden-test evidence
tests/test_api_sync_golden.py(new) operates on a scratch copy of the real repo, not a synthetic fixture:WebhookEndpointsResource(Sync).get_secret()(GET, single path param, inline/unnamed response schema) and.create()(POST, request+response both$ref'd to named, spec-mapped schemas:WebhookEndpointIn/WebhookEndpointOut), plus their TypedDicts and the two spec-map entries.--applyagainst the repo's own committed spec snapshot: both methods were regenerated. Verified the regeneratedget_secret's route, verb, andGetWebhookEndpointSecretResponsetype match the original exactly, and same forcreate's route, verb,CreateWebhookEndpointInput/CreateWebhookEndpointResponse.WebhookEndpointIn->CreateWebhookEndpointInput,WebhookEndpointOut->CreateWebhookEndpointResponse) were restored.pyright,mypy, andpytestagainst the regenerated tree: all green.--applya second time: no-op (applied: [],bump: null), confirming idempotency.--checkon the regenerated tree: green.multipart/form-datarequest body correctly routes to needs-human with a reason namingmultipart/form-dataexplicitly (verified against the same golden repo, using a path that resource-matches cleanly so it's specifically the content-type check, not resource matching, being exercised).Local gauntlet run
ruff format --check,ruff check,pyright,mypy,pytest(233 existing + 10 new),sync.py --validate-map,sync.py --check,check_contract.py, and the determinism proof fromapi-sync-check.yaml(apply twice against the committed snapshot, diff the two results and diff against the untouched tree) all pass locally.Claude-Session: https://claude.ai/code/session_01F1stiNzuNtJXoXtiW9ZCbs