Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
108 commits
Select commit Hold shift + click to select a range
368e33d
chore: keep docs/ out of version control
yauch-dev Aug 1, 2026
ee7cc69
fix: return null instead of throwing from GetIntegerIfPresent/GetFloa…
yauch-dev Aug 1, 2026
8607e3f
fix: serialize bool and DateTime values in attribution dictionaries
yauch-dev Aug 1, 2026
7c9e5db
fix: make AdaptyCustomerIdentity.IsEmpty actually detect an empty ide…
yauch-dev Aug 1, 2026
4eaa8d5
fix: apply the server cluster selected through the configuration builder
yauch-dev Aug 1, 2026
b9c07a0
test: add golden snapshot tests for the JSON layer
yauch-dev Aug 1, 2026
9c8927f
ci: run the JSON layer tests on every push
yauch-dev Aug 1, 2026
13227d6
chore: fork the SDK C# layer into com.adapty.unity-sdk.next
yauch-dev Aug 1, 2026
1466ca5
feat: add the Newtonsoft serialization infrastructure to the next pac…
yauch-dev Aug 1, 2026
1785a68
test: cover the serialization infrastructure and record the IL2CPP probe
yauch-dev Aug 1, 2026
204b627
fix: initialize the default placement fetch policy
yauch-dev Aug 2, 2026
b1d2ce4
feat: annotate the next package's models for Newtonsoft
yauch-dev Aug 2, 2026
d93dc40
feat: move the next package's transport and events to Newtonsoft
yauch-dev Aug 2, 2026
67fd557
test: compare the public surface of the two packages
yauch-dev Aug 2, 2026
49d3e38
fix: make the next package survive managed stripping on IL2CPP
yauch-dev Aug 5, 2026
cd82656
feat: replace the SDK's JSON layer with Newtonsoft
yauch-dev Aug 5, 2026
6220484
docs: bring the project instructions in line with the Newtonsoft layer
yauch-dev Aug 5, 2026
8219e78
feat: install the SDK's package dependencies from the Editor
yauch-dev Aug 8, 2026
7cf222e
chore: declare the minimum supported Unity version
yauch-dev Aug 8, 2026
d94e91c
docs: add the 3.17 to 4.0 migration guide
yauch-dev Aug 8, 2026
bbca3de
docs: record the dependency gate in the changelog and instructions
yauch-dev Aug 8, 2026
ca76edf
fix: restore AdaptyErrorCode.NoPurchasesToRestore
yauch-dev Aug 8, 2026
8c299ee
fix: name the error codes the native SDKs already send
yauch-dev Aug 8, 2026
7cb9c9f
docs: correct two stale notes found during release prep
yauch-dev Aug 8, 2026
9b625b5
docs: say which Unity versions v4.0 was verified on
yauch-dev Aug 8, 2026
32d85af
refactor: remove members nothing outside the SDK could use
yauch-dev Aug 8, 2026
ce1ab12
chore: mark the whole legacy onboarding API obsolete
yauch-dev Aug 8, 2026
f70ad89
docs: record the cleanup in the changelog and migration guide
yauch-dev Aug 8, 2026
48d7dea
docs: bring the repository docs up to what 4.0 actually is
yauch-dev Aug 8, 2026
cefc019
feat: add a contract-conformance check
yauch-dev Aug 9, 2026
622cb30
docs: say which compiler warnings Unity actually shows
yauch-dev Aug 9, 2026
a61fcc2
fix: two defects that fire today, both older than the migration
yauch-dev Aug 9, 2026
4772a1e
fix: enforce the offer keys the contract requires
yauch-dev Aug 9, 2026
4b61b55
docs: record the four contract defects in the changelog
yauch-dev Aug 9, 2026
d81f4ba
fix: reject enum values the contract does not list
yauch-dev Aug 9, 2026
d571fb4
refactor: take the loose JSON converter out of the shared settings
yauch-dev Aug 9, 2026
9f4a788
fix: hand dates back as local time again, on the paths that ship
yauch-dev Aug 9, 2026
11d7b06
refactor: flatten the installation status into one type
yauch-dev Aug 9, 2026
d23cac1
refactor: take the SDK's own conventions out of the contract resolver
yauch-dev Aug 9, 2026
9c81ed5
refactor: gather the deprecated onboarding API under Runtime/Obsolete
yauch-dev Aug 9, 2026
03c803b
refactor: one interface per file, and partial only where it means som…
yauch-dev Aug 10, 2026
b01771b
refactor: seal the concrete models
yauch-dev Aug 10, 2026
1fc37f5
refactor: hand out read-only collections, and copy the ones handed in
yauch-dev Aug 10, 2026
3e0b7d0
docs: say what the legacy onboarding API is and is not owed
yauch-dev Aug 10, 2026
5a7ec70
refactor: one policy for calling back into the app
yauch-dev Aug 10, 2026
0f56f88
docs: record why the convenience overloads stay overloads
yauch-dev Aug 10, 2026
c519902
docs: narrow three claims the audit stated too broadly
yauch-dev Aug 10, 2026
18e05c0
style: drop the file header blocks and stray byte order marks
yauch-dev Aug 10, 2026
90f8639
style: usings outside the namespace, `is not` patterns, and a named p…
yauch-dev Aug 10, 2026
c746731
docs: cite the convention the sweep actually kept
yauch-dev Aug 10, 2026
3110fe5
refactor: take the redundant platform check out of a constructor
yauch-dev Aug 10, 2026
fb27f95
fix: clear the listeners a Play Mode run registered
yauch-dev Aug 10, 2026
0da89be
fix: clear the Editor install state too, and check when a reset runs
yauch-dev Aug 10, 2026
63b3fa6
docs: say what each error code means and which platform produces it
yauch-dev Aug 10, 2026
3f2bc24
docs: document the public surface, and gate it on summaries
yauch-dev Aug 11, 2026
66d6af3
test: make the documentation gate fail on a missing overload or a bad…
yauch-dev Aug 11, 2026
e5432fd
test: promote the rest of the XML documentation diagnostics
yauch-dev Aug 11, 2026
d6ea727
fix: make the Editor reset a callback Unity actually calls
yauch-dev Aug 11, 2026
988214f
test: define UNITY_EDITOR for the editor leg of the matrix
yauch-dev Aug 12, 2026
2177eff
fix: stop reporting success from the Editor for three iOS-only methods
yauch-dev Aug 12, 2026
195df0f
refactor: take the Kids Mode trait edit out of the post-processor
yauch-dev Aug 12, 2026
2f7187c
test: check that Unity calls the Play Mode reset
yauch-dev Aug 12, 2026
2eeff7c
ci: run the JSON layer tests when the Editor sources change
yauch-dev Aug 12, 2026
be2b6c2
docs: name Xcode 26 as a requirement of the whole SDK
yauch-dev Aug 12, 2026
9b72a6b
chore: add the StoreKit configuration the iOS acceptance used
yauch-dev Aug 12, 2026
bf3be2a
docs: count the platform conditionals again, and say what the Editor …
yauch-dev Aug 12, 2026
d5e97a4
docs: say what SetFallback actually sends in the Editor
yauch-dev Aug 12, 2026
7f0f549
docs: scope the no-Unknown rule to the enums it is about
yauch-dev Aug 12, 2026
47508a3
docs: record how this repository releases
yauch-dev Aug 12, 2026
92aab75
docs: move the release notes to the script, and cut what the code alr…
yauch-dev Aug 12, 2026
7620c63
fix: refuse to publish a release whose notes are a commit message
yauch-dev Aug 12, 2026
d1482d6
fix: remove two error codes no native SDK can raise
yauch-dev Aug 12, 2026
fda5243
refactor: force the Kids Mode flag where the object is built
yauch-dev Aug 12, 2026
d838aad
docs: say nullability where it is checked, not in a comment beside it
yauch-dev Aug 12, 2026
9e1bed5
docs: remove the trailing markers that restate an attribute or a doc
yauch-dev Aug 12, 2026
c8ecce0
docs: say which store an offer id comes from
yauch-dev Aug 12, 2026
5987643
docs: record the removed error codes in the migration guide
yauch-dev Aug 12, 2026
61e92a4
fix: guard the demo purchase callback against a null result
yauch-dev Aug 12, 2026
ea5d623
fix: pin a gradient asset's payload to when it was built
yauch-dev Aug 12, 2026
6f34a80
refactor: make the custom asset discriminators static
yauch-dev Aug 12, 2026
6adfffd
refactor: name the JSON require helper after the SDK
yauch-dev Aug 12, 2026
02bef1f
docs: say what an Adapty product id is and where to get one
yauch-dev Aug 12, 2026
94c4d05
style: one shape for a summary, one order for usings
yauch-dev Aug 12, 2026
ccf8d54
build: declare line endings and binaries in .gitattributes
yauch-dev Aug 12, 2026
d6a4eb3
style: renormalize the files that were stored with CRLF
yauch-dev Aug 12, 2026
6cea107
build: correct two .gitattributes patterns, and credit the template
yauch-dev Aug 12, 2026
bdabe2d
refactor: name the Kids Mode trait edit for the platform it edits
yauch-dev Aug 12, 2026
716f1b6
docs: finish the trait rename in the workflow comments
yauch-dev Aug 12, 2026
34aa2b4
refactor: give every request one completion policy
yauch-dev Aug 13, 2026
550260f
refactor: prefix the three runtime types that had no prefix
yauch-dev Aug 13, 2026
b978645
docs: finish the rename in the layout list, and scope the callback claim
yauch-dev Aug 13, 2026
769d695
refactor: move AdaptyRequest into its own file
yauch-dev Aug 13, 2026
9bdaea6
docs: correct the iOS reference skill against the tag it describes
yauch-dev Aug 13, 2026
e4307a0
fix: register the callback transport before the first scene
yauch-dev Aug 13, 2026
7c9fce7
fix: close the package audit findings, and the docs they turned up
yauch-dev Aug 14, 2026
e7bdf5c
test: give Kids Mode a leg of the matrix, and snapshots to fail against
yauch-dev Aug 14, 2026
9796140
docs: move the guidance to AGENTS.md, and check what it claims
yauch-dev Aug 14, 2026
f7333c1
docs: narrow the %3B explanation to what MSBuild actually needs
yauch-dev Aug 15, 2026
260b8be
fix: check the version of the dependency manager, not just its presence
yauch-dev Aug 15, 2026
c802a3a
docs: check the migration guide against v3, and cut it to the actions
yauch-dev Aug 15, 2026
1cd4ebb
docs: give the README what a new integrator actually needs
yauch-dev Aug 15, 2026
4cfb648
docs: name the open iOS limitation in the README itself
yauch-dev Aug 15, 2026
0df77a9
docs: name what a 3.x upgrade has to delete, and correct the installe…
yauch-dev Aug 15, 2026
fb6ad5d
test: let the snapshots see the date format the SDK actually writes
yauch-dev Aug 15, 2026
143db48
fix: read a date carrying no zone the way the other branch already does
yauch-dev Aug 15, 2026
76b210a
fix: register the Android handler before the wrapper claims to have one
yauch-dev Aug 15, 2026
46e8dec
docs: pin the two URLs that would otherwise point at the previous major
yauch-dev Aug 15, 2026
622cfeb
fix: answer a permission request from whatever thread the app is on
yauch-dev Aug 15, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
158 changes: 158 additions & 0 deletions .claude/skills/contract-conformance/SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,158 @@
---
name: contract-conformance
description: Use when checking that the Unity SDK's C# models, enums and converters still match cross_platform.yaml — before a release, after bumping the native SDKs or the contract version, or when a payload behaves differently than the contract says it should
---

