refactor(specs): nest client and commerce specifications under specs/openiap - #421
Conversation
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## main #421 +/- ##
=======================================
Coverage 76.00% 76.00%
=======================================
Files 155 155
Lines 16561 16561
Branches 4763 4763
=======================================
Hits 12588 12588
Misses 3973 3973
Flags with carried forward coverage won't be shown. Click here to find out more.
🚀 New features to boost your workflow:
|
Device regression (
|
CodeQL: the five
|
| this PR | line | main |
line |
|---|---|---|---|
| #406 | 318 | #299 | 273 |
| #407 | 320 | #300 | 275 |
| #408 | 328 | #301 | 282 |
| #409 | 333 | #302 | 285 |
| #410 | 341 | #303 | 292 |
Each pair covers the same statements: the read of the refreshed file, then the later path-based reads and writes for the idempotency, invalid-body, and download-failure cases. They are reported as new only because the file moved and was reformatted for the destination directory's Prettier config, which breaks alert fingerprint matching. refs/pull/421/merge lists exactly these five and nothing else, so the PR introduces no new rule and no additional finding.
They are false positives: every path is a mktemp directory created for that one case, and the test is a single sequential process, so nothing can replace the file between the check and the use.
245fac9e moved the success-case read behind one descriptor. That shape is still right — content and mode then describe one inode across the refresher's atomic rename — but it does not change the CodeQL outcome, so e76e2742 corrects the comment that claimed it would.
Unrelated to the alerts, the check summary also warns that /language:swift/component:flutter and /language:swift/component:godot were not run. That is the scope detector working as intended: this PR changes only README.md and AGENTS.md under those two libraries.
…openiap Move the client GraphQL contract from packages/gql to specs/openiap/client (published as @hyodotdev/openiap) and the Commerce Protocol from specs/openiap-kit to specs/openiap/commerce-protocol, so specs/openiap holds exactly the two publishable, never-deployed specifications while packages/kit stays the IAPKit implementation. - Root workspaces become packages/* and specs/openiap/*; the root manifest is renamed to openiap-monorepo and every workspace consumer, CI path filter, release lane, Docker build, audit script, and human/AI document follows the new layout. - The client package ships compiled dist declarations for npm consumers and an openiap-source export condition for workspace TypeScript; mergeHeaders in kit-api.ts returns NonNullable<RequestInit["headers"]> so strict consumer flags compile the tarball. - Release, provenance, and SBOM scripts keep a historical manifest fallback so openiap-commerce-protocol-0.1.0 stays recoverable without stderr noise. - audit-repo-layout rejects legacy roots and service deployment manifests under specs/; its test no longer needs npm dependencies for the Node-only CI job. audit-schema-semver compares against pre-move refs. - sync-sponsors audits specs/openiap/*/README.md (specifications opt in with the block markers) and renders a Prettier-stable block; managed READMEs are regenerated. - Dependency advisories that turned the unconditional audits red are resolved through root overrides (qs, fast-uri), react-native-iap transitive bumps, and time-boxed Expo OSV exceptions. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
- expo-iap: the Vega example build copies `src/` through the same library import rewrite as `app/`, and the rewrite accepts any nesting depth; the Expo Vega build has failed on main since #419 because `src/utils/errorUtils.ts` imports `../../../src/utils/errorMapping` - release tooling: declare the Commerce Protocol manifest pair (canonical and historical path) once in `release-branch-policy.mjs` and reuse it from `assert-release-tag.mjs` and `generate-sbom.mjs`; a missing manifest now reports the canonical path - client spec: give the packed-tarball consumer test an explicit hook budget and quieter npm install; read the refreshed file through one descriptor so the refresher test no longer stats and then reads the same path - workspace runtimes: kit `dev:server`/`build:server` and mcp-server `start`/`start:http` pass `--conditions=openiap-source` so bun resolves the authored client-spec sources like the tsconfigs do, guarded by the parity audit; the client README logo uses the hosted asset so the npm README renders - sponsors: test the Prettier-stable shape of the generated block, and make the parity audit require the sponsor markers in the client spec README - docs: describe the specifications by package name rather than as published, rename the directory-responsibility headings, drop the last `gql` package mentions, and state exactly which `verify` steps the kit pre-commit gate mirrors; compiled agent context regenerated Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
The Vega copy of the example rewrote `src/utils/errorMapping` imports to `expo-iap`, but the Kepler entry does not export `getUserFriendlyErrorMessage`, so Release builds would have thrown on the first error display. Route that import to a local alias package that re-exports the copied library module, and correct the parity-audit comment about unresolved value imports. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
The comment claimed the single-descriptor read avoids a CodeQL check-then-use finding. It does not: js/file-system-race still reports the same five alerts main carries for this test, because the later path-based accesses pair with the openSync instead of the previous statSync. State the real reason — one descriptor keeps the content and mode assertions on one inode across the refresher's atomic rename. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…ired Rebasing onto main's Android IAP opt-out merge refreshed libraries/expo-iap/example/bun.lock, bumping @xmldom/xmldom 0.8.14 to 0.8.15 and plist's copy 0.9.11 to 0.9.12. GHSA-6gmq-8vp8-gcm6 no longer matches, so audit:dependencies failed the branch on the unused exception. Removing it makes the file identical to main again; libraries/expo-iap/osv-scanner.toml went away with main's own deletion during the same rebase. Also repad the review-pr.md check table, whose first column this branch widened without realigning the other rows. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
e76e274 to
16fa73f
Compare
Rebased onto
|
| File | Change |
|---|---|
specs/openiap/client/src/generated/Types.swift |
byte-identical rename from packages/gql/src/generated/Types.swift |
packages/apple/Sources/Models/OpenIapError.swift |
one doc-comment path |
libraries/react-native-iap/src/kit-api.ts |
RequestInit["headers"] → NonNullable<RequestInit["headers"]> (type-only, erased at emit) |
libraries/expo-iap/src/kit-api.ts |
same type-only change |
libraries/react-native-iap/src/index.ts |
one comment |
The only behavioural change anywhere is libraries/expo-iap/example/scripts/build-vega-example.mjs, and both Vega rows already passed on hardware.
This is evidence for the gate decision, not a waiver: the three BLOCKED iOS rows still need either a run or an explicit written waiver from the repository owner before merge.
…rface CodeQL reported five js/file-system-race alerts against the standalone refresher test, the same five main carries, because the run interleaved a stat of one refreshed target with the reads and writes the later cases perform on it. Collect the replaced paths and assert their modes in a second pass, so no check sits between those uses. The assertion keeps its teeth: reverting a refresher's chmod 0644 to 0600 fails the test with expected 420, actual 384. The ecosystem diagram described openiap-google as Play Billing only and named neither Amazon target, and the independent server contract stood alone with no implementation next to it. - openiap-google reads "Play, Amazon, Horizon" and its Android mark is labelled for Fire OS, which is an Android target built from the same package through the amazon product flavor - expo-iap and react-native-iap list Vega OS ahead of the Onside module, both pointing at the existing Amazon store guide; Onside stays Expo-only - the contract band lists implementations under the role each one declares, so a role renders its own group, starting with IAPKit as a conforming provider The Swift Package badge had a hard-coded 2.* tag filter and rendered "no matching tags found" once the package reached 3.x; the filter now derives its major from openiap-versions.json. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…layers specs/openiap/ repeated the repository's own name one level down, and every sibling root — packages/, libraries/, plugins/, knowledge/ — is flat. The two specifications now sit directly under specs/, which is also the vocabulary the repository already uses for them. - specs/openiap/client -> specs/client, specs/openiap/commerce-protocol -> specs/commerce-protocol, and the umbrella README becomes specs/README.md - audit-repo-layout enforces the flat layout from a SPECIFICATION_ROOTS list, rejects the old umbrella, and names an unknown root instead of demanding it move under openiap; sync-sponsors scans specs/ for the same reason - commerce-protocol schema layers renumber 10..70 to 01..07, keeping assembly order; the rebuilt artifacts are byte-identical Flattening shortened every path inside specs/ by one segment, which broke three things that had encoded the old depth: - nine scripts resolved the repository root with ../../../.. - audit-schema-semver could no longer find a base ref, so it failed before comparing anything - the agent indexer matched specs/openiap literally when naming a file's package Also commit the Prettier formatting that the previous commit left in the working tree, which is what Test Docs was failing on. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The Commerce Protocol release test pinned the bump step's working directory to specs/openiap/commerce-protocol, so flattening the specifications failed the release-state audit in CI. The legacy specs/openiap-kit SPEC_PATH assertion stays: that fallback still recovers tags cut before the move. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Device regression: the three iOS rows now passRe-run against The rows were previously BLOCKED because The two product types resolve to different states through the same binding — The local IAPKit server ran the compiled binary against the real Martie Convex deployment on One finding worth recording: the first verification attempt failed with |
An external review of the previous restructuring pointed out that deferring the mode assertion to the end of the run let a later successful replace mask a wrong mode on the first one: the idempotent run rewrites the same path at 0644, so a refresher that got the first replace wrong still passed. Split the case in two. The first test seeds 0600, runs each refresher once, and records that replacement's own mode for the post-loop assertion, so 0644 now proves the refresher set it rather than inheriting it. The idempotent and preserve-on-failure cases move to their own test, which asserts no modes at all. The check still sits outside the loop, so the js/file-system-race pattern stays gone. Mutation proof for the masking case: making the refresher chmod 0600 on the first replace and 0644 afterwards fails the new test with expected 420, actual 384. Under the previous structure that mutation passed. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Device regression, complete matrixRe-run against The generated type files for all six libraries are byte-identical to Lifecycle states resolved per store and product type through the same binding: Every verification carried |
Summary
packages/gqltospecs/openiap/clientand rename its package to@hyodotdev/openiap(root manifest becomesopeniap-monorepo).specs/openiap-kittospecs/openiap/commerce-protocol;specs/openiap/*now holds exactly the two publishable specifications whilepackages/kitstays the deployable implementation.openiap-commerce-protocol-0.1.0) recoverable.Changes
Specifications (
specs/openiap/*)specs/openiap/client: pure rename ofpackages/gql(137 R100 renames; generated outputs byte-identical). The package becomes publishable as@hyodotdev/openiap:publishConfig,files, compileddist/viaprepack, publishedtypespointing at the compiled declarations (plustypesVersionsfor legacy resolvers), and anopeniap-sourceexport condition so workspace consumers keep resolving the authored sources. A consumer smoke test packs the tarball and compiles it under strict TypeScript flags (exactOptionalPropertyTypes,erasableSyntaxOnly).specs/openiap/commerce-protocol: pure rename ofspecs/openiap-kit;repository.directoryupdated. The README stays free of funding content, as its decentralization test requires.specs/openiap/README.mddocuments the boundary: specifications may publish packages but are never deployed services. The root README lists both contracts in a dedicated Specifications section instead of under Packages.Workspace and consumers
workspaces=packages/*+specs/openiap/*;bun.lockworkspace names follow.packages/apple,packages/google,packages/mcp-server,packages/conformance,packages/kittests, and framework docs point at the new paths and package name;packages/mcp-serverandpackages/kittsconfigs opt into theopeniap-sourcecondition.kit-api.tsmergeHeadersreturnsNonNullable<RequestInit["headers"]>so the published tarball compiles for consumers withexactOptionalPropertyTypes(synced to react-native-iap and expo-iap through the manifest).overridesblock:qs6.16.0 andfast-uri3.1.7 overrides in the root Bun lock, transitive bumps (@humanfs/node,qs,@xmldom/xmldom,fast-uri) in the react-native-iap Yarn lock, and time-boxed OSV exceptions for@xmldom/xmldomin the Expo lockfiles (build-time tooling only).CI, Docker, release
ci.yml, framework CI workflows,deploy-kit.yml, and the Apple/Google/MAUI release lanes use the new paths;deploy-kit.ymlPR paths include the clientkit-apicontract and the Docker build stays rooted at the monorepo.dependabot-bun-lockfile.ymlcoversspecs/**/package.json.packages/kit/Dockerfilecopies both nested specification packages.release-commerce-protocol.yml,assert-release-tag.mjs,release-branch-policy.mjs, andgenerate-sbom.mjskeep a historical manifest fallback (the canonical + historical manifest pair is declared once inrelease-branch-policy.mjsand reused by the other two scripts) socurrentretries, provenance checks, and SBOM recovery still work for tags that predate the move; expected misses no longer printfatal:noise, and a tag with no manifest reports every probe.audit-repo-layout.mjsrejects legacy roots, an umbrellaspecs/openiap/package.json, and service deployment manifests (Dockerfile, compose,fly*.toml,vercel.json,convex.json) underspecs/; its test stays dependency-free for the Node-only CI job.audit-ci-path-filters.mjsguards the kit consumer filters and the monorepo Docker context.Tooling
scripts/sync-sponsors.mjsalso auditsspecs/openiap/*/README.md(specifications receive the block only after opting in with the markers; markerless ones are still checked for unmanaged sponsor sections and hardcoded funding links) and renders a Prettier-stable block so a formatter pass can no longer drift managed READMEs; all managed READMEs were regenerated.audit-schema-semver.mjscompares against pre-move refs (fallback to the prior canonical directory) and drops a dead export.libraries/expo-iap/example/scripts/build-vega-example.mjs: the Vega build now copies the examplesrc/through the same library-import rewrite asapp/, the rewrite accepts any nesting depth, and the example's error-mapping helper (not part of the public entry) resolves through a local alias of the copied library module.main's Expo Vega build has failed since feat: add OpenIAP Commerce Protocol #419 becausesrc/utils/errorUtils.tsimports../../../src/utils/errorMapping; the device regression for this PR surfaced it.Documentation and AI context
AGENTS.md,CONTRIBUTING.md,SECURITY.md,README.md,knowledge/internal/*, Claude/Codex/Gemini adapters, libraryAGENTS.mdfiles, kit docs (deploy triggers and pre-commit gate scope now match the workflow and hook), and the docs site describe the nested specification layout; the compiled agent context andllms*.txtare regenerated from those sources. The specifications are described by package name (nothing is on npm yet), the kit pre-commit docs name exactly theverifysteps the hook mirrors, and the lastgqlpackage mentions are gone.Test plan
bun install --frozen-lockfile,git diff --checkbun run generateis a no-op, 182 vitest tests (incl. packed-tarball consumer under strict flags), schema semver audit clean vsorigin/main--checkand Prettieryarn install --immutablebun audit+ OSV on all 8 locks)openiap-commerce-protocol-0.1.0) verified for release retry, provenance, and SBOM scripts in a scratch clonee2e-tests) for the implicated rows, including RN/Expo Vega on a Fire TV Stick and the Expo Onside iOS build, is recorded in a PR comment; iOS device rows are blocked on a USB connectionPreview recording: not applicable — this change has no visual or interactive surface; the proof is the test and audit matrix above.
🤖 Generated with Claude Code