simcord 3.0.2__tar.gz → 3.2.0__tar.gz

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 (368) hide show
  1. {simcord-3.0.2 → simcord-3.2.0}/CHANGELOG.md +53 -1
  2. {simcord-3.0.2 → simcord-3.2.0}/PKG-INFO +2 -1
  3. {simcord-3.0.2 → simcord-3.2.0}/docs/api.md +25 -1
  4. {simcord-3.0.2 → simcord-3.2.0}/docs/concepts.md +25 -1
  5. {simcord-3.0.2 → simcord-3.2.0}/docs/guides/ai-coding-agents.md +6 -0
  6. {simcord-3.0.2 → simcord-3.2.0}/docs/guides/fixtures.md +15 -2
  7. {simcord-3.0.2 → simcord-3.2.0}/docs/guides/intents.md +2 -0
  8. {simcord-3.0.2 → simcord-3.2.0}/docs/guides/interactions.md +40 -9
  9. {simcord-3.0.2 → simcord-3.2.0}/docs/guides/messages.md +36 -0
  10. simcord-3.2.0/docs/guides/monetization.md +728 -0
  11. {simcord-3.0.2 → simcord-3.2.0}/docs/guides/time-control.md +43 -11
  12. {simcord-3.0.2 → simcord-3.2.0}/docs/llms.txt +2 -0
  13. {simcord-3.0.2 → simcord-3.2.0}/docs/parity-matrix.md +15 -13
  14. {simcord-3.0.2 → simcord-3.2.0}/docs/stability.md +12 -0
  15. {simcord-3.0.2 → simcord-3.2.0}/mkdocs.yml +1 -0
  16. {simcord-3.0.2 → simcord-3.2.0}/pyproject.toml +2 -2
  17. simcord-3.2.0/scripts/capture_monetization_reference.py +912 -0
  18. simcord-3.2.0/scripts/capture_remaining_monetization.py +1370 -0
  19. simcord-3.2.0/scripts/monetization_reference_checkout.py +537 -0
  20. simcord-3.2.0/scripts/monetization_reference_expiry.py +192 -0
  21. simcord-3.2.0/scripts/monetization_reference_lifecycle.py +597 -0
  22. simcord-3.2.0/scripts/monetization_reference_upgrade.py +537 -0
  23. simcord-3.2.0/scripts/validate_monetization_reference.py +933 -0
  24. {simcord-3.0.2 → simcord-3.2.0}/src/simcord/__init__.py +3 -0
  25. simcord-3.2.0/src/simcord/_clock.py +15 -0
  26. {simcord-3.0.2 → simcord-3.2.0}/src/simcord/_dpy_internals.py +92 -10
  27. {simcord-3.0.2 → simcord-3.2.0}/src/simcord/_runtime.py +73 -45
  28. {simcord-3.0.2 → simcord-3.2.0}/src/simcord/_settlement.py +7 -0
  29. {simcord-3.0.2 → simcord-3.2.0}/src/simcord/actors.py +39 -7
  30. {simcord-3.0.2 → simcord-3.2.0}/src/simcord/backend/access.py +7 -1
  31. {simcord-3.0.2 → simcord-3.2.0}/src/simcord/backend/errors.py +41 -4
  32. {simcord-3.0.2 → simcord-3.2.0}/src/simcord/backend/models/__init__.py +3 -0
  33. {simcord-3.0.2 → simcord-3.2.0}/src/simcord/backend/models/interaction.py +9 -9
  34. {simcord-3.0.2 → simcord-3.2.0}/src/simcord/backend/models/message.py +1 -0
  35. simcord-3.2.0/src/simcord/backend/models/monetization.py +38 -0
  36. {simcord-3.0.2 → simcord-3.2.0}/src/simcord/backend/ops/base.py +53 -34
  37. {simcord-3.0.2 → simcord-3.2.0}/src/simcord/backend/ops/commands.py +12 -1
  38. {simcord-3.0.2 → simcord-3.2.0}/src/simcord/backend/ops/guilds.py +15 -2
  39. {simcord-3.0.2 → simcord-3.2.0}/src/simcord/backend/ops/messages.py +128 -8
  40. simcord-3.2.0/src/simcord/backend/ops/monetization.py +349 -0
  41. {simcord-3.0.2 → simcord-3.2.0}/src/simcord/backend/ops/permissions.py +0 -12
  42. {simcord-3.0.2 → simcord-3.2.0}/src/simcord/backend/ops/polls.py +11 -0
  43. {simcord-3.0.2 → simcord-3.2.0}/src/simcord/backend/ops/scheduled_events.py +18 -0
  44. {simcord-3.0.2 → simcord-3.2.0}/src/simcord/backend/permissions.py +4 -4
  45. {simcord-3.0.2 → simcord-3.2.0}/src/simcord/backend/serializers.py +82 -5
  46. {simcord-3.0.2 → simcord-3.2.0}/src/simcord/backend/state.py +2 -0
  47. {simcord-3.0.2 → simcord-3.2.0}/src/simcord/builders.py +3 -9
  48. {simcord-3.0.2 → simcord-3.2.0}/src/simcord/env.py +263 -49
  49. {simcord-3.0.2 → simcord-3.2.0}/src/simcord/gateway.py +4 -0
  50. {simcord-3.0.2 → simcord-3.2.0}/src/simcord/http/_helpers.py +26 -12
  51. {simcord-3.0.2 → simcord-3.2.0}/src/simcord/http/router.py +1 -0
  52. {simcord-3.0.2 → simcord-3.2.0}/src/simcord/http/routes/__init__.py +2 -0
  53. {simcord-3.0.2 → simcord-3.2.0}/src/simcord/http/routes/interactions.py +43 -19
  54. simcord-3.2.0/src/simcord/http/routes/monetization.py +105 -0
  55. {simcord-3.0.2 → simcord-3.2.0}/src/simcord/intents.py +12 -0
  56. {simcord-3.0.2 → simcord-3.2.0}/src/simcord/interactions.py +1 -1
  57. simcord-3.2.0/src/simcord/monetization.py +104 -0
  58. {simcord-3.0.2 → simcord-3.2.0}/src/simcord/preview/_actions.py +3 -1
  59. {simcord-3.0.2 → simcord-3.2.0}/src/simcord/preview/_assets.py +40 -15
  60. {simcord-3.0.2 → simcord-3.2.0}/src/simcord/preview/_messages.py +53 -5
  61. {simcord-3.0.2 → simcord-3.2.0}/src/simcord/preview/_pages.py +9 -1
  62. {simcord-3.0.2 → simcord-3.2.0}/src/simcord/preview/_queries.py +18 -11
  63. {simcord-3.0.2 → simcord-3.2.0}/src/simcord/preview/static/app.js +3 -3
  64. {simcord-3.0.2 → simcord-3.2.0}/src/simcord/preview/static/messages.js +70 -53
  65. {simcord-3.0.2 → simcord-3.2.0}/src/simcord/preview/static/preview.css +5 -0
  66. {simcord-3.0.2 → simcord-3.2.0}/src/simcord/pytest_plugin.py +20 -2
  67. {simcord-3.0.2 → simcord-3.2.0}/src/simcord/results.py +9 -2
  68. {simcord-3.0.2 → simcord-3.2.0}/tests/conformance/_world.py +5 -0
  69. {simcord-3.0.2 → simcord-3.2.0}/tests/conformance/test_field_honesty.py +2 -2
  70. {simcord-3.0.2 → simcord-3.2.0}/tests/conformance/test_route_coverage.py +0 -1
  71. {simcord-3.0.2 → simcord-3.2.0}/tests/conftest.py +7 -1
  72. simcord-3.2.0/tests/integration/test_entitlement_journey.py +506 -0
  73. simcord-3.2.0/tests/integration/test_forwarding.py +482 -0
  74. {simcord-3.0.2 → simcord-3.2.0}/tests/integration/test_interaction_followups.py +206 -11
  75. {simcord-3.0.2 → simcord-3.2.0}/tests/integration/test_interactions.py +6 -4
  76. {simcord-3.0.2 → simcord-3.2.0}/tests/integration/test_members.py +4 -0
  77. {simcord-3.0.2 → simcord-3.2.0}/tests/integration/test_message_content_rest.py +20 -7
  78. {simcord-3.0.2 → simcord-3.2.0}/tests/integration/test_message_routes.py +136 -3
  79. {simcord-3.0.2 → simcord-3.2.0}/tests/integration/test_preview.py +23 -23
  80. {simcord-3.0.2 → simcord-3.2.0}/tests/integration/test_preview_additional.py +11 -17
  81. {simcord-3.0.2 → simcord-3.2.0}/tests/integration/test_preview_capture_dx.py +1 -1
  82. {simcord-3.0.2 → simcord-3.2.0}/tests/integration/test_preview_edges.py +14 -19
  83. {simcord-3.0.2 → simcord-3.2.0}/tests/integration/test_preview_message_actions.py +5 -2
  84. {simcord-3.0.2 → simcord-3.2.0}/tests/integration/test_preview_security.py +142 -0
  85. {simcord-3.0.2 → simcord-3.2.0}/tests/integration/test_settlement_contract.py +64 -0
  86. simcord-3.2.0/tests/integration/test_shared_clock.py +320 -0
  87. {simcord-3.0.2 → simcord-3.2.0}/tests/integration/test_time.py +73 -11
  88. {simcord-3.0.2 → simcord-3.2.0}/tests/unit/test_command_access.py +2 -2
  89. {simcord-3.0.2 → simcord-3.2.0}/tests/unit/test_intent_rules.py +39 -0
  90. simcord-3.2.0/tests/unit/test_monetization_backend.py +230 -0
  91. simcord-3.2.0/tests/unit/test_monetization_http.py +474 -0
  92. simcord-3.2.0/tests/unit/test_monetization_reference_checkout.py +308 -0
  93. simcord-3.2.0/tests/unit/test_monetization_reference_expiry.py +246 -0
  94. simcord-3.2.0/tests/unit/test_monetization_reference_lifecycle.py +162 -0
  95. simcord-3.2.0/tests/unit/test_monetization_reference_upgrade.py +511 -0
  96. simcord-3.2.0/tests/unit/test_monetization_reference_validation.py +582 -0
  97. {simcord-3.0.2 → simcord-3.2.0}/tests/unit/test_permissions.py +9 -6
  98. {simcord-3.0.2 → simcord-3.2.0}/tests/unit/test_preview_action_specs.py +22 -1
  99. simcord-3.2.0/tests/unit/test_pytest_clock.py +48 -0
  100. simcord-3.2.0/tests/unit/test_remaining_monetization_capture.py +367 -0
  101. {simcord-3.0.2 → simcord-3.2.0}/uv.lock +7 -5
  102. {simcord-3.0.2 → simcord-3.2.0}/.github/ISSUE_TEMPLATE/bug.md +0 -0
  103. {simcord-3.0.2 → simcord-3.2.0}/.github/ISSUE_TEMPLATE/feature.md +0 -0
  104. {simcord-3.0.2 → simcord-3.2.0}/.github/ISSUE_TEMPLATE/parity-gap.md +0 -0
  105. {simcord-3.0.2 → simcord-3.2.0}/.github/dependabot.yml +0 -0
  106. {simcord-3.0.2 → simcord-3.2.0}/.github/workflows/ci.yml +0 -0
  107. {simcord-3.0.2 → simcord-3.2.0}/.github/workflows/dpy-master.yml +0 -0
  108. {simcord-3.0.2 → simcord-3.2.0}/.github/workflows/release.yml +0 -0
  109. {simcord-3.0.2 → simcord-3.2.0}/.github/workflows/verify.yml +0 -0
  110. {simcord-3.0.2 → simcord-3.2.0}/.gitignore +0 -0
  111. {simcord-3.0.2 → simcord-3.2.0}/.pre-commit-config.yaml +0 -0
  112. {simcord-3.0.2 → simcord-3.2.0}/.python-version +0 -0
  113. {simcord-3.0.2 → simcord-3.2.0}/.readthedocs.yaml +0 -0
  114. {simcord-3.0.2 → simcord-3.2.0}/CODE_OF_CONDUCT.md +0 -0
  115. {simcord-3.0.2 → simcord-3.2.0}/CONTRIBUTING.md +0 -0
  116. {simcord-3.0.2 → simcord-3.2.0}/LICENSE +0 -0
  117. {simcord-3.0.2 → simcord-3.2.0}/README.md +0 -0
  118. {simcord-3.0.2 → simcord-3.2.0}/SECURITY.md +0 -0
  119. {simcord-3.0.2 → simcord-3.2.0}/benchmarks/_helpers.py +0 -0
  120. {simcord-3.0.2 → simcord-3.2.0}/benchmarks/test_dispatch_benchmarks.py +0 -0
  121. {simcord-3.0.2 → simcord-3.2.0}/benchmarks/test_perf_guards.py +0 -0
  122. {simcord-3.0.2 → simcord-3.2.0}/changes/README.md +0 -0
  123. {simcord-3.0.2 → simcord-3.2.0}/docs/architecture.md +0 -0
  124. {simcord-3.0.2 → simcord-3.2.0}/docs/assets/adventure/alpine.png +0 -0
  125. {simcord-3.0.2 → simcord-3.2.0}/docs/assets/adventure/canyon.png +0 -0
  126. {simcord-3.0.2 → simcord-3.2.0}/docs/assets/preview/attachments.png +0 -0
  127. {simcord-3.0.2 → simcord-3.2.0}/docs/assets/preview/buttons.png +0 -0
  128. {simcord-3.0.2 → simcord-3.2.0}/docs/assets/preview/embeds.png +0 -0
  129. {simcord-3.0.2 → simcord-3.2.0}/docs/assets/preview/entities.png +0 -0
  130. {simcord-3.0.2 → simcord-3.2.0}/docs/assets/preview/layouts.png +0 -0
  131. {simcord-3.0.2 → simcord-3.2.0}/docs/assets/preview/markdown.png +0 -0
  132. {simcord-3.0.2 → simcord-3.2.0}/docs/assets/preview/modals.png +0 -0
  133. {simcord-3.0.2 → simcord-3.2.0}/docs/assets/preview/polls.png +0 -0
  134. {simcord-3.0.2 → simcord-3.2.0}/docs/assets/preview/replies.png +0 -0
  135. {simcord-3.0.2 → simcord-3.2.0}/docs/assets/preview/selects.png +0 -0
  136. {simcord-3.0.2 → simcord-3.2.0}/docs/assets/preview/slash-commands.png +0 -0
  137. {simcord-3.0.2 → simcord-3.2.0}/docs/assets/simcord-showcase-poll-refresh.gif +0 -0
  138. {simcord-3.0.2 → simcord-3.2.0}/docs/assets/simcord-terminal-demo.gif +0 -0
  139. {simcord-3.0.2 → simcord-3.2.0}/docs/assets/simcord-transparent.webp +0 -0
  140. {simcord-3.0.2 → simcord-3.2.0}/docs/assets/social-card.png +0 -0
  141. {simcord-3.0.2 → simcord-3.2.0}/docs/changelog.md +0 -0
  142. {simcord-3.0.2 → simcord-3.2.0}/docs/cookbook.md +0 -0
  143. {simcord-3.0.2 → simcord-3.2.0}/docs/faq.md +0 -0
  144. {simcord-3.0.2 → simcord-3.2.0}/docs/guides/components.md +0 -0
  145. {simcord-3.0.2 → simcord-3.2.0}/docs/guides/diagnostics.md +0 -0
  146. {simcord-3.0.2 → simcord-3.2.0}/docs/guides/discord-visual-calibration.md +0 -0
  147. {simcord-3.0.2 → simcord-3.2.0}/docs/guides/mocks-vs-simulation.md +0 -0
  148. {simcord-3.0.2 → simcord-3.2.0}/docs/guides/moderation-and-guild.md +0 -0
  149. {simcord-3.0.2 → simcord-3.2.0}/docs/guides/permissions.md +0 -0
  150. {simcord-3.0.2 → simcord-3.2.0}/docs/guides/preview.md +0 -0
  151. {simcord-3.0.2 → simcord-3.2.0}/docs/guides/test-without-token.md +0 -0
  152. {simcord-3.0.2 → simcord-3.2.0}/docs/guides/testing-discord-py-bots.md +0 -0
  153. {simcord-3.0.2 → simcord-3.2.0}/docs/guides/testing-slash-commands.md +0 -0
  154. {simcord-3.0.2 → simcord-3.2.0}/docs/guides/threads-reactions-dms.md +0 -0
  155. {simcord-3.0.2 → simcord-3.2.0}/docs/index.md +0 -0
  156. {simcord-3.0.2 → simcord-3.2.0}/docs/installation.md +0 -0
  157. {simcord-3.0.2 → simcord-3.2.0}/docs/performance.md +0 -0
  158. {simcord-3.0.2 → simcord-3.2.0}/docs/preview-attestation.md +0 -0
  159. {simcord-3.0.2 → simcord-3.2.0}/docs/quickstart.md +0 -0
  160. {simcord-3.0.2 → simcord-3.2.0}/docs/robots.txt +0 -0
  161. {simcord-3.0.2 → simcord-3.2.0}/examples/README.md +0 -0
  162. {simcord-3.0.2 → simcord-3.2.0}/examples/__init__.py +0 -0
  163. {simcord-3.0.2 → simcord-3.2.0}/examples/bot.py +0 -0
  164. {simcord-3.0.2 → simcord-3.2.0}/examples/conftest.py +0 -0
  165. {simcord-3.0.2 → simcord-3.2.0}/examples/preview_example.py +0 -0
  166. {simcord-3.0.2 → simcord-3.2.0}/examples/test_bot.py +0 -0
  167. {simcord-3.0.2 → simcord-3.2.0}/overrides/main.html +0 -0
  168. {simcord-3.0.2 → simcord-3.2.0}/scripts/capture_discord_reference.py +0 -0
  169. {simcord-3.0.2 → simcord-3.2.0}/scripts/capture_visual_reference.py +0 -0
  170. {simcord-3.0.2 → simcord-3.2.0}/scripts/check_discoverability.py +0 -0
  171. {simcord-3.0.2 → simcord-3.2.0}/scripts/check_package.py +0 -0
  172. {simcord-3.0.2 → simcord-3.2.0}/scripts/check_reference_modal_offline.py +0 -0
  173. {simcord-3.0.2 → simcord-3.2.0}/scripts/compare_visual_reference.py +0 -0
  174. {simcord-3.0.2 → simcord-3.2.0}/scripts/discord_reference_bot.py +0 -0
  175. {simcord-3.0.2 → simcord-3.2.0}/scripts/preview_dogfood.py +0 -0
  176. {simcord-3.0.2 → simcord-3.2.0}/scripts/record_readme_demo.py +0 -0
  177. {simcord-3.0.2 → simcord-3.2.0}/scripts/replay_visual_reference.py +0 -0
  178. {simcord-3.0.2 → simcord-3.2.0}/scripts/validate_discord_reference_pack.py +0 -0
  179. {simcord-3.0.2 → simcord-3.2.0}/src/simcord/_modal.py +0 -0
  180. {simcord-3.0.2 → simcord-3.2.0}/src/simcord/asserts.py +0 -0
  181. {simcord-3.0.2 → simcord-3.2.0}/src/simcord/backend/__init__.py +0 -0
  182. {simcord-3.0.2 → simcord-3.2.0}/src/simcord/backend/cdn.py +0 -0
  183. {simcord-3.0.2 → simcord-3.2.0}/src/simcord/backend/command_access.py +0 -0
  184. {simcord-3.0.2 → simcord-3.2.0}/src/simcord/backend/models/auditlog.py +0 -0
  185. {simcord-3.0.2 → simcord-3.2.0}/src/simcord/backend/models/automod.py +0 -0
  186. {simcord-3.0.2 → simcord-3.2.0}/src/simcord/backend/models/channel.py +0 -0
  187. {simcord-3.0.2 → simcord-3.2.0}/src/simcord/backend/models/expression.py +0 -0
  188. {simcord-3.0.2 → simcord-3.2.0}/src/simcord/backend/models/guild.py +0 -0
  189. {simcord-3.0.2 → simcord-3.2.0}/src/simcord/backend/models/invite.py +0 -0
  190. {simcord-3.0.2 → simcord-3.2.0}/src/simcord/backend/models/member.py +0 -0
  191. {simcord-3.0.2 → simcord-3.2.0}/src/simcord/backend/models/role.py +0 -0
  192. {simcord-3.0.2 → simcord-3.2.0}/src/simcord/backend/models/scheduled_event.py +0 -0
  193. {simcord-3.0.2 → simcord-3.2.0}/src/simcord/backend/models/stage_instance.py +0 -0
  194. {simcord-3.0.2 → simcord-3.2.0}/src/simcord/backend/models/user.py +0 -0
  195. {simcord-3.0.2 → simcord-3.2.0}/src/simcord/backend/models/voice.py +0 -0
  196. {simcord-3.0.2 → simcord-3.2.0}/src/simcord/backend/models/webhook.py +0 -0
  197. {simcord-3.0.2 → simcord-3.2.0}/src/simcord/backend/ops/__init__.py +0 -0
  198. {simcord-3.0.2 → simcord-3.2.0}/src/simcord/backend/ops/audit.py +0 -0
  199. {simcord-3.0.2 → simcord-3.2.0}/src/simcord/backend/ops/channels.py +0 -0
  200. {simcord-3.0.2 → simcord-3.2.0}/src/simcord/backend/ops/expressions.py +0 -0
  201. {simcord-3.0.2 → simcord-3.2.0}/src/simcord/backend/ops/invites.py +0 -0
  202. {simcord-3.0.2 → simcord-3.2.0}/src/simcord/backend/ops/reactions.py +0 -0
  203. {simcord-3.0.2 → simcord-3.2.0}/src/simcord/backend/ops/voice.py +0 -0
  204. {simcord-3.0.2 → simcord-3.2.0}/src/simcord/backend/ops/webhooks.py +0 -0
  205. {simcord-3.0.2 → simcord-3.2.0}/src/simcord/components.py +0 -0
  206. {simcord-3.0.2 → simcord-3.2.0}/src/simcord/enums.py +0 -0
  207. {simcord-3.0.2 → simcord-3.2.0}/src/simcord/http/__init__.py +0 -0
  208. {simcord-3.0.2 → simcord-3.2.0}/src/simcord/http/client.py +0 -0
  209. {simcord-3.0.2 → simcord-3.2.0}/src/simcord/http/routes/application.py +0 -0
  210. {simcord-3.0.2 → simcord-3.2.0}/src/simcord/http/routes/auditlogs.py +0 -0
  211. {simcord-3.0.2 → simcord-3.2.0}/src/simcord/http/routes/automod.py +0 -0
  212. {simcord-3.0.2 → simcord-3.2.0}/src/simcord/http/routes/channels.py +0 -0
  213. {simcord-3.0.2 → simcord-3.2.0}/src/simcord/http/routes/commands.py +0 -0
  214. {simcord-3.0.2 → simcord-3.2.0}/src/simcord/http/routes/expressions.py +0 -0
  215. {simcord-3.0.2 → simcord-3.2.0}/src/simcord/http/routes/guilds.py +0 -0
  216. {simcord-3.0.2 → simcord-3.2.0}/src/simcord/http/routes/invites.py +0 -0
  217. {simcord-3.0.2 → simcord-3.2.0}/src/simcord/http/routes/messages.py +0 -0
  218. {simcord-3.0.2 → simcord-3.2.0}/src/simcord/http/routes/reactions.py +0 -0
  219. {simcord-3.0.2 → simcord-3.2.0}/src/simcord/http/routes/scheduled_events.py +0 -0
  220. {simcord-3.0.2 → simcord-3.2.0}/src/simcord/http/routes/stage_instances.py +0 -0
  221. {simcord-3.0.2 → simcord-3.2.0}/src/simcord/parity.py +0 -0
  222. {simcord-3.0.2 → simcord-3.2.0}/src/simcord/preview/__init__.py +0 -0
  223. {simcord-3.0.2 → simcord-3.2.0}/src/simcord/preview/_action_plans.py +0 -0
  224. {simcord-3.0.2 → simcord-3.2.0}/src/simcord/preview/_capture.py +0 -0
  225. {simcord-3.0.2 → simcord-3.2.0}/src/simcord/preview/_commands.py +0 -0
  226. {simcord-3.0.2 → simcord-3.2.0}/src/simcord/preview/_component_projection.py +0 -0
  227. {simcord-3.0.2 → simcord-3.2.0}/src/simcord/preview/_diagnostics.py +0 -0
  228. {simcord-3.0.2 → simcord-3.2.0}/src/simcord/preview/_identity.py +0 -0
  229. {simcord-3.0.2 → simcord-3.2.0}/src/simcord/preview/_markdown.py +0 -0
  230. {simcord-3.0.2 → simcord-3.2.0}/src/simcord/preview/_media.py +0 -0
  231. {simcord-3.0.2 → simcord-3.2.0}/src/simcord/preview/_media_worker.py +0 -0
  232. {simcord-3.0.2 → simcord-3.2.0}/src/simcord/preview/_server.py +0 -0
  233. {simcord-3.0.2 → simcord-3.2.0}/src/simcord/preview/_snapshot.py +0 -0
  234. {simcord-3.0.2 → simcord-3.2.0}/src/simcord/preview/protocol.schema.json +0 -0
  235. {simcord-3.0.2 → simcord-3.2.0}/src/simcord/preview/static/commands.js +0 -0
  236. {simcord-3.0.2 → simcord-3.2.0}/src/simcord/preview/static/components.js +0 -0
  237. {simcord-3.0.2 → simcord-3.2.0}/src/simcord/preview/static/composer.js +0 -0
  238. {simcord-3.0.2 → simcord-3.2.0}/src/simcord/preview/static/dom.js +0 -0
  239. {simcord-3.0.2 → simcord-3.2.0}/src/simcord/preview/static/fonts/OFL-1.1-Noto-Arabic.txt +0 -0
  240. {simcord-3.0.2 → simcord-3.2.0}/src/simcord/preview/static/fonts/OFL-1.1-Noto-CJK.txt +0 -0
  241. {simcord-3.0.2 → simcord-3.2.0}/src/simcord/preview/static/fonts/OFL-1.1-Noto-Devanagari.txt +0 -0
  242. {simcord-3.0.2 → simcord-3.2.0}/src/simcord/preview/static/fonts/OFL-1.1-Noto-Emoji.txt +0 -0
  243. {simcord-3.0.2 → simcord-3.2.0}/src/simcord/preview/static/fonts/OFL-1.1-Noto-Hebrew.txt +0 -0
  244. {simcord-3.0.2 → simcord-3.2.0}/src/simcord/preview/static/fonts/OFL-1.1-Noto.txt +0 -0
  245. {simcord-3.0.2 → simcord-3.2.0}/src/simcord/preview/static/fonts/manifest.json +0 -0
  246. {simcord-3.0.2 → simcord-3.2.0}/src/simcord/preview/static/fonts/noto-color-emoji-v2.051.ttf +0 -0
  247. {simcord-3.0.2 → simcord-3.2.0}/src/simcord/preview/static/fonts/noto-sans-arabic-v2.012.ttf +0 -0
  248. {simcord-3.0.2 → simcord-3.2.0}/src/simcord/preview/static/fonts/noto-sans-devanagari-v2.007.ttf +0 -0
  249. {simcord-3.0.2 → simcord-3.2.0}/src/simcord/preview/static/fonts/noto-sans-hebrew-v3.001.ttf +0 -0
  250. {simcord-3.0.2 → simcord-3.2.0}/src/simcord/preview/static/fonts/noto-sans-latin-italic-v2.015.ttf +0 -0
  251. {simcord-3.0.2 → simcord-3.2.0}/src/simcord/preview/static/fonts/noto-sans-latin-v2.015.ttf +0 -0
  252. {simcord-3.0.2 → simcord-3.2.0}/src/simcord/preview/static/fonts/noto-sans-mono-v2.014.ttf +0 -0
  253. {simcord-3.0.2 → simcord-3.2.0}/src/simcord/preview/static/fonts/noto-sans-sc-v2.004.ttf +0 -0
  254. {simcord-3.0.2 → simcord-3.2.0}/src/simcord/preview/static/index.html +0 -0
  255. {simcord-3.0.2 → simcord-3.2.0}/src/simcord/preview/static/listbox.js +0 -0
  256. {simcord-3.0.2 → simcord-3.2.0}/src/simcord/preview/static/media.js +0 -0
  257. {simcord-3.0.2 → simcord-3.2.0}/src/simcord/preview/static/select-drafts.js +0 -0
  258. {simcord-3.0.2 → simcord-3.2.0}/src/simcord/preview/static/selects.js +0 -0
  259. {simcord-3.0.2 → simcord-3.2.0}/src/simcord/preview/static/text.js +0 -0
  260. {simcord-3.0.2 → simcord-3.2.0}/src/simcord/preview/static/timeline.js +0 -0
  261. {simcord-3.0.2 → simcord-3.2.0}/src/simcord/preview/static/transport.js +0 -0
  262. {simcord-3.0.2 → simcord-3.2.0}/src/simcord/preview/static/vendor/highlight/LICENSE +0 -0
  263. {simcord-3.0.2 → simcord-3.2.0}/src/simcord/preview/static/vendor/highlight/SHA256SUMS +0 -0
  264. {simcord-3.0.2 → simcord-3.2.0}/src/simcord/preview/static/vendor/highlight/es/core.min.js +0 -0
  265. {simcord-3.0.2 → simcord-3.2.0}/src/simcord/preview/static/vendor/highlight/es/languages/bash.min.js +0 -0
  266. {simcord-3.0.2 → simcord-3.2.0}/src/simcord/preview/static/vendor/highlight/es/languages/cpp.min.js +0 -0
  267. {simcord-3.0.2 → simcord-3.2.0}/src/simcord/preview/static/vendor/highlight/es/languages/csharp.min.js +0 -0
  268. {simcord-3.0.2 → simcord-3.2.0}/src/simcord/preview/static/vendor/highlight/es/languages/css.min.js +0 -0
  269. {simcord-3.0.2 → simcord-3.2.0}/src/simcord/preview/static/vendor/highlight/es/languages/diff.min.js +0 -0
  270. {simcord-3.0.2 → simcord-3.2.0}/src/simcord/preview/static/vendor/highlight/es/languages/dockerfile.min.js +0 -0
  271. {simcord-3.0.2 → simcord-3.2.0}/src/simcord/preview/static/vendor/highlight/es/languages/go.min.js +0 -0
  272. {simcord-3.0.2 → simcord-3.2.0}/src/simcord/preview/static/vendor/highlight/es/languages/ini.min.js +0 -0
  273. {simcord-3.0.2 → simcord-3.2.0}/src/simcord/preview/static/vendor/highlight/es/languages/java.min.js +0 -0
  274. {simcord-3.0.2 → simcord-3.2.0}/src/simcord/preview/static/vendor/highlight/es/languages/javascript.min.js +0 -0
  275. {simcord-3.0.2 → simcord-3.2.0}/src/simcord/preview/static/vendor/highlight/es/languages/json.min.js +0 -0
  276. {simcord-3.0.2 → simcord-3.2.0}/src/simcord/preview/static/vendor/highlight/es/languages/markdown.min.js +0 -0
  277. {simcord-3.0.2 → simcord-3.2.0}/src/simcord/preview/static/vendor/highlight/es/languages/python.min.js +0 -0
  278. {simcord-3.0.2 → simcord-3.2.0}/src/simcord/preview/static/vendor/highlight/es/languages/rust.min.js +0 -0
  279. {simcord-3.0.2 → simcord-3.2.0}/src/simcord/preview/static/vendor/highlight/es/languages/sql.min.js +0 -0
  280. {simcord-3.0.2 → simcord-3.2.0}/src/simcord/preview/static/vendor/highlight/es/languages/typescript.min.js +0 -0
  281. {simcord-3.0.2 → simcord-3.2.0}/src/simcord/preview/static/vendor/highlight/es/languages/xml.min.js +0 -0
  282. {simcord-3.0.2 → simcord-3.2.0}/src/simcord/preview/static/vendor/highlight/es/languages/yaml.min.js +0 -0
  283. {simcord-3.0.2 → simcord-3.2.0}/src/simcord/preview/static/vendor/lottie/5.12.2/lottie_light_canvas.min.js +0 -0
  284. {simcord-3.0.2 → simcord-3.2.0}/src/simcord/preview/static/vendor/lottie/LICENSE +0 -0
  285. {simcord-3.0.2 → simcord-3.2.0}/src/simcord/preview/static/vendor/lottie/SHA256SUMS +0 -0
  286. {simcord-3.0.2 → simcord-3.2.0}/src/simcord/preview/static/workbench.js +0 -0
  287. {simcord-3.0.2 → simcord-3.2.0}/src/simcord/py.typed +0 -0
  288. {simcord-3.0.2 → simcord-3.2.0}/src/simcord/types.py +0 -0
  289. {simcord-3.0.2 → simcord-3.2.0}/tests/conformance/test_field_honesty_fuzz.py +0 -0
  290. {simcord-3.0.2 → simcord-3.2.0}/tests/conformance/test_serializers.py +0 -0
  291. {simcord-3.0.2 → simcord-3.2.0}/tests/fixtures/__init__.py +0 -0
  292. {simcord-3.0.2 → simcord-3.2.0}/tests/fixtures/commands/option_cases.json +0 -0
  293. {simcord-3.0.2 → simcord-3.2.0}/tests/fixtures/preview/__init__.py +0 -0
  294. {simcord-3.0.2 → simcord-3.2.0}/tests/fixtures/preview/catalog.py +0 -0
  295. {simcord-3.0.2 → simcord-3.2.0}/tests/fixtures/preview/coverage.json +0 -0
  296. {simcord-3.0.2 → simcord-3.2.0}/tests/fixtures/preview/measurements.json +0 -0
  297. {simcord-3.0.2 → simcord-3.2.0}/tests/fixtures/preview/moving-square.json +0 -0
  298. {simcord-3.0.2 → simcord-3.2.0}/tests/fixtures/preview/profiles.json +0 -0
  299. {simcord-3.0.2 → simcord-3.2.0}/tests/fixtures/preview/states.json +0 -0
  300. {simcord-3.0.2 → simcord-3.2.0}/tests/fixtures/preview/video.mp4 +0 -0
  301. {simcord-3.0.2 → simcord-3.2.0}/tests/fixtures/preview/voice.ogg +0 -0
  302. {simcord-3.0.2 → simcord-3.2.0}/tests/fixtures/sample_bot/__init__.py +0 -0
  303. {simcord-3.0.2 → simcord-3.2.0}/tests/fixtures/sample_bot/events.py +0 -0
  304. {simcord-3.0.2 → simcord-3.2.0}/tests/fixtures/sample_bot/general.py +0 -0
  305. {simcord-3.0.2 → simcord-3.2.0}/tests/fixtures/sample_bot/interactions.py +0 -0
  306. {simcord-3.0.2 → simcord-3.2.0}/tests/fixtures/sample_bot/moderation.py +0 -0
  307. {simcord-3.0.2 → simcord-3.2.0}/tests/fixtures/sample_bot/picker.py +0 -0
  308. {simcord-3.0.2 → simcord-3.2.0}/tests/integration/preview_helpers.py +0 -0
  309. {simcord-3.0.2 → simcord-3.2.0}/tests/integration/test_app_commands.py +0 -0
  310. {simcord-3.0.2 → simcord-3.2.0}/tests/integration/test_application_emojis.py +0 -0
  311. {simcord-3.0.2 → simcord-3.2.0}/tests/integration/test_asserts.py +0 -0
  312. {simcord-3.0.2 → simcord-3.2.0}/tests/integration/test_audit_logs.py +0 -0
  313. {simcord-3.0.2 → simcord-3.2.0}/tests/integration/test_automod.py +0 -0
  314. {simcord-3.0.2 → simcord-3.2.0}/tests/integration/test_bot_users_and_webhooks.py +0 -0
  315. {simcord-3.0.2 → simcord-3.2.0}/tests/integration/test_channels.py +0 -0
  316. {simcord-3.0.2 → simcord-3.2.0}/tests/integration/test_command_permissions.py +0 -0
  317. {simcord-3.0.2 → simcord-3.2.0}/tests/integration/test_command_visibility.py +0 -0
  318. {simcord-3.0.2 → simcord-3.2.0}/tests/integration/test_common_bot_sweep.py +0 -0
  319. {simcord-3.0.2 → simcord-3.2.0}/tests/integration/test_components_v2_interactions.py +0 -0
  320. {simcord-3.0.2 → simcord-3.2.0}/tests/integration/test_components_v2_messages.py +0 -0
  321. {simcord-3.0.2 → simcord-3.2.0}/tests/integration/test_coverage_sweep.py +0 -0
  322. {simcord-3.0.2 → simcord-3.2.0}/tests/integration/test_diagnostics.py +0 -0
  323. {simcord-3.0.2 → simcord-3.2.0}/tests/integration/test_edge_cases.py +0 -0
  324. {simcord-3.0.2 → simcord-3.2.0}/tests/integration/test_error_capture_and_edits.py +0 -0
  325. {simcord-3.0.2 → simcord-3.2.0}/tests/integration/test_expressions.py +0 -0
  326. {simcord-3.0.2 → simcord-3.2.0}/tests/integration/test_forum.py +0 -0
  327. {simcord-3.0.2 → simcord-3.2.0}/tests/integration/test_gateway_and_automod_exempt.py +0 -0
  328. {simcord-3.0.2 → simcord-3.2.0}/tests/integration/test_guilds.py +0 -0
  329. {simcord-3.0.2 → simcord-3.2.0}/tests/integration/test_intents.py +0 -0
  330. {simcord-3.0.2 → simcord-3.2.0}/tests/integration/test_invites.py +0 -0
  331. {simcord-3.0.2 → simcord-3.2.0}/tests/integration/test_lifecycle.py +0 -0
  332. {simcord-3.0.2 → simcord-3.2.0}/tests/integration/test_messages.py +0 -0
  333. {simcord-3.0.2 → simcord-3.2.0}/tests/integration/test_misc_branches.py +0 -0
  334. {simcord-3.0.2 → simcord-3.2.0}/tests/integration/test_moderation.py +0 -0
  335. {simcord-3.0.2 → simcord-3.2.0}/tests/integration/test_observation_contracts.py +0 -0
  336. {simcord-3.0.2 → simcord-3.2.0}/tests/integration/test_polls.py +0 -0
  337. {simcord-3.0.2 → simcord-3.2.0}/tests/integration/test_preview_capture.py +0 -0
  338. {simcord-3.0.2 → simcord-3.2.0}/tests/integration/test_preview_channel.py +0 -0
  339. {simcord-3.0.2 → simcord-3.2.0}/tests/integration/test_preview_command_picker.py +0 -0
  340. {simcord-3.0.2 → simcord-3.2.0}/tests/integration/test_preview_commands.py +0 -0
  341. {simcord-3.0.2 → simcord-3.2.0}/tests/integration/test_preview_coverage.py +0 -0
  342. {simcord-3.0.2 → simcord-3.2.0}/tests/integration/test_preview_dx.py +0 -0
  343. {simcord-3.0.2 → simcord-3.2.0}/tests/integration/test_preview_fixes.py +0 -0
  344. {simcord-3.0.2 → simcord-3.2.0}/tests/integration/test_preview_fonts.py +0 -0
  345. {simcord-3.0.2 → simcord-3.2.0}/tests/integration/test_preview_identity.py +0 -0
  346. {simcord-3.0.2 → simcord-3.2.0}/tests/integration/test_reactions.py +0 -0
  347. {simcord-3.0.2 → simcord-3.2.0}/tests/integration/test_reference_replay.py +0 -0
  348. {simcord-3.0.2 → simcord-3.2.0}/tests/integration/test_roles_and_ordering.py +0 -0
  349. {simcord-3.0.2 → simcord-3.2.0}/tests/integration/test_scheduled_events.py +0 -0
  350. {simcord-3.0.2 → simcord-3.2.0}/tests/integration/test_setup_hook_suspension.py +0 -0
  351. {simcord-3.0.2 → simcord-3.2.0}/tests/integration/test_sharding.py +0 -0
  352. {simcord-3.0.2 → simcord-3.2.0}/tests/integration/test_stage_instances.py +0 -0
  353. {simcord-3.0.2 → simcord-3.2.0}/tests/integration/test_threads.py +0 -0
  354. {simcord-3.0.2 → simcord-3.2.0}/tests/integration/test_transport_edges.py +0 -0
  355. {simcord-3.0.2 → simcord-3.2.0}/tests/integration/test_voice_state.py +0 -0
  356. {simcord-3.0.2 → simcord-3.2.0}/tests/integration/test_world_helpers.py +0 -0
  357. {simcord-3.0.2 → simcord-3.2.0}/tests/unit/test_assert_helpers.py +0 -0
  358. {simcord-3.0.2 → simcord-3.2.0}/tests/unit/test_backend_lookup_errors.py +0 -0
  359. {simcord-3.0.2 → simcord-3.2.0}/tests/unit/test_components_v2_validation.py +0 -0
  360. {simcord-3.0.2 → simcord-3.2.0}/tests/unit/test_dpy_internals.py +0 -0
  361. {simcord-3.0.2 → simcord-3.2.0}/tests/unit/test_errors.py +0 -0
  362. {simcord-3.0.2 → simcord-3.2.0}/tests/unit/test_lazy_import.py +0 -0
  363. {simcord-3.0.2 → simcord-3.2.0}/tests/unit/test_option_validation.py +0 -0
  364. {simcord-3.0.2 → simcord-3.2.0}/tests/unit/test_parity.py +0 -0
  365. {simcord-3.0.2 → simcord-3.2.0}/tests/unit/test_parity_tooling.py +0 -0
  366. {simcord-3.0.2 → simcord-3.2.0}/tests/unit/test_permission_validators.py +0 -0
  367. {simcord-3.0.2 → simcord-3.2.0}/tests/unit/test_pytest_plugin.py +0 -0
  368. {simcord-3.0.2 → simcord-3.2.0}/tests/unit/test_router.py +0 -0