# Contract conformance

`cross_platform.yaml` is the canonical description of every request, response and event that
crosses the bridge. The C# side restates it a second time, in attributes and hand-written
converters. Nothing in the build compares the two, so they drift apart silently.

This skill is that comparison. `extract.py`, next to this file, does the mechanical half; the rest
is reading and judgement, and that is where the findings actually are.

Work through the steps in order. Do not skip step 5 — the mechanical half agrees with itself and
still misses most of what matters.

## What this cannot tell you

Put these in the report rather than leaving them implied:

- **The contract may be wrong, or ahead of the implementations.** It is maintained in AdaptySDK-iOS
and describes all platforms. A key can be declared there and implemented nowhere.
- **Error codes are not in the contract at all.** `AdaptyErrorCode` can only be checked against the
native Swift and Kotlin sources. Different job, do not attempt it here.
- **Value semantics** — units, ranges, what a value means. Shape and naming only.

A `format` declared in the contract is the exception: it is part of the shape. If a key says
`format: "YYYY-MM-dd"`, whether the C# side actually produces that is in scope, and reading the
code may not settle it — hand-built strings are where this goes wrong. Serialize a value and look
at the bytes. Fixtures will not save you: a snapshot whose only date is `1815-12-10` passes
whether or not the writer pads single digits.

