spaps 0.8.2__tar.gz → 0.8.3__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 (134) hide show
  1. {spaps-0.8.2 → spaps-0.8.3}/.gitignore +2 -2
  2. {spaps-0.8.2 → spaps-0.8.3}/CHANGELOG.md +22 -1
  3. {spaps-0.8.2 → spaps-0.8.3}/PKG-INFO +68 -2
  4. {spaps-0.8.2 → spaps-0.8.3}/README.md +67 -1
  5. spaps-0.8.3/docs/account-security.md +61 -0
  6. {spaps-0.8.2 → spaps-0.8.3}/pyproject.toml +1 -1
  7. {spaps-0.8.2 → spaps-0.8.3}/src/spaps_client/__init__.py +101 -1
  8. {spaps-0.8.2 → spaps-0.8.3}/src/spaps_client/async_client.py +36 -0
  9. {spaps-0.8.2 → spaps-0.8.3}/src/spaps_client/auth.py +910 -105
  10. {spaps-0.8.2 → spaps-0.8.3}/src/spaps_client/auth_async.py +720 -82
  11. {spaps-0.8.2 → spaps-0.8.3}/src/spaps_client/capability.py +8 -0
  12. spaps-0.8.3/src/spaps_client/cfo_actions.py +417 -0
  13. {spaps-0.8.2 → spaps-0.8.3}/src/spaps_client/client.py +36 -0
  14. spaps-0.8.3/src/spaps_client/dashboard.py +545 -0
  15. spaps-0.8.3/src/spaps_client/dashboard_async.py +360 -0
  16. {spaps-0.8.2 → spaps-0.8.3}/src/spaps_client/dev_portal.py +25 -5
  17. {spaps-0.8.2 → spaps-0.8.3}/src/spaps_client/dev_portal_async.py +7 -1
  18. {spaps-0.8.2 → spaps-0.8.3}/src/spaps_client/payments.py +26 -3
  19. {spaps-0.8.2 → spaps-0.8.3}/src/spaps_client/payments_async.py +21 -1
  20. spaps-0.8.3/src/spaps_client/purchase_journeys.py +541 -0
  21. spaps-0.8.3/src/spaps_client/purchase_journeys_async.py +131 -0
  22. spaps-0.8.3/src/spaps_client/storage.py +295 -0
  23. {spaps-0.8.2 → spaps-0.8.3}/src/spaps_client/webhooks.py +1 -1
  24. {spaps-0.8.2 → spaps-0.8.3}/src/spaps_client/whitelist.py +63 -39
  25. {spaps-0.8.2 → spaps-0.8.3}/src/spaps_client/whitelist_async.py +16 -36
  26. {spaps-0.8.2 → spaps-0.8.3}/tests/unit/test_async_client.py +1 -1
  27. {spaps-0.8.2 → spaps-0.8.3}/tests/unit/test_auth.py +1 -1
  28. spaps-0.8.3/tests/unit/test_auth_security_parity.py +495 -0
  29. {spaps-0.8.2 → spaps-0.8.3}/tests/unit/test_capability.py +77 -0
  30. spaps-0.8.3/tests/unit/test_cfo_actions.py +171 -0
  31. spaps-0.8.3/tests/unit/test_dashboard.py +221 -0
  32. spaps-0.8.3/tests/unit/test_dashboard_authentication.py +167 -0
  33. spaps-0.8.3/tests/unit/test_dashboard_billing.py +64 -0
  34. spaps-0.8.3/tests/unit/test_dashboard_communications.py +39 -0
  35. spaps-0.8.3/tests/unit/test_dashboard_entitlement_options.py +38 -0
  36. spaps-0.8.3/tests/unit/test_dashboard_management_routes.py +58 -0
  37. spaps-0.8.3/tests/unit/test_dashboard_onboarding.py +312 -0
  38. {spaps-0.8.2 → spaps-0.8.3}/tests/unit/test_dev_portal.py +99 -0
  39. {spaps-0.8.2 → spaps-0.8.3}/tests/unit/test_dev_portal_async.py +73 -1
  40. spaps-0.8.3/tests/unit/test_metadata.py +67 -0
  41. {spaps-0.8.2 → spaps-0.8.3}/tests/unit/test_passkey_parity.py +34 -17
  42. spaps-0.8.3/tests/unit/test_prepared_checkout.py +99 -0
  43. spaps-0.8.3/tests/unit/test_purchase_journeys.py +442 -0
  44. spaps-0.8.3/tests/unit/test_session_context_capabilities.py +40 -0
  45. spaps-0.8.3/tests/unit/test_staged_passkey_bootstrap.py +365 -0
  46. spaps-0.8.3/tests/unit/test_storage.py +373 -0
  47. {spaps-0.8.2 → spaps-0.8.3}/tests/unit/test_webhooks.py +20 -0
  48. {spaps-0.8.2 → spaps-0.8.3}/tests/unit/test_whitelist.py +211 -2
  49. spaps-0.8.2/src/spaps_client/storage.py +0 -199
  50. spaps-0.8.2/tests/unit/test_metadata.py +0 -32
  51. spaps-0.8.2/tests/unit/test_storage.py +0 -166
  52. {spaps-0.8.2 → spaps-0.8.3}/FULL_CHECKLIST.md +0 -0
  53. {spaps-0.8.2 → spaps-0.8.3}/LICENSE +0 -0
  54. {spaps-0.8.2 → spaps-0.8.3}/TDD_PLAN.md +0 -0
  55. {spaps-0.8.2 → spaps-0.8.3}/TODO.md +0 -0
  56. {spaps-0.8.2 → spaps-0.8.3}/docs/RELEASE_CHECKLIST.md +0 -0
  57. {spaps-0.8.2 → spaps-0.8.3}/scripts/manage_version.py +0 -0
  58. {spaps-0.8.2 → spaps-0.8.3}/src/spaps_client/app_links.py +0 -0
  59. {spaps-0.8.2 → spaps-0.8.3}/src/spaps_client/app_links_async.py +0 -0
  60. {spaps-0.8.2 → spaps-0.8.3}/src/spaps_client/cfo.py +0 -0
  61. {spaps-0.8.2 → spaps-0.8.3}/src/spaps_client/cfo_async.py +0 -0
  62. {spaps-0.8.2 → spaps-0.8.3}/src/spaps_client/cfo_browser_session.py +0 -0
  63. {spaps-0.8.2 → spaps-0.8.3}/src/spaps_client/config.py +0 -0
  64. {spaps-0.8.2 → spaps-0.8.3}/src/spaps_client/crypto.py +0 -0
  65. {spaps-0.8.2 → spaps-0.8.3}/src/spaps_client/crypto_async.py +0 -0
  66. {spaps-0.8.2 → spaps-0.8.3}/src/spaps_client/dayrate.py +0 -0
  67. {spaps-0.8.2 → spaps-0.8.3}/src/spaps_client/dayrate_async.py +0 -0
  68. {spaps-0.8.2 → spaps-0.8.3}/src/spaps_client/developer_projects.py +0 -0
  69. {spaps-0.8.2 → spaps-0.8.3}/src/spaps_client/device_flow.py +0 -0
  70. {spaps-0.8.2 → spaps-0.8.3}/src/spaps_client/email.py +0 -0
  71. {spaps-0.8.2 → spaps-0.8.3}/src/spaps_client/email_async.py +0 -0
  72. {spaps-0.8.2 → spaps-0.8.3}/src/spaps_client/entitlements.py +0 -0
  73. {spaps-0.8.2 → spaps-0.8.3}/src/spaps_client/entitlements_async.py +0 -0
  74. {spaps-0.8.2 → spaps-0.8.3}/src/spaps_client/http.py +0 -0
  75. {spaps-0.8.2 → spaps-0.8.3}/src/spaps_client/http_async.py +0 -0
  76. {spaps-0.8.2 → spaps-0.8.3}/src/spaps_client/issue_reporting.py +0 -0
  77. {spaps-0.8.2 → spaps-0.8.3}/src/spaps_client/issue_reporting_async.py +0 -0
  78. {spaps-0.8.2 → spaps-0.8.3}/src/spaps_client/marketing.py +0 -0
  79. {spaps-0.8.2 → spaps-0.8.3}/src/spaps_client/metrics.py +0 -0
  80. {spaps-0.8.2 → spaps-0.8.3}/src/spaps_client/metrics_async.py +0 -0
  81. {spaps-0.8.2 → spaps-0.8.3}/src/spaps_client/permissions.py +0 -0
  82. {spaps-0.8.2 → spaps-0.8.3}/src/spaps_client/py.typed +0 -0
  83. {spaps-0.8.2 → spaps-0.8.3}/src/spaps_client/secure_messages.py +0 -0
  84. {spaps-0.8.2 → spaps-0.8.3}/src/spaps_client/secure_messages_async.py +0 -0
  85. {spaps-0.8.2 → spaps-0.8.3}/src/spaps_client/sessions.py +0 -0
  86. {spaps-0.8.2 → spaps-0.8.3}/src/spaps_client/sessions_async.py +0 -0
  87. {spaps-0.8.2 → spaps-0.8.3}/src/spaps_client/skill_evals.py +0 -0
  88. {spaps-0.8.2 → spaps-0.8.3}/src/spaps_client/skill_evals_async.py +0 -0
  89. {spaps-0.8.2 → spaps-0.8.3}/src/spaps_client/support_telemetry.py +0 -0
  90. {spaps-0.8.2 → spaps-0.8.3}/src/spaps_client/token_gates.py +0 -0
  91. {spaps-0.8.2 → spaps-0.8.3}/src/spaps_client/token_gates_async.py +0 -0
  92. {spaps-0.8.2 → spaps-0.8.3}/src/spaps_client/usage.py +0 -0
  93. {spaps-0.8.2 → spaps-0.8.3}/src/spaps_client/usage_async.py +0 -0
  94. {spaps-0.8.2 → spaps-0.8.3}/src/spaps_client/users.py +0 -0
  95. {spaps-0.8.2 → spaps-0.8.3}/src/spaps_client/users_async.py +0 -0
  96. {spaps-0.8.2 → spaps-0.8.3}/src/spaps_client/x402.py +0 -0
  97. {spaps-0.8.2 → spaps-0.8.3}/src/spaps_client/x402_async.py +0 -0
  98. {spaps-0.8.2 → spaps-0.8.3}/tests/conftest.py +0 -0
  99. {spaps-0.8.2 → spaps-0.8.3}/tests/integration/test_build.py +0 -0
  100. {spaps-0.8.2 → spaps-0.8.3}/tests/integration/test_smoke.py +0 -0
  101. {spaps-0.8.2 → spaps-0.8.3}/tests/unit/test_app_links.py +0 -0
  102. {spaps-0.8.2 → spaps-0.8.3}/tests/unit/test_auth_methods_origin.py +0 -0
  103. {spaps-0.8.2 → spaps-0.8.3}/tests/unit/test_auth_sessions.py +0 -0
  104. {spaps-0.8.2 → spaps-0.8.3}/tests/unit/test_cfo.py +0 -0
  105. {spaps-0.8.2 → spaps-0.8.3}/tests/unit/test_cfo_async.py +0 -0
  106. {spaps-0.8.2 → spaps-0.8.3}/tests/unit/test_cfo_browser_session.py +0 -0
  107. {spaps-0.8.2 → spaps-0.8.3}/tests/unit/test_client.py +0 -0
  108. {spaps-0.8.2 → spaps-0.8.3}/tests/unit/test_config.py +0 -0
  109. {spaps-0.8.2 → spaps-0.8.3}/tests/unit/test_crypto_invoice_flags.py +0 -0
  110. {spaps-0.8.2 → spaps-0.8.3}/tests/unit/test_dayrate.py +0 -0
  111. {spaps-0.8.2 → spaps-0.8.3}/tests/unit/test_developer_projects.py +0 -0
  112. {spaps-0.8.2 → spaps-0.8.3}/tests/unit/test_device_flow.py +0 -0
  113. {spaps-0.8.2 → spaps-0.8.3}/tests/unit/test_email.py +0 -0
  114. {spaps-0.8.2 → spaps-0.8.3}/tests/unit/test_entitlements.py +0 -0
  115. {spaps-0.8.2 → spaps-0.8.3}/tests/unit/test_http.py +0 -0
  116. {spaps-0.8.2 → spaps-0.8.3}/tests/unit/test_issue_reporting.py +0 -0
  117. {spaps-0.8.2 → spaps-0.8.3}/tests/unit/test_marketing.py +0 -0
  118. {spaps-0.8.2 → spaps-0.8.3}/tests/unit/test_metrics.py +0 -0
  119. {spaps-0.8.2 → spaps-0.8.3}/tests/unit/test_payments.py +0 -0
  120. {spaps-0.8.2 → spaps-0.8.3}/tests/unit/test_permissions.py +0 -0
  121. {spaps-0.8.2 → spaps-0.8.3}/tests/unit/test_quickstart_full_adoption_async.py +0 -0
  122. {spaps-0.8.2 → spaps-0.8.3}/tests/unit/test_readme_snippets.py +0 -0
  123. {spaps-0.8.2 → spaps-0.8.3}/tests/unit/test_recovery_landing_profile.py +0 -0
  124. {spaps-0.8.2 → spaps-0.8.3}/tests/unit/test_recovery_mfa.py +0 -0
  125. {spaps-0.8.2 → spaps-0.8.3}/tests/unit/test_secure_messages.py +0 -0
  126. {spaps-0.8.2 → spaps-0.8.3}/tests/unit/test_sessions.py +0 -0
  127. {spaps-0.8.2 → spaps-0.8.3}/tests/unit/test_skill_evals.py +0 -0
  128. {spaps-0.8.2 → spaps-0.8.3}/tests/unit/test_support_telemetry.py +0 -0
  129. {spaps-0.8.2 → spaps-0.8.3}/tests/unit/test_token_gates.py +0 -0
  130. {spaps-0.8.2 → spaps-0.8.3}/tests/unit/test_token_gates_docs_snippets.py +0 -0
  131. {spaps-0.8.2 → spaps-0.8.3}/tests/unit/test_usage.py +0 -0
  132. {spaps-0.8.2 → spaps-0.8.3}/tests/unit/test_users.py +0 -0
  133. {spaps-0.8.2 → spaps-0.8.3}/tests/unit/test_users_async.py +0 -0
  134. {spaps-0.8.2 → spaps-0.8.3}/tests/unit/test_x402.py +0 -0
