diff --git a/.claude/commands/e2e-tests.md b/.claude/commands/e2e-tests.md index eb35d5c75..8356b61cc 100644 --- a/.claude/commands/e2e-tests.md +++ b/.claude/commands/e2e-tests.md @@ -91,6 +91,12 @@ Run this row as part of every full E2E regression. When the request is narrowed to IAPKit, run this row plus the focused package/example checks that support it; do not rerun unrelated framework/store rows. +Read the "Device and Workspace State That Silently Breaks a Row" section of +`.codex/skills/iapkit-e2e-martie/SKILL.md` first. A leftover store flavor in the +generated Android project, a leftover iOS scene session from another app sharing +the bundle id, and prebuilt React Native each break a row in a way that looks +like a store or account failure. + Prerequisites: - A connected iPhone or Google Play-capable Android phone with a sandbox/tester diff --git a/.claude/commands/verify-all.md b/.claude/commands/verify-all.md index 7c71b76db..f99bc1b2c 100644 --- a/.claude/commands/verify-all.md +++ b/.claude/commands/verify-all.md @@ -40,6 +40,10 @@ set -euo pipefail # Docs formatting, typecheck, and production bundle (cd packages/docs && bun run format:check && bun run build) +# Advisory: recorded IAPKit interop evidence versus the current sources +bun test ./scripts/audit-commerce-evidence.test.mjs +bun run audit:commerce-evidence || echo "commerce evidence differs from current sources; re-record before deploying docs" + # Swift build and unit tests (packages/apple) (cd packages/apple && swift test) diff --git a/.codex/skills/iapkit-e2e-martie/SKILL.md b/.codex/skills/iapkit-e2e-martie/SKILL.md index 1bff62833..7404180aa 100644 --- a/.codex/skills/iapkit-e2e-martie/SKILL.md +++ b/.codex/skills/iapkit-e2e-martie/SKILL.md @@ -261,6 +261,61 @@ inspect the generated key value. After building, install with `VEGA_DEVICE_ID="$VEGA_DEVICE_ID" bun run run:vega:firetv`, then require the same local-server and purchases-view evidence as the other live lanes. +## Device and Workspace State That Silently Breaks a Row + +Every item below has cost a full debugging session. Check them before +concluding that a store, an account, or the code is at fault. + +**The Android project keeps the last store it was prebuilt for.** The FireOS +and Horizon rows run `expo prebuild --platform android --clean` with +`EXPO_IAP_FIREOS=1` or `EXPO_IAP_HORIZON=1`, and the generated `android/` +directory keeps that store afterwards. A later Play run then links the wrong +`openiap-google` flavor, so the example sits on `Connecting to Store...` with +`initConnection failed: Failed to initialize connection` and +`getStorefront failed: Billing client not ready`. Re-run +`bunx expo prebuild --platform android --clean` with no store variable before +the Play row, then confirm `horizonEnabled=false` and `fireOsEnabled=false` in +`android/gradle.properties` and `missingDimensionStrategy "platform", "play"` +in `android/app/build.gradle`. + +**A Play "not compatible with your device" banner does not block billing.** The +Martie production listing sets `minSdkVersion 31`, so Play marks an Android 11 +device incompatible and refuses to install that artifact. The examples this +repository builds declare a lower minimum and install fine: `packages/google` +Example inherits `minSdk = 23` from the library, and the Expo example ships 24. +Both use the `dev.hyo.martie` application id, so a license tester buys and +verifies through them normally despite the banner. Confirm with the +`packages/google` Example, whose subscription screen enables `OpenIapLog` and +prints the real `BillingResult`, before blaming the store. + +**iOS keeps a scene session per bundle id.** Any other app built with +`dev.hyo.martie` — the SwiftUI `packages/apple/Example`, or the Godot and +Flutter Martie examples — leaves a scene session behind. Installing the Expo +example over it restores that session, so UIKit attaches the previous app's +scene delegate and the example's own `SceneDelegate`, which is what starts +React Native, never runs. The process stays alive, the screen is black, Metro +receives no bundle request, and nothing crashes. Run +`xcrun devicectl device uninstall app --device "$IOS_UDID" dev.hyo.martie` +before installing; an upgrade install does not clear it. + +**Prebuilt React Native has no packager support.** Expo links React Native as a +prebuilt binary by default, and that slice compiles without `DEBUG`, so +`RCTBundleURLProvider` returns no bundle URL and a Debug build never contacts +Metro whatever host `ip.txt` holds. Set `"ios.buildReactNativeFromSource"` to +`"true"` and `"EXPO_USE_PRECOMPILED_MODULES"` to `"false"` in +`ios/Podfile.properties.json`, then run `pod install` and rebuild. + +**Reinstalling resets the iOS local-network permission.** Allow it again when +the prompt appears, otherwise both Metro and the local server are unreachable. + +**The local origin differs per platform and is baked in at bundle time.** +Android reaches the server through an `adb reverse` rule on `127.0.0.1`; iOS +needs the Mac's LAN address. `EXPO_PUBLIC_*` values are inlined when Metro +starts, so restart Metro and relaunch the app after editing the environment +file. A stale value sends verification to the hosted service instead, which +surfaces as `Unable to parse verification response` while the local server log +stays empty. + ## Martie Catalog - `dev.hyo.martie.10bulbs`: consumable; preferred repeatable receipt fixture diff --git a/.codex/skills/ship-release/SKILL.md b/.codex/skills/ship-release/SKILL.md index e42d6dced..b29a27e6f 100644 --- a/.codex/skills/ship-release/SKILL.md +++ b/.codex/skills/ship-release/SKILL.md @@ -107,14 +107,17 @@ still return to the normal PR loop. From a clean local `main` equal to `origin/main`: -1. Run `npm run deploy` and wait for successful production completion. -2. Fetch the production release page and generated LLM documents with a cache +1. Run `bun run audit:commerce-evidence`. If it reports drift, re-record the + IAPKit interop per `packages/kit/scripts/docs/commerce-interop.md` before + deploying; the guide page shows the recorded revision either way. +2. Run `npm run deploy` and wait for successful production completion. +3. Fetch the production release page and generated LLM documents with a cache buster. Confirm the new release title, API name, versions, and generated timestamp are present. -3. If the OpenIAP Spec advanced, dispatch the docs release workflow with the +4. If the OpenIAP Spec advanced, dispatch the docs release workflow with the current version and verify the resulting `docs-{spec}` GitHub Release points to the deployed commit. -4. Recheck required CI for the final `main` head and report any still-pending +5. Recheck required CI for the final `main` head and report any still-pending external listing or registry state separately. ## 6. Leave the shipped comment diff --git a/.github/ISSUE_TEMPLATE/commerce_protocol.yml b/.github/ISSUE_TEMPLATE/commerce_protocol.yml new file mode 100644 index 000000000..9ab376a57 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/commerce_protocol.yml @@ -0,0 +1,54 @@ +name: Commerce Protocol proposal +description: Propose shared behavior or contribute implementation interoperability evidence. +title: "[Commerce Protocol]: " +body: + - type: markdown + attributes: + value: | + Start with a concrete connection between products. See the [contribution procedure](https://github.com/hyodotdev/openiap/blob/main/specs/commerce-protocol/CONVENTION.md#public-collaboration-and-implementation-evidence). + IAPKit and other implementations are reviewed against the same contract. Submitting a report is not a conformance or endorsement claim. + - type: dropdown + id: contribution + attributes: + label: Contribution + options: + - Interoperability reproduction + - Contract extension + - Compatibility problem + validations: + required: true + - type: textarea + id: outcome + attributes: + label: Use case and expected outcome + description: Which services or roles need to connect, and what should the user observe? + validations: + required: true + - type: textarea + id: implementations + attributes: + label: Implementations and reviewers + description: List source revisions, roles, stores, bindings, declared profiles, and relevant affiliations. Distinguish project-authored fixtures from an external implementation or review. + validations: + required: true + - type: textarea + id: reproduction + attributes: + label: Runnable evidence + description: Provide commands, source/report links, expected results, and a failure or rejection case. List configuration fields and adapter/client changes; omit credentials and customer data. + validations: + required: true + - type: textarea + id: compatibility + attributes: + label: Compatibility and alternatives + description: Can existing profiles or extensions express this? For a contract change, identify MAJOR/MINOR impact and migration work. For a reproduction, state whether the client and receiver code changed. + validations: + required: true + - type: textarea + id: limits + attributes: + label: Limits and unresolved questions + description: State what remains untested, who has reproduced the result, and any disagreement about expected behavior. + validations: + required: true diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 9f7836993..766176c68 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -219,6 +219,8 @@ jobs: # time, so a spec change must rebuild and probe the binary. - 'specs/commerce-protocol/**' - 'scripts/e2e-web-sites.mjs' + - 'scripts/audit-commerce-evidence.mjs' + - 'scripts/audit-commerce-evidence.test.mjs' - 'package.json' - 'bun.lock' - '.github/workflows/ci.yml' @@ -592,6 +594,10 @@ jobs: bun test scripts/audit-docs.test.ts bun run audit:docs + - name: Check static discovery guards + working-directory: packages/docs + run: bun run test:discoverability + - name: Lint working-directory: packages/docs run: bun run lint @@ -631,7 +637,13 @@ jobs: done - name: Install Playwright chromium - run: bunx playwright install --with-deps chromium + # `--with-deps` runs apt, which fails whenever Google's Chrome + # repository serves a stale index. Playwright downloads its own + # chromium, so drop that source first: nothing here needs it. + run: | + sudo rm -f /etc/apt/sources.list.d/google-chrome.list \ + /etc/apt/sources.list.d/google-chrome.sources + bunx playwright install --with-deps chromium - name: Build docs site working-directory: packages/docs @@ -643,6 +655,15 @@ jobs: VITE_KIT_CONVEX_URL: https://placeholder-build-1.convex.cloud run: bun run build:all + - name: Test the Commerce evidence audit + run: bun test ./scripts/audit-commerce-evidence.test.mjs + + - name: Report Commerce evidence freshness + # Advisory: the recorded IAPKit interop stays valid for its recorded + # revision; drift only means the guide shows older sources than main. + continue-on-error: true + run: bun run audit:commerce-evidence + - name: Run docs and IAPKit web E2E env: WEB_E2E_DOCS_BASE_URL: http://127.0.0.1:4173 diff --git a/.github/workflows/deploy-kit.yml b/.github/workflows/deploy-kit.yml index b832f45b5..8ed5c3b11 100644 --- a/.github/workflows/deploy-kit.yml +++ b/.github/workflows/deploy-kit.yml @@ -152,7 +152,13 @@ jobs: - name: Install Playwright chromium # smoke-browser.ts loads the SPA in headless Chromium to catch # runtime bundle crashes that HTTP probes miss (see PR #120). - run: bunx playwright install --with-deps chromium + # `--with-deps` runs apt, which fails whenever Google's Chrome + # repository serves a stale index. Playwright downloads its own + # chromium, so drop that source first: nothing here needs it. + run: | + sudo rm -f /etc/apt/sources.list.d/google-chrome.list \ + /etc/apt/sources.list.d/google-chrome.sources + bunx playwright install --with-deps chromium - name: Smoke test compiled server run: ./scripts/smoke-server.sh diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 822428ade..552d66814 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -96,6 +96,12 @@ second type-copy command or maintain another target list. ### Changing the Commerce Protocol +Start shared behavior proposals with the **Commerce Protocol proposal** issue +form. The [protocol contribution procedure](specs/commerce-protocol/CONVENTION.md#public-collaboration-and-implementation-evidence) +defines the evidence and compatibility review. You can also contribute an +independent reproduction using the [service composition example](https://openiap.dev/commerce-protocol/ecosystem#composition-proof) +without proposing a contract change. + 1. Edit `specs/commerce-protocol/SPEC.md` and the owning GraphQL layer under `schema/`. 2. Run `cd specs/commerce-protocol && bun run build` to regenerate the diff --git a/knowledge/_agent-context/context.md b/knowledge/_agent-context/context.md index 0b0a394dc..69e4ac821 100644 --- a/knowledge/_agent-context/context.md +++ b/knowledge/_agent-context/context.md @@ -1,7 +1,7 @@ # OpenIAP Project Context > **Auto-generated shared context for AI assistants** -> Last updated: 2026-09-07T14:34:17.286Z +> Last updated: 2026-09-09T00:37:16.148Z > > Canonical file: `knowledge/_agent-context/context.md` @@ -1689,6 +1689,47 @@ Before finishing, read the rendered page as a user. Remove any sentence that does not clarify what changed, how to use it, who is affected, or what action is required. +## Human and AI Acceptance + +Apply these checks whenever changing a guide, example, SDK entry point, or AI +implementation brief. They are completion criteria, not an optional final polish. + +- **First-time reader:** walk through the rendered page on desktop and mobile. + The reader should understand what they get, which decisions they own, and what + to do next before seeing an API table or a long AI prompt. Introduce terms at + the step that needs them; keep detailed references available afterward. +- **Two implementations:** Commerce Protocol guides must connect each relevant + responsibility to the runnable example and to at least one independent + implementation's code and checks (today `openiap-commerce-protocol-example` + and IAPKit). Explain differences in supported operations, stores, and + profiles. Do not present a fixture as a real purchase, one provider as + evidence of interoperability with another, or any single implementation as + the protocol. Follow the source links and operate the guide. +- **Fresh AI implementation:** when a brief or runnable example changes, replay + its install, implementation, startup, and acceptance instructions in a clean + project using only the published inputs. Record the input revision, commands, + observed results, failures, and remaining limits. Retain previously exercised + cases; do not hide failures by shrinking declarations or accepting known + conformance failures. Reuse an earlier run only when its relevant inputs have + not changed, and identify that run rather than calling it a new reproduction. +- **Independent acceptance:** test the promised customer behavior, including + failure and recovery, against the running result, and record the commands + and observed results in the PR body or the published run report. An agent + simulation is useful evidence, but must be labeled as a simulation, not a + human user study. +- **SDK discovery:** verify the initial HTTP response contains the page's actual + text, title, description, and canonical URL without JavaScript. Canonical + pages belong in the generated sitemap. Framework names, install commands, + versions, and setup links come from their existing metadata sources. Generated + `llms.txt` references must lead to the same current contracts and examples. + +Run `bun run build` and `bun run test:discoverability` in `packages/docs` for +static content and metadata checks. The repository's `bun run e2e:web` +checks the served HTML and the interactive Commerce Protocol walkthrough. +These checks catch regressions; they do not prove that a person understood the +page or that an AI chose the SDK, and `llms.txt` or structured data do not +guarantee indexing or recommendation. Do not claim adoption effects from them. + ## Modal Pattern with Preact Signals ### Global Modal Management diff --git a/knowledge/internal/05-docs-patterns.md b/knowledge/internal/05-docs-patterns.md index 2ae1c47ff..d2cb6240f 100644 --- a/knowledge/internal/05-docs-patterns.md +++ b/knowledge/internal/05-docs-patterns.md @@ -24,6 +24,47 @@ Before finishing, read the rendered page as a user. Remove any sentence that does not clarify what changed, how to use it, who is affected, or what action is required. +## Human and AI Acceptance + +Apply these checks whenever changing a guide, example, SDK entry point, or AI +implementation brief. They are completion criteria, not an optional final polish. + +- **First-time reader:** walk through the rendered page on desktop and mobile. + The reader should understand what they get, which decisions they own, and what + to do next before seeing an API table or a long AI prompt. Introduce terms at + the step that needs them; keep detailed references available afterward. +- **Two implementations:** Commerce Protocol guides must connect each relevant + responsibility to the runnable example and to at least one independent + implementation's code and checks (today `openiap-commerce-protocol-example` + and IAPKit). Explain differences in supported operations, stores, and + profiles. Do not present a fixture as a real purchase, one provider as + evidence of interoperability with another, or any single implementation as + the protocol. Follow the source links and operate the guide. +- **Fresh AI implementation:** when a brief or runnable example changes, replay + its install, implementation, startup, and acceptance instructions in a clean + project using only the published inputs. Record the input revision, commands, + observed results, failures, and remaining limits. Retain previously exercised + cases; do not hide failures by shrinking declarations or accepting known + conformance failures. Reuse an earlier run only when its relevant inputs have + not changed, and identify that run rather than calling it a new reproduction. +- **Independent acceptance:** test the promised customer behavior, including + failure and recovery, against the running result, and record the commands + and observed results in the PR body or the published run report. An agent + simulation is useful evidence, but must be labeled as a simulation, not a + human user study. +- **SDK discovery:** verify the initial HTTP response contains the page's actual + text, title, description, and canonical URL without JavaScript. Canonical + pages belong in the generated sitemap. Framework names, install commands, + versions, and setup links come from their existing metadata sources. Generated + `llms.txt` references must lead to the same current contracts and examples. + +Run `bun run build` and `bun run test:discoverability` in `packages/docs` for +static content and metadata checks. The repository's `bun run e2e:web` +checks the served HTML and the interactive Commerce Protocol walkthrough. +These checks catch regressions; they do not prove that a person understood the +page or that an AI chose the SDK, and `llms.txt` or structured data do not +guarantee indexing or recommendation. Do not claim adoption effects from them. + ## Modal Pattern with Preact Signals ### Global Modal Management diff --git a/package.json b/package.json index 3252a7361..395283b40 100644 --- a/package.json +++ b/package.json @@ -22,6 +22,7 @@ "audit:parity": "node scripts/audit-non-godot-parity.mjs", "audit:kit-contract": "node --test scripts/audit-kit-spec-contract.test.mjs && node scripts/audit-kit-spec-contract.mjs", "audit:docs": "bun run scripts/audit-docs.ts", + "audit:commerce-evidence": "bun scripts/audit-commerce-evidence.mjs", "audit:schema-semver": "bun run --cwd specs/client audit:schema-semver", "audit:research": "node --test scripts/audit-research.test.mjs && node scripts/audit-research.mjs", "audit:whitepaper": "node --test scripts/audit-whitepaper.test.mjs && node scripts/audit-whitepaper.mjs", diff --git a/packages/apple/Example/OpenIapExample/Screens/AllProductsView.swift b/packages/apple/Example/OpenIapExample/Screens/AllProductsView.swift index 6796ddebe..319b4eef5 100644 --- a/packages/apple/Example/OpenIapExample/Screens/AllProductsView.swift +++ b/packages/apple/Example/OpenIapExample/Screens/AllProductsView.swift @@ -293,6 +293,10 @@ struct AllProductsView: View { return "auto-renewable" case .nonRenewingSubscription: return "non-renewing" + case .subscriptionBundle: + return "subscription-bundle" + case .subscriptionSuite: + return "subscription-suite" } } @@ -306,6 +310,10 @@ struct AllProductsView: View { return .blue case .nonRenewingSubscription: return .indigo + case .subscriptionBundle: + return .teal + case .subscriptionSuite: + return .mint } } diff --git a/packages/apple/Example/OpenIapExample/Screens/SubscriptionFlowScreen.swift b/packages/apple/Example/OpenIapExample/Screens/SubscriptionFlowScreen.swift index 382fe168c..1be5e4ba0 100644 --- a/packages/apple/Example/OpenIapExample/Screens/SubscriptionFlowScreen.swift +++ b/packages/apple/Example/OpenIapExample/Screens/SubscriptionFlowScreen.swift @@ -811,7 +811,7 @@ struct SubscriptionFlowScreen: View { print(" šŸ“‹ Purchase Details:") print(" • Transaction ID: \(purchase.id)") print(" • Product ID: \(purchase.productId)") - print(" • Platform: \(purchase.platform)") + print(" • Store: \(purchase.store)") print(" • Purchase State: \(purchase.purchaseState)") print(" • Is Auto-Renewing: \(purchase.isAutoRenewing)") diff --git a/packages/apple/Example/OpenIapExample/Screens/uis/PurchaseDetailSheet.swift b/packages/apple/Example/OpenIapExample/Screens/uis/PurchaseDetailSheet.swift index 4d0647ec4..7a455aa57 100644 --- a/packages/apple/Example/OpenIapExample/Screens/uis/PurchaseDetailSheet.swift +++ b/packages/apple/Example/OpenIapExample/Screens/uis/PurchaseDetailSheet.swift @@ -16,7 +16,7 @@ struct PurchaseDetailSheet: View { var items: [DetailItem] = [ DetailItem(label: "Purchase ID", value: purchase.id), DetailItem(label: "Product ID", value: purchase.productId), - DetailItem(label: "Platform", value: purchase.platform.rawValue.uppercased()), + DetailItem(label: "Store", value: purchase.store.rawValue.uppercased()), DetailItem(label: "Purchase State", value: purchase.purchaseState.rawValue.capitalized), DetailItem(label: "Quantity", value: String(purchase.quantity)), DetailItem(label: "Auto Renewing", value: boolLabel(purchase.isAutoRenewing)) diff --git a/packages/docs/CONVENTION.md b/packages/docs/CONVENTION.md index b95b22ab6..0ece4d7a8 100644 --- a/packages/docs/CONVENTION.md +++ b/packages/docs/CONVENTION.md @@ -1,5 +1,15 @@ # Conventions +## Documentation acceptance + +Apply the [human and AI acceptance criteria](../../knowledge/internal/05-docs-patterns.md#human-and-ai-acceptance) +before finishing guide, example, SDK discovery, or AI brief changes. +`src/components/SEO.tsx` declarations own canonical page addresses. The build +prerenders those React pages and generates `dist/sitemap.xml`; do not maintain +a separate sitemap or a second copy of a page for crawlers. A `