@mmerterden/multi-agent-pipeline 20.0.0 → 20.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (90) hide show
  1. package/CHANGELOG.md +55 -0
  2. package/README.md +5 -5
  3. package/README.tr.md +5 -5
  4. package/SECURITY.md +3 -3
  5. package/docs/adr/0011-dormant-ci.md +10 -1
  6. package/docs/architecture.md +2 -2
  7. package/docs/ecosystem.md +5 -5
  8. package/docs/facts.json +8 -7
  9. package/install/_codex-agents.mjs +1 -1
  10. package/manifest.json +92 -64
  11. package/package.json +1 -1
  12. package/pipeline/agents/code-reviewer.md +2 -2
  13. package/pipeline/agents/dev-critic.md +5 -5
  14. package/pipeline/agents/security-auditor.md +80 -72
  15. package/pipeline/commands/figma-to-swiftui.md +1 -1
  16. package/pipeline/commands/multi-agent/SKILL.md +1 -1
  17. package/pipeline/commands/multi-agent/channels/SKILL.md +1 -1
  18. package/pipeline/commands/multi-agent/diff-explain/SKILL.md +1 -1
  19. package/pipeline/commands/multi-agent/help/SKILL.md +2 -0
  20. package/pipeline/commands/multi-agent/scan/SKILL.md +2 -2
  21. package/pipeline/commands/multi-agent/security-review/SKILL.md +52 -0
  22. package/pipeline/commands/multi-agent/sync/SKILL.md +3 -3
  23. package/pipeline/multi-agent-refs/component-dispatch.md +5 -5
  24. package/pipeline/multi-agent-refs/cross-cli-contract.md +6 -6
  25. package/pipeline/multi-agent-refs/features/security-audit.md +55 -0
  26. package/pipeline/multi-agent-refs/phases/modes.md +1 -1
  27. package/pipeline/multi-agent-refs/phases/phase-3-review.md +9 -15
  28. package/pipeline/multi-agent-refs/phases/phase-5-report.md +1 -1
  29. package/pipeline/multi-agent-refs/threat-model.md +39 -0
  30. package/pipeline/schemas/agent-state.schema.json +23 -0
  31. package/pipeline/schemas/phases.json +1 -2
  32. package/pipeline/schemas/prefs.schema.json +0 -4
  33. package/pipeline/schemas/reviewer-output.schema.json +99 -2
  34. package/pipeline/schemas/security-finding.schema.json +144 -0
  35. package/pipeline/scripts/_stack-routing.mjs +1 -0
  36. package/pipeline/scripts/gc-abandoned.sh +16 -9
  37. package/pipeline/scripts/render-work-summary.sh +7 -4
  38. package/pipeline/skills/.skill-manifest.json +47 -23
  39. package/pipeline/skills/.skills-index.json +75 -9
  40. package/pipeline/skills/shared/README.md +13 -7
  41. package/pipeline/skills/shared/core/multi-agent/SKILL.md +3 -4
  42. package/pipeline/skills/shared/core/multi-agent-scan/SKILL.md +2 -2
  43. package/pipeline/skills/shared/core/multi-agent-security-review/SKILL.md +29 -0
  44. package/pipeline/skills/shared/core/multi-agent-sync/SKILL.md +3 -3
  45. package/pipeline/skills/shared/external/android-architecture/SKILL.md +71 -0
  46. package/pipeline/skills/shared/external/android-architecture/references/patterns.md +142 -0
  47. package/pipeline/skills/shared/external/android-build-quality-gates/SKILL.md +314 -0
  48. package/pipeline/skills/shared/external/android-build-quality-gates/references/patterns.md +432 -0
  49. package/pipeline/skills/shared/external/android-datastore/SKILL.md +236 -0
  50. package/pipeline/skills/shared/external/android-datastore/references/patterns.md +297 -0
  51. package/pipeline/skills/shared/external/android-design-tokens-codegen/SKILL.md +249 -0
  52. package/pipeline/skills/shared/external/android-design-tokens-codegen/references/patterns.md +270 -0
  53. package/pipeline/skills/shared/external/android-jetpack-compose-expert/SKILL.md +62 -0
  54. package/pipeline/skills/shared/external/android-mvi-viewmodel/SKILL.md +255 -0
  55. package/pipeline/skills/shared/external/android-mvi-viewmodel/references/patterns.md +257 -0
  56. package/pipeline/skills/shared/external/android-performance/SKILL.md +86 -602
  57. package/pipeline/skills/shared/external/android-performance/references/patterns.md +659 -0
  58. package/pipeline/skills/shared/external/android-security/SKILL.md +117 -430
  59. package/pipeline/skills/shared/external/android-security/references/patterns.md +690 -0
  60. package/pipeline/skills/shared/external/{android_ui_verification → android-ui-verification}/SKILL.md +1 -1
  61. package/pipeline/skills/shared/external/api-security-best-practices/SKILL.md +35 -733
  62. package/pipeline/skills/shared/external/api-security-best-practices/references/auth.md +299 -0
  63. package/pipeline/skills/shared/external/api-security-best-practices/references/input-validation.md +255 -0
  64. package/pipeline/skills/shared/external/api-security-best-practices/references/rate-limiting.md +167 -0
  65. package/pipeline/skills/shared/external/app-intents/SKILL.md +39 -174
  66. package/pipeline/skills/shared/external/app-intents/references/appintents-advanced.md +178 -0
  67. package/pipeline/skills/shared/external/compose-components/SKILL.md +48 -0
  68. package/pipeline/skills/shared/external/compose-components/references/patterns.md +200 -0
  69. package/pipeline/skills/shared/external/compose-navigation/SKILL.md +66 -3
  70. package/pipeline/skills/shared/external/compose-navigation/references/patterns.md +191 -0
  71. package/pipeline/skills/shared/external/compose-testing/SKILL.md +107 -397
  72. package/pipeline/skills/shared/external/compose-testing/references/patterns.md +631 -0
  73. package/pipeline/skills/shared/external/gradle-kotlin-dsl/SKILL.md +121 -449
  74. package/pipeline/skills/shared/external/gradle-kotlin-dsl/references/patterns.md +715 -0
  75. package/pipeline/skills/shared/external/kotlin-coroutines-expert/SKILL.md +143 -0
  76. package/pipeline/skills/shared/external/mapkit-location/SKILL.md +27 -102
  77. package/pipeline/skills/shared/external/mapkit-location/references/mapkit-patterns.md +42 -0
  78. package/pipeline/skills/shared/external/retrofit-networking/SKILL.md +94 -383
  79. package/pipeline/skills/shared/external/retrofit-networking/references/patterns.md +640 -0
  80. package/pipeline/skills/shared/external/room-database/SKILL.md +101 -440
  81. package/pipeline/skills/shared/external/room-database/references/patterns.md +614 -0
  82. package/pipeline/skills/shared/external/security-review/SKILL.md +64 -0
  83. package/pipeline/skills/shared/external/security-review/references/owasp-mobile-top10-2024.md +53 -0
  84. package/pipeline/skills/shared/external/security-review/references/owasp-web-api-top10-2021.md +56 -0
  85. package/pipeline/skills/shared/external/storekit/SKILL.md +69 -343
  86. package/pipeline/skills/shared/external/storekit/references/core-patterns.md +371 -0
  87. package/pipeline/skills/shared/external/widgetkit/SKILL.md +25 -101
  88. package/pipeline/skills/shared/external/widgetkit/references/widgetkit-advanced.md +107 -0
  89. package/pipeline/skills/skills-index.md +8 -2
  90. package/pipeline/commands/security-review.md +0 -6