@@ -1,5 +1,5 @@
1
1
  # Dependencies
2
- node_modules/
2
+ node_modules
3
3
  *.log
4
4
  npm-debug.log*
5
5
  yarn-debug.log*
@@ -13,7 +13,7 @@ sweet-potato-threat-model.md
13
13
  *.py[cod]
14
14
  *.pyo
15
15
  *.pyd
16
- .venv/
16
+ .venv
17
17
  *.egg-info/
18
18
 
19
19
  # Testing
@@ -8,9 +8,30 @@ The format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/) and
8
8
 
9
9
  - No changes yet.
10
10
 
11
+ ## [0.8.3] - 2026-10-24
12
+
13
+ **Unpublished source candidate.** This date records candidate preparation, not
14
+ registry publication or downstream deployment.
15
+
16
+ - Prepare the `0.8.3.dev0` checkout as `0.8.3` alongside server `0.9.1`
17
+ ([54a0059a](https://github.com/build000r/sweet-potato/commit/54a0059aba568f750da284496c4863a0db695cf4)).
18
+ - Include sync/async purchase-journey and developer-portal helpers, scoped
19
+ dashboard controls, capability decisions, and authenticated CFO action-journey
20
+ helpers added since the retained public `0.8.2` baseline.
21
+
22
+ ## [0.8.3.dev0] - 2026-10-22
23
+
24
+ **Unpublished development checkout.**
25
+
26
+ - Separate the expanded checkout API from the public `0.8.2` artifact observed
27
+ on 2026-10-22. The offline surface guard rejects different APIs using the same
28
+ version and validates retained artifact provenance before comparing them.
29
+
11
30
  ## [0.8.2] - 2026-10-01
12
31
 
13
- **Unpublished source candidate.** Supersedes tagged but unpublished `0.8.1`.
32
+ **Public artifact observed 2026-10-22.** This heading originally described a
33
+ source candidate. Registry observation does not establish its publication date.
34
+ The original candidate superseded tagged but unpublished `0.8.1`.
14
35
 
15
36
  - Repack the reviewed source cohort with corrected exact-artifact inspection; no Python client API change.
16
37
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: spaps
3
- Version: 0.8.2
3
+ Version: 0.8.3
4
4
  Summary: Python client for the hosted Sweet Potato / SPAPS API
5
5
  Project-URL: Homepage, https://sweetpotato.dev
6
6
  Project-URL: Documentation, https://docs.sweetpotato.dev
@@ -214,6 +214,28 @@ credential = client.auth.webauthn_register_verify(
214
214
 
215
215
  The sync and async auth clients also cover passkey-first bootstrap, credential listing/labeling/revocation, application passkey profiles, action-bound step-up, user recovery, and administrator recovery preparation/execution. Pass `origin` on ceremonies so SPAPS can validate the browser origin. Public ceremonies send only `X-API-Key`; lifecycle, profile, step-up, and administrator operations also require a bearer token, using an explicit `access_token` before stored credentials.
216
216
 
217
+ Passkey signup's `webauthn_bootstrap_verify` returns a restricted
218
+ `WebAuthnBootstrapPendingResponse`, without account/session creation or token
219
+ storage. Keep its `bootstrap_grant` in attempt memory, obtain a fresh assertion
220
+ from the user's authenticator, then submit that grant, `challenge_id`, and
221
+ credential through `webauthn_bootstrap_assertion_verify`. Only the validated final
222
+ response stores tokens. Preserve the original application, Origin/RP, user
223
+ handle, and expiry; discard the grant when the attempt ends. Ordinary membership
224
+ does not confer a dashboard grant.
225
+
226
+ Bootstrap also binds configuration identity/version and the security profile
227
+ fingerprint across both ceremonies; security profile changes require fresh
228
+ options, while cosmetic display-name changes remain valid. Signup requests a
229
+ discoverable passkey with `resident_key="required"`; authenticated enrollment
230
+ keeps its configured preference. This changed contract in checkout Python
231
+ `0.8.3` requires coordinated server and matching SDK/types/Python package/channel
232
+ adoption, including the server/keyscope stored-rule contract. Publishable final
233
+ assertion needs an exact singleton POST rule in
234
+ `publishable_key_access.allowed_routes`; declared scopes, legacy defaults, and
235
+ pattern backfill cannot enable it. Compiled browser/dashboard settings contain
236
+ the rule; custom/legacy settings need an explicit adoption upgrade. Checkout
237
+ source does not prove published or deployed compatibility.
238
+
217
239
  Python coordinates the server half of each WebAuthn ceremony. It returns typed registration or assertion options and accepts the credential JSON produced by a browser or another WebAuthn-capable user agent. It does not call `navigator.credentials`, emulate an authenticator, perform CTAP, or sign challenges. Recovery registration rotates the recovery grant; it does not issue login tokens until the new credential completes the recovery assertion.
218
240
 
219
241
  ## Core Surface
@@ -853,6 +875,9 @@ payload = verify_spaps_webhook(
853
875
  print(payload.type)
854
876
  ```
855
877
 
878
+ The default five-minute verification window allows bounded clock skew in either
879
+ direction. Timestamps farther in the future or past are rejected.
880
+
856
881
  ## Validation
857
882
 
858
883
  From the repository root:
@@ -920,6 +945,8 @@ The default is `InMemoryTokenStorage`. For persistence, pass
920
945
  `FileTokenStorage(path=...)`: it replaces the token file only after a complete
921
946
  write, keeps file permissions owner-only on POSIX, and rejects symlinked storage
922
947
  directories. Loading ignores symlinked or malformed token files.
948
+ On platforms without directory-relative no-follow I/O, symlink checks are
949
+ best-effort; use a private storage path with trusted ancestors.
923
950
 
924
951
  ### Does it retry requests automatically?
925
952
 
@@ -927,11 +954,17 @@ It can. Pass `RetryConfig` if you want retry and backoff behavior.
927
954
 
928
955
  ## Metadata
929
956
 
957
+ This checkout is the unpublished `0.8.3` source candidate. It includes APIs beyond
958
+ the retained public `0.8.2` artifact and has no publication receipt. After publishing a version,
959
+ advance the checkout to its next development version before changing its API.
960
+ The offline package-surface check compares checkout declarations with retained
961
+ public artifacts; release and consumer adoption still require their own receipts.
962
+
930
963
  The metadata below describes this checkout. `latest_version` is the source
931
964
  package version; it does not establish registry publication or availability.
932
965
 
933
966
  - `package_name`: `spaps`
934
- - `latest_version`: `0.8.2`
967
+ - `latest_version`: `0.8.3`
935
968
  - `minimum_runtime`: `Python >=3.9`
936
969
  - `api_base_url`: `https://api.sweetpotato.dev`
937
970
 
@@ -983,3 +1016,36 @@ The bridge constructor requires `spaps_origin`, allowed application `origin`, br
983
1016
  `response_handler(status, cookies, location)` receives ordered raw allowlisted `Set-Cookie` values, including deletions and failure responses. It must apply them at the actual SPAPS browser origin before forwarding an optional validated Intuit navigation. A BFF on an unrelated hostname cannot set SPAPS host-only cookies. The callback URLs and cookies are sensitive trusted bridge inputs: do not serialize them into ordinary application results or logs. A local cookie sink does not provision this deployed bridge.
984
1017
 
985
1018
  On refresh-cookie rotation, `invalidate_access` must invalidate the cached bearer. Before another deliberate call, the host must reacquire current access through shared SPAPS auth, apply any further cookie updates, and expose that new bearer. Neither adapter refreshes or replays the operation. `CfoError` exposes only bounded `code`, `status`, and optional `request_id`; it does not retain the raw HTTP error. Deployment still requires the real origin/CSRF/cookie bridge, safe host logging, external CFO executor adoption, and the human retirement gate. These checkout APIs do not establish published package availability.
1019
+ # Dashboard (checkout)
1020
+
1021
+ `client.dashboard` provides `context()`, `list_applications()`,
1022
+ `get_application(id)`, `list_records(kind, **filters)`, and `get_record(kind, id)`.
1023
+ `AsyncDashboardClient` has matching asynchronous reads. Each read requires an
1024
+ application credential and a current user session. Results contain stored
1025
+ records for owned applications, or global-admin scope, with pagination and
1026
+ source freshness. Retained email content is detail-only; authentication content
1027
+ is restricted. See [dashboard reads](https://github.com/build000r/sweet-potato/blob/main/docs/api/dashboard.md).
1028
+ Application dictionaries preserve `entitlement_options` for configured keys,
1029
+ sources and enabled flags, independently of `overview.stored_grants`. Missing/null
1030
+ catalog data from older servers is unavailable, not an empty catalog.
1031
+ Use `list_records('email-templates')` for template definitions and
1032
+ `list_records('email-logs')` for individual sends. Sync and async dictionaries
1033
+ preserve definition name/key, enabled state, stream, category and dates.
1034
+ Package publication and production deployment require separate evidence.
1035
+
1036
+ For the dedicated operator console, the sync and async clients also expose
1037
+ `management_options(application_id)`, `invite`, `send_recovery`, `grant`, `revoke`,
1038
+ `create_entitlement_option`, and `create_application`. These require the console application's session and
1039
+ Viewer/Support/Admin entitlement checks; Viewer cannot write. Support handles
1040
+ approved ordinary app access, while Admin also manages console access and creates
1041
+ applications. See [console roles and setup](https://github.com/build000r/sweet-potato/blob/main/docs/guides/dashboard-access.md).
1042
+ Application option creation requires exact-app Admin authority and adds a manual
1043
+ key without granting user access. Existing console applications need the new exact
1044
+ POST route opt-in; checkout source does not establish production availability.
1045
+
1046
+ Application Billing uses `billing_workspace(application_id)` and
1047
+ `billing_action(operation, application_id=..., expected_revision=..., data=...)`
1048
+ in both clients. Actions use a fixed operation list and retain the caller's
1049
+ session; the server rechecks target-app authority. Account attachment requires
1050
+ portfolio Admin. An ambiguous provider failure is not retried automatically.
1051
+ See [Billing configuration](https://github.com/build000r/sweet-potato/blob/main/docs/demos/spaps-dashboard/BILLING.md).
@@ -146,6 +146,28 @@ credential = client.auth.webauthn_register_verify(
146
146
 
147
147
  The sync and async auth clients also cover passkey-first bootstrap, credential listing/labeling/revocation, application passkey profiles, action-bound step-up, user recovery, and administrator recovery preparation/execution. Pass `origin` on ceremonies so SPAPS can validate the browser origin. Public ceremonies send only `X-API-Key`; lifecycle, profile, step-up, and administrator operations also require a bearer token, using an explicit `access_token` before stored credentials.
148
148
 
149
+ Passkey signup's `webauthn_bootstrap_verify` returns a restricted
150
+ `WebAuthnBootstrapPendingResponse`, without account/session creation or token
151
+ storage. Keep its `bootstrap_grant` in attempt memory, obtain a fresh assertion
152
+ from the user's authenticator, then submit that grant, `challenge_id`, and
153
+ credential through `webauthn_bootstrap_assertion_verify`. Only the validated final
154
+ response stores tokens. Preserve the original application, Origin/RP, user
155
+ handle, and expiry; discard the grant when the attempt ends. Ordinary membership
156
+ does not confer a dashboard grant.
157
+
158
+ Bootstrap also binds configuration identity/version and the security profile
159
+ fingerprint across both ceremonies; security profile changes require fresh
160
+ options, while cosmetic display-name changes remain valid. Signup requests a
161
+ discoverable passkey with `resident_key="required"`; authenticated enrollment
162
+ keeps its configured preference. This changed contract in checkout Python
163
+ `0.8.3` requires coordinated server and matching SDK/types/Python package/channel
164
+ adoption, including the server/keyscope stored-rule contract. Publishable final
165
+ assertion needs an exact singleton POST rule in
166
+ `publishable_key_access.allowed_routes`; declared scopes, legacy defaults, and
167
+ pattern backfill cannot enable it. Compiled browser/dashboard settings contain
168
+ the rule; custom/legacy settings need an explicit adoption upgrade. Checkout
169
+ source does not prove published or deployed compatibility.
170
+
149
171
  Python coordinates the server half of each WebAuthn ceremony. It returns typed registration or assertion options and accepts the credential JSON produced by a browser or another WebAuthn-capable user agent. It does not call `navigator.credentials`, emulate an authenticator, perform CTAP, or sign challenges. Recovery registration rotates the recovery grant; it does not issue login tokens until the new credential completes the recovery assertion.
150
172
 
151
173
  ## Core Surface
@@ -785,6 +807,9 @@ payload = verify_spaps_webhook(
785
807
  print(payload.type)
786
808
  ```
787
809
 
810
+ The default five-minute verification window allows bounded clock skew in either
811
+ direction. Timestamps farther in the future or past are rejected.
812
+
788
813
  ## Validation
789
814
 
790
815
  From the repository root:
@@ -852,6 +877,8 @@ The default is `InMemoryTokenStorage`. For persistence, pass
852
877
  `FileTokenStorage(path=...)`: it replaces the token file only after a complete
853
878
  write, keeps file permissions owner-only on POSIX, and rejects symlinked storage
854
879
  directories. Loading ignores symlinked or malformed token files.
880
+ On platforms without directory-relative no-follow I/O, symlink checks are
881
+ best-effort; use a private storage path with trusted ancestors.
855
882
 
856
883
  ### Does it retry requests automatically?
857
884
 
@@ -859,11 +886,17 @@ It can. Pass `RetryConfig` if you want retry and backoff behavior.
859
886
 
860
887
  ## Metadata
861
888
 
889
+ This checkout is the unpublished `0.8.3` source candidate. It includes APIs beyond
890
+ the retained public `0.8.2` artifact and has no publication receipt. After publishing a version,
891
+ advance the checkout to its next development version before changing its API.
892
+ The offline package-surface check compares checkout declarations with retained
893
+ public artifacts; release and consumer adoption still require their own receipts.
894
+
862
895
  The metadata below describes this checkout. `latest_version` is the source
863
896
  package version; it does not establish registry publication or availability.
864
897
 
865
898
  - `package_name`: `spaps`
866
- - `latest_version`: `0.8.2`
899
+ - `latest_version`: `0.8.3`
867
900
  - `minimum_runtime`: `Python >=3.9`
868
901
  - `api_base_url`: `https://api.sweetpotato.dev`
869
902
 
@@ -915,3 +948,36 @@ The bridge constructor requires `spaps_origin`, allowed application `origin`, br
915
948
  `response_handler(status, cookies, location)` receives ordered raw allowlisted `Set-Cookie` values, including deletions and failure responses. It must apply them at the actual SPAPS browser origin before forwarding an optional validated Intuit navigation. A BFF on an unrelated hostname cannot set SPAPS host-only cookies. The callback URLs and cookies are sensitive trusted bridge inputs: do not serialize them into ordinary application results or logs. A local cookie sink does not provision this deployed bridge.
916
949
 
917
950
  On refresh-cookie rotation, `invalidate_access` must invalidate the cached bearer. Before another deliberate call, the host must reacquire current access through shared SPAPS auth, apply any further cookie updates, and expose that new bearer. Neither adapter refreshes or replays the operation. `CfoError` exposes only bounded `code`, `status`, and optional `request_id`; it does not retain the raw HTTP error. Deployment still requires the real origin/CSRF/cookie bridge, safe host logging, external CFO executor adoption, and the human retirement gate. These checkout APIs do not establish published package availability.
951
+ # Dashboard (checkout)
952
+
953
+ `client.dashboard` provides `context()`, `list_applications()`,
954
+ `get_application(id)`, `list_records(kind, **filters)`, and `get_record(kind, id)`.
955
+ `AsyncDashboardClient` has matching asynchronous reads. Each read requires an
956
+ application credential and a current user session. Results contain stored
957
+ records for owned applications, or global-admin scope, with pagination and
958
+ source freshness. Retained email content is detail-only; authentication content
959
+ is restricted. See [dashboard reads](https://github.com/build000r/sweet-potato/blob/main/docs/api/dashboard.md).
960
+ Application dictionaries preserve `entitlement_options` for configured keys,
961
+ sources and enabled flags, independently of `overview.stored_grants`. Missing/null
962
+ catalog data from older servers is unavailable, not an empty catalog.
963
+ Use `list_records('email-templates')` for template definitions and
964
+ `list_records('email-logs')` for individual sends. Sync and async dictionaries
965
+ preserve definition name/key, enabled state, stream, category and dates.
966
+ Package publication and production deployment require separate evidence.
967
+
968
+ For the dedicated operator console, the sync and async clients also expose
969
+ `management_options(application_id)`, `invite`, `send_recovery`, `grant`, `revoke`,
970
+ `create_entitlement_option`, and `create_application`. These require the console application's session and
971
+ Viewer/Support/Admin entitlement checks; Viewer cannot write. Support handles
972
+ approved ordinary app access, while Admin also manages console access and creates
973
+ applications. See [console roles and setup](https://github.com/build000r/sweet-potato/blob/main/docs/guides/dashboard-access.md).
974
+ Application option creation requires exact-app Admin authority and adds a manual
975
+ key without granting user access. Existing console applications need the new exact
976
+ POST route opt-in; checkout source does not establish production availability.
977
+
978
+ Application Billing uses `billing_workspace(application_id)` and
979
+ `billing_action(operation, application_id=..., expected_revision=..., data=...)`
980
+ in both clients. Actions use a fixed operation list and retain the caller's
981
+ session; the server rechecks target-app authority. Account attachment requires
982
+ portfolio Admin. An ambiguous provider failure is not retried automatically.
983
+ See [Billing configuration](https://github.com/build000r/sweet-potato/blob/main/docs/demos/spaps-dashboard/BILLING.md).
@@ -0,0 +1,61 @@
1
+ # Account security and human GitHub OAuth
2
+
3
+ `client.auth` exposes the same canonical account-security endpoints as the
4
+ TypeScript SDK. `AsyncSpapsClient` exposes the same method names and arguments;
5
+ await each call. Result models are exported from `spaps_client.auth`.
6
+
7
+ | Python method | Canonical path under `/api/auth` |
8
+ | --- | --- |
9
+ | `get_security_summary()` | `GET /security` |
10
+ | `security_wallet_challenge(wallet_address=..., chain_type=...)` | `POST /security/wallets/challenge` |
11
+ | `security_wallet_link(...)` | `POST /security/wallets/link` |
12
+ | `security_wallet_remove(record_id=...)` | `DELETE /security/wallets/{id}` |
13
+ | `security_provider_nonce(provider=..., client_type=..., redirect_uri=...)` | `POST /security/providers/nonce` |
14
+ | `security_provider_link(...)` | `POST /security/providers/link` |
15
+ | `security_provider_remove(record_id=...)` | `DELETE /security/providers/{id}` |
16
+ | `security_phone_request(phone_number=...)` | `POST /security/phone/request` |
17
+ | `security_phone_verify(phone_number=..., code=..., challenge_id=...)` | `POST /security/phone/verify` |
18
+ | `security_phone_remove()` | `DELETE /security/phone` |
19
+ | `security_email_request(email=...)` | `POST /security/email/request` |
20
+ | `security_email_verify(email=..., code=..., challenge_id=...)` | `POST /security/email/verify` |
21
+ | `github_start(redirect_url=...)` / `github_complete(code=..., state=...)` | `POST /github/start` and `/github/complete` |
22
+ | `security_github_start(redirect_url=...)` / `security_github_complete(code=..., state=...)` | `POST /security/github/start` and `/security/github/complete` |
23
+ | `sign_up(email=..., password=...)` | `POST /signup` |
24
+ | `request_signup_confirmation(email=..., redirect_url=...)` | `POST /signup/confirmation/request` |
25
+ | `verify_signup_confirmation(token=...)` | `POST /signup/confirmation/verify` |
26
+
27
+ Security methods require the current access token, supplied through token
28
+ storage or `access_token=...`. All methods above accept `origin=...`; send the
29
+ registered application origin where server policy requires it. They retain the
30
+ application API key and use the configured HTTP client's cookie jar.
31
+
32
+ Linking returns `AuthLinkResult`, never a replacement login session. If a
33
+ successful change reports `session_ended=True`, the client clears stored
34
+ credentials. Removal returns `AuthRemoveResult`; the server decides whether an
35
+ alternative sign-in method remains viable. There is no email-removal endpoint.
36
+ Delivery status may describe a console simulation; it does not prove SMS arrived.
37
+
38
+ Human GitHub uses `oauth:github`, separate from machine `oidc:github`. The start
39
+ and completion calls require the same app/origin/browser binding. A Python HTTP
40
+ client cannot transfer an HttpOnly browser cookie across clients automatically.
41
+ Browser integrations should use the shared browser SDK popup helper, retaining
42
+ the current bearer session for account linking. Do not retry a failed link as a
43
+ sign-in operation. Provider access tokens are never client results.
44
+
45
+ `sign_up` returns `SignupPending`, `MfaRequiredChallenge`, or `TokenPair`.
46
+ Pending confirmation and MFA challenges do not replace stored credentials.
47
+ `verify_signup_confirmation` returns a restricted `password_setup_token`; it
48
+ does not authenticate or store that grant as an access token. Continue through
49
+ the existing password setup and normal sign-in flow.
50
+
51
+ Both clients accept `origin=...` on `oidc_nonce` and `oidc_sign_in`. For Apple
52
+ browser calls, pass the same exact configured origin on nonce and completion;
53
+ the client forwards it as the request's `Origin` header. A redirect URI does not
54
+ set that header. The server rejects a missing or mismatched browser origin.
55
+
56
+ Apple OIDC methods support `client_type` and `redirect_uri`; completion also
57
+ accepts `authorization_code`. Omit those Apple profile fields for Google.
58
+ Native Apple calls keep their existing profile without requiring browser Origin.
59
+ Wallet
60
+ `request_nonce(..., chain="base")` preserves its existing Python argument name
61
+ and sends the server's canonical `chain_type` field.
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "spaps"
7
- version = "0.8.2"
7
+ version = "0.8.3"
8
8
  description = "Python client for the hosted Sweet Potato / SPAPS API"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.9"
@@ -7,6 +7,14 @@ payments, and supporting utilities as implementation progresses.
7
7
 
8
8
  from .cfo import CfoClient, CfoDecision, CfoDecisionRequest, CfoError, CfoStartRequest
9
9
  from .cfo_async import AsyncCfoClient
10
+ from .cfo_actions import (
11
+ CfoActionsClient, AsyncCfoActionsClient, CfoActionVerifier, AsyncCfoActionVerifier,
12
+ CfoActionBrowserSession, AsyncCfoActionBrowserSession,
13
+ Invitation as CfoActionInvitation, Delivery as CfoActionDelivery,
14
+ Status as CfoActionStatus, Claim as CfoActionClaim,
15
+ VerificationBinding as CfoActionVerificationBinding,
16
+ RuntimeEnvelope as CfoActionRuntimeEnvelope,
17
+ )
10
18
  from .cfo_browser_session import CfoBrowserCredentials, CfoBrowserSession, AsyncCfoBrowserSession
11
19
  from .auth import (
12
20
  AuthClient,
@@ -33,6 +41,7 @@ from .auth import (
33
41
  WebAuthnAssertionOptionsResponse,
34
42
  WebAuthnRegisterVerifyResponse,
35
43
  WebAuthnBootstrapVerifyResponse,
44
+ WebAuthnBootstrapPendingResponse,
36
45
  WebAuthnCredentialDescriptor,
37
46
  PasskeyTransport,
38
47
  WebAuthnRelyingParty,
@@ -76,12 +85,20 @@ from .payments import (
76
85
  PaymentsClient,
77
86
  PaymentsError,
78
87
  CheckoutSession,
88
+ CheckoutSessionDetails,
89
+ CheckoutSessionSummary,
90
+ CheckoutSessionList,
91
+ ExpireCheckoutSessionResult,
79
92
  PaymentIntent,
80
93
  WalletDeposit,
81
94
  WalletTransaction,
82
95
  SubscriptionPlan,
96
+ SubscriptionItemPriceRecurring,
97
+ SubscriptionItemPrice,
98
+ SubscriptionItem,
83
99
  SubscriptionDetail,
84
100
  SubscriptionCancellation,
101
+ SubscriptionList,
85
102
  BalanceOverview,
86
103
  BalanceAmounts,
87
104
  UsageSummary,
@@ -89,6 +106,12 @@ from .payments import (
89
106
  ProductPrice,
90
107
  Product,
91
108
  ProductList,
109
+ GuestCheckoutSession,
110
+ GuestCheckoutSessionSummary,
111
+ GuestCheckoutSessionList,
112
+ GuestCheckoutConversionResult,
113
+ PaymentRecord,
114
+ PaymentHistory,
92
115
  )
93
116
  from .whitelist import (
94
117
  WhitelistClient,
@@ -185,9 +208,12 @@ from .developer_projects import (
185
208
  ResetProjectPairResponse,
186
209
  RotateEnvironmentKeyResponse,
187
210
  )
211
+ from .dashboard import DashboardClient, DashboardDetail, DashboardKind, DashboardPage, DashboardSource
212
+ from .dashboard_async import AsyncDashboardClient
188
213
  from .dev_portal import (
189
214
  DEV_PORTAL_HELPERS,
190
215
  DeleteApplicationResponse,
216
+ DevPortalCheckoutSessionResponse,
191
217
  DevPortalClient,
192
218
  DevPortalError,
193
219
  ListOwnedApplicationsResponse,
@@ -370,7 +396,66 @@ from .storage import (
370
396
  )
371
397
  from .http import RetryConfig, LoggingHooks, default_logging_hooks
372
398
 
399
+ from .purchase_journeys import (
400
+ SubmittedJourneyIdentity,
401
+ CanonicalJourneyIdentity,
402
+ PreparedConsent,
403
+ ReceiptPaymentView,
404
+ ReceiptAccessView,
405
+ ReceiptIdentityView,
406
+ ConsumerSupportRequest,
407
+ ConsumerSupportResponse,
408
+ OnboardingVersion,
409
+ OnboardingFlow,
410
+ OnboardingFlowMutation,
411
+ ApproveOnboardingFlowRequest,
412
+ WithdrawOnboardingApprovalRequest,
413
+ RegisterJourneyPolicyRequest,
414
+ JourneyPolicyResponse,
415
+ AcceptJourneyConsentRequest,
416
+ JourneyConsentResponse,
417
+ PrepareJourneyRequest,
418
+ PreparedJourneyResponse,
419
+ ResumeJourneyRequest,
420
+ ResumeJourneyResponse,
421
+ ProvisioningInputRequest,
422
+ ProvisioningInputResponse,
423
+ IssueReceiptCapabilityRequest,
424
+ IssuedReceiptCapabilityResponse,
425
+ ReceiptStatusRequest,
426
+ ReceiptStatusResponse,
427
+ IssueAccessChallengeRequest,
428
+ IssuedAccessChallengeResponse,
429
+ VerifyAccessChallengeRequest,
430
+ VerifyAccessChallengeResponse,
431
+ AppProvisioningReportRequest,
432
+ AppProvisioningReportResponse,
433
+ PurchaseJourneysClient,
434
+ OnboardingDefinition,
435
+ OnboardingQuestion,
436
+ QuestionBase,
437
+ TextQuestion,
438
+ NumberQuestion,
439
+ BooleanQuestion,
440
+ ChoiceOption,
441
+ ChoiceQuestion,
442
+ FlowApproval,
443
+ )
444
+ from .purchase_journeys_async import AsyncPurchaseJourneysClient
445
+ from .dashboard import (
446
+ DashboardOnboardingWorkspace,
447
+ DashboardOnboardingJourneys,
448
+ DashboardOnboardingJourney,
449
+ DashboardOnboardingComparison,
450
+ OnboardingDraftWrite,
451
+ OnboardingPublishWrite,
452
+ OnboardingRollbackWrite,
453
+ OnboardingFlowWrite,
454
+ OnboardingCohort,
455
+ )
456
+
373
457
  __all__ = [
458
+ "DashboardClient", "AsyncDashboardClient", "DashboardDetail", "DashboardKind", "DashboardPage", "DashboardSource",
374
459
  "__version__",
375
460
  "AuthClient",
376
461
  "AuthMethodResult",
@@ -396,6 +481,7 @@ __all__ = [
396
481
  "WebAuthnAssertionOptionsResponse",
397
482
  "WebAuthnRegisterVerifyResponse",
398
483
  "WebAuthnBootstrapVerifyResponse",
484
+ "WebAuthnBootstrapPendingResponse",
399
485
  "WebAuthnCredentialDescriptor",
400
486
  "PasskeyTransport",
401
487
  "WebAuthnRelyingParty",
@@ -548,6 +634,7 @@ __all__ = [
548
634
  "RotateEnvironmentKeyResponse",
549
635
  "DEV_PORTAL_HELPERS",
550
636
  "DeleteApplicationResponse",
637
+ "DevPortalCheckoutSessionResponse",
551
638
  "DevPortalClient",
552
639
  "DevPortalError",
553
640
  "ListOwnedApplicationsResponse",
@@ -718,7 +805,20 @@ __all__ = [
718
805
  ]
719
806
 
720
807
  # Temporary development version; replaced during release automation.
721
- __version__ = "0.8.2"
808
+ __version__ = "0.8.3"
722
809
 
723
810
 
724
811
  __all__ += ["CfoClient", "AsyncCfoClient", "CfoDecision", "CfoDecisionRequest", "CfoError", "CfoStartRequest", "CfoBrowserCredentials", "CfoBrowserSession", "AsyncCfoBrowserSession"]
812
+
813
+
814
+ __all__ += ['ConsumerSupportRequest', 'ConsumerSupportResponse', 'OnboardingVersion', 'OnboardingFlow', 'OnboardingFlowMutation', 'ApproveOnboardingFlowRequest', 'WithdrawOnboardingApprovalRequest', 'RegisterJourneyPolicyRequest', 'JourneyPolicyResponse', 'AcceptJourneyConsentRequest', 'JourneyConsentResponse', 'PrepareJourneyRequest', 'PreparedJourneyResponse', 'ResumeJourneyRequest', 'ResumeJourneyResponse', 'ProvisioningInputRequest', 'ProvisioningInputResponse', 'IssueReceiptCapabilityRequest', 'IssuedReceiptCapabilityResponse', 'ReceiptStatusRequest', 'ReceiptStatusResponse', 'IssueAccessChallengeRequest', 'IssuedAccessChallengeResponse', 'VerifyAccessChallengeRequest', 'VerifyAccessChallengeResponse', 'AppProvisioningReportRequest', 'AppProvisioningReportResponse', 'PurchaseJourneysClient', 'OnboardingDefinition', 'OnboardingQuestion', 'QuestionBase', 'TextQuestion', 'NumberQuestion', 'BooleanQuestion', 'ChoiceOption', 'ChoiceQuestion', 'FlowApproval', 'AsyncPurchaseJourneysClient']
815
+
816
+ __all__ += ['DashboardOnboardingWorkspace', 'DashboardOnboardingJourneys', 'DashboardOnboardingJourney', 'DashboardOnboardingComparison', 'OnboardingDraftWrite', 'OnboardingPublishWrite', 'OnboardingRollbackWrite', 'OnboardingFlowWrite', 'OnboardingCohort']
817
+
818
+ __all__ += ["SubmittedJourneyIdentity", "CanonicalJourneyIdentity", "PreparedConsent",
819
+ "ReceiptPaymentView", "ReceiptAccessView", "ReceiptIdentityView"]
820
+
821
+ __all__ += ["CfoActionsClient", "AsyncCfoActionsClient", "CfoActionVerifier", "AsyncCfoActionVerifier",
822
+ "CfoActionBrowserSession", "AsyncCfoActionBrowserSession", "CfoActionInvitation",
823
+ "CfoActionDelivery", "CfoActionStatus", "CfoActionClaim",
824
+ "CfoActionVerificationBinding", "CfoActionRuntimeEnvelope"]
@@ -7,6 +7,10 @@ from typing import Optional, TYPE_CHECKING
7
7
 
8
8
  import httpx
9
9
 
10
+ from .cfo_actions import AsyncCfoActionsClient
11
+ from .purchase_journeys_async import AsyncPurchaseJourneysClient
12
+ from .dashboard_async import AsyncDashboardClient
13
+
10
14
  from .cfo_async import AsyncCfoClient
11
15
 
12
16
  from .app_links_async import AsyncAppLinksClient
@@ -79,6 +83,8 @@ class AsyncSpapsClient:
79
83
  self._usage: Optional[AsyncUsageClient] = None
80
84
  self._whitelist: Optional[AsyncWhitelistClient] = None
81
85
  self._secure_messages: Optional[AsyncSecureMessagesClient] = None
86
+ self._purchase_journeys: Optional[AsyncPurchaseJourneysClient] = None
87
+ self._dashboard: Optional[AsyncDashboardClient] = None
82
88
  self._dev_portal: Optional[AsyncDevPortalClient] = None
83
89
  self._issue_reporting: Optional[AsyncIssueReportingClient] = None
84
90
  self._metrics: Optional[AsyncMetricsClient] = None
@@ -92,6 +98,11 @@ class AsyncSpapsClient:
92
98
 
93
99
  # Factories --------------------------------------------------------
94
100
 
101
+ @property
102
+ def cfo_actions(self) -> AsyncCfoActionsClient:
103
+ return AsyncCfoActionsClient(base_url=self.settings.base_url, api_key=self.settings.api_key,
104
+ access_token_provider=self._get_access_token_or_none)
105
+
95
106
  @property
96
107
  def cfo(self) -> AsyncCfoClient:
97
108
  # Resolve the token at each operation, including on an already-held facade.
@@ -172,6 +183,29 @@ class AsyncSpapsClient:
172
183
  self._secure_messages.access_token = access_token
173
184
  return self._secure_messages
174
185
 
186
+ @property
187
+ def purchase_journeys(self) -> AsyncPurchaseJourneysClient:
188
+ """Backend-only onboarding and receipt contracts; no human JWT required."""
189
+ if self._purchase_journeys is None:
190
+ self._purchase_journeys = AsyncPurchaseJourneysClient(
191
+ base_url=self.settings.base_url, api_key=self.settings.api_key,
192
+ client=self._client,
193
+ )
194
+ return self._purchase_journeys
195
+
196
+ @property
197
+ def dashboard(self) -> AsyncDashboardClient:
198
+ """Stored application-owner and administrator dashboard records."""
199
+ access_token = self._require_access_token()
200
+ if self._dashboard is None:
201
+ self._dashboard = AsyncDashboardClient(
202
+ base_url=self.settings.base_url, api_key=self.settings.api_key,
203
+ access_token=access_token, client=self._client,
204
+ )
205
+ else:
206
+ self._dashboard.access_token = access_token
207
+ return self._dashboard
208
+
175
209
  @property
176
210
  def dev_portal(self) -> AsyncDevPortalClient:
177
211
  """Entitlement-gated staging-app minting (server-side only: needs the secret key)."""
@@ -347,6 +381,8 @@ class AsyncSpapsClient:
347
381
  self._whitelist,
348
382
  self._secure_messages,
349
383
  self._dev_portal,
384
+ self._purchase_journeys,
385
+ self._dashboard,
350
386
  self._issue_reporting,
351
387
  self._metrics,
352
388
  self._entitlements,