## Step 1. Run the mechanical pass

```bash
python3 -m venv /tmp/cc-venv && /tmp/cc-venv/bin/pip install --quiet pyyaml
```

```bash
/tmp/cc-venv/bin/python .claude/skills/contract-conformance/extract.py . --json /tmp/cc.json
```

It refuses to run if its own walk found implausibly little, so a clean exit means the input was
read. Read its first two lines and sanity-check the counts before trusting anything after them.

## Step 2. Check the copy of the contract

The repo's copy has to match the canonical one in AdaptySDK-iOS. If `.ios-sdk/` is checked out
(see the `ios-sdk-reference` skill) this is one command, not an act of faith:

```bash
diff .ios-sdk/Sources.AdaptyPlugin/cross_platform.yaml cross_platform.yaml && echo IDENTICAL
```

If `.ios-sdk/` is missing, say in the report that this was not verified. Do not claim it matches.

## Step 3. Triage what the script printed

Every line it prints is a **candidate**, not a finding. Confirm each one by opening the file at the
line it names. Expect a good share to dissolve on contact — that is the script working as intended,
not failing.

- **UNMAPPED** — a contract object with no C# type matched. Either map it in `MAPPING` or add it to
`NO_MODEL` with a reason, then rerun. Never leave one unexplained: a new contract object landing
unmapped is exactly the signal this whole exercise exists for.
- **C# TYPES WITH NO CONTRACT OBJECT** — usually request-shaping helpers and nested holders. For
each, satisfy yourself it is internal plumbing and not an invented wire shape.
- **contract key with no `[DataMember]`** — the script also tells you whether that string appears
anywhere in `Runtime/`. If it does, the key is probably supplied by a converter rather than an
attribute, and there is no defect; go read that line. If it appears nowhere, you likely have one.
- **required mismatch** — a key the contract requires in every `oneOf` branch that C# does not mark
`IsRequired`, or the reverse. Keys required in only some branches are annotated as conditional and
are usually fine.
- **platform** — the contract marks a key iOS/Android Only and C# does not gate it behind `#if`, or
the reverse. **Before calling this a defect, grep `CHANGELOG.md`, the surrounding comments and the
tests.** Some of these are deliberate: request-side parameter objects are intentionally ungated so
one call site compiles for every target.
- **STRING ENUMS** — a member in C# that the contract does not list is a defect. There is no
fallback: an unlisted string fails the read, and the only `Unknown` members left are the two the
contract spells out itself, on `AdaptyPaymentMode` and `AdaptySubscriptionPeriodUnit`. The reverse
is a defect too — a contract value with no member — and so is a member with no
`[EnumMember]`, which would be sent under its C# name.
- **WIRE NAMES** — every `method`/`id` constant in the contract, and whether that literal occurs in
`Runtime/`. An absent one is a request the SDK cannot make or an event it cannot receive.