@@ -4,13 +4,65 @@ This changelog is generated with [towncrier](https://towncrier.readthedocs.io/).
4
4
 
5
5
  <!-- towncrier release notes start -->
6
6
 
7
+ ## 3.2.0 (2026-10-22)
8
+
9
+ ### Features
10
+
11
+ - Add offline SKU and entitlement simulation: catalog/history/live fixtures, six REST routes, interaction access, gateway lifecycle, consumable fulfilment, replay and restart reconciliation. Publish explicit uncalibrated Discord edge policies and the observations needed to reach parity. Unify virtual UTC with chronological resource/timer advancement and supported SDK clock helpers; allocating IDs no longer moves time. Enforce one live Env per loop and preserve application entitlements when the bot leaves a guild.
12
+
13
+ Retain undelivered automatic-clock settlement failures until a public operation receives them, and select pytest virtual UTC from the unique running environment even when fixtures also expose inactive foreign worlds.
14
+
15
+ Add an opt-in user-subscription reference recorder with hidden token entry, raw protocol evidence, authorized interaction checkpoints, session-owned test-grant cleanup and a local boundary self-check.
16
+
17
+ Correct reference-recorder subscription scope detection using named SDK flags; accept unpublished user SKUs and reject guild-only or ambiguous matches.
18
+
19
+ Fix stalled reference interaction checkpoints by decoding discord.py's supported raw Gateway receive event; exercise probe advancement through a local WebSocket and SDK dispatch in the self-check.
20
+
21
+ Preserve rejected different-SKU and post-deletion recreation probes as reference evidence without aborting the remaining subscription capture sequence.
22
+
23
+ Add a manual Application Test Mode purchase capture mode for consumable/repeated consumption and durable wrong-kind consumption. Require operator test-mode confirmation and new type-4 app/SKU/user-matched entitlements before consumption; retain purchase IDs and leave preexisting/live entitlements untouched.
24
+
25
+ Prepare remaining reference evidence with a paced rejection/duplicate/pagination runner, optional guild and legitimate lifecycle capture, durable ownership journals and safe recovery. Add a raw-evidence validator for new and existing bundles; report unavailable prerequisites without claiming full parity or repeating purchase flows.
26
+
27
+ Extend the follow-up recorder with unpublished same-app guild SKU selection, two-server/two-member projection checks, cross-scope grant ordering and owner validation. Add explicit fresh deleted-unconsumed purchase capture and optional zero-total team live-upgrade capture, with per-server command provenance, raw-evidence validation, resumable manual purchase IDs and persistent real-subscription cancellation obligations.
28
+
29
+ Fix checkout baselines stopping on 100 retained test grants by traversing complete history and validating both cursor branches. Resume blocked checkout stages without replaying completed guild/purchase sections, correlate interaction checkpoints to their accepted invocation, print blocker reasons and numbered checkout steps, and retain deleted-unconsumed evidence without a stale missing-fixture blocker.
30
+
31
+ Recognize observed PURCHASE-type live subscription entitlements alongside APPLICATION_SUBSCRIPTION for selection and cancellation recovery, retaining exact app/SKU/user and type-4 mutation boundaries. Preserve cancellation-only recovery after failed checkout identification and validate cleaned historical commands across recorder resumes.
32
+
33
+ Use complete subscription-list objects as authoritative cancellation proof instead of discarding them when a redundant detail lookup fails. Clear stale blocker reasons after positive verification and retain exact-reference, audited offline recovery without repeating Discord actions or altering raw protocol streams.
34
+
35
+ Add an expiry-only follow-up that derives and hashes an authenticated canceled-subscription source, reuses its before interaction, revalidates the real billing schedule, captures entitlement/subscription events and polls, and needs one post-end command. Automate a provenance-safe post-end sandbox grant probe/cleanup, preserve late-start and unavailable-reporting limits, and resume without repeating successful interactions or known grant outcomes.
36
+
37
+ Add an immediate upgrade/scheduled-downgrade follow-up linked to existing canceled-A evidence. Reuse, resume or freely restart A, capture A→B and cross-tier sandbox overlap, record pending B→A renewal, and verify cancellation for every managed real entitlement. Preserve missing-probe-only recovery, bounded read-only propagation settling and raw downgrade evidence links; reject manual/wrong-type/SKU/owner create receipts before cleanup ownership.
38
+
39
+ Calibrate monetization against bounded Discord reference captures without claiming full parity. Migrate catalog fixtures to explicit subscription groups and matching scope flags; interaction entitlements are stably guild-first, while REST pagination descends for default/before and ascends for after. API test grants retain null dates and guild bot purchasers, cross-tier scoped subscription conflicts, structured owner-validation errors and corrected Invalid SKU/Unknown SKU statuses. Consume/delete now reject repeats or deleted rows with calibrated errors; type-4 purchases permit DELETE without implying recorder cleanup ownership. Correct consumed-field presence and fulfillment/source metadata, preserving observed upgrade UPDATE snapshots separately from committed REST state. Document discord.py 2.7.1 raw-history traversal and distinguish fixture policy, unsupported billing/reporting and unverified lifecycle boundaries.
40
+
41
+ Harden recorder credential redaction and verify test-create receipts before granting cleanup ownership. Require explicit unconsumed state for consumables while preserving omitted consumed fields on durable purchases.
42
+
43
+ ### Miscellaneous
44
+
45
+ - Require certifi>=2026.10.19 for documentation dependencies and update the lock, removing the affected eMudhra root certificate from its CA bundle. Existing environments require a separate dependency sync.
46
+
47
+
48
+ ## 3.1.0 (2026-10-16)
49
+
50
+ ### Features
51
+
52
+ - Thinking interaction defers now create fetchable, stable-ID originals; completing them through the first followup preserves defer-time visibility, while later followups are independent. Initial callbacks expire after three seconds (`10062`), separately from 15-minute continuation tokens (`10015`) and deleted originals (`10008`).
53
+
54
+ Add native actor and SDK forwarding with immutable rich snapshots, shared Message Content projection, permissions, and frozen Preview media authorized against the forward rather than the live source. Reject foreign-environment message handles before deterministic-ID resolution. Already-forwarded sources remain explicitly unsupported; snapshot metadata is copied while immutable CDN bytes are reused.
55
+
56
+ Keep strict task-scoped settlement diagnostics, but withdraw the unsafe blanket framework-run adapter recipe: only verified external handoffs should be declared, and no generic pydantic-ai adapter is established.
57
+
58
+
7
59
  ## 3.0.2 (2026-10-16)
8
60
 
9
61
  ### Bug fixes
10
62
 
11
63
  - Honor explicit application Message Content approval on REST message responses independently of Gateway intents, including nested references. Restore ping-enabled replies to regular bot messages without exposing replies to interaction responses, preserve contextual interaction/token access, and keep backend fixtures complete. The default `approved_intents=None` retains unrestricted REST access; explicit absent approval is restricted.
12
64
 
13
- Explicitly unapproved applications now receive redacted REST content, so tests that previously relied on unauthorized reads may fail and expose consumer bugs. Selecting a message in an interaction does not authorize later REST reads. Reply wire metadata and edit/deletion details remain offline-tested rather than live-Discord calibrated. ([#message-content-visibility](https://github.com/SilentHacks/simcord/issues/message-content-visibility))
65
+ Explicitly unapproved applications now receive redacted REST content, so tests that previously relied on unauthorized reads may fail and expose consumer bugs. Selecting a message in an interaction does not authorize later REST reads. Reply wire metadata and edit/deletion details remain offline-tested rather than live-Discord calibrated.
14
66
 
15
67
 
16
68
  ## 3.0.1 (2026-10-06)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: simcord
3
- Version: 3.0.2
3
+ Version: 3.2.0
4
4
  Summary: Offline discord.py bot testing with pytest. No network, token, or test server.
5
5
  Project-URL: Homepage, https://github.com/SilentHacks/simcord
6
6
  Project-URL: Documentation, https://simcord.readthedocs.io/
@@ -39,6 +39,7 @@ Requires-Dist: pytest>=8; extra == 'dev'
39
39
  Requires-Dist: ruff>=0.8; extra == 'dev'
40
40
  Requires-Dist: towncrier>=24; extra == 'dev'
41
41
  Provides-Extra: docs
42
+ Requires-Dist: certifi>=2026.10.19; extra == 'docs'
42
43
  Requires-Dist: mkdocs-material>=9; extra == 'docs'
43
44
  Requires-Dist: mkdocstrings[python]>=0.26; extra == 'docs'
44
45
  Provides-Extra: preview
@@ -24,7 +24,17 @@ top-level `simcord` package unless a module-qualified path is shown.
24
24
  `settle_timeout` is the default maximum time spent joining runnable bot work. A direct
25
25
  `await env.settle(timeout=...)` override is available; `idle=` is only the polling interval.
26
26
  Use `await env.external_wait(awaitable, reason="...")` for one explicitly scoped external
27
- input wait. Unknown waits remain active and produce a diagnostic timeout.
27
+ input wait. It applies to the calling task only. Unknown waits remain active and produce
28
+ a diagnostic timeout. Do not label whole framework runs as external waits; see the
29
+ [framework integration limits](concepts.md#actors-act-as-a-real-user).
30
+
31
+ ### `Env(approved_intents=None)`
32
+
33
+ `None` skips explicit privileged-intent approval checks and permits REST Message Content
34
+ access, preserving the permissive fixture default. An explicit `discord.Intents.none()`
35
+ denies that access. REST authorization follows this Portal/API setting independently of
36
+ the bot's requested Gateway mask; normal exemptions and interaction-selected access remain.
37
+ Bot-visible projections never redact authoritative actor/backend fixture state.
28
38
 
29
39
 
30
40
  ## Request observability
@@ -50,6 +60,20 @@ methods. See [Core concepts → Builders](concepts.md#builders-arrange-the-world
50
60
 
51
61
  ::: simcord.UserHandle
52
62
 
63
+ ### SKUs and entitlements
64
+
65
+ `Env.create_sku()` arranges explicit catalog entries (including non-grantable groups);
66
+ `seed_entitlement()` arranges history without CREATE; `await grant_entitlement()` emits
67
+ a live grant and settles. Recover a committed grant by ID with `get_entitlement()`.
68
+ See [SKUs & entitlements](guides/monetization.md) for calibrated REST fields/errors,
69
+ cross-tier sandbox conflicts, guild-first access, raw history traversal and the separate
70
+ fixture policies, unsupported subscription billing/reporting and unverified boundaries.
71
+
72
+ ::: simcord.SKUHandle
73
+
74
+ ::: simcord.EntitlementHandle
75
+
76
+
53
77
  ## Actors
54
78
 
55
79
  The simulated human that drives your bot. Created by `guild.add_member(...)`. See
@@ -57,11 +57,19 @@ Common builders:
57
57
  | `guild.create_text_channel(name, *, overwrites=…, topic=…)` | A `ChannelHandle`. |
58
58
  | `guild.create_role(name, *, permissions=…)` | A `RoleHandle`. |
59
59
  | `guild.add_member(user, *, roles=…, nick=…)` | A `MemberActor` (the user, now in the guild). |
60
+ | `env.create_sku(name)` | A `SKUHandle`; subscription groups are generated automatically. |
61
+ | `env.seed_entitlement(sku, guild=… / user=…)` | Historical `EntitlementHandle`, without CREATE. |
62
+ | `await env.grant_entitlement(sku, guild=… / user=…)` | Live entitlement, CREATE, and settled bot reactions. |
60
63
 
61
64
  !!! tip "`env.guild` is a shortcut"
62
65
  The first guild you create is available as `env.guild`, for the common single-guild
63
66
  test. `env.guild.create_text_channel(...)` reads nicely.
64
67
 
68
+ Live entitlement grants are deliberately async: unlike historical seeding, they deliver
69
+ a causal event and settle it. Both handles expose detached real SDK `snapshot()` models.
70
+ The grant may already be committed when settlement times out; recover by ID, do not retry.
71
+ See [SKUs & entitlements](guides/monetization.md) for lifecycle and parity boundaries.
72
+
65
73
  ## Actors — act as a real user
66
74
 
67
75
  **Actors are async and permission-checked.** A `MemberActor` (returned by
@@ -112,6 +120,22 @@ input:
112
120
  await env.external_wait(stop.wait(), reason="wait for the next message")
113
121
  ```
114
122
 
123
+ Declarations are **task-scoped**, not inherited. A framework can have three distinct waits:
124
+ graph-event delivery in its caller, a lifecycle wrapper waiting for that caller to finish,
125
+ and a model/provider task waiting for external input. Declaring only the provider wait does
126
+ not make the other two safe to ignore. Declare only verified external handoffs through
127
+ the framework's public hooks; do not infer safety from coroutine names or suppress
128
+ every sibling task.
129
+
130
+ Do **not** wrap an entire `agent.run()` or lifecycle `wrap_run(handler)` in
131
+ `external_wait`. Ordinary initialization and finalization also run there, and timers
132
+ created by the declaring task become virtual. Even an ordinary initialization sleep
133
+ can then stop progressing without `env.advance_time()`. No safe generic pydantic-ai
134
+ framework adapter is established here: declaring a provider/backoff wait alone does
135
+ not solve its orchestration waits. Keep undeclared workers visible as errors rather
136
+ than weakening settlement to make the integration pass.
137
+
138
+
115
139
  Successful settlement means no runnable tracked bot work remains. Public operations reject
116
140
  overlap before mutating state, and teardown cancels only bot-owned work; caller tasks survive.
117
141
 
@@ -119,7 +143,7 @@ The actor verbs, by area:
119
143
 
120
144
  | Area | Verbs |
121
145
  | --- | --- |
122
- | Text | `send`, `edit`, `delete`, `typing`, `send_dm` |
146
+ | Text | `send`, `forward`, `edit`, `delete`, `typing`, `send_dm` |
123
147
  | Reactions | `react`, `unreact` |
124
148
  | App commands | `slash`, `context_menu`, `autocomplete` |
125
149
  | Components | `click`, `select`, `submit_modal` |
@@ -25,6 +25,7 @@ Copy the following into your project's `AGENTS.md`, `CLAUDE.md`, or equivalent a
25
25
  - Exercise behavior through `simcord_env` builders and actors. Do not call a
26
26
  command callback directly when the contract depends on discord.py dispatch.
27
27
  - Slash, autocomplete, and context-menu actor calls enforce command visibility; inspect `available_commands()` or arrange permissions and contexts in the fixture, and use `UserHandle.slash()` / `.autocomplete()` for bot-DM commands.
28
+ - For premium flows use `create_sku`, `seed_entitlement` for history and async `grant_entitlement` for live CREATE; exercise real SDK consume/reconcile paths. Read the monetization guide's open parity checks before assuming undocumented Discord edge behavior.
28
29
  - Keep tests offline. Never request, invent, log, or connect with a Discord token.
29
30
  - Assert observable results from SimCord handles or the bot's real discord.py cache.
30
31
  - Keep pure business logic in ordinary unit tests and mock only application-owned
@@ -92,6 +93,11 @@ The agent can now implement or repair the command against a stable contract.
92
93
 
93
94
  This loop works for prefix commands, slash commands, autocomplete, context menus, views, modals, permissions, events, cooldowns, and sharded bots.
94
95
 
96
+ For premium access and fulfilment, use [SKUs & entitlements](monetization.md).
97
+ SimCord owns Discord resource simulation, not the bot's plans, allowances, trials or ledger.
98
+ Use `env.now` as an injected application clock and `env.time_context()` for manual-driver
99
+ SDK UTC reads; never replace the application's global datetime module.
100
+
95
101
  ## Prompt for a coding agent
96
102
 
97
103
  ```text
@@ -67,8 +67,9 @@ asyncio_mode = "auto"
67
67
 
68
68
  ## Without pytest
69
69
 
70
- Don't use pytest, or want a second environment in one test? Use `simcord.run` directly — an
71
- async context manager that yields the same `Env`:
70
+ Use `simcord.run` directly for explicit lifecycle control — an async context manager that
71
+ yields the same `Env`. Only one live environment per event loop is supported; shut it
72
+ down before starting another on that loop.
72
73
 
73
74
  ```python
74
75
  import simcord
@@ -108,6 +109,18 @@ migration bridge does not change command registration.
108
109
 
109
110
  External waits must be scoped with `await env.external_wait(awaitable, reason="...")`; unknown waits time out. Overlapping actor, builder, lifecycle, and time-control operations reject before mutation. Teardown cancels bot-owned work but leaves caller-owned tasks alive.
110
111
 
112
+ Supported SDK UTC helpers use the selected Env clock automatically during the actual
113
+ pytest test call (including fixtures aliased under another name) when there is one
114
+ running Env. Direct `async with simcord.run(...)` also selects it. For manual
115
+ `Env.start()` / `shutdown()` use `with env.time_context():` around driver SDK time reads;
116
+ inject `env.now` for your own application clocks. Custom async fixtures should use manual
117
+ start/shutdown rather than retaining a `run` ContextVar token across `yield`, which older
118
+ pytest-asyncio versions can resume in another context.
119
+
120
+ Before startup, `create_sku` and `seed_entitlement` can arrange the state that
121
+ `setup_hook` reconciles over REST. Live `grant_entitlement` and handle transitions require
122
+ a running Env. See [SKUs & entitlements](monetization.md).
123
+
111
124
  ```python
112
125
  # An isolated unit test that doesn't care about sync, and inspects errors itself:
113
126
  async with simcord.run(bot, strict_sync=False) as env:
@@ -108,6 +108,8 @@ the bot's event callbacks or SDK fetches to assert its restricted view. The tran
108
108
  also retains authoritative event data and is **not** a privacy-redacted export.
109
109
  These contracts are covered offline; reply wire metadata and edit/deletion details
110
110
  have not been calibrated against a live Discord capture.
111
+ Forward snapshots inherit their containing message's content access: redaction removes
112
+ content-bearing fields while retaining snapshot metadata.
111
113
 
112
114
  ## Member chunking
113
115
 
@@ -43,13 +43,13 @@ everything the bot did in response:
43
43
 
44
44
  ```python
45
45
  result.acknowledged # did the bot respond at all?
46
- result.deferred # responded with "thinking…" (deferred)
46
+ result.deferred # an original is still loading, or a component update was deferred
47
47
  result.ephemeral # the response is only visible to the invoker
48
48
  result.response # the response message (ResponseMessage | None)
49
49
  result.response.content # its text
50
50
  result.response.embeds # list[discord.Embed]
51
51
  result.response.message # the full discord.Message, when you need it
52
- result.followups # messages sent via interaction.followup
52
+ result.followups # independent followup messages, excluding completion of a thinking original
53
53
  result.modal # the modal payload, if one was shown
54
54
  result.autocomplete_choices # choices, for autocomplete interactions
55
55
  ```
@@ -83,23 +83,54 @@ matching error-acknowledgement transition.
83
83
 
84
84
  The full lifecycle behaves as on Discord:
85
85
 
86
- - **Deferring** — `await interaction.response.defer()` sets `result.deferred`; a later
87
- `edit_original_response` / `followup.send` materialises the message.
86
+ - **Initial deadline** — reply or defer before three seconds on the backend clock.
87
+ An unacknowledged callback at or after that deadline fails with `10062` (Unknown
88
+ Interaction), and its continuation token is invalid. Use `env.advance_time()` to
89
+ exercise this boundary without wall-clock sleeps.
90
+
91
+ - **Thinking defers (type 5)** — `await interaction.response.defer(thinking=True)` immediately
92
+ creates a fetchable original with `flags.loading`, its chosen ephemeral flag, and a stable
93
+ message ID. Public originals appear in public history; private ones are visible only to
94
+ the invoker.
95
+ - **Completing a thinking defer** — `edit_original_response()` is the preferred endpoint.
96
+ Discord's deprecated compatibility behavior also lets the first `followup.send()` complete
97
+ that same original. Both clear loading without changing its ID or visibility; a conflicting
98
+ `ephemeral=` flag cannot turn an existing public message private (or vice versa).
99
+ - **Component update defers (type 6)** — `defer()` without `thinking=True` keeps the clicked
100
+ message as the original. A followup is independent and does not consume or replace it.
88
101
  - **Double-acknowledging** — responding twice raises the real Discord error `40060`. (A
89
102
  callback that *fails* — e.g. a 400 from an oversized embed — does **not** consume the
90
103
  interaction, so a retried response still gets through.)
91
- - **Followups** — messages sent via `interaction.followup` land in `result.followups`.
92
- - **`@original` operations** — editing, fetching and deleting the original response resolve
93
- to the right message, including for deferred component interactions.
104
+ - **Followups** — after the original is completed, `interaction.followup.send()` creates an
105
+ independent message with its requested visibility. Only these messages enter
106
+ `result.followups`; completion of a thinking original is available as `result.response`.
107
+ - **`@original` operations** — fetching, editing and deleting resolve the same stored message.
108
+ Deletion returns `result.response` to `None`, and later original reads/edits fail with
109
+ `10008`; a deleted pending original cannot be resurrected by a followup. Deleting an
110
+ already-completed original does not prevent independent followups.
111
+ - **Token expiry** — continuation webhooks expire after 15 minutes on the backend clock.
112
+ Expired tokens fail with `10015`, distinct from an absent/deleted message's `10008`.
94
113
 
95
114
  ```python
96
115
  async def test_defer_then_followup(simcord_env):
97
116
  ...
98
117
  result = await alice.slash(channel, "slow-task")
99
- assert result.deferred
100
- assert result.followups[0].content == "Done!"
118
+ assert not result.deferred
119
+ assert result.response.content == "Done!"
120
+ assert result.followups == []
101
121
  ```
102
122
 
123
+ This corrects the former first-followup-as-new-message behavior: migrate assertions from
124
+ `result.followups[0]` to `result.response` when the send completes a thinking original.
125
+ Pending originals also consume a message ID immediately, rather than on completion.
126
+
127
+ discord.py caches `interaction.original_response()` as a snapshot. To observe a later edit,
128
+ fetch fresh state with `interaction.followup.fetch_message(original.id)`; SimCord does not
129
+ patch the SDK cache. Choose private visibility at the initial defer when a response may
130
+ contain sensitive data. If visibility must change, complete the original without sensitive
131
+ content before sending a new, independently visible followup.
132
+
133
+
103
134
  ## Autocomplete
104
135
 
105
136
  Type into an autocomplete option and get back the choices the bot offered:
@@ -47,6 +47,42 @@ Editing a message preserves immutable flags: ephemeral messages stay ephemeral,
47
47
  on. If your test setup denies the action, you get a `SetupError` — see
48
48
  [Permissions](permissions.md).
49
49
 
50
+ ## Forwarding immutable message snapshots
51
+
52
+ ```python
53
+ source = await alice.send(channel, "Evidence", attachments=[("proof.txt", b"original")])
54
+ forwarded = await alice.forward(source, destination)
55
+ assert forwarded.reference.type is discord.MessageReferenceType.forward
56
+ assert forwarded.message_snapshots[0].content == "Evidence"
57
+ assert await forwarded.message_snapshots[0].attachments[0].read() == b"original"
58
+ ```
59
+
60
+ Bot code can use native `await message.forward(destination)` too. Both paths store a
61
+ type-1 reference and a snapshot, rather than a reply's live `referenced_message`. Forwarding
62
+ does not ping the original author. Source visibility/history and destination send/attachment
63
+ permissions are enforced; SDK forwarding also requires application-level content access.
64
+ Actor message handles must belong to the same environment; colliding deterministic
65
+ IDs from another environment do not identify a local source. Cross-guild forwarding
66
+ within one environment remains supported, including SDK interaction originals and
67
+ webhook followups. Message handles retained across a bot restart remain environment-owned.
68
+
69
+ Snapshot metadata is copied when the forward is created and detached on observation. Later
70
+ source edits/deletion and mutations of returned objects do not alter it. Existing immutable
71
+ CDN bytes are reused, not duplicated. Actors and fixture history see authoritative snapshots;
72
+ bot Gateway/REST observations apply the normal Message Content policy.
73
+
74
+ Preview renders the frozen text, embeds, attachments, stickers and components without
75
+ inventing a source author or resolving a live reply. Historical interaction controls are
76
+ disabled; safe link buttons remain links. Snapshot downloads are authorized against the
77
+ forwarding message, not the source: readers of a deliberately shared forward need not have
78
+ source-channel access, but deleting the forward revokes its retained downloads.
79
+
80
+ **Explicit limit:** forwarding an already-forwarded message raises `NotImplementedError`.
81
+ Discord documents a nesting cap but not the transformation of nested snapshots plus outer
82
+ annotations; SimCord does not silently flatten/drop content or fabricate a Discord error.
83
+ Ordinary forwards, including outer annotations sent through the native SDK, are supported.
84
+
85
+
50
86
  ## Converters and checks run for real
51
87
 
52
88
  Because the cache is populated through discord.py's actual parsers, converters resolve