[Protocol] Adopt UCP 2026-08-25 across Checkout Kit - #765
adrian-augustyn wants to merge 1 commit into
Conversation
f6f1b4c to
fc0bbb0
Compare
|
|
||
| const unitPricePath = path.join(specDir, "types", "unit_price.json"); | ||
| const unitPrice = await readJson(unitPricePath); | ||
| unitPrice.properties.measure.title = "ProductMeasure"; |
There was a problem hiding this comment.
Should these be their own types? Putting the title on the allOf wrapper makes quicktype add a suffix, so we get ProductMeasureClass/ReferenceMeasureClass (*Object in TS) — and they have exactly the same fields as the existing Measure. If you point both fields at measure.json in the codegen copy, they just reuse Measure and the odd names never ship.
| @@ -206,9 +218,33 @@ async function prepareCodegenSchemas(tempDir) { | |||
| for (const fulfillmentSchema of ["fulfillment_available_method", "fulfillment_method"]) { | |||
| const schema = await readJson(path.join(specDir, "types", `${fulfillmentSchema}.json`)); | |||
| schema.properties.type.title = "FulfillmentMethodType"; | |||
There was a problem hiding this comment.
This title does nothing now — in August type is a plain string, quicktype ignores titles on strings, and FulfillmentMethodType is gone from the generated code. I'd drop it and trim the comment so nobody looks for a model that doesn't exist. Only fulfillment_method needs the openness flag, so the loop can go too.
| await writeJson(path.join(specDir, "types", "totals.json"), totals); | ||
| await writeJson(path.join(schemaDir, "common", "types", "totals.json"), totals); | ||
|
|
||
| // Keep new anonymous payment/measure shapes from renaming existing public models. |
There was a problem hiding this comment.
Are ConstraintsElement/PropertyValue names you're happy shipping? They're just where quicktype found the shapes in the recursive constraint schema, and they become public API in all three languages. Titling the recursive parts in the codegen copy would get you ConstraintExpression/ValueConstraint — or if these are fine, a quick note here saying so.
| } | ||
| relocateCommonRefs(destinationFields); | ||
| destination.properties = {...destinationFields, ...destination.properties}; | ||
| destination.required = ["id"]; |
There was a problem hiding this comment.
One gap: an April string description on a fulfillment option still breaks native decode (August made it an object), and the fixtures have no groups[].options[] at all — so the one known break is the one shape the tests never touch. Since it's already called out as out of scope, maybe add a fixture option with a string description that pins the current behavior?
| schema.properties.type.title = "FulfillmentMethodType"; | ||
| // August leaves methods implicitly open. Make that explicit for the native | ||
| // extension generator so the existing additionalProperties API survives. | ||
| if (fulfillmentSchema === "fulfillment_method") schema.additionalProperties = true; |
There was a problem hiding this comment.
If another model quietly stops being open on the next upstream bump, nothing fails — the existing guard only checks the other direction. A list of expected open models that codegen checks would catch it.
| ${conformances} | ||
|
|
||
| extension EmbeddedCheckoutProtocol { | ||
| public static let specVersion = "${specVersion}" |
There was a problem hiding this comment.
Could this get a short /// comment saying it comes from source-lock.json and shouldn't be edited by hand? Everything else in this generated extension has one.
|
|
||
| @Test | ||
| fun `decodes August and April checkouts preserving extensions`() { | ||
| for (version in listOf("2026-08-25", "2026-04-08")) { |
There was a problem hiding this comment.
Small one: Swift and TS run one test per revision, but the loop here means if August fails you never see the April result. Two @Tests calling a shared helper would match the others.
| "methods": [ | ||
| { | ||
| "id": "drone-1", | ||
| "type": "pickup", |
There was a problem hiding this comment.
drone-1 is a pickup method here but its destination is a bare postal address — April pickups used retail locations, so this isn't a payload April would actually send. Making it "type": "shipping" (in all three copies, plus the 'pickup' asserts) would make the April/August pair easier to follow.
|
|
||
| // Catch skipped regeneration even when a native test still expects an older | ||
| // revision. These files are emitted by the same codegen commands used in CI. | ||
| test.each([ |
There was a problem hiding this comment.
Nice — checking the generated Swift/Kotlin constants against the lockfile means forgetting to regenerate now fails CI.
| const bytes = readFileSync(new URL('./fixtures/' + name + '.json', import.meta.url), 'utf8'); | ||
| // SwiftPM bundles resources inside its test target; JVM tests use classpath | ||
| // resources. Keep the three harness-native copies byte-for-byte equivalent. | ||
| for (const directory of [ |
There was a problem hiding this comment.
The byte-equality check across the three fixture copies is exactly what duplicated fixtures need.
ec2522f to
ce90809
Compare
Assisted-By: devx/9ecb7751-82f9-4062-885f-a23b95dfc4a7
ce90809 to
3ac2bb0
Compare
What changes are you making?
Checkout Kit currently replaces caller-supplied
ec_versionwith its April protocol constant. This change pins the August UCP snapshot and updates the SDK sources to request and decode2026-08-25.63967cce95c05f43bbab8c1fc53b0b3d2679b56c, fetched fromorigin/mainand rebased on September 14, 2026. The original September 10 implementation was based ondd79f3628a2f5ba6aead42bfce1a628ddb138e21.ucp-2026-08-25, kept as one squashed commit. The September 14 rebase alone preserved the original patch (git range-diffreported=); the later review fixes and their verification are recorded below.v2026-08-25, resolved by the updater tocd78fb38e819de77d9b527d110476eccb876f1bd.Changes by layer:
source/services/shopping/embedded.openrpc.json; all 412$refreferences resolve, including their JSON pointers. The updater replaced 72 files, and the snapshot matches the pinned commit byte-for-byte.typeMapdeclaration mismatch with tests for exported and unexported declarations. Suppress the internal rename-map declaration artifact emitted bytsc. Preserve typed destination address/location fields and accept April destinations without August's discriminator. Keep fulfillment-method extension preservation when August makes schema openness implicit.Client.onDecodeError/DecodeErrorContextdeclarations, whose drift was reproduced before the schema upgrade.mapOrder: nil, update open-string fulfillment expectations and duplicate/stale URL coverage at the protocol and SDK layers, and regenerate the protocol API report. The other Swift module reports remain unchanged. The review follow-up movesspecVersioninto the generated extension without changing its name, type or getter.--localpasses 73/73; Android local integration passed 26/26 debug and 26/26 release on September 14; published native SDK pointers and package/release versions remain unchanged.This is schema/runtime adoption. The protocol-agnostic public-event work in #748 is outside this change. No changes were taken from stale draft #203.
Checkout-web is a separate state. Gitstream source inspected on September 10 at
405cff8f197d2309f91ed44ac83124af1bf485aacontains the August client, mapper, registration and selection tests. The client factory explicitly selects August while its default/fallback remains April. The source implementation prerequisite was present in that inspection. A fresh September 14 issue check confirms #1236 is closed (September 10, 13:17 UTC). Checkout-web tests were not rerun here, and deployed readiness has not been proved. This PR changes Checkout Kit's own negotiated version.Compatibility and public API
Equivalent JSON fixtures run through TypeScript, Swift and Kotlin. They cover August
ucp.version, typedmap_order,drone_deliveryin both fulfillment method lists, August shipping-address and business-location destinations, and representative April checkout/error payloads including postal addresses and retail locations withouttype. All three languages assert typed business-locationid,name, and nested address fields and verify the full checkout round-trip. SwiftPM and JVM resource conventions require harness-local copies; the TypeScript tests enforce byte-for-byte equality across all three copies.Round-trip checks preserve unknown checkout and fulfillment-method properties plus arbitrary signals. Native codecs accept and ignore unknown UCP metadata; TypeScript preserves it. The tests explicitly reflect that existing boundary. This is representative April wire compatibility, not a claim that every historical shape or public API is unchanged.
Public API review:
FulfillmentMethodTypeandMethodType(plus Kotlin companions); fulfillmenttypeand expectationmethodTypebecome open strings. Native callers using enum cases or exhaustive switches must migrate; TypeScript callers importing the removed union aliases must also migrate.FulfillmentDestination.typeis optional in the SDK compatibility model; businesses emitting August responses must still supply it.copy, and component signatures change.FulfillmentOption.descriptionbecomes a structuredDescriptionobject withplain/html/markdownfields, and payment-instrumentconstraintschanges from an arbitrary map toConstraintsElement. The generated description has no legacy string alternative, so April string-valued fulfillment descriptions are outside the tested compatibility guarantee. Legacy constraint keys outside the new closed schema are not promised native round-trip preservation.specVersionin the API report; the API digester reports no compatibility changes.How to test
The full initial verification below was performed locally on September 10, 2026. A separate September 14 rebase-verification section records fresh checks; earlier passes are not presented as new runs. Every
devcommand means:shadowenv exec --dir /Users/adrianaugustyn_1/src/github.com/Shopify/checkout-kit-ucp-2026-08-25 -- /opt/dev/bin/dev ...JVM commands additionally used
env JAVA_HOME=/private/tmp/jdk-17.0.9+9/Contents/Homeinside that prefix after a temporary Temurin 17 installation. It is a per-command environment setting, not a repository or system default.dev protocol check-toolsdev protocol update-upstream --ref v2026-08-25dev protocol check-upstreamdev codegen typescriptdev codegen swiftdev codegen kotlindev protocol test typescriptdev web testdev protocol test kotlindev protocol test swiftdev protocol checkdev android testdev android api dumpdev android api checkdev android formatdev android lintdev swift testdev swift test EmbeddedCheckoutProtocolTests/VersionCompatibilityTestsdev swift test ShopifyCheckoutKitTests/CheckoutURLDecoratorTestsdev swift api dumpdev swift api checkdev swift lint/dev swift formatdev rn test jsdev rn api checkdev rn lintdev rn test ios --localPodfile.lockwas restored and excluded.dev rn test android --localdev web lintdev protocol lintdev web startSeptember 10 additional commands and diagnostics (with the same
shadowenvprefix):git fetch origin main,git worktree add -b ucp-2026-08-25 <worktree> origin/main,git rev-parse HEAD, and repository instruction/status reads: PASS; original checkout remains untouched.gh issue view 1237,gh issue view 1236, issue timeline reads,gh pr view 548, and open-PR searches/listing: PASS; both issues and Use SemVer for the Kotlin protocol artifact #548 were open at that time. An initial broad PR search was unsuitable; a full list of 80 open Checkout Kit PRs was inspected and no current August adoption implementation was found.?recursive=1endpoint was rejected by shell globbing and succeeded when quoted.git fetch origin main,git show origin/main:<client/mapper/factory/test paths>, and a GitHub commit lookup established the source evidence above.dev codegen typescript: FAIL withtypeMap literal not found; the generator requiredexport constwhile quicktype emittedconst. Retrying with only that fix succeeded and reproduced stale client declarations and the unwanted internal.d.tsbefore the snapshot upgrade.dev protocol check: Swift build passed, then Java was missing. Temurin download/extraction andjava -version: PASS; JVM checks were rerun with the explicit temporaryJAVA_HOME.protocol/languages/kotlin/gradlew -p protocol/languages/kotlin clean :embedded-checkout-protocol:test :embedded-checkout-protocol:apiDump: PASS; later final:embedded-checkout-protocol:test :embedded-checkout-protocol:apiDumpand:embedded-checkout-protocol:apiCheckpass. Earlier overlapping Gradle invocations produced a transient cache error, and an API check failed before the reviewed report was updated; serialized reruns resolved both.swift test --verbose,swift test --disable-xctest, andswift testagainst a clean-main package exported withgit archive: all reproduce the macOS runner's exit 1 after compilation. The initial target-onlydev swift test EmbeddedCheckoutProtocolTestswas rejected because that argument expects a test class; the explicit target/suite command in the table passes.xcrun xcresulttool get test-results summaryconfirms the full Swift package and final fixture-suite results.dev upand its outside-sandbox retry: BLOCKED downloading Maestro with an SSL connection error.scripts/setup_dev_workspacerecovered storefront configuration and gems, then hit a keychain error while installing Swift tooling. Its outside-sandbox retry PASSED, including pinned SwiftLint, SwiftFormat, XcodeGen, sample generation and gems.ProtocolRenameMap.d.tsis absent.git diff --check,git diff --cached --check, and release-pointer/lockfile reviews: PASS. No published coordinate, RN native pointer orPodfile.lockchange is included.git restore -- platforms/react-native/test/rct-integration-app/Podfile.lockrestored only the task-generated local lockfile.git add -- <reviewed paths>andgit commit -m 'feat(protocol): adopt UCP 2026-08-25': PASS.git status --short --branchconfirms both worktrees are clean;git diff HEAD^ HEAD --checkpasses. The local commit changes 131 files, mostly vendored schemas, generated models and API reports.missing field sandboxPolicy; no deployed checkout was exercised. No alternate browser path was used.No remote package publication or release was performed. The successful September 14 RN Android
--localrun published only to Maven Local for validation. React Native API dump was skipped because its check passed without changes. Checkout-web tests and the deployed August handshake remain unverified.Rebase verification — September 14, 2026
The rebase applied cleanly over nine new main commits. No implementation or public API changes were required. #1236 is now closed; #1237 and #548 remain open. There was no existing PR or remote branch for this change.
git fetch origin main;git rebase origin/main63967cce95c05f43bbab8c1fc53b0b3d2679b56c; no conflicts.git range-diff dd79f362..1ebd06fe origin/main..HEADgh issue view 1236;gh issue view 1237;gh pr view 548; PR/remote-branch lookuppnpm --dir protocol install --frozen-lockfiledev protocol check-tools;dev protocol check-upstreamdev protocol test typescript;dev protocol lintdev web test;dev web lintdev protocol checkdev swift testdev up/Library/Java/JavaVirtualMachines. Existing per-command JDK 17 remains usable.scripts/setup_storefront_env;scripts/check_storefront_envdev swift test sampledev swift api checkdev swift lintdev rn test js;dev rn api check;dev rn lintdev rn test ios --localPodfile.lockafterward.dev android testdev android api check;dev android lintdev protocol check.dev rn test android --localxcrun xcresulttool get test-results summarygit diff --check; final status and release-pointer reviewsGenerators and API dumps were not repeated because the rebase preserved the exact reviewed patch and did not change the schema or generator inputs. API checks were rerun. The deployed checkout-web handshake was not retried; its September 10 browser blocker remains an unverified integration gap, not evidence of deployed readiness. No release coordinate or published React Native native SDK pointer changed.
Review follow-up — September 14, 2026
Squashed review head:
fc0bbb069132543d632b59fbd848263367b9a114.Addresses the two review findings:
EmbeddedCheckoutProtocol.specVersionis now emitted bygenerate_swift_catalog.mjsfromsource-lock.json, as TypeScript and Kotlin already were. Protocol tests also compare the TypeScript runtime and both generated native constants with the lockfile, so skipped regeneration fails verification. The Swift test previously namedmatchesOpenRPCInfoVersionis renamed to reflect its actual August-version assertion.id,name, and six nested address fields and verifies the complete decode/re-encode result. August carriestype: "business_location"; April retains its historical retail-location shape without that discriminator. Both TypeScript decoding paths are exercised. No destination model or serializer change was needed.The Swift API report changes only the placement of
specVersionand itsisFromExtensionmetadata. A structural comparison confirmed the variable/getter signatures are identical and the rest of the report is unchanged; the digester reports no compatibility changes. Both other Swift module reports, both Android/Kotlin API reports, the RN public report, release coordinates, and published native SDK pointers are unchanged by this follow-up. The original upgrade's source/binary compatibility considerations above still apply.All commands below use the checkout's required
shadowenv exec --dir <worktree> --prefix. Android commands useenv ANDROID_HOME=/Users/adrianaugustyn_1/Library/Android/sdkbefore/opt/dev/bin/dev; Java is the installed OpenJDK 17, without the earlier temporary override.dev codegen swiftModels.swiftis unchanged.dev protocol check-upstreamdev protocol test typescriptdev protocol lintdev web testdev android testdev android api checkdev android lintdev swift test EmbeddedCheckoutProtocolTests/VersionCompatibilityTestsdev swift test EmbeddedCheckoutProtocolTests/DescriptorTestsdev swift api checkdev swift api dumpspecVersionplacement/extension metadata changes. Neither other Swift module drifts.dev swift lintdev rn test ios --localPodfile.lock; it is excluded from the commit.dev rn api checkxcrun xcresulttool get test-results summary; Python JUnit/fixture/API comparisonsgit diff --check; generated-file/release-pointer/lockfile/status reviewgh pr view 765;gh pr view 548f6f1b4cccaff02bccb00ec592ac2ba22b548fc71; #765 remains a draft; #548 is still open.git branch backup/ucp-2026-08-25-pre-feedback-20260914;git add -- <15 reviewed paths>;git diff --cached --check;git commit --amend --no-edit;git rev-list --count <base>..HEADgit ls-remote fork refs/heads/ucp-2026-08-25;git push --force-with-lease=refs/heads/ucp-2026-08-25:f6f1b4cccaff02bccb00ec592ac2ba22b548fc71 fork HEAD:refs/heads/ucp-2026-08-25Android Studio follow-up earlier on September 14 also completed
dev rn test android --local: PASS, exit 0, 26/26 debug and 26/26 release, with no failures, errors or skips. These are two variants of the suite. That run used commitf6f1b4cccaff02bccb00ec592ac2ba22b548fc71; this review follow-up does not change Kotlin/Android production sources. Its successful local-only Maven publication and native integration supersede the historical missing-SDK blockers retained in the earlier tables.Verification scope: TypeScript/Kotlin codegen, the full Swift package/sample suites, RN JavaScript/native Android suites and their unchanged platform lint were not repeated for these focused fixes; their earlier September 14 results are retained above. No additional Android/RN API dump was needed. The standalone macOS Swift runner and aggregate
dev protocol checkwere not retried here; that diagnosis remains separate. GitHub CI and the deployed August handshake remain unverified. No package release or remote publication was performed.Before you merge
Required future release sequence, not executed here:
Keep this as a draft until the versioning decision and outstanding validation are resolved.