Repository navigation
feat: deterministic api-sync patcher (Phase A) - #68
Merged
Merged
Conversation
Replaces the AI-driven api-sync pipeline with a deterministic, state-based patcher (scripts/api-sync/), a curated spec-map.json, and an honest unmodeled.json inventory of every known gap between the spec and this SDK. Applies the two changes verified safe: QuoteIn.refund_wallet_address on CreateQuoteInput, and the missing "portage" banking_partner enum member (both physical BankingPartner declarations). Bumps to 5.1.0 (enum member added). Everything else found along the way (tracking_payment/tracking_transaction gaps, kyc_status, several bare-string enum regressions, one real string-vs-number type bug on Payin.billing_fee_amount, and more) is recorded in unmodeled.json with an owner and a specific reason, not silently applied. Adds sync/sync:check/audit:types/sync:coverage scripts, a CI job wired into main.yaml (state+map validity, a determinism proof, and a non-blocking coverage + type-audit report), and rewrites api-sync.yml to run the patcher instead of Claude, removing CLAUDE_CODE_OAUTH_TOKEN entirely. Claude-Session: https://claude.ai/code/session_01F1stiNzuNtJXoXtiW9ZCbs
copyFileSync(src, dest) with src === dest (e.g. running --apply --spec .api-sync/spec-snapshot.json, as the determinism-proof CI step does) truncated the destination before reading the identical source, deleting the snapshot. Extracted the refresh into its own module with a same-path no-op guard, and added tests. Claude-Session: https://claude.ai/code/session_01F1stiNzuNtJXoXtiW9ZCbs
Contributor
✅ 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. |
…ge with the PAT sync:check as documented in package.json failed for anyone running it locally: it defaulted to spec-current.json, which only exists on the api-sync-data branch, while CI passed --spec explicitly. Check, coverage and audit assert against committed state, so the snapshot is the correct default. A sync PR merged with GITHUB_TOKEN produces a push that starts no workflow, so the merge would land on main and never publish to npm. Claude-Session: https://claude.ai/code/session_01F1stiNzuNtJXoXtiW9ZCbs
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 this is
Phase A of removing the AI (Claude +
CLAUDE_CODE_OAUTH_TOKEN) from this repo's api-sync pipeline, replacing it with a deterministic, state-based patcher, per the shared design doc.scripts/api-sync/reconciles this SDK's declared TypeScript surface against the current delivered spec state (not an event diff against the previous snapshot, since event diffing is exactly the mechanism that letrefund_wallet_addressand theportagebanking partner sit unmodeled for who knows how long: neither ever showed up as a "diff" once the spec settled). For every schema/enum in the curated.api-sync/spec-map.json:field?: T, scoped-anchor text edit, never a reformat);.api-sync/unmodeled.json(property gaps) or as aknownDivergencesentry (enum-value mismatches, enum members, or type mismatches) is a known divergence: skipped, but greppable, with a reason and an owner.Determinism: sorted output, no timestamps, byte-verbatim snapshot refresh (
copyFileSync, neverJSON.parse+stringify), surgical text insertion only (never reformats a touched file).Applied this run
Exactly the two changes verified safe against the spec and the API source, nothing more:
QuoteIn.refund_wallet_address->CreateQuoteInput.refund_wallet_address?: string | null(quotes/index.ts)portageadded to thebanking_partnerenum -> both physicalBankingPartnerdeclarations (types/index.d.ts, which is dead/unused code, andvirtual-accounts/index.ts, which is the one actually referenced)Version bump: minor, 5.0.1 -> 5.1.0 (an enum member was added).
.api-sync/spec-map.jsonBootstrapped by scoring every exported SDK type's field-set against every spec schema's property-set, then every one of the 39 enum entries and 85 type entries was verified by reading the source. Covers the two discriminator fan-outs called out in the brief:
CreateBankAccountIn-> the 11 rail create-input types (not 10,CreateTedInputwas missing from an earlier draft of the map; corrected.typeis a factory-injected discriminator on all 11, never carried on the input type by design).CreateCustomerIn/CustomerOut-> the 3 KYC/KYB variants (type/kyc_typelikewise factory-injected).ignore.schemas(28 entries): the Rfi family (+2 orphan*Bodyremnants),UploadAnalyzeIn/Out,LedgerOperation(orphan, zero$refanywhere), and the 17 webhook-payload-only schemas this SDK never models (it only verifies signatures; consumers parse the JSON body themselves). Every one of the 84 reachable schemas (transitive$reffrom paths plus the top-levelwebhooksobject) is either mapped or listed here, verified programmatically and by a vitest map-validity test that runs against the real committed files..api-sync/unmodeled.jsonTwo sections:
properties(80 entries, spec properties absent from a mapped SDK type) andknownDivergences(43 entries: enum, property, or type-mismatch gaps), each with a specific reason andowner: eric@blindpay.com.The three items called out explicitly in the brief:
tracking_payment: I have to correct the brief here after reading the code. This SDK'sPayoutTrackingPaymentalready models 12 of 20 payout fields (the 6 recipient_* fields, plusprovider_name/provider_transaction_id/provider_status/estimated_time_of_arrival/completed_at), not 6. The genuinely missing 8 arecoelsa_id,end_to_end_id,provider_clearing_system,provider_error_reason,provider_imad,provider_integration,provider_reference,provider_uetr, recorded, not applied, same reasoning (needs one reviewed cross-SDK PR). Payin-sidetracking_paymentalso turned out to have its own 3-field gap (review_source,review_context,approved_risk_sources) not mentioned in the brief; recorded too.PayoutOut.tracking_documents(an entire new tracking step) and severaltracking_transaction/tracking_completefields are also missing on both sides, all recorded.kyc_status: 7 of 8 modeled, missingapproved_rfi, recorded as a known divergence (reached only through the unmodeled RFI resource)."checking" | "saving"matchespackages/api-contract/src/bank-account.schema.tsexactly).Also recorded:
Error's envelope only ever exposesmessage(not the newercode/description/errors/success/trace_id); a real type bug,Payin.billing_fee_amountis declaredstring | nullin the SDK butnumberin the spec; several bare-string enum regressions (BankAccountOut.business_industry/spei_protocol/status/swift_payment_code,AvailableRails.country,CustomerOut.source_of_funds_doc_type,PayoutOut.ach_cop_document_type);Railmissingsepaeven though SEPA bank-account creation already works;BankDetailKeymissing 24 of 73 display-label values;EstimatedTimeOfArrivalmissing5_30_min; and two dead SDK-only types with no live spec operation behind them,CreatePayoutInput(already known, see below) andCreatePayinInput(same shape of problem, field name does not even match the spec).Known follow-up (documented, not acted on)
blindpay-v2 PR #1925 prunes 49 unreachable schemas from the public spec (my own reachability computation independently found exactly 49 orphans, a good cross-check). Against that pruned spec,
CreatePayoutInput.reference(payouts/index.ts:112, dead code, no live operation posts a body shaped like it) stops resolving under contract-check.mjs's weak "exists anywhere in the spec" check, because today it coincidentally matches an internal billing-invoice schema'sreferenceproperty. Not fixing it now: this repo's contract-check hard-fails on an allow-list entry that matches nothing, and the current snapshot still contains those orphans.scripts/contract-check.mjsitself is unchanged and will surface this the moment the snapshot is refreshed to the pruned spec, with its existing file:line message.CI
.github/workflows/api-sync-check.yaml(new, wired intomain.yamlnext tocontract-check):sync:check(state plus map validity) against the committed snapshot, a determinism proof (apply into twogit worktrees, diff byte-identical), and a non-blocking coverage plus type-audit report.bun run audit:types(--audit-types, new): full state-based type comparison including nullability, whichsync:checkdeliberately does not enforce. This codebase's response types do not mirror JSON nullability into| nullas a matter of pervasive pre-existing style (454 instances), not oversight, and retrofitting that is separate, human-reviewed, Phase C work. 1 genuinetype-kind-mismatchfinding (thebilling_fee_amountbug above); the rest is nullability plus a handful of enum-narrowed-or-dropped cases already tracked inunmodeled.json..github/workflows/api-sync.yml: keeps therepository_dispatch: [api-sync]trigger, drops theclaude-code-actionstep and allCLAUDE_CODE_OAUTH_TOKENreferences. Fetches.api-sync/spec-current.jsonfromorigin/api-sync-data, runs the patcher, exits quietly with nothing to apply, fails loudly with theneedsHumanlist otherwise, else commits code plus refreshed snapshot plus version bump toapi-sync, opens or updates the PR (feat: ... (vX.Y.Z)for minor,fix:for patch, labeledapi-sync), and enables auto-merge.api-sync-merged.ymluntouched, still works (operates onapi-sync-databookkeeping, independent of this change).Proof (local gates, all green)
Determinism proof (two independent
git worktrees from the same commit,--apply --spec .api-sync/spec-snapshot.json):Also re-verified from a clean
v5.0.1checkout applying the real pending drift (refund_wallet_addressplusportage) into two independent clones: byte-identical trees, identical JSON reports.For a human to decide
Payin.billing_fee_amount(string vs. spec's number), real bug, not touched here.Railmissingsepa, looks like a safe one-line follow-up (SEPA bank-account creation already works).tracking_payment/tracking_transaction/tracking_documentsgaps, flagged as needing one reviewed cross-SDK PR, not a per-repo blind append.EstimatedAnnualRevenue's top tier is literally"2500000000_plus"in this SDK vs. spec's"250000000_plus"(one fewer zero), a real value mismatch, direction of the fix needs a human call.unmodeled.json, Phase C territory (RFI resource, upload/analyze, the rest of the tracking gaps, the bare-string enum regressions).https://claude.ai/code/session_01F1stiNzuNtJXoXtiW9ZCbs