## Step 4. Cover what the script only lists

The script prints converter `case` labels but does not compare them. Do it by hand: for every
`oneOf` in the contract that is not the generic `error`/`success` envelope, find its discriminator
values and match them against the converter that reads that type, or against the string literals in
the model when the discriminator is chosen by a static factory rather than a `switch`.

Also read the converters in `Runtime/Serialization/` directly, one by one. Types built by a
converter have no `[DataMember]` at all, so nothing in step 3 says anything about them, and each
converter is a hand-written restatement of a contract object. For each key the converter reads, ask
whether the contract requires it and whether the converter enforces that — `AdaptyJsonRequire.*` enforces,
a plain `node["x"]?` does not.

## Step 5. Ask the questions the script cannot

This is where the findings are.

- **The write path is not the read path.** For every value C# can produce, ask: can it be serialized
into a request, and does the contract allow it *there*? Requests often have their own contract
object, distinct from the response object of the same name — an enum listed in a response may have
fewer values allowed in the request that carries it back.
- **Optional keys the C# side cannot express.** A contract key that is optional and absent from C#
breaks nothing, and is still a capability the SDK does not offer.
- **Enforcement, not just presence.** A required key that is read leniently is a conformance gap even
though the property exists.

## Rules that are not optional

Two failure modes have already produced confident, wrong reports. Guard against both explicitly.