@@ -0,0 +1,53 @@
1
+ # OWASP Mobile Top 10 2024 - review checklist with CWE mapping
2
+
3
+ The category id (`M1:2024` .. `M10:2024`) goes in a finding's `security.owaspCategory`; the CWE that names the actual weakness goes in `security.cwe`. For the fuller mobile catalog that ties these to MASVS / MASTG and the store-compliance rules, the swift-security compliance mapping is the deeper reference; this is the review checklist.
4
+
5
+ ## M1:2024 - Improper Credential Usage
6
+
7
+ Check: hardcoded API keys / passwords / tokens, credentials in source or resource files, credentials in logs, a secret shipped in the binary.
8
+ CWEs: CWE-798 (hardcoded credentials), CWE-259, CWE-522.
9
+
10
+ ## M2:2024 - Inadequate Supply Chain Security
11
+
12
+ Check: a dependency at a version with a known advisory, an unpinned SDK, a build step pulling an unverified artifact. This is the `security_dep_inventory` -> CVE path (`A06:2021`'s mobile analog).
13
+ CWEs: CWE-1104, plus the advisory's CWE.
14
+
15
+ ## M3:2024 - Insecure Authentication/Authorization
16
+
17
+ Check: auth decided client-side, a token accepted without verification, missing authorization on a sensitive action, biometric gate that only hides UI without protecting data.
18
+ CWEs: CWE-287, CWE-306 (missing authentication), CWE-862, CWE-863.
19
+
20
+ ## M4:2024 - Insufficient Input/Output Validation
21
+
22
+ Check: untrusted input into a SQL/content-provider query, a WebView `evaluateJavascript` / `postMessage` handler trusting page content, deep-link parameters used without validation, format-string or path built from input.
23
+ CWEs: CWE-20, CWE-79 (WebView XSS), CWE-89, CWE-22.
24
+
25
+ ## M5:2024 - Insecure Communication
26
+
27
+ Check: HTTP instead of HTTPS, disabled ATS / cleartext-traffic permitted, `TrustManager` that accepts all certs, missing certificate pinning on a sensitive endpoint, ignored TLS errors.
28
+ CWEs: CWE-319 (cleartext), CWE-295 (improper certificate validation).
29
+
30
+ ## M6:2024 - Inadequate Privacy Controls
31
+
32
+ Check: PII collected without need, location/contacts/identifiers sent off-device without disclosure, tracking before consent, PII in logs or analytics events.
33
+ CWEs: CWE-359, CWE-200, CWE-532.
34
+
35
+ ## M7:2024 - Insufficient Binary Protections
36
+
37
+ Check: no tamper/integrity check where the threat model needs one, debug symbols or verbose logging left in a release build, an easily-reversible secret embedded in the binary. Judge against the threat model - most apps do not need anti-reversing, and a `blocking` here needs a named attacker.
38
+ CWEs: CWE-656, CWE-489 (debug code left in).
39
+
40
+ ## M8:2024 - Security Misconfiguration
41
+
42
+ Check: an exported Android component with no permission, `android:debuggable=true` or `allowBackup=true` on sensitive data, an overly-broad entitlement, a permissive `network_security_config`, default or weak settings.
43
+ CWEs: CWE-16, CWE-276 (incorrect default permissions), CWE-926 (improper export).
44
+
45
+ ## M9:2024 - Insecure Data Storage
46
+
47
+ Check: sensitive data in `UserDefaults` / `SharedPreferences` / plist / plain files instead of the Keychain / Keystore, a database without encryption, a cache holding secrets, pasteboard leakage.
48
+ CWEs: CWE-312 (cleartext storage), CWE-922 (insecure storage of sensitive info).
49
+
50
+ ## M10:2024 - Insufficient Cryptography
51
+
52
+ Check: weak or deprecated algorithm (MD5/SHA1 for integrity, DES/ECB), a hardcoded key/IV, a home-rolled cipher, a predictable random source for a security purpose.
53
+ CWEs: CWE-327 (broken/risky algorithm), CWE-326, CWE-330 (insufficient randomness), CWE-338.
@@ -0,0 +1,56 @@
1
+ # OWASP Top 10 2021 (Web / API) - review checklist with CWE mapping
2
+
3
+ The category id (`A01:2021` .. `A10:2021`) goes in a finding's `security.owaspCategory`; the CWE most associated with the specific weakness goes in `security.cwe`. One weakness per finding. The CWEs listed per category are the common ones, not the whole set - pick the one that names the actual defect.
4
+
5
+ ## A01:2021 - Broken Access Control
6
+
7
+ Check: missing authorization on a route or action, IDOR (object id from the request trusted without an ownership check), path traversal, forced browsing, CORS misconfiguration allowing credentialed cross-origin reads, privilege escalation through a mass-assignable field.
8
+ CWEs: CWE-284, CWE-285, CWE-639 (IDOR), CWE-862 (missing authorization), CWE-863 (incorrect authorization), CWE-22 (path traversal), CWE-352 (CSRF).
9
+ Evidence to cite: the handler that reads an id from input and queries without a `where owner = current_user` predicate; a route with no auth middleware.
10
+
11
+ ## A02:2021 - Cryptographic Failures
12
+
13
+ Check: secrets or PII sent or stored in clear, weak or deprecated algorithms (MD5, SHA1 for passwords, DES, ECB), hardcoded keys, missing TLS, disabled certificate validation, predictable IVs/nonces, passwords hashed without a slow KDF (bcrypt/scrypt/argon2).
14
+ CWEs: CWE-311 (missing encryption), CWE-319 (cleartext transmission), CWE-327 (broken/risky algorithm), CWE-326 (inadequate strength), CWE-798 (hardcoded credentials), CWE-916 (weak password hash).
15
+
16
+ ## A03:2021 - Injection
17
+
18
+ Check: SQL/NoSQL/ORM query built by string concatenation of untrusted input, OS command built from input, LDAP/XPath injection, unsanitized input reflected into HTML (XSS), template injection, header injection.
19
+ CWEs: CWE-89 (SQL), CWE-78 (OS command), CWE-79 (XSS), CWE-90 (LDAP), CWE-94 (code injection), CWE-943 (NoSQL/query).
20
+ Evidence: the untrusted source and the sink on the same path, with no parameterization or encoding between.
21
+
22
+ ## A04:2021 - Insecure Design
23
+
24
+ Check: a missing control the design needed - no rate limit on a credential endpoint, no anti-automation on a costly action, trust placed in a client-supplied value that decides server behaviour, a workflow that can be replayed.
25
+ CWEs: CWE-73, CWE-183, CWE-209 (info leak by design), CWE-256, CWE-501 (trust boundary violation), CWE-522.
26
+
27
+ ## A05:2021 - Security Misconfiguration
28
+
29
+ Check: debug or verbose errors in production, default credentials, an unnecessary feature or port enabled, permissive CORS, missing security headers (CSP, HSTS, X-Content-Type-Options), directory listing, an overly-permissive cloud bucket or IAM policy in config.
30
+ CWEs: CWE-16, CWE-611 (XXE), CWE-732 (incorrect permissions), CWE-1032, CWE-756.
31
+
32
+ ## A06:2021 - Vulnerable and Outdated Components
33
+
34
+ Check: a dependency at a version with a known advisory. This is the `security_dep_inventory` -> CVE-lookup path. Carry the `cve` and the advisory's CWE; put the finding on the manifest at line 0.
35
+ CWEs: CWE-1104, plus the advisory's own CWE.
36
+
37
+ ## A07:2021 - Identification and Authentication Failures
38
+
39
+ Check: credential stuffing possible (no throttle/lockout), weak password policy, session id in the URL, session not rotated on login, missing or weak MFA, JWT with `alg:none` accepted or signature not verified, long-lived non-revocable tokens.
40
+ CWEs: CWE-287 (improper auth), CWE-297, CWE-384 (session fixation), CWE-521 (weak password), CWE-613 (insufficient expiration), CWE-347 (improper signature verification).
41
+
42
+ ## A08:2021 - Software and Data Integrity Failures
43
+
44
+ Check: insecure deserialization of untrusted data, an update or plugin loaded without signature verification, a CI/CD step pulling an unpinned or unverified artifact, client-side data trusted without integrity check.
45
+ CWEs: CWE-502 (deserialization), CWE-345, CWE-494 (download without integrity check), CWE-829.
46
+
47
+ ## A09:2021 - Security Logging and Monitoring Failures
48
+
49
+ Check: security-relevant events not logged (auth failures, access-control denials, high-value actions), logs containing secrets or PII, no alerting path. Note it as a `suggestion`/`important` gap, rarely `blocking` on its own.
50
+ CWEs: CWE-778 (insufficient logging), CWE-532 (secrets in logs), CWE-223.
51
+
52
+ ## A10:2021 - Server-Side Request Forgery (SSRF)
53
+
54
+ Check: the server fetches a URL built from user input without an allowlist, letting a caller reach internal services, cloud metadata endpoints, or the loopback interface.
55
+ CWEs: CWE-918.
56
+ Evidence: the input-derived URL reaching an HTTP client with no host allowlist or scheme restriction.
@@ -16,6 +16,10 @@ When reviewing StoreKit code, explicitly separate "preferred SwiftUI path" from
16
16
  direct `product.purchase(options:)` is still valid for lower-level custom
17
17
  StoreKit flows.
18
18
 
19
+ Full, copyable code for the core flows lives in
20
+ [references/core-patterns.md](references/core-patterns.md). This guide keeps the
21
+ review guidance and short snippets; load the reference for a complete example.
22
+
19
23
  ## Contents
20
24
 
21
25
  - [Implementation Review Minimums](#implementation-review-minimums)
@@ -24,11 +28,10 @@ StoreKit flows.
24
28
  - [Purchase Flow](#purchase-flow)
25
29
  - [Transaction.updates Listener](#transactionupdates-listener)
26
30
  - [Entitlement Checking](#entitlement-checking)
27
- - [SubscriptionStoreView (iOS 17+)](#subscriptionstoreview-ios-17)
28
- - [StoreView (iOS 17+)](#storeview-ios-17)
31
+ - [Store Views (SubscriptionStoreView, StoreView, ProductView)](#store-views-subscriptionstoreview-storeview-productview)
29
32
  - [Subscription Status Checking](#subscription-status-checking)
30
33
  - [Restore Purchases](#restore-purchases)
31
- - [App Transaction (App Purchase Verification)](#app-transaction-app-purchase-verification)
34
+ - [App Transaction](#app-transaction)
32
35
  - [Purchase Options](#purchase-options)
33
36
  - [SwiftUI Purchase Callbacks](#swiftui-purchase-callbacks)
34
37
  - [Common Mistakes](#common-mistakes)
@@ -76,20 +79,13 @@ points explicitly:
76
79
  Define product IDs as constants. Fetch products with `Product.products(for:)`.
77
80
 
78
81
  ```swift
79
- import StoreKit
80
-
81
82
  enum ProductID {
82
83
  static let premium = "com.myapp.premium"
83
- static let gems100 = "com.myapp.gems100"
84
84
  static let monthlyPlan = "com.myapp.monthly"
85
- static let yearlyPlan = "com.myapp.yearly"
86
- static let all: [String] = [premium, gems100, monthlyPlan, yearlyPlan]
85
+ static let all: [String] = [premium, monthlyPlan]
87
86
  }
88
87
 
89
88
  let products = try await Product.products(for: ProductID.all)
90
- for product in products {
91
- print("\(product.displayName): \(product.displayPrice)")
92
- }
93
89
  ```
94
90
 
95
91
  ## Purchase Flow
@@ -105,35 +101,8 @@ Review wording: do not call `product.purchase(options:)` inherently wrong. Say
105
101
  "prefer `PurchaseAction` for SwiftUI buttons; keep `product.purchase(options:)`
106
102
  for lower-level custom flows that need direct StoreKit control."
107
103
 
108
- ```swift
109
- @Environment(\.purchase) private var purchase
110
-
111
- func purchaseProduct(_ product: Product) async throws {
112
- let result = try await purchase(product, options: [
113
- .appAccountToken(userAccountToken)
114
- ])
115
- switch result {
116
- case .success(let verification):
117
- let transaction = try checkVerified(verification)
118
- await deliverContent(for: transaction)
119
- await transaction.finish()
120
- case .userCancelled:
121
- break
122
- case .pending:
123
- // Ask to Buy or deferred approval: show pending UI, no unlock yet.
124
- showPendingApprovalMessage()
125
- @unknown default:
126
- break
127
- }
128
- }
129
-
130
- func checkVerified<T>(_ result: VerificationResult<T>) throws -> T {
131
- switch result {
132
- case .verified(let value): return value
133
- case .unverified(_, let error): throw error
134
- }
135
- }
136
- ```
104
+ Full purchase-flow example with `checkVerified` and `.pending` handling:
105
+ references/core-patterns.md#purchase-flow
137
106
 
138
107
  ## Transaction.updates Listener
139
108
 
@@ -146,30 +115,8 @@ In implementation reviews, name the launch-time coverage explicitly: purchases
146
115
  made on other devices, Family Sharing changes, subscription renewals, Ask to Buy
147
116
  approvals, refunds, revocations, and unfinished transactions.
148
117
 
149
- ```swift
150
- @main
151
- struct MyApp: App {
152
- private let transactionListener: Task<Void, Never>
153
-
154
- init() {
155
- transactionListener = Self.listenForTransactions()
156
- }
157
-
158
- var body: some Scene {
159
- WindowGroup { ContentView() }
160
- }
161
-
162
- static func listenForTransactions() -> Task<Void, Never> {
163
- Task(priority: .background) {
164
- for await result in Transaction.updates {
165
- guard case .verified(let transaction) = result else { continue }
166
- await StoreManager.shared.updateEntitlements()
167
- await transaction.finish()
168
- }
169
- }
170
- }
171
- }
172
- ```
118
+ Full launch-time listener wired into `App.init`:
119
+ references/core-patterns.md#transactionupdates-listener
173
120
 
174
121
  ## Entitlement Checking
175
122
 
@@ -185,144 +132,35 @@ covers non-consumables, active or grace-period auto-renewable subscriptions, and
185
132
  non-renewing subscriptions; it does not include consumable purchase or delivery
186
133
  history." Do not replace this with only a code sample or a revocation check.
187
134
 
188
- ```swift
189
- @Observable
190
- @MainActor
191
- class StoreManager {
192
- static let shared = StoreManager()
193
- var purchasedProductIDs: Set<String> = []
194
- var isPremium: Bool { purchasedProductIDs.contains(ProductID.premium) }
195
-
196
- func updateEntitlements() async {
197
- var purchased = Set<String>()
198
- for await result in Transaction.currentEntitlements {
199
- if case .verified(let transaction) = result,
200
- transaction.revocationDate == nil {
201
- purchased.insert(transaction.productID)
202
- }
203
- }
204
- purchasedProductIDs = purchased
205
- }
206
- }
207
- ```
135
+ Full `StoreManager.updateEntitlements()` and the `.currentEntitlementTask`
136
+ SwiftUI gate: references/core-patterns.md#entitlement-checking
208
137
 
209
- ### SwiftUI .currentEntitlementTask Modifier
138
+ ## Store Views (SubscriptionStoreView, StoreView, ProductView)
210
139
 
211
- ```swift
212
- struct PremiumGatedView: View {
213
- @State private var state: EntitlementTaskState<VerificationResult<Transaction>?> = .loading
214
-
215
- var body: some View {
216
- Group {
217
- switch state {
218
- case .loading: ProgressView()
219
- case .failure: PaywallView()
220
- case .success(.some(.verified(let transaction))) where transaction.revocationDate == nil:
221
- PremiumContentView()
222
- case .success:
223
- PaywallView()
224
- }
225
- }
226
- .currentEntitlementTask(for: ProductID.premium) { state in
227
- self.state = state
228
- }
229
- }
230
- }
231
- ```
232
-
233
- ## SubscriptionStoreView (iOS 17+)
234
-
235
- Built-in SwiftUI view for subscription paywalls. Handles product loading,
236
- purchase UI, and restore purchases automatically.
140
+ `SubscriptionStoreView(groupID:)` is the built-in subscription paywall; it loads
141
+ products, runs the purchase UI, and can expose restore and policy controls.
142
+ `StoreView(ids:)` merchandises multiple products, and `ProductView(id:)` renders
143
+ a single product. All expose `.onInAppPurchaseCompletion` and
144
+ `.storeButton(.visible, for: .restorePurchases)`.
237
145
 
238
146
  ```swift
239
147
  SubscriptionStoreView(groupID: "YOUR_GROUP_ID")
240
- .subscriptionStoreControlStyle(.prominentPicker)
241
- .subscriptionStoreButtonLabel(.multiline)
242
148
  .storeButton(.visible, for: .restorePurchases)
243
- .storeButton(.visible, for: .redeemCode)
244
149
  .subscriptionStorePolicyDestination(url: termsURL, for: .termsOfService)
245
150
  .subscriptionStorePolicyDestination(url: privacyURL, for: .privacyPolicy)
246
- .onInAppPurchaseCompletion { product, result in
247
- if case .success(.success(.verified(let transaction))) = result {
248
- await deliverContent(for: transaction)
249
- await transaction.finish()
250
- }
251
- }
252
- ```
253
-
254
- ### Custom Marketing Content
255
-
256
- ```swift
257
- SubscriptionStoreView(groupID: "YOUR_GROUP_ID") {
258
- VStack {
259
- Image(systemName: "crown.fill").font(.system(size: 60)).foregroundStyle(.yellow)
260
- Text("Unlock Premium").font(.largeTitle.bold())
261
- Text("Access all features").foregroundStyle(.secondary)
262
- }
263
- }
264
- .containerBackground(.blue.gradient, for: .subscriptionStore)
265
- ```
266
-
267
- ### Hierarchical Layout
268
-
269
- `SubscriptionOptionGroup`, `SubscriptionOptionSection`, and
270
- `SubscriptionPeriodGroupSet` are iOS 18+ helper views for organizing options
271
- inside `SubscriptionStoreView`.
272
-
273
- ```swift
274
- SubscriptionStoreView(groupID: "YOUR_GROUP_ID") {
275
- SubscriptionPeriodGroupSet()
276
- }
277
- .subscriptionStoreControlStyle(.picker)
278
- ```
279
-
280
- ## StoreView (iOS 17+)
281
-
282
- Merchandises multiple products with localized names, prices, and purchase buttons.
283
-
284
- ```swift
285
- StoreView(ids: [ProductID.gems100, ProductID.premium], prefersPromotionalIcon: true)
286
- .productViewStyle(.large)
287
- .storeButton(.visible, for: .restorePurchases)
288
- .onInAppPurchaseCompletion { product, result in
289
- if case .success(.success(.verified(let transaction))) = result {
290
- await deliverContent(for: transaction)
291
- await transaction.finish()
292
- }
293
- }
294
151
  ```
295
152
 
296
- ### ProductView for Individual Products
153
+ Full paywall, custom marketing content, `StoreView`, and `ProductView` examples:
154
+ references/core-patterns.md#subscriptionstoreview and
155
+ references/core-patterns.md#storeview-and-productview
297
156
 
298
- ```swift
299
- ProductView(id: ProductID.premium) { iconPhase in
300
- switch iconPhase {
301
- case .success(let image): image.resizable().scaledToFit()
302
- case .loading: ProgressView()
303
- default: Image(systemName: "star.fill")
304
- }
305
- }
306
- .productViewStyle(.large)
307
- ```
157
+ For control styles, hierarchical option layouts, offers, and container
158
+ backgrounds see [references/storekit-advanced.md](references/storekit-advanced.md).
308
159
 
309
160
  ## Subscription Status Checking
310
161
 
311
- ```swift
312
- func checkSubscriptionActive(groupID: String) async throws -> Bool {
313
- let statuses = try await Product.SubscriptionInfo.status(for: groupID)
314
- for status in statuses {
315
- guard case .verified = status.renewalInfo,
316
- case .verified = status.transaction else { continue }
317
- if status.state == .subscribed || status.state == .inGracePeriod {
318
- return true
319
- }
320
- }
321
- return false
322
- }
323
- ```
324
-
325
- ### Renewal States
162
+ Read status with `Product.SubscriptionInfo.status(for: groupID)`; treat
163
+ `.subscribed` and `.inGracePeriod` as active.
326
164
 
327
165
  | State | Meaning |
328
166
  |---|---|
@@ -332,6 +170,10 @@ func checkSubscriptionActive(groupID: String) async throws -> Bool {
332
170
  | `.inGracePeriod` | Payment failed but access continues during grace period |
333
171
  | `.revoked` | Apple refunded or revoked the subscription |
334
172
 
173
+ Full status-check helper: references/core-patterns.md#subscription-status-checking.
174
+ For renewal-state access decisions, expiration reasons, grace period, and billing
175
+ retry see [references/storekit-advanced.md](references/storekit-advanced.md).
176
+
335
177
  ## Restore Purchases
336
178
 
337
179
  StoreKit 2 handles restoration via `Transaction.currentEntitlements`. Add a
@@ -346,173 +188,46 @@ func restorePurchases() async throws {
346
188
 
347
189
  On store views: `.storeButton(.visible, for: .restorePurchases)`
348
190
 
349
- ## App Transaction (App Purchase Verification)
191
+ ## App Transaction
350
192
 
351
- Verify the legitimacy of the app installation. Use for business model changes
352
- or detecting tampered installations (iOS 16+).
193
+ Verify the legitimacy of the app installation with `AppTransaction.shared`. Use
194
+ for business model changes or detecting tampered installations (iOS 16+); read
195
+ `originalAppVersion` / `originalPurchaseDate` on the verified result and restrict
196
+ features on `.unverified`.
353
197
 
354
- ```swift
355
- func verifyAppPurchase() async {
356
- do {
357
- let result = try await AppTransaction.shared
358
- switch result {
359
- case .verified(let appTransaction):
360
- let originalVersion = appTransaction.originalAppVersion
361
- let purchaseDate = appTransaction.originalPurchaseDate
362
- // Migration logic for users who paid before subscription model
363
- case .unverified:
364
- // Potentially tampered -- restrict features as appropriate
365
- break
366
- }
367
- } catch { /* Could not retrieve app transaction */ }
368
- }
369
- ```
198
+ Full `verifyAppPurchase()` example: references/core-patterns.md#app-transaction
370
199
 
371
200
  ## Purchase Options
372
201
 
373
202
  ```swift
374
- // App account token for server-side reconciliation
375
- try await product.purchase(options: [.appAccountToken(UUID())])
376
-
377
- // Consumable quantity
378
- try await product.purchase(options: [.quantity(5)])
379
-
380
- // Simulate Ask to Buy in sandbox
381
- try await product.purchase(options: [.simulatesAskToBuyInSandbox(true)])
203
+ try await product.purchase(options: [.appAccountToken(UUID())]) // server reconciliation
204
+ try await product.purchase(options: [.quantity(5)]) // consumable quantity
205
+ try await product.purchase(options: [.simulatesAskToBuyInSandbox(true)]) // sandbox
382
206
  ```
383
207
 
384
208
  ## SwiftUI Purchase Callbacks
385
209
 
386
- ```swift
387
- .onInAppPurchaseStart { product in
388
- await analytics.trackPurchaseStarted(product.id)
389
- }
390
- .onInAppPurchaseCompletion { product, result in
391
- if case .success(.success(.verified(let transaction))) = result {
392
- await deliverContent(for: transaction)
393
- await transaction.finish()
394
- }
395
- }
396
- .inAppPurchaseOptions { product in
397
- [.appAccountToken(userAccountToken)]
398
- }
399
- ```
210
+ Store views expose `.onInAppPurchaseStart`, `.onInAppPurchaseCompletion`, and
211
+ `.inAppPurchaseOptions`. In the completion handler, verify the transaction,
212
+ deliver content, then `finish()`.
400
213
 
401
- ## Common Mistakes
402
-
403
- ### 1. Not starting Transaction.updates at app launch
404
-
405
- ```swift
406
- // WRONG: No listener -- misses renewals, refunds, Ask to Buy approvals
407
- @main struct MyApp: App {
408
- var body: some Scene { WindowGroup { ContentView() } }
409
- }
410
- // CORRECT: Start listener in App init (see Transaction.updates section above)
411
- ```
412
-
413
- ### 2. Forgetting transaction.finish()
414
-
415
- ```swift
416
- // WRONG: Never finished -- reappears in unfinished queue forever
417
- let transaction = try checkVerified(verification)
418
- unlockFeature(transaction.productID)
419
-
420
- // CORRECT: Deliver durably, then finish. If delivery fails, do not finish yet.
421
- let transaction = try checkVerified(verification)
422
- try await recordDelivery(transaction)
423
- await transaction.finish()
424
- ```
425
-
426
- ### 3. Ignoring verification result
427
-
428
- ```swift
429
- // WRONG: Using unverified transaction -- security risk
430
- let transaction = verification.unsafePayloadValue
431
-
432
- // CORRECT: Verify before using
433
- let transaction = try checkVerified(verification)
434
- ```
435
-
436
- ### 4. Using original In-App Purchase APIs in new StoreKit 2 code
437
-
438
- ```swift
439
- // AVOID: Original In-App Purchase APIs
440
- let request = SKProductsRequest(productIdentifiers: ["com.app.premium"])
441
- SKPaymentQueue.default().add(payment)
442
-
443
- // PREFERRED: StoreKit 2
444
- let products = try await Product.products(for: ["com.app.premium"])
445
- let result = try await product.purchase()
446
- ```
447
-
448
- ### 5. Not checking revocationDate
449
-
450
- ```swift
451
- // WRONG: Grants access to refunded purchases
452
- if case .verified(let transaction) = result {
453
- purchased.insert(transaction.productID)
454
- }
455
-
456
- // CORRECT: Skip revoked transactions
457
- if case .verified(let transaction) = result, transaction.revocationDate == nil {
458
- purchased.insert(transaction.productID)
459
- }
460
- ```
214
+ Full callback example: references/core-patterns.md#swiftui-purchase-callbacks
461
215
 
462
- ### 6. Hardcoding prices
463
-
464
- ```swift
465
- // WRONG: Wrong for other currencies and regions
466
- Text("Buy Premium for $4.99")
467
-
468
- // CORRECT: Localized price from Product
469
- Text("Buy \(product.displayName) for \(product.displayPrice)")
470
- ```
471
-
472
- ### 7. Not handling .pending purchase result
473
-
474
- ```swift
475
- // WRONG: Silently drops pending Ask to Buy
476
- default: break
477
-
478
- // CORRECT: Explain approval is pending; unlock only after Transaction.updates
479
- case .pending:
480
- showPendingApprovalMessage()
481
- ```
482
-
483
- ### 8. Checking entitlements only once at launch
484
-
485
- ```swift
486
- // WRONG: Check once, never update
487
- func appDidFinish() { Task { await updateEntitlements() } }
488
-
489
- // CORRECT: Re-check on Transaction.updates AND on foreground return
490
- // Transaction.updates listener handles mid-session changes.
491
- // Also use .task { await storeManager.updateEntitlements() } on content views.
492
- ```
493
-
494
- ### 9. Missing restore purchases button
495
-
496
- ```swift
497
- // WRONG: No restore option -- App Store rejection risk
498
- SubscriptionStoreView(groupID: "group_id")
499
-
500
- // CORRECT
501
- SubscriptionStoreView(groupID: "group_id")
502
- .storeButton(.visible, for: .restorePurchases)
503
- ```
504
-
505
- ### 10. Subscription views without policy links
216
+ ## Common Mistakes
506
217
 
507
- ```swift
508
- // WRONG: No terms or privacy policy
509
- SubscriptionStoreView(groupID: "group_id")
218
+ The ten most common StoreKit 2 defects, each with a WRONG/CORRECT example, are in
219
+ references/core-patterns.md#common-mistakes:
510
220
 
511
- // CORRECT
512
- SubscriptionStoreView(groupID: "group_id")
513
- .subscriptionStorePolicyDestination(url: termsURL, for: .termsOfService)
514
- .subscriptionStorePolicyDestination(url: privacyURL, for: .privacyPolicy)
515
- ```
221
+ 1. Not starting `Transaction.updates` at app launch
222
+ 2. Forgetting `transaction.finish()`
223
+ 3. Ignoring the verification result
224
+ 4. Using original In-App Purchase APIs in new StoreKit 2 code
225
+ 5. Not checking `revocationDate`
226
+ 6. Hardcoding prices instead of `product.displayPrice`
227
+ 7. Not handling the `.pending` purchase result
228
+ 8. Checking entitlements only once at launch
229
+ 9. Missing restore purchases button
230
+ 10. Subscription views without Terms / Privacy policy links
516
231
 
517
232
  ## Review Checklist
518
233
 
@@ -536,8 +251,19 @@ SubscriptionStoreView(groupID: "group_id")
536
251
 
537
252
  ## References
538
253
 
539
- - See [references/app-review-guidelines.md](references/app-review-guidelines.md) for IAP rules (Guideline 3.1.1), subscription display requirements, and rejection prevention.
540
- - See [references/storekit-advanced.md](references/storekit-advanced.md) for subscription control styles, offer management, testing patterns, and advanced subscription handling.
254
+ - See [references/core-patterns.md](references/core-patterns.md) for full core
255
+ code: purchase flow, launch-time transaction listener, entitlement checking,
256
+ store views, subscription status, app transaction, purchase callbacks, and the
257
+ ten annotated common mistakes. Load it when writing or reviewing a core flow.
258
+ - See [references/storekit-advanced.md](references/storekit-advanced.md) for
259
+ subscription control styles, offer management (introductory, promotional,
260
+ win-back, offer codes), server-side validation, StoreKit testing, renewal
261
+ states, grace period/billing retry, refunds, and Family Sharing. Load it for
262
+ offers, testing setup, or advanced subscription handling.
263
+ - See [references/app-review-guidelines.md](references/app-review-guidelines.md)
264
+ for IAP rules (Guideline 3.1.1), subscription display requirements, and
265
+ rejection prevention. Load it before a submission or when diagnosing a payment
266
+ rejection.
541
267
  - For submission, privacy, metadata, screenshots, and rejection-risk audits use `app-store-review`.
542
268
  - For keyword, screenshot-caption, ranking, and conversion strategy use `app-store-optimization`.
543
269
  - Official Apple docs: [Choosing a StoreKit API](https://sosumi.ai/documentation/storekit/choosing-a-storekit-api-for-in-app-purchases), [Transaction.updates](https://sosumi.ai/documentation/storekit/transaction/updates), [Transaction.currentEntitlements](https://sosumi.ai/documentation/storekit/transaction/currententitlements),