@mmerterden/multi-agent-pipeline 20.7.0 → 20.8.1

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 (284) hide show
  1. package/CHANGELOG.md +21 -0
  2. package/LICENSE +0 -10
  3. package/docs/facts.json +1 -1
  4. package/manifest.json +285 -285
  5. package/package.json +3 -3
  6. package/pipeline/lib/redact.mjs +3 -2
  7. package/pipeline/scripts/_notices.mjs +1 -1
  8. package/pipeline/scripts/gen-skills-index.mjs +13 -1
  9. package/pipeline/scripts/pre-commit-check.sh +4 -0
  10. package/pipeline/skills/.skill-manifest.json +69 -69
  11. package/pipeline/skills/shared/README.md +70 -70
  12. package/pipeline/skills/shared/external/alarmkit/SKILL.md +373 -381
  13. package/pipeline/skills/shared/external/alarmkit/evals/evals.json +23 -18
  14. package/pipeline/skills/shared/external/alarmkit/references/alarmkit-patterns.md +328 -378
  15. package/pipeline/skills/shared/external/app-clips/SKILL.md +260 -160
  16. package/pipeline/skills/shared/external/app-clips/evals/evals.json +27 -27
  17. package/pipeline/skills/shared/external/app-clips/references/data-handoff-notifications-location.md +150 -83
  18. package/pipeline/skills/shared/external/app-clips/references/routing-and-experiences.md +135 -83
  19. package/pipeline/skills/shared/external/app-clips/references/size-capabilities-and-promotion.md +143 -85
  20. package/pipeline/skills/shared/external/app-intents/SKILL.md +302 -304
  21. package/pipeline/skills/shared/external/app-intents/evals/evals.json +21 -21
  22. package/pipeline/skills/shared/external/app-intents/references/appintents-advanced.md +594 -894
  23. package/pipeline/skills/shared/external/app-store-optimization/SKILL.md +345 -277
  24. package/pipeline/skills/shared/external/app-store-optimization/evals/evals.json +27 -23
  25. package/pipeline/skills/shared/external/app-store-optimization/references/keyword-research-methodology.md +107 -121
  26. package/pipeline/skills/shared/external/app-store-optimization/references/product-page-variants.md +145 -165
  27. package/pipeline/skills/shared/external/app-store-review/SKILL.md +306 -326
  28. package/pipeline/skills/shared/external/app-store-review/evals/evals.json +21 -21
  29. package/pipeline/skills/shared/external/app-store-review/references/privacy-manifest.md +105 -67
  30. package/pipeline/skills/shared/external/app-store-review/references/review-checklists.md +114 -101
  31. package/pipeline/skills/shared/external/apple-on-device-ai/SKILL.md +335 -360
  32. package/pipeline/skills/shared/external/apple-on-device-ai/evals/evals.json +24 -27
  33. package/pipeline/skills/shared/external/apple-on-device-ai/references/coreml-conversion.md +215 -322
  34. package/pipeline/skills/shared/external/apple-on-device-ai/references/coreml-optimization.md +161 -256
  35. package/pipeline/skills/shared/external/apple-on-device-ai/references/foundation-models.md +277 -387
  36. package/pipeline/skills/shared/external/apple-on-device-ai/references/mlx-swift.md +196 -210
  37. package/pipeline/skills/shared/external/authentication/SKILL.md +277 -381
  38. package/pipeline/skills/shared/external/authentication/evals/evals.json +25 -25
  39. package/pipeline/skills/shared/external/authentication/references/keychain-biometric.md +135 -178
  40. package/pipeline/skills/shared/external/authentication/references/passkeys.md +111 -147
  41. package/pipeline/skills/shared/external/avkit/SKILL.md +267 -364
  42. package/pipeline/skills/shared/external/avkit/evals/evals.json +26 -26
  43. package/pipeline/skills/shared/external/avkit/references/avkit-patterns.md +375 -493
  44. package/pipeline/skills/shared/external/background-processing/SKILL.md +274 -384
  45. package/pipeline/skills/shared/external/background-processing/evals/evals.json +22 -22
  46. package/pipeline/skills/shared/external/background-processing/references/background-task-patterns.md +173 -321
  47. package/pipeline/skills/shared/external/callkit-voip/SKILL.md +290 -371
  48. package/pipeline/skills/shared/external/callkit-voip/evals/evals.json +24 -24
  49. package/pipeline/skills/shared/external/callkit-voip/references/callkit-patterns.md +175 -343
  50. package/pipeline/skills/shared/external/cloudkit-sync/SKILL.md +292 -381
  51. package/pipeline/skills/shared/external/cloudkit-sync/evals/evals.json +33 -30
  52. package/pipeline/skills/shared/external/cloudkit-sync/references/cloudkit-patterns.md +227 -355
  53. package/pipeline/skills/shared/external/contacts-framework/SKILL.md +197 -346
  54. package/pipeline/skills/shared/external/contacts-framework/evals/evals.json +19 -21
  55. package/pipeline/skills/shared/external/contacts-framework/references/contacts-patterns.md +169 -308
  56. package/pipeline/skills/shared/external/core-bluetooth/SKILL.md +228 -376
  57. package/pipeline/skills/shared/external/core-bluetooth/evals/evals.json +25 -22
  58. package/pipeline/skills/shared/external/core-bluetooth/references/ble-patterns.md +257 -337
  59. package/pipeline/skills/shared/external/core-data/SKILL.md +302 -368
  60. package/pipeline/skills/shared/external/core-data/evals/evals.json +30 -27
  61. package/pipeline/skills/shared/external/core-motion/SKILL.md +235 -324
  62. package/pipeline/skills/shared/external/core-motion/evals/evals.json +31 -27
  63. package/pipeline/skills/shared/external/core-motion/references/motion-patterns.md +210 -310
  64. package/pipeline/skills/shared/external/core-nfc/SKILL.md +292 -366
  65. package/pipeline/skills/shared/external/core-nfc/evals/evals.json +22 -24
  66. package/pipeline/skills/shared/external/core-nfc/references/nfc-patterns.md +233 -329
  67. package/pipeline/skills/shared/external/coreml/SKILL.md +323 -367
  68. package/pipeline/skills/shared/external/coreml/evals/evals.json +24 -21
  69. package/pipeline/skills/shared/external/coreml/references/coreml-swift-integration.md +562 -565
  70. package/pipeline/skills/shared/external/cryptokit/SKILL.md +253 -394
  71. package/pipeline/skills/shared/external/cryptokit/evals/evals.json +20 -18
  72. package/pipeline/skills/shared/external/cryptokit/references/cryptokit-patterns.md +299 -488
  73. package/pipeline/skills/shared/external/debugging-instruments/SKILL.md +270 -323
  74. package/pipeline/skills/shared/external/debugging-instruments/evals/evals.json +27 -30
  75. package/pipeline/skills/shared/external/debugging-instruments/references/instruments-guide.md +167 -315
  76. package/pipeline/skills/shared/external/debugging-instruments/references/lldb-patterns.md +140 -193
  77. package/pipeline/skills/shared/external/device-integrity/SKILL.md +236 -353
  78. package/pipeline/skills/shared/external/device-integrity/evals/evals.json +25 -21
  79. package/pipeline/skills/shared/external/device-integrity/references/device-integrity-patterns.md +159 -197
  80. package/pipeline/skills/shared/external/energykit/SKILL.md +225 -392
  81. package/pipeline/skills/shared/external/energykit/evals/evals.json +29 -28
  82. package/pipeline/skills/shared/external/energykit/references/energykit-patterns.md +174 -470
  83. package/pipeline/skills/shared/external/eventkit-calendar/SKILL.md +261 -383
  84. package/pipeline/skills/shared/external/eventkit-calendar/evals/evals.json +25 -22
  85. package/pipeline/skills/shared/external/eventkit-calendar/references/eventkit-patterns.md +165 -268
  86. package/pipeline/skills/shared/external/healthkit/SKILL.md +252 -303
  87. package/pipeline/skills/shared/external/healthkit/evals/evals.json +24 -23
  88. package/pipeline/skills/shared/external/healthkit/references/healthkit-patterns.md +369 -523
  89. package/pipeline/skills/shared/external/homekit-matter/SKILL.md +233 -348
  90. package/pipeline/skills/shared/external/homekit-matter/evals/evals.json +27 -22
  91. package/pipeline/skills/shared/external/homekit-matter/references/matter-commissioning.md +199 -305
  92. package/pipeline/skills/shared/external/ios-accessibility/SKILL.md +368 -340
  93. package/pipeline/skills/shared/external/ios-accessibility/evals/evals.json +28 -27
  94. package/pipeline/skills/shared/external/ios-accessibility/references/a11y-patterns.md +314 -260
  95. package/pipeline/skills/shared/external/ios-accessibility/references/media-accessibility.md +97 -67
  96. package/pipeline/skills/shared/external/ios-accessibility/references/nutrition-labels.md +165 -101
  97. package/pipeline/skills/shared/external/ios-localization/SKILL.md +258 -371
  98. package/pipeline/skills/shared/external/ios-localization/evals/evals.json +23 -23
  99. package/pipeline/skills/shared/external/ios-localization/references/formatstyle-locale.md +283 -491
  100. package/pipeline/skills/shared/external/ios-localization/references/string-catalogs.md +313 -440
  101. package/pipeline/skills/shared/external/ios-networking/SKILL.md +265 -341
  102. package/pipeline/skills/shared/external/ios-networking/evals/evals.json +24 -24
  103. package/pipeline/skills/shared/external/ios-networking/references/background-websocket.md +425 -652
  104. package/pipeline/skills/shared/external/ios-networking/references/file-storage-patterns.md +143 -285
  105. package/pipeline/skills/shared/external/ios-networking/references/lightweight-clients.md +93 -53
  106. package/pipeline/skills/shared/external/ios-networking/references/network-framework.md +231 -456
  107. package/pipeline/skills/shared/external/ios-networking/references/urlsession-patterns.md +517 -784
  108. package/pipeline/skills/shared/external/ios-simulator/SKILL.md +265 -393
  109. package/pipeline/skills/shared/external/ios-simulator/evals/evals.json +21 -21
  110. package/pipeline/skills/shared/external/ios-simulator/references/simctl-commands.md +177 -270
  111. package/pipeline/skills/shared/external/live-activities/SKILL.md +318 -360
  112. package/pipeline/skills/shared/external/live-activities/evals/evals.json +21 -21
  113. package/pipeline/skills/shared/external/live-activities/references/activitykit-patterns.md +478 -710
  114. package/pipeline/skills/shared/external/localization-reuse-map/SKILL.md +3 -3
  115. package/pipeline/skills/shared/external/localization-reuse-map/example-mapping.json +1 -1
  116. package/pipeline/skills/shared/external/localization-reuse-map/reference/format-and-output.md +8 -7
  117. package/pipeline/skills/shared/external/localization-reuse-map/reference/publish-and-snapshot.md +2 -2
  118. package/pipeline/skills/shared/external/localization-reuse-map/reference/sources-and-recipes.md +5 -2
  119. package/pipeline/skills/shared/external/localization-reuse-map/scripts/_shared.py +100 -0
  120. package/pipeline/skills/shared/external/localization-reuse-map/scripts/build-artifact.py +45 -26
  121. package/pipeline/skills/shared/external/localization-reuse-map/scripts/build-spreadsheet.py +14 -16
  122. package/pipeline/skills/shared/external/localization-reuse-map/scripts/fetch-annotations.py +12 -5
  123. package/pipeline/skills/shared/external/localization-reuse-map/scripts/fetch-legacy-labels.py +2 -1
  124. package/pipeline/skills/shared/external/localization-reuse-map/scripts/publish-confluence.py +6 -5
  125. package/pipeline/skills/shared/external/localization-reuse-map/scripts/render-overlay.py +44 -18
  126. package/pipeline/skills/shared/external/localization-reuse-map/scripts/resolve-legacy-values.py +5 -2
  127. package/pipeline/skills/shared/external/localization-reuse-map/scripts/resolve-new-values.py +10 -11
  128. package/pipeline/skills/shared/external/localization-reuse-map/scripts/scan-screen-keys.py +4 -33
  129. package/pipeline/skills/shared/external/localization-reuse-map/scripts/verify-map.py +12 -59
  130. package/pipeline/skills/shared/external/mapkit-location/SKILL.md +297 -267
  131. package/pipeline/skills/shared/external/mapkit-location/evals/evals.json +28 -24
  132. package/pipeline/skills/shared/external/mapkit-location/references/mapkit-corelocation-patterns.md +378 -532
  133. package/pipeline/skills/shared/external/mapkit-location/references/mapkit-patterns.md +397 -499
  134. package/pipeline/skills/shared/external/metrickit-diagnostics/SKILL.md +165 -348
  135. package/pipeline/skills/shared/external/metrickit-diagnostics/evals/evals.json +26 -23
  136. package/pipeline/skills/shared/external/metrickit-diagnostics/references/metrickit-patterns.md +123 -130
  137. package/pipeline/skills/shared/external/musickit-audio/SKILL.md +189 -315
  138. package/pipeline/skills/shared/external/musickit-audio/evals/evals.json +22 -21
  139. package/pipeline/skills/shared/external/musickit-audio/references/musickit-patterns.md +181 -270
  140. package/pipeline/skills/shared/external/natural-language/SKILL.md +188 -340
  141. package/pipeline/skills/shared/external/natural-language/evals/evals.json +21 -21
  142. package/pipeline/skills/shared/external/natural-language/references/translation-patterns.md +171 -225
  143. package/pipeline/skills/shared/external/passkit-wallet/SKILL.md +258 -392
  144. package/pipeline/skills/shared/external/passkit-wallet/evals/evals.json +30 -29
  145. package/pipeline/skills/shared/external/passkit-wallet/references/wallet-passes.md +164 -231
  146. package/pipeline/skills/shared/external/pdfkit/SKILL.md +312 -344
  147. package/pipeline/skills/shared/external/pdfkit/evals/evals.json +19 -19
  148. package/pipeline/skills/shared/external/pdfkit/references/pdfkit-patterns.md +413 -624
  149. package/pipeline/skills/shared/external/pencilkit-drawing/SKILL.md +242 -358
  150. package/pipeline/skills/shared/external/pencilkit-drawing/evals/evals.json +25 -21
  151. package/pipeline/skills/shared/external/pencilkit-drawing/references/pencilkit-patterns.md +161 -226
  152. package/pipeline/skills/shared/external/permissionkit/SKILL.md +282 -400
  153. package/pipeline/skills/shared/external/permissionkit/evals/evals.json +27 -30
  154. package/pipeline/skills/shared/external/permissionkit/references/permissionkit-patterns.md +237 -350
  155. package/pipeline/skills/shared/external/photos-camera-media/SKILL.md +276 -325
  156. package/pipeline/skills/shared/external/photos-camera-media/references/av-playback.md +299 -545
  157. package/pipeline/skills/shared/external/photos-camera-media/references/camera-capture.md +344 -588
  158. package/pipeline/skills/shared/external/photos-camera-media/references/image-loading-caching.md +316 -660
  159. package/pipeline/skills/shared/external/photos-camera-media/references/photokit-patterns.md +270 -416
  160. package/pipeline/skills/shared/external/push-notifications/SKILL.md +312 -340
  161. package/pipeline/skills/shared/external/push-notifications/evals/evals.json +27 -26
  162. package/pipeline/skills/shared/external/push-notifications/references/notification-patterns.md +328 -485
  163. package/pipeline/skills/shared/external/push-notifications/references/rich-notifications.md +327 -560
  164. package/pipeline/skills/shared/external/realitykit-ar/SKILL.md +218 -410
  165. package/pipeline/skills/shared/external/realitykit-ar/evals/evals.json +24 -27
  166. package/pipeline/skills/shared/external/realitykit-ar/references/realitykit-patterns.md +221 -348
  167. package/pipeline/skills/shared/external/shareplay-activities/SKILL.md +222 -393
  168. package/pipeline/skills/shared/external/shareplay-activities/evals/evals.json +23 -24
  169. package/pipeline/skills/shared/external/shareplay-activities/references/shareplay-patterns.md +280 -420
  170. package/pipeline/skills/shared/external/skill-creator/template.md +7 -1
  171. package/pipeline/skills/shared/external/speech-recognition/SKILL.md +217 -421
  172. package/pipeline/skills/shared/external/speech-recognition/evals/evals.json +23 -26
  173. package/pipeline/skills/shared/external/speech-recognition/references/speechanalyzer-patterns.md +133 -125
  174. package/pipeline/skills/shared/external/storekit/SKILL.md +228 -204
  175. package/pipeline/skills/shared/external/storekit/evals/evals.json +27 -24
  176. package/pipeline/skills/shared/external/storekit/references/app-review-guidelines.md +98 -109
  177. package/pipeline/skills/shared/external/storekit/references/core-patterns.md +302 -241
  178. package/pipeline/skills/shared/external/storekit/references/storekit-advanced.md +356 -649
  179. package/pipeline/skills/shared/external/swift-api-design-guidelines/SKILL.md +274 -399
  180. package/pipeline/skills/shared/external/swift-api-design-guidelines/evals/evals.json +22 -24
  181. package/pipeline/skills/shared/external/swift-api-design-guidelines/references/argument-labels-and-parameters.md +107 -108
  182. package/pipeline/skills/shared/external/swift-api-design-guidelines/references/conventions-and-special-rules.md +93 -165
  183. package/pipeline/skills/shared/external/swift-api-design-guidelines/references/naming-and-clarity.md +99 -137
  184. package/pipeline/skills/shared/external/swift-api-design-guidelines/references/side-effects-and-mutating-pairs.md +77 -120
  185. package/pipeline/skills/shared/external/swift-architecture/SKILL.md +334 -350
  186. package/pipeline/skills/shared/external/swift-architecture/evals/evals.json +22 -22
  187. package/pipeline/skills/shared/external/swift-charts/SKILL.md +208 -394
  188. package/pipeline/skills/shared/external/swift-charts/evals/evals.json +27 -30
  189. package/pipeline/skills/shared/external/swift-charts/references/charts-patterns.md +351 -762
  190. package/pipeline/skills/shared/external/swift-codable/SKILL.md +339 -343
  191. package/pipeline/skills/shared/external/swift-codable/evals/evals.json +20 -20
  192. package/pipeline/skills/shared/external/swift-concurrency/SKILL.md +304 -351
  193. package/pipeline/skills/shared/external/swift-concurrency/evals/evals.json +27 -24
  194. package/pipeline/skills/shared/external/swift-concurrency/references/approachable-concurrency.md +65 -80
  195. package/pipeline/skills/shared/external/swift-concurrency/references/async-algorithms.md +48 -84
  196. package/pipeline/skills/shared/external/swift-concurrency/references/bridging-interop.md +134 -79
  197. package/pipeline/skills/shared/external/swift-concurrency/references/concurrency-patterns.md +145 -167
  198. package/pipeline/skills/shared/external/swift-concurrency/references/diagnostics.md +62 -50
  199. package/pipeline/skills/shared/external/swift-concurrency/references/swiftui-concurrency.md +92 -121
  200. package/pipeline/skills/shared/external/swift-concurrency/references/synchronization-primitives.md +177 -241
  201. package/pipeline/skills/shared/external/swift-formatstyle/SKILL.md +258 -234
  202. package/pipeline/skills/shared/external/swift-language/SKILL.md +342 -382
  203. package/pipeline/skills/shared/external/swift-language/evals/evals.json +24 -27
  204. package/pipeline/skills/shared/external/swift-language/references/swift-attributes-interop.md +79 -56
  205. package/pipeline/skills/shared/external/swift-language/references/swift-patterns-extended.md +297 -340
  206. package/pipeline/skills/shared/external/swift-security/SKILL.md +183 -162
  207. package/pipeline/skills/shared/external/swift-security/evals/evals.json +25 -25
  208. package/pipeline/skills/shared/external/swift-security/references/biometric-authentication.md +314 -469
  209. package/pipeline/skills/shared/external/swift-security/references/certificate-trust.md +411 -476
  210. package/pipeline/skills/shared/external/swift-security/references/common-anti-patterns.md +260 -530
  211. package/pipeline/skills/shared/external/swift-security/references/compliance-owasp-mapping.md +270 -477
  212. package/pipeline/skills/shared/external/swift-security/references/credential-storage-patterns.md +573 -571
  213. package/pipeline/skills/shared/external/swift-security/references/cryptokit-public-key.md +370 -441
  214. package/pipeline/skills/shared/external/swift-security/references/cryptokit-symmetric.md +332 -433
  215. package/pipeline/skills/shared/external/swift-security/references/keychain-access-control.md +346 -468
  216. package/pipeline/skills/shared/external/swift-security/references/keychain-fundamentals.md +375 -491
  217. package/pipeline/skills/shared/external/swift-security/references/keychain-item-classes.md +431 -432
  218. package/pipeline/skills/shared/external/swift-security/references/keychain-sharing.md +328 -425
  219. package/pipeline/skills/shared/external/swift-security/references/migration-legacy-stores.md +341 -579
  220. package/pipeline/skills/shared/external/swift-security/references/secure-enclave.md +397 -457
  221. package/pipeline/skills/shared/external/swift-security/references/testing-security-code.md +354 -614
  222. package/pipeline/skills/shared/external/swift-testing/SKILL.md +191 -175
  223. package/pipeline/skills/shared/external/swift-testing/evals/evals.json +26 -24
  224. package/pipeline/skills/shared/external/swift-testing/references/testing-advanced.md +81 -84
  225. package/pipeline/skills/shared/external/swift-testing/references/testing-patterns.md +317 -433
  226. package/pipeline/skills/shared/external/swiftdata/SKILL.md +394 -256
  227. package/pipeline/skills/shared/external/swiftdata/evals/evals.json +24 -24
  228. package/pipeline/skills/shared/external/swiftdata/references/core-data-coexistence.md +206 -402
  229. package/pipeline/skills/shared/external/swiftdata/references/indexing.md +59 -52
  230. package/pipeline/skills/shared/external/swiftdata/references/predicate-pitfalls.md +57 -33
  231. package/pipeline/skills/shared/external/swiftdata/references/swiftdata-advanced.md +354 -747
  232. package/pipeline/skills/shared/external/swiftdata/references/swiftdata-queries.md +300 -508
  233. package/pipeline/skills/shared/external/swiftlint/SKILL.md +175 -226
  234. package/pipeline/skills/shared/external/swiftlint/references/adoption-and-configuration.md +141 -208
  235. package/pipeline/skills/shared/external/swiftlint/references/custom-rules-and-analyze.md +100 -109
  236. package/pipeline/skills/shared/external/swiftlint/references/plugins-run-scripts-and-integrations.md +159 -179
  237. package/pipeline/skills/shared/external/swiftlint/references/rule-reference.md +383 -18
  238. package/pipeline/skills/shared/external/swiftlint/references/rules-suppressions-and-baselines.md +143 -229
  239. package/pipeline/skills/shared/external/swiftui-animation/SKILL.md +283 -366
  240. package/pipeline/skills/shared/external/swiftui-animation/references/animation-advanced.md +396 -608
  241. package/pipeline/skills/shared/external/swiftui-animation/references/core-animation-bridge.md +336 -385
  242. package/pipeline/skills/shared/external/swiftui-gestures/SKILL.md +239 -349
  243. package/pipeline/skills/shared/external/swiftui-gestures/references/gesture-patterns.md +228 -310
  244. package/pipeline/skills/shared/external/swiftui-layout-components/SKILL.md +260 -249
  245. package/pipeline/skills/shared/external/swiftui-layout-components/references/form.md +92 -74
  246. package/pipeline/skills/shared/external/swiftui-layout-components/references/grids.md +112 -177
  247. package/pipeline/skills/shared/external/swiftui-layout-components/references/list.md +61 -64
  248. package/pipeline/skills/shared/external/swiftui-layout-components/references/scrollview.md +94 -134
  249. package/pipeline/skills/shared/external/swiftui-liquid-glass/SKILL.md +193 -225
  250. package/pipeline/skills/shared/external/swiftui-liquid-glass/references/liquid-glass.md +173 -327
  251. package/pipeline/skills/shared/external/swiftui-navigation/SKILL.md +201 -168
  252. package/pipeline/skills/shared/external/swiftui-navigation/references/deeplinks.md +127 -150
  253. package/pipeline/skills/shared/external/swiftui-navigation/references/navigationstack.md +134 -133
  254. package/pipeline/skills/shared/external/swiftui-navigation/references/sheets.md +152 -117
  255. package/pipeline/skills/shared/external/swiftui-navigation/references/tabview.md +111 -138
  256. package/pipeline/skills/shared/external/swiftui-patterns/SKILL.md +316 -252
  257. package/pipeline/skills/shared/external/swiftui-patterns/references/architecture-patterns.md +341 -332
  258. package/pipeline/skills/shared/external/swiftui-patterns/references/deprecated-migration.md +547 -854
  259. package/pipeline/skills/shared/external/swiftui-patterns/references/design-polish.md +485 -537
  260. package/pipeline/skills/shared/external/swiftui-patterns/references/platform-and-sharing.md +417 -499
  261. package/pipeline/skills/shared/external/swiftui-performance/SKILL.md +213 -376
  262. package/pipeline/skills/shared/external/swiftui-performance/references/demystify-swiftui-performance-wwdc23.md +86 -175
  263. package/pipeline/skills/shared/external/swiftui-performance/references/optimizing-swiftui-performance-instruments.md +89 -195
  264. package/pipeline/skills/shared/external/swiftui-performance/references/understanding-hangs-in-your-app.md +95 -182
  265. package/pipeline/skills/shared/external/swiftui-performance/references/understanding-improving-swiftui-performance.md +71 -149
  266. package/pipeline/skills/shared/external/swiftui-performance/references/wwdc-session-sources.md +21 -27
  267. package/pipeline/skills/shared/external/swiftui-uikit-interop/SKILL.md +303 -295
  268. package/pipeline/skills/shared/external/swiftui-uikit-interop/references/hosting-migration.md +204 -387
  269. package/pipeline/skills/shared/external/swiftui-uikit-interop/references/representable-recipes.md +469 -683
  270. package/pipeline/skills/shared/external/swiftui-webkit/SKILL.md +140 -186
  271. package/pipeline/skills/shared/external/swiftui-webkit/references/loading-and-observation.md +75 -86
  272. package/pipeline/skills/shared/external/swiftui-webkit/references/local-content-and-custom-schemes.md +63 -60
  273. package/pipeline/skills/shared/external/swiftui-webkit/references/migration-and-fallbacks.md +69 -137
  274. package/pipeline/skills/shared/external/swiftui-webkit/references/navigation-and-javascript.md +95 -67
  275. package/pipeline/skills/shared/external/tipkit/SKILL.md +220 -335
  276. package/pipeline/skills/shared/external/tipkit/references/tipkit-patterns.md +356 -494
  277. package/pipeline/skills/shared/external/vision-framework/SKILL.md +260 -375
  278. package/pipeline/skills/shared/external/vision-framework/references/vision-requests.md +393 -515
  279. package/pipeline/skills/shared/external/vision-framework/references/visionkit-scanner.md +363 -539
  280. package/pipeline/skills/shared/external/weatherkit/SKILL.md +160 -315
  281. package/pipeline/skills/shared/external/weatherkit/references/weatherkit-patterns.md +288 -407
  282. package/pipeline/skills/shared/external/widgetkit/SKILL.md +224 -288
  283. package/pipeline/skills/shared/external/widgetkit/references/widgetkit-advanced.md +416 -719
  284. package/pipeline/skills/shared/external/NOTICE-swift-ios-skills.md +0 -39
@@ -1,439 +1,322 @@
1
1
  ---
2
2
  name: device-integrity
3
- description: "Verify device legitimacy and app integrity using DeviceCheck (DCDevice per-device bits) and App Attest (DCAppAttestService key generation, attestation, and assertion flows). Use when implementing fraud prevention, detecting compromised devices, validating app authenticity with Apple's servers, protecting sensitive API endpoints with attested requests, or adding device verification to a backend architecture."
3
+ description: "DeviceCheck and App Attest: DCDevice tokens and per-device bits, DCAppAttestService key generation, attestation and assertions. Use when adding fraud prevention, detecting compromised devices, validating app authenticity with Apple's servers, protecting sensitive endpoints with attested requests, or designing backend device verification. Not for sign-in, OAuth, JWT sessions, TLS or pinning."
4
4
  metadata:
5
- source: "dpearson2699/swift-ios-skills (PolyForm Perimeter 1.0.0)"
5
+ source: multi-agent-pipeline
6
6
  ---
7
7
 
8
8
  # Device Integrity
9
9
 
10
- Verify that requests to your server come from a genuine Apple device running a
11
- legitimate instance of your app. DeviceCheck provides per-device bits for
12
- simple flags (e.g., "claimed promo offer"). App Attest uses Secure Enclave keys
13
- and Apple attestation to cryptographically prove app legitimacy on sensitive
14
- requests.
10
+ Two frameworks let a backend decide whether a request really originates from
11
+ real Apple hardware running an unmodified build of your app:
15
12
 
16
- ## Contents
13
+ - **DeviceCheck** (`DCDevice`) hands your server a token it can trade with Apple
14
+ for two developer-defined bits per device. Good for simple flags such as
15
+ "this phone already redeemed the trial".
16
+ - **App Attest** (`DCAppAttestService`) creates a hardware key inside the Secure
17
+ Enclave, has Apple certify it, and then signs chosen requests with it. Good
18
+ for cryptographic proof on sensitive calls.
17
19
 
18
- - [DCDevice (DeviceCheck Tokens)](#dcdevice-devicecheck-tokens)
19
- - [DCAppAttestService (App Attest)](#dcappattestservice-app-attest)
20
- - [App Attest Key Generation](#app-attest-key-generation)
21
- - [App Attest Attestation Flow](#app-attest-attestation-flow)
22
- - [App Attest Assertion Flow](#app-attest-assertion-flow)
23
- - [Server Verification Guidance](#server-verification-guidance)
24
- - [Error Handling](#error-handling)
25
- - [Common Patterns](#common-patterns)
26
- - [Common Mistakes](#common-mistakes)
27
- - [Review Checklist](#review-checklist)
28
- - [References](#references)
20
+ Server algorithms, retries, rejected-key recovery, an integration outline and
21
+ rollout advice live in the [patterns reference](references/device-integrity-patterns.md).
22
+ The `IntegrityError` enum used below is defined there too.
29
23
 
30
- ## DCDevice (DeviceCheck Tokens)
24
+ ## DeviceCheck tokens (`DCDevice`, iOS 11+)
31
25
 
32
- [`DCDevice`](https://sosumi.ai/documentation/devicecheck/dcdevice) generates a
33
- unique, ephemeral token that identifies a device. Treat each token as
34
- single-use: generate a new token for each server operation instead of caching or
35
- reusing one. The token is sent to your server, which then communicates with
36
- Apple's servers to read or set two per-device bits. Available on iOS 11+.
37
-
38
- ### Token Generation
26
+ `DCDevice` produces an ephemeral token that identifies the device to Apple.
27
+ Each token is single-use: create a fresh one for every server operation and
28
+ never cache it. The server forwards the token to Apple to read or change the
29
+ two bits.
39
30
 
40
31
  ```swift
41
32
  import DeviceCheck
42
33
 
43
- func generateDeviceToken() async throws -> Data {
44
- guard DCDevice.current.isSupported else {
45
- throw DeviceIntegrityError.deviceCheckUnsupported
46
- }
47
-
48
- return try await DCDevice.current.generateToken()
34
+ func sendJSON(_ fields: [String: String], to url: URL) async throws -> Bool {
35
+ var req = URLRequest(url: url)
36
+ req.httpMethod = "POST"
37
+ req.addValue("application/json", forHTTPHeaderField: "Content-Type")
38
+ let payload = try JSONEncoder().encode(fields)
39
+ let (_, reply) = try await URLSession.shared.upload(for: req, from: payload)
40
+ return (reply as? HTTPURLResponse).map { $0.statusCode == 200 } ?? false
49
41
  }
50
- ```
51
-
52
- ### Sending the Token to Your Server
53
42
 
54
- ```swift
55
- func sendTokenToServer(_ token: Data) async throws {
56
- let tokenString = token.base64EncodedString()
57
-
58
- var request = URLRequest(url: serverURL.appending(path: "verify-device"))
59
- request.httpMethod = "POST"
60
- request.setValue("application/json", forHTTPHeaderField: "Content-Type")
61
- request.httpBody = try JSONEncoder().encode(["device_token": tokenString])
62
-
63
- let (_, response) = try await URLSession.shared.data(for: request)
64
- guard let httpResponse = response as? HTTPURLResponse,
65
- httpResponse.statusCode == 200 else {
66
- throw DeviceIntegrityError.serverVerificationFailed
67
- }
43
+ func claimTrial(at endpoint: URL) async throws {
44
+ guard DCDevice.current.isSupported else { throw IntegrityError.deviceCheckUnsupported }
45
+ let token = try await DCDevice.current.generateToken() // new token every call
46
+ let accepted = try await sendJSON(["device_token": token.base64EncodedString()], to: endpoint)
47
+ if !accepted { throw IntegrityError.serverVerificationFailed }
68
48
  }
69
49
  ```
70
50
 
71
- ### Server-Side Overview
72
-
73
- Your server uses the device token to call Apple's DeviceCheck API endpoints:
74
-
75
- | Endpoint | Purpose |
76
- |----------|---------|
77
- | `https://api.devicecheck.apple.com/v1/query_two_bits` | Read the two bits for a device |
78
- | `https://api.devicecheck.apple.com/v1/update_two_bits` | Set the two bits for a device |
79
- | `https://api.devicecheck.apple.com/v1/validate_device_token` | Validate a device token without reading bits |
51
+ ### What the server does
80
52
 
81
- The server authenticates with a DeviceCheck private key from the Apple Developer
82
- portal, creating a signed JWT for each request.
53
+ All three calls live under `/v1/` on the DeviceCheck host:
83
54
 
84
- Use `https://api.development.devicecheck.apple.com` only while testing; use
85
- `https://api.devicecheck.apple.com` for production.
55
+ | Path | Purpose |
56
+ |---|---|
57
+ | `query_two_bits` | Returns the current bit values |
58
+ | `update_two_bits` | Changes one or both bits |
59
+ | `validate_device_token` | Confirms a token is real, bits untouched |
86
60
 
87
- ### What the Two Bits Are For
61
+ The server signs a JWT for each call with a DeviceCheck private key created in
62
+ the Developer portal. Hosts: `api.devicecheck.apple.com` for production traffic,
63
+ and `api.development.devicecheck.apple.com` only while testing.
88
64
 
89
- Apple stores two Boolean values per device per developer team. You decide what
90
- they mean. Common uses:
65
+ ### The two bits
91
66
 
92
- - **Bit 0:** Device has claimed a promotional offer.
93
- - **Bit 1:** Device has been flagged for fraud.
67
+ Apple keeps two Booleans per device for each developer team, and you decide
68
+ what they mean. For example bit 0 might record "promo redeemed" and bit 1
69
+ "flagged for abuse". They survive deleting and reinstalling the app; only
70
+ your server can reset them through the API.
94
71
 
95
- Bits persist across app reinstall. You control when to reset them via the
96
- server API.
72
+ ## App Attest (`DCAppAttestService`, iOS 14+)
97
73
 
98
- ## DCAppAttestService (App Attest)
74
+ App Attest ties a specific app instance on a specific device to a Secure
75
+ Enclave key. It runs in three phases:
99
76
 
100
- [`DCAppAttestService`](https://sosumi.ai/documentation/devicecheck/dcappattestservice)
101
- validates that a specific instance of your app on a specific device is
102
- legitimate. It uses a hardware-backed key in the Secure Enclave to create
103
- cryptographic attestations and assertions. Available on iOS 14+.
77
+ 1. **Key generation** - the Secure Enclave makes a key pair.
78
+ 2. **Attestation** - Apple vouches that this key sits on real hardware inside
79
+ your app.
80
+ 3. **Assertion** - sign chosen requests with the attested key.
104
81
 
105
- The flow has three phases:
106
- 1. **Key generation** -- create a key pair in the Secure Enclave.
107
- 2. **Attestation** -- Apple certifies the key belongs to a genuine Apple device running your app.
108
- 3. **Assertion** -- sign server requests with the attested key to prove ongoing legitimacy.
109
-
110
- ### Checking Support
82
+ ### Support check
111
83
 
112
84
  ```swift
113
- import DeviceCheck
114
-
115
- let attestService = DCAppAttestService.shared
116
-
117
- guard attestService.isSupported else {
118
- // Fall back to DCDevice token or other risk assessment.
119
- // App Attest is not available on simulators or all device models.
120
- return
85
+ let attest = DCAppAttestService.shared
86
+ if !attest.isSupported {
87
+ // Fall back to a DCDevice token or another risk signal.
121
88
  }
122
89
  ```
123
90
 
124
- For app extensions, App Attest is supported only in Action, extensible SSO, and
125
- watchOS extensions. Treat other extension types as unsupported even if
126
- `isSupported` returns `true`.
91
+ The service is unavailable in the Simulator and on some hardware. For app
92
+ extensions only Action, extensible single sign-on and watchOS extensions are
93
+ supported; treat any other extension type as unsupported even when
94
+ `isSupported` returns true.
127
95
 
128
- ## App Attest Key Generation
96
+ ### Key generation
129
97
 
130
- Generate one cryptographic key pair per user account on each device. The
131
- private key stays in the Secure Enclave. The returned `keyId` is the only
132
- identifier your app can later use to access the key, so record and reuse the
133
- account/device-scoped `keyId`; do not share one key across users. Avoid
134
- unnecessary regeneration because each new key affects App Attest key-count risk
135
- metrics. Only treat the `keyId` as usable after your server verifies
136
- attestation. If server verification fails, discard the `keyId` and generate a
98
+ Make one key for each signed-in account on a given device. Its private half never leaves
99
+ the Secure Enclave and the returned `keyId` is your only handle to it, so store
100
+ it and reuse it for that account on that device. Never share a key between
101
+ users, and avoid regenerating: every new key moves Apple's per-device key
102
+ count, which feeds risk metrics. Treat a `keyId` as usable only once your
103
+ server accepts its attestation; if verification fails, discard it and make a
137
104
  new key before retrying.
138
105
 
139
106
  ```swift
140
107
  import DeviceCheck
108
+ import Foundation
109
+ import Security
141
110
 
142
- actor AppAttestManager {
111
+ actor AttestKeyStore {
143
112
  private let service = DCAppAttestService.shared
144
- private var keyId: String?
113
+ private let accountID: String
114
+ var keyID: String?
145
115
 
146
- /// Generate and record a key pair for App Attest.
147
- func generateKeyIfNeeded() async throws -> String {
148
- if let existingKeyId = loadKeyIdFromKeychain() {
149
- self.keyId = existingKeyId
150
- return existingKeyId
151
- }
152
-
153
- let newKeyId = try await service.generateKey()
154
- saveKeyIdToKeychain(newKeyId)
155
- self.keyId = newKeyId
156
- return newKeyId
157
- }
116
+ init(accountID: String) { self.accountID = accountID }
158
117
 
159
- // MARK: - Keychain helpers (simplified)
118
+ private var keychainAccount: String { "attest-key.\(accountID)" }
160
119
 
161
- private func saveKeyIdToKeychain(_ keyId: String) {
162
- let data = Data(keyId.utf8)
163
- let query: [String: Any] = [
120
+ var baseQuery: [String: Any] {
121
+ [
164
122
  kSecClass as String: kSecClassGenericPassword,
165
- kSecAttrAccount as String: "app-attest-key-id-\(currentAccountID)",
166
- kSecAttrService as String: Bundle.main.bundleIdentifier ?? "",
167
- kSecValueData as String: data,
168
- kSecAttrAccessible as String: kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly
123
+ kSecAttrService as String: Bundle.main.bundleIdentifier ?? "attest",
124
+ kSecAttrAccount as String: keychainAccount,
169
125
  ]
170
- SecItemDelete(query as CFDictionary) // Remove old if exists
171
- SecItemAdd(query as CFDictionary, nil)
172
126
  }
173
127
 
174
- private func loadKeyIdFromKeychain() -> String? {
175
- let query: [String: Any] = [
176
- kSecClass as String: kSecClassGenericPassword,
177
- kSecAttrAccount as String: "app-attest-key-id-\(currentAccountID)",
178
- kSecAttrService as String: Bundle.main.bundleIdentifier ?? "",
179
- kSecReturnData as String: true,
180
- kSecMatchLimit as String: kSecMatchLimitOne
181
- ]
182
- var result: AnyObject?
183
- let status = SecItemCopyMatching(query as CFDictionary, &result)
184
- guard status == errSecSuccess, let data = result as? Data else { return nil }
185
- return String(data: data, encoding: .utf8)
186
- }
187
- }
188
- ```
189
-
190
- **Important:** Generate the key once per user account on a device, persist that
191
- account/device `keyId`, and keep the key count low. Generating unnecessary keys
192
- pollutes App Attest risk metrics.
193
-
194
- ## App Attest Attestation Flow
195
-
196
- Attestation proves that the key was generated on a genuine Apple device running
197
- a legitimate instance of your app. You perform attestation once per key, then
198
- store the verified public key and receipt on your server. The app stores the
199
- `keyId` for future assertions after the server accepts the attestation.
200
-
201
- ### Client-Side Attestation
202
-
203
- ```swift
204
- import DeviceCheck
205
- import CryptoKit
206
-
207
- extension AppAttestManager {
208
- /// Attest the key with Apple. Send the attestation object to your server.
209
- func attestKey() async throws -> Data {
210
- guard let keyId else {
211
- throw DeviceIntegrityError.keyNotGenerated
128
+ func generateKeyIfNeeded() async throws -> String {
129
+ if let keyID { return keyID }
130
+ if let stored = loadKeyID() {
131
+ keyID = stored
132
+ return stored
212
133
  }
213
-
214
- // 1. Request a one-time challenge from your server
215
- let challenge = try await fetchServerChallenge()
216
-
217
- // 2. Hash the challenge (Apple requires a SHA-256 hash)
218
- let challengeHash = Data(SHA256.hash(data: challenge))
219
-
220
- // 3. Ask Apple to attest the key
221
- let attestation = try await service.attestKey(keyId, clientDataHash: challengeHash)
222
-
223
- // 4. Send the attestation object to your server for verification
224
- try await sendAttestationToServer(
225
- keyId: keyId,
226
- attestation: attestation,
227
- challenge: challenge
228
- )
229
-
230
- return attestation
134
+ let fresh = try await service.generateKey()
135
+ try saveKeyID(fresh)
136
+ keyID = fresh
137
+ return fresh
231
138
  }
232
139
 
233
- private func fetchServerChallenge() async throws -> Data {
234
- let url = serverURL.appending(path: "attest/challenge")
235
- let (data, _) = try await URLSession.shared.data(from: url)
236
- return data
140
+ private func saveKeyID(_ value: String) throws {
141
+ let data = Data(value.utf8)
142
+ var item = baseQuery
143
+ item[kSecValueData as String] = data
144
+ item[kSecAttrAccessible as String] = kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly
145
+ let status = SecItemAdd(item as CFDictionary, nil)
146
+ if status == errSecSuccess { return }
147
+ guard status == errSecDuplicateItem else { throw IntegrityError.keyNotGenerated }
148
+ let updated = SecItemUpdate(baseQuery as CFDictionary,
149
+ [kSecValueData as String: data] as CFDictionary)
150
+ guard updated == errSecSuccess else { throw IntegrityError.keyNotGenerated }
237
151
  }
238
152
 
239
- private func sendAttestationToServer(
240
- keyId: String,
241
- attestation: Data,
242
- challenge: Data
243
- ) async throws {
244
- var request = URLRequest(url: serverURL.appending(path: "attest/verify"))
245
- request.httpMethod = "POST"
246
- request.setValue("application/json", forHTTPHeaderField: "Content-Type")
247
-
248
- let payload: [String: String] = [
249
- "key_id": keyId,
250
- "attestation": attestation.base64EncodedString(),
251
- "challenge": challenge.base64EncodedString()
252
- ]
253
- request.httpBody = try JSONEncoder().encode(payload)
254
-
255
- let (_, response) = try await URLSession.shared.data(for: request)
256
- guard let httpResponse = response as? HTTPURLResponse,
257
- httpResponse.statusCode == 200 else {
258
- throw DeviceIntegrityError.attestationVerificationFailed
259
- }
153
+ private func loadKeyID() -> String? {
154
+ var query = baseQuery
155
+ query[kSecReturnData as String] = true
156
+ query[kSecMatchLimit as String] = kSecMatchLimitOne
157
+ var found: CFTypeRef?
158
+ let status = SecItemCopyMatching(query as CFDictionary, &found)
159
+ guard status == errSecSuccess, let bytes = found as? Data else { return nil }
160
+ return String(data: bytes, encoding: .utf8)
260
161
  }
261
162
  }
262
163
  ```
263
164
 
264
- ### Server-Side Attestation Verification
165
+ `saveKeyID` adds the item and falls back to `SecItemUpdate` on
166
+ `errSecDuplicateItem`, the same add-or-update rule the `swift-security` skill
167
+ sets for every keychain save. Save, load and delete all build on the same
168
+ `baseQuery`, so the account attribute always carries the account suffix. The delete path in the
169
+ [rejected-key section](references/device-integrity-patterns.md#rejected-keys)
170
+ reuses it for that reason.
265
171
 
266
- Your server validates the attestation object (CBOR), verifies the certificate
267
- chain against Apple's App Attest root CA, checks Apple's nonce calculation, and
268
- stores the verified public key and receipt for future assertion verification.
269
- The attestation nonce is not `SHA256(challenge)` alone; it is
270
- `SHA256(authData || SHA256(challenge))` and is compared with the credential
271
- certificate extension `1.2.840.113635.100.8.2`. See
272
- [references/device-integrity-patterns.md](references/device-integrity-patterns.md)
273
- for the full server verification flow.
172
+ ### Attestation
274
173
 
275
- ## App Attest Assertion Flow
174
+ Attestation proves the key was created on a real device inside your genuine
175
+ app. Do it once per key. The server records the public key and receipt it verified;
176
+ the app keeps using the `keyId` for assertions once the server has accepted it.
276
177
 
277
- After attestation, use assertions to sign sensitive requests. Each assertion
278
- proves the request came from the attested app instance and includes a
279
- server-issued, one-time challenge to prevent replay.
280
-
281
- ### Client-Side Assertion
178
+ Client order: ask the server for a one-time challenge, hash it with SHA-256,
179
+ call `attestKey(_:clientDataHash:)`, and upload the result.
282
180
 
283
181
  ```swift
284
- import DeviceCheck
285
182
  import CryptoKit
286
183
 
287
- extension AppAttestManager {
288
- /// Generate an assertion for encoded client data.
289
- /// Client data should include a one-time server challenge and request context.
290
- func generateAssertion(for clientData: Data) async throws -> Data {
291
- guard let keyId else {
292
- throw DeviceIntegrityError.keyNotGenerated
293
- }
294
-
295
- let clientDataHash = Data(SHA256.hash(data: clientData))
184
+ extension AttestKeyStore {
185
+ func attest(challenge: Data, uploadTo url: URL) async throws {
186
+ let keyID = try await generateKeyIfNeeded()
187
+ let hash = Data(SHA256.hash(data: challenge))
188
+ let attestation = try await DCAppAttestService.shared.attestKey(keyID, clientDataHash: hash)
296
189
 
297
- return try await service.generateAssertion(keyId, clientDataHash: clientDataHash)
190
+ let accepted = try await sendJSON([
191
+ "key_id": keyID,
192
+ "attestation": attestation.base64EncodedString(),
193
+ "challenge": challenge.base64EncodedString(),
194
+ ], to: url)
195
+ guard accepted else { throw IntegrityError.attestationVerificationFailed }
298
196
  }
299
197
  }
300
198
  ```
301
199
 
302
- ### Using Assertions in Network Requests
200
+ On the server: parse the CBOR object, walk its certificates up to Apple's
201
+ App Attest root, check the nonce, then persist the public key and receipt. The
202
+ nonce is **not** `SHA256(challenge)`. It is `SHA256(authData || SHA256(challenge))`,
203
+ and it has to match the octet string stored under OID `1.2.840.113635.100.8.2`
204
+ in the leaf certificate.
205
+
206
+ ### Assertion
207
+
208
+ An assertion signs one sensitive request and embeds a server-issued, one-time
209
+ challenge so a captured request cannot be replayed. Call
210
+ `generateAssertion(_:clientDataHash:)` with the SHA-256 of encoded client data
211
+ that holds the challenge plus request context.
303
212
 
304
213
  ```swift
305
- struct AppAttestClientData: Encodable {
214
+ struct SignedRequestContext: Codable {
306
215
  let challenge: String
307
216
  let method: String
308
217
  let path: String
309
- let bodySHA256: String
218
+ let bodyDigest: String
310
219
  }
311
220
 
312
- extension AppAttestManager {
313
- /// Perform an attested API request.
314
- func makeAttestedRequest(
315
- to url: URL,
316
- method: String = "POST",
317
- body: Data
318
- ) async throws -> (Data, URLResponse) {
319
- let challenge = try await fetchAssertionChallenge()
320
- let bodyHash = Data(SHA256.hash(data: body)).base64EncodedString()
321
- let clientData = try JSONEncoder().encode(
322
- AppAttestClientData(
323
- challenge: challenge,
324
- method: method,
325
- path: url.path,
326
- bodySHA256: bodyHash
327
- )
221
+ extension AttestKeyStore {
222
+ func signed(_ original: URLRequest, body: Data, challenge: Data) async throws -> URLRequest {
223
+ var request = original
224
+ let keyID = try await generateKeyIfNeeded()
225
+ let context = SignedRequestContext(
226
+ challenge: challenge.base64EncodedString(),
227
+ method: request.httpMethod ?? "GET",
228
+ path: request.url?.path ?? "/",
229
+ bodyDigest: SHA256.hash(data: body).map { String(format: "%02x", $0) }.joined()
328
230
  )
329
- let assertion = try await generateAssertion(for: clientData)
330
-
331
- var request = URLRequest(url: url)
332
- request.httpMethod = method
333
- request.setValue("application/json", forHTTPHeaderField: "Content-Type")
334
- request.setValue(assertion.base64EncodedString(), forHTTPHeaderField: "X-App-Attest-Assertion")
335
- request.setValue(clientData.base64EncodedString(), forHTTPHeaderField: "X-App-Attest-Client-Data")
231
+ let encoded = try JSONEncoder().encode(context)
232
+ let assertion = try await DCAppAttestService.shared.generateAssertion(
233
+ keyID, clientDataHash: Data(SHA256.hash(data: encoded))
234
+ )
235
+ let headers = [
236
+ "X-App-Attest-Assertion": assertion.base64EncodedString(),
237
+ "X-App-Attest-Client-Data": encoded.base64EncodedString(),
238
+ ]
239
+ headers.forEach { request.addValue($0.value, forHTTPHeaderField: $0.key) }
336
240
  request.httpBody = body
337
-
338
- return try await URLSession.shared.data(for: request)
339
- }
340
-
341
- private func fetchAssertionChallenge() async throws -> String {
342
- let url = serverURL.appending(path: "assert/challenge")
343
- let (data, _) = try await URLSession.shared.data(from: url)
344
- return String(decoding: data, as: UTF8.self)
241
+ return request
345
242
  }
346
243
  }
347
244
  ```
348
245
 
349
- ### Server-Side Assertion Verification
350
-
351
- Your server decodes the assertion (CBOR), verifies the authenticator data and
352
- counter, recomputes `clientDataHash` from the submitted client data, verifies
353
- the signature over `SHA256(authenticatorData || clientDataHash)` with the
354
- stored public key, and confirms the embedded challenge and request context. See
355
- [references/device-integrity-patterns.md](references/device-integrity-patterns.md)
356
- for step-by-step server verification.
357
-
358
- ## Server Verification Guidance
359
-
360
- See [references/device-integrity-patterns.md](references/device-integrity-patterns.md) for full server architecture guidance including attestation vs. assertion comparison, recommended endpoint design, and risk assessment.
361
-
362
- ### Security Boundaries
363
-
364
- App Attest proves app-instance integrity for selected requests. It does not
365
- replace user authentication, OAuth/JWT/session handling, API token design,
366
- entitlement or subscription authorization, TLS, certificate pinning, or general
367
- networking security. Treat those as handoffs to authentication, networking, or
368
- broader security guidance, and still enforce normal authentication and
369
- authorization after App Attest passes.
370
-
371
- ## Error Handling
372
-
373
- Handle `DCError` codes from DeviceCheck operations. Key cases:
374
-
375
- - `.serverUnavailable` - retry with exponential backoff
376
- - `.invalidKey` - the key was already attested, assertion used an unattested key, or the service rejected the key
377
- - `.featureUnsupported` - fall back to `DCDevice` tokens
378
- - `.invalidInput` - malformed `clientDataHash` or `keyId`
379
-
380
- For `attestKey`, retry `.serverUnavailable` later with the same `keyId` and the
381
- same `clientDataHash`. For other attestation errors, discard the key identifier
382
- and create a new key before retrying. See
383
- [references/device-integrity-patterns.md](references/device-integrity-patterns.md)
384
- for full error handling code, retry strategy, and rejected-key recovery.
385
-
386
- ## Common Patterns
387
-
388
- ### Environment Entitlement
389
-
390
- Set the App Attest environment in your entitlements file. Use `development`
391
- during testing and `production` for App Store builds:
392
-
393
- ```xml
394
- <key>com.apple.developer.devicecheck.appattest-environment</key>
395
- <string>production</string>
396
- ```
397
-
398
- When the entitlement is omitted during development, the app uses the App Attest
399
- sandbox by default. After distribution through TestFlight, the App Store, or the
400
- Apple Developer Enterprise Program, the app ignores the entitlement value and
401
- uses production.
402
-
403
- See [references/device-integrity-patterns.md](references/device-integrity-patterns.md) for the full integration manager pattern, gradual rollout guidance, and error type definition.
404
-
405
- ## Common Mistakes
406
-
407
- 1. **Generating a new key on every launch.** Generate once per user account on a device, persist the `keyId`, and keep key counts low.
408
- 2. **Reusing `DCDevice` tokens.** Treat generated tokens as single-use. Generate a new token for each server operation.
409
- 3. **Skipping the fallback for unsupported devices or extensions.** Not all devices and extension types support App Attest. Use `DCDevice` tokens or other risk assessment as fallback.
410
- 4. **Trusting attestation client-side.** All verification must happen on your server.
411
- 5. **Signing only the raw request body.** Assertion client data must include a one-time server challenge and enough request context for the server to bind the assertion to the request.
412
- 6. **Verifying the wrong attestation nonce.** Compare the certificate extension with `SHA256(authData || SHA256(challenge))`, not `SHA256(challenge)` alone.
413
- 7. **Not implementing replay protection.** The server must validate one-time challenges and track the assertion counter.
414
- 8. **Mixing development and production environments.** Sandbox keys and receipts do not work in production, and production keys and receipts do not work in sandbox.
415
- 9. **Not handling `DCError.invalidKey`.** Check for repeated attestation, unattested assertion keys, or service rejection; regenerate only after the state is known bad.
416
-
417
- ## Review Checklist
418
-
419
- - [ ] `DCDevice` tokens generated per server operation and never cached for reuse
420
- - [ ] `DCAppAttestService.isSupported` checked before use; unsupported devices and extension types have a fallback
421
- - [ ] Key generated once per user account on each device and `keyId` persisted only for that app account/device
422
- - [ ] Attestation performed once per key; server stores verified public key and receipt
423
- - [ ] Server validates attestation certificate chain, App ID hash, environment `aaguid`, credential ID, and nonce `SHA256(authData || SHA256(challenge))`
424
- - [ ] Assertions include one-time challenge plus request context; server verifies signature, RP ID, counter, challenge, and request binding
425
- - [ ] Protected endpoints still enforce normal user authentication and entitlement authorization after App Attest passes
426
- - [ ] `DCError` cases handled: `.serverUnavailable` retries attestation with the same key/hash; bad keys are discarded and regenerated
427
- - [ ] App Attest environment entitlement and sandbox/production server routing are consistent
428
- - [ ] Gradual rollout considered; feature flag in place for enabling/disabling
246
+ Fetch a new assertion challenge from your challenge endpoint for every request.
247
+
248
+ Server side: decode the CBOR assertion, inspect authenticator data and its
249
+ counter, hash the client data it received to get `clientDataHash` again, check
250
+ that the stored public key signed `SHA256(authenticatorData || clientDataHash)`,
251
+ and make sure the challenge and the request context both line up.
252
+
253
+ ## Security boundaries
254
+
255
+ App Attest says "this request came from a genuine instance of the app". It
256
+ says nothing about who the user is. Sign-in, OAuth or JWT sessions, how API
257
+ tokens are designed, paid-feature and subscription checks, TLS and certificate
258
+ pinning all remain separate jobs. Keep enforcing authentication and authorization after the
259
+ attestation check passes. Hand-offs:
260
+
261
+ - Apple ID sign-in, passkeys, OAuth flows, credential state: `authentication`
262
+ - Keychain policy, Secure Enclave, pinning, storage hardening: `swift-security`
263
+ - Hashing and signing primitives: `cryptokit`
264
+ - Transport, request pipelines, TLS configuration: `ios-networking`
265
+
266
+ ## Errors
267
+
268
+ | `DCError.Code` | Meaning | Action |
269
+ |---|---|---|
270
+ | `.serverUnavailable` | Apple's service is temporarily unreachable | Retry with exponential backoff |
271
+ | `.invalidKey` | Key already attested, assertion with an unattested key, or key rejected | Find out which, then recover |
272
+ | `.featureUnsupported` | Device or OS lacks App Attest | Use `DCDevice` tokens instead |
273
+ | `.invalidInput` | Malformed `clientDataHash` or `keyId` | Fix the caller |
274
+
275
+ When `attestKey` reports `.serverUnavailable`, try again later passing the
276
+ **same** `keyId` and the **same** `clientDataHash`. Any other attestation
277
+ failure means the key identifier is spent: drop it and make a fresh key first.
278
+
279
+ ## Environment entitlement
280
+
281
+ The App Attest environment entitlement
282
+ (`com.apple.developer.devicecheck.appattest-environment`) takes `development`
283
+ for testing or `production` for release. Without it, development builds talk
284
+ to the App Attest sandbox. TestFlight, App Store and Enterprise Program builds
285
+ ignore the value and always use production.
286
+
287
+ ## Common mistakes
288
+
289
+ - Generating a key on every launch instead of once per account per device.
290
+ - Caching or reusing a `DCDevice` token.
291
+ - No fallback path for unsupported devices or extension types.
292
+ - Trusting attestation on the client. Only the server can verify it.
293
+ - Signing just the raw body. Include the one-time challenge and request context.
294
+ - Checking the nonce as `SHA256(challenge)`.
295
+ - No replay defense. The backend has to accept each challenge once and keep the per-key counter moving forward.
296
+ - Mixing environments. Sandbox keys and receipts fail against production and the reverse.
297
+ - Regenerating on `invalidKey` without knowing why. Establish whether the key
298
+ was already attested, never attested, or rejected, and only replace it when
299
+ its state is known to be bad.
300
+
301
+ ## Review checklist
302
+
303
+ - [ ] A new `DCDevice` token per operation; none cached
304
+ - [ ] `isSupported` checked, with a fallback for devices and extension types that lack App Attest
305
+ - [ ] One key per account per device; `keyId` stored only for that account on that device
306
+ - [ ] Keychain save, load and delete all use the same account-scoped query
307
+ - [ ] One attestation per key; server keeps the public key and receipt
308
+ - [ ] Attestation verifier covers: certificates, App ID hash, `aaguid` for the environment, credential ID, and a nonce of `SHA256(authData || SHA256(challenge))`
309
+ - [ ] Each assertion signs a challenge plus request context; the backend checks the signature, RP ID, counter, the challenge and how it binds to the request
310
+ - [ ] Protected endpoints still require user authentication and authorization
311
+ - [ ] `DCError` handled: `.serverUnavailable` retried with same key and hash, bad keys discarded and regenerated
312
+ - [ ] Environment entitlement agrees with sandbox or production server routing
313
+ - [ ] Rollout gated behind a feature flag
429
314
 
430
315
  ## References
431
316
 
432
- - Extended patterns: [references/device-integrity-patterns.md](references/device-integrity-patterns.md)
433
- - [DeviceCheck framework](https://sosumi.ai/documentation/devicecheck)
434
- - [DCDevice](https://sosumi.ai/documentation/devicecheck/dcdevice)
435
- - [DCAppAttestService](https://sosumi.ai/documentation/devicecheck/dcappattestservice)
436
- - [Establishing your app's integrity](https://sosumi.ai/documentation/devicecheck/establishing-your-app-s-integrity)
437
- - [Validating apps that connect to your server](https://sosumi.ai/documentation/devicecheck/validating-apps-that-connect-to-your-server)
438
- - [Attestation Object Validation Guide](https://sosumi.ai/documentation/devicecheck/attestation-object-validation-guide)
439
- - [App Attest Environment](https://sosumi.ai/documentation/bundleresources/entitlements/com.apple.developer.devicecheck.appattest-environment)
317
+ - [Patterns reference](references/device-integrity-patterns.md) - server verification steps, architecture, retries, rejected keys, rollout
318
+ - Apple documentation: [DeviceCheck](https://developer.apple.com/documentation/devicecheck),
319
+ [DCDevice](https://developer.apple.com/documentation/devicecheck/dcdevice),
320
+ [DCAppAttestService](https://developer.apple.com/documentation/devicecheck/dcappattestservice),
321
+ plus the DeviceCheck articles on app integrity, server-side validation,
322
+ attestation objects and the environment entitlement (see the patterns reference)