1. **Re-read every line you cite.** Before using `file:line` as evidence — in your reasoning or in
the report — open that exact line and confirm it says what you think. A single misread line
reference has produced an entire well-argued finding about a divergence that did not exist, with
correct supporting quotes from native sources hung off a false premise.
2. **A rule inferred from a sample must be checked against the whole family.** If you conclude
"these enums all behave this way" or "this convention holds everywhere", enumerate every member
and check each. The exception is what you were looking for. A run that established a project-wide
rule from five of six enums missed the sixth, which was the only real finding in that area.
3. **When any line in a method draws your attention, read the whole method.** Whatever led you there
— a script candidate, a failing test, a diff — is not the only thing in it. A run that correctly
reported a lenient read of a required key missed a second lenient read of another required key
fifteen lines below it, in the same method, because only the first one had drawn attention.

And: prefer a hedged finding to a confident one. If you cannot tell whether something is deliberate,
say so and say what you looked at — that is a useful report. A wrong classification sends someone
into three repositories after nothing.

## Step 6. Report

One entry per finding, each carrying:

- **evidence on both sides** — `cross_platform.yaml` line, and `file.cs:line`, each re-read per
rule 1;
- **classification**:
- *our defect* — contract and native SDKs agree, the Unity side does not;
- *question for the contract owners* — the contract says something no implementation does, or the
implementations disagree with it. Check the native sources before choosing this over the first;
- *deliberate divergence* — differs on purpose, e.g. a Unity type with no wire equivalent, or
behaviour preserved from v3. Say what pins it: a comment, a test, a changelog entry.
- **whether it can fire today**, or only once a native SDK adds something.

Close with coverage: how many contract objects were compared, how many keys, what was skipped and
why. A report that does not say what it did not look at is not finished.

## Not your call

Do not edit `cross_platform.yaml` — it is a copy of the canonical file, and changing it here only
hides the divergence.

Do not add a member to a model because the contract has one, or remove one because it does not.
Either can be a public API change, and either may be the contract's mistake rather than ours.
Report, classify, stop.
Loading
Loading