pushframe 5.1.0__tar.gz → 5.1.2__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 (413) hide show
  1. {pushframe-5.1.0 → pushframe-5.1.2}/CHANGELOG.md +43 -1
  2. {pushframe-5.1.0 → pushframe-5.1.2}/PKG-INFO +1 -1
  3. {pushframe-5.1.0 → pushframe-5.1.2}/docs/CLI.md +15 -0
  4. {pushframe-5.1.0 → pushframe-5.1.2}/pushframe/__init__.py +1 -1
  5. {pushframe-5.1.0 → pushframe-5.1.2}/pushframe/cli.py +30 -0
  6. {pushframe-5.1.0 → pushframe-5.1.2}/pushframe/client.py +9 -1
  7. {pushframe-5.1.0 → pushframe-5.1.2}/pushframe/utils/settings.py +5 -1
  8. {pushframe-5.1.0 → pushframe-5.1.2}/pyproject.toml +1 -1
  9. pushframe-5.1.2/tests/conftest.py +100 -0
  10. {pushframe-5.1.0 → pushframe-5.1.2}/tests/test_config_wizard.py +69 -1
  11. pushframe-5.1.2/tests/test_network_guard.py +41 -0
  12. {pushframe-5.1.0 → pushframe-5.1.2}/tests/test_retry_401.py +6 -0
  13. {pushframe-5.1.0 → pushframe-5.1.2}/tests/test_settings_resolution.py +20 -0
  14. {pushframe-5.1.0 → pushframe-5.1.2}/tests/test_version.py +1 -1
  15. {pushframe-5.1.0 → pushframe-5.1.2}/tests/test_write_throttling.py +23 -15
  16. {pushframe-5.1.0 → pushframe-5.1.2}/uv.lock +1 -1
  17. pushframe-5.1.0/tests/conftest.py +0 -32
  18. {pushframe-5.1.0 → pushframe-5.1.2}/.env.sample +0 -0
  19. {pushframe-5.1.0 → pushframe-5.1.2}/.freebuff/project-id +0 -0
  20. {pushframe-5.1.0 → pushframe-5.1.2}/.github/workflows/release.yml +0 -0
  21. {pushframe-5.1.0 → pushframe-5.1.2}/.github/workflows/tests.yml +0 -0
  22. {pushframe-5.1.0 → pushframe-5.1.2}/.gitignore +0 -0
  23. {pushframe-5.1.0 → pushframe-5.1.2}/.gsd/dispatch-isolation-sentinel.json +0 -0
  24. {pushframe-5.1.0 → pushframe-5.1.2}/.planning/MILESTONES.md +0 -0
  25. {pushframe-5.1.0 → pushframe-5.1.2}/.planning/PROJECT.md +0 -0
  26. {pushframe-5.1.0 → pushframe-5.1.2}/.planning/REQUIREMENTS.md +0 -0
  27. {pushframe-5.1.0 → pushframe-5.1.2}/.planning/RETROSPECTIVE.md +0 -0
  28. {pushframe-5.1.0 → pushframe-5.1.2}/.planning/ROADMAP.md +0 -0
  29. {pushframe-5.1.0 → pushframe-5.1.2}/.planning/STATE.md +0 -0
  30. {pushframe-5.1.0 → pushframe-5.1.2}/.planning/WINDOWS.md +0 -0
  31. {pushframe-5.1.0 → pushframe-5.1.2}/.planning/codebase/ARCHITECTURE.md +0 -0
  32. {pushframe-5.1.0 → pushframe-5.1.2}/.planning/codebase/CONCERNS.md +0 -0
  33. {pushframe-5.1.0 → pushframe-5.1.2}/.planning/codebase/CONVENTIONS.md +0 -0
  34. {pushframe-5.1.0 → pushframe-5.1.2}/.planning/codebase/INTEGRATIONS.md +0 -0
  35. {pushframe-5.1.0 → pushframe-5.1.2}/.planning/codebase/STACK.md +0 -0
  36. {pushframe-5.1.0 → pushframe-5.1.2}/.planning/codebase/STRUCTURE.md +0 -0
  37. {pushframe-5.1.0 → pushframe-5.1.2}/.planning/codebase/TESTING.md +0 -0
  38. {pushframe-5.1.0 → pushframe-5.1.2}/.planning/config.json +0 -0
  39. {pushframe-5.1.0 → pushframe-5.1.2}/.planning/debug/knowledge-base.md +0 -0
  40. {pushframe-5.1.0 → pushframe-5.1.2}/.planning/debug/login-475-null-creds.md +0 -0
  41. {pushframe-5.1.0 → pushframe-5.1.2}/.planning/debug/resolved/cli-status-verbose-logging.md +0 -0
  42. {pushframe-5.1.0 → pushframe-5.1.2}/.planning/debug/resolved/inspect-asset-null-fields.md +0 -0
  43. {pushframe-5.1.0 → pushframe-5.1.2}/.planning/debug/resolved/select-asset-401-unauthorized.md +0 -0
  44. {pushframe-5.1.0 → pushframe-5.1.2}/.planning/milestones/v1.0-REQUIREMENTS.md +0 -0
  45. {pushframe-5.1.0 → pushframe-5.1.2}/.planning/milestones/v1.0-ROADMAP.md +0 -0
  46. {pushframe-5.1.0 → pushframe-5.1.2}/.planning/milestones/v1.0-phases/01-toolchain-revival/01-01-PLAN.md +0 -0
  47. {pushframe-5.1.0 → pushframe-5.1.2}/.planning/milestones/v1.0-phases/01-toolchain-revival/01-01-SUMMARY.md +0 -0
  48. {pushframe-5.1.0 → pushframe-5.1.2}/.planning/milestones/v1.0-phases/01-toolchain-revival/01-02-PLAN.md +0 -0
  49. {pushframe-5.1.0 → pushframe-5.1.2}/.planning/milestones/v1.0-phases/01-toolchain-revival/01-02-SUMMARY.md +0 -0
  50. {pushframe-5.1.0 → pushframe-5.1.2}/.planning/milestones/v1.0-phases/01-toolchain-revival/01-CONTEXT.md +0 -0
  51. {pushframe-5.1.0 → pushframe-5.1.2}/.planning/milestones/v1.0-phases/01-toolchain-revival/01-DISCUSSION-LOG.md +0 -0
  52. {pushframe-5.1.0 → pushframe-5.1.2}/.planning/milestones/v1.0-phases/01-toolchain-revival/01-PATTERNS.md +0 -0
  53. {pushframe-5.1.0 → pushframe-5.1.2}/.planning/milestones/v1.0-phases/01-toolchain-revival/01-RESEARCH.md +0 -0
  54. {pushframe-5.1.0 → pushframe-5.1.2}/.planning/milestones/v1.0-phases/01-toolchain-revival/01-REVIEW.md +0 -0
  55. {pushframe-5.1.0 → pushframe-5.1.2}/.planning/milestones/v1.0-phases/01-toolchain-revival/01-VERIFICATION.md +0 -0
  56. {pushframe-5.1.0 → pushframe-5.1.2}/.planning/milestones/v1.0-phases/02-live-read-path-verification/02-01-PLAN.md +0 -0
  57. {pushframe-5.1.0 → pushframe-5.1.2}/.planning/milestones/v1.0-phases/02-live-read-path-verification/02-01-SUMMARY.md +0 -0
  58. {pushframe-5.1.0 → pushframe-5.1.2}/.planning/milestones/v1.0-phases/02-live-read-path-verification/02-02-PLAN.md +0 -0
  59. {pushframe-5.1.0 → pushframe-5.1.2}/.planning/milestones/v1.0-phases/02-live-read-path-verification/02-02-SUMMARY.md +0 -0
  60. {pushframe-5.1.0 → pushframe-5.1.2}/.planning/milestones/v1.0-phases/02-live-read-path-verification/02-CONTEXT.md +0 -0
  61. {pushframe-5.1.0 → pushframe-5.1.2}/.planning/milestones/v1.0-phases/02-live-read-path-verification/02-DISCUSSION-LOG.md +0 -0
  62. {pushframe-5.1.0 → pushframe-5.1.2}/.planning/milestones/v1.0-phases/02-live-read-path-verification/02-LIVE-EVIDENCE.md +0 -0
  63. {pushframe-5.1.0 → pushframe-5.1.2}/.planning/milestones/v1.0-phases/02-live-read-path-verification/02-PATTERNS.md +0 -0
  64. {pushframe-5.1.0 → pushframe-5.1.2}/.planning/milestones/v1.0-phases/02-live-read-path-verification/02-RESEARCH.md +0 -0
  65. {pushframe-5.1.0 → pushframe-5.1.2}/.planning/milestones/v1.0-phases/02-live-read-path-verification/02-REVIEW.md +0 -0
  66. {pushframe-5.1.0 → pushframe-5.1.2}/.planning/milestones/v1.0-phases/02-live-read-path-verification/02-VERIFICATION.md +0 -0
  67. {pushframe-5.1.0 → pushframe-5.1.2}/.planning/milestones/v1.0-phases/03-run-docs-verification-report/03-01-PLAN.md +0 -0
  68. {pushframe-5.1.0 → pushframe-5.1.2}/.planning/milestones/v1.0-phases/03-run-docs-verification-report/03-01-SUMMARY.md +0 -0
  69. {pushframe-5.1.0 → pushframe-5.1.2}/.planning/milestones/v1.0-phases/03-run-docs-verification-report/03-CONTEXT.md +0 -0
  70. {pushframe-5.1.0 → pushframe-5.1.2}/.planning/milestones/v1.0-phases/03-run-docs-verification-report/03-DISCUSSION-LOG.md +0 -0
  71. {pushframe-5.1.0 → pushframe-5.1.2}/.planning/milestones/v1.0-phases/03-run-docs-verification-report/03-PATTERNS.md +0 -0
  72. {pushframe-5.1.0 → pushframe-5.1.2}/.planning/milestones/v1.0-phases/03-run-docs-verification-report/03-RESEARCH.md +0 -0
  73. {pushframe-5.1.0 → pushframe-5.1.2}/.planning/milestones/v1.0-phases/03-run-docs-verification-report/03-REVIEW.md +0 -0
  74. {pushframe-5.1.0 → pushframe-5.1.2}/.planning/milestones/v1.0-phases/03-run-docs-verification-report/03-VERIFICATION.md +0 -0
  75. {pushframe-5.1.0 → pushframe-5.1.2}/.planning/milestones/v1.1-ROADMAP.md +0 -0
  76. {pushframe-5.1.0 → pushframe-5.1.2}/.planning/milestones/v1.1-phases/04-client-transport-seam-for-offline-testability/.gitkeep +0 -0
  77. {pushframe-5.1.0 → pushframe-5.1.2}/.planning/milestones/v1.1-phases/04-client-transport-seam-for-offline-testability/04-01-PLAN.md +0 -0
  78. {pushframe-5.1.0 → pushframe-5.1.2}/.planning/milestones/v1.1-phases/04-client-transport-seam-for-offline-testability/04-01-SUMMARY.md +0 -0
  79. {pushframe-5.1.0 → pushframe-5.1.2}/.planning/milestones/v1.1-phases/04-client-transport-seam-for-offline-testability/04-02-PLAN.md +0 -0
  80. {pushframe-5.1.0 → pushframe-5.1.2}/.planning/milestones/v1.1-phases/04-client-transport-seam-for-offline-testability/04-02-SUMMARY.md +0 -0
  81. {pushframe-5.1.0 → pushframe-5.1.2}/.planning/milestones/v1.1-phases/04-client-transport-seam-for-offline-testability/04-03-PLAN.md +0 -0
  82. {pushframe-5.1.0 → pushframe-5.1.2}/.planning/milestones/v1.1-phases/04-client-transport-seam-for-offline-testability/04-03-SUMMARY.md +0 -0
  83. {pushframe-5.1.0 → pushframe-5.1.2}/.planning/milestones/v1.1-phases/04-client-transport-seam-for-offline-testability/04-PATTERNS.md +0 -0
  84. {pushframe-5.1.0 → pushframe-5.1.2}/.planning/milestones/v1.1-phases/04-client-transport-seam-for-offline-testability/04-RESEARCH.md +0 -0
  85. {pushframe-5.1.0 → pushframe-5.1.2}/.planning/milestones/v1.1-phases/04-client-transport-seam-for-offline-testability/04-REVIEW.md +0 -0
  86. {pushframe-5.1.0 → pushframe-5.1.2}/.planning/milestones/v1.1-phases/04-client-transport-seam-for-offline-testability/04-SECURITY.md +0 -0
  87. {pushframe-5.1.0 → pushframe-5.1.2}/.planning/milestones/v1.1-phases/04-client-transport-seam-for-offline-testability/04-UAT.md +0 -0
  88. {pushframe-5.1.0 → pushframe-5.1.2}/.planning/milestones/v1.1-phases/04-client-transport-seam-for-offline-testability/04-VERIFICATION.md +0 -0
  89. {pushframe-5.1.0 → pushframe-5.1.2}/.planning/milestones/v2.0-REQUIREMENTS.md +0 -0
  90. {pushframe-5.1.0 → pushframe-5.1.2}/.planning/milestones/v2.0-ROADMAP.md +0 -0
  91. {pushframe-5.1.0 → pushframe-5.1.2}/.planning/milestones/v2.0-phases/05-cli-skeleton-status/05-01-PLAN.md +0 -0
  92. {pushframe-5.1.0 → pushframe-5.1.2}/.planning/milestones/v2.0-phases/05-cli-skeleton-status/05-01-SUMMARY.md +0 -0
  93. {pushframe-5.1.0 → pushframe-5.1.2}/.planning/milestones/v2.0-phases/05-cli-skeleton-status/05-02-PLAN.md +0 -0
  94. {pushframe-5.1.0 → pushframe-5.1.2}/.planning/milestones/v2.0-phases/05-cli-skeleton-status/05-02-SUMMARY.md +0 -0
  95. {pushframe-5.1.0 → pushframe-5.1.2}/.planning/milestones/v2.0-phases/05-cli-skeleton-status/05-CONTEXT.md +0 -0
  96. {pushframe-5.1.0 → pushframe-5.1.2}/.planning/milestones/v2.0-phases/05-cli-skeleton-status/05-DISCUSSION-LOG.md +0 -0
  97. {pushframe-5.1.0 → pushframe-5.1.2}/.planning/milestones/v2.0-phases/05-cli-skeleton-status/05-PATTERNS.md +0 -0
  98. {pushframe-5.1.0 → pushframe-5.1.2}/.planning/milestones/v2.0-phases/05-cli-skeleton-status/05-REVIEW.md +0 -0
  99. {pushframe-5.1.0 → pushframe-5.1.2}/.planning/milestones/v2.0-phases/05-cli-skeleton-status/05-SECURITY.md +0 -0
  100. {pushframe-5.1.0 → pushframe-5.1.2}/.planning/milestones/v2.0-phases/05-cli-skeleton-status/05-UAT.md +0 -0
  101. {pushframe-5.1.0 → pushframe-5.1.2}/.planning/milestones/v2.0-phases/05-cli-skeleton-status/05-VERIFICATION.md +0 -0
  102. {pushframe-5.1.0 → pushframe-5.1.2}/.planning/milestones/v2.0-phases/06-inspect-frame-resolution/06-01-PLAN.md +0 -0
  103. {pushframe-5.1.0 → pushframe-5.1.2}/.planning/milestones/v2.0-phases/06-inspect-frame-resolution/06-01-SUMMARY.md +0 -0
  104. {pushframe-5.1.0 → pushframe-5.1.2}/.planning/milestones/v2.0-phases/06-inspect-frame-resolution/06-02-PLAN.md +0 -0
  105. {pushframe-5.1.0 → pushframe-5.1.2}/.planning/milestones/v2.0-phases/06-inspect-frame-resolution/06-02-SUMMARY.md +0 -0
  106. {pushframe-5.1.0 → pushframe-5.1.2}/.planning/milestones/v2.0-phases/06-inspect-frame-resolution/06-CONTEXT.md +0 -0
  107. {pushframe-5.1.0 → pushframe-5.1.2}/.planning/milestones/v2.0-phases/06-inspect-frame-resolution/06-DISCUSSION-LOG.md +0 -0
  108. {pushframe-5.1.0 → pushframe-5.1.2}/.planning/milestones/v2.0-phases/06-inspect-frame-resolution/06-PATTERNS.md +0 -0
  109. {pushframe-5.1.0 → pushframe-5.1.2}/.planning/milestones/v2.0-phases/06-inspect-frame-resolution/06-RESEARCH.md +0 -0
  110. {pushframe-5.1.0 → pushframe-5.1.2}/.planning/milestones/v2.0-phases/06-inspect-frame-resolution/06-REVIEW.md +0 -0
  111. {pushframe-5.1.0 → pushframe-5.1.2}/.planning/milestones/v2.0-phases/06-inspect-frame-resolution/06-SECURITY.md +0 -0
  112. {pushframe-5.1.0 → pushframe-5.1.2}/.planning/milestones/v2.0-phases/06-inspect-frame-resolution/06-VERIFICATION.md +0 -0
  113. {pushframe-5.1.0 → pushframe-5.1.2}/.planning/milestones/v2.0-phases/07-sync-diffing-engine-dry-run-only/07-01-PLAN.md +0 -0
  114. {pushframe-5.1.0 → pushframe-5.1.2}/.planning/milestones/v2.0-phases/07-sync-diffing-engine-dry-run-only/07-01-SUMMARY.md +0 -0
  115. {pushframe-5.1.0 → pushframe-5.1.2}/.planning/milestones/v2.0-phases/07-sync-diffing-engine-dry-run-only/07-02-PLAN.md +0 -0
  116. {pushframe-5.1.0 → pushframe-5.1.2}/.planning/milestones/v2.0-phases/07-sync-diffing-engine-dry-run-only/07-02-SUMMARY.md +0 -0
  117. {pushframe-5.1.0 → pushframe-5.1.2}/.planning/milestones/v2.0-phases/07-sync-diffing-engine-dry-run-only/07-03-PLAN.md +0 -0
  118. {pushframe-5.1.0 → pushframe-5.1.2}/.planning/milestones/v2.0-phases/07-sync-diffing-engine-dry-run-only/07-03-SUMMARY.md +0 -0
  119. {pushframe-5.1.0 → pushframe-5.1.2}/.planning/milestones/v2.0-phases/07-sync-diffing-engine-dry-run-only/07-CONTEXT.md +0 -0
  120. {pushframe-5.1.0 → pushframe-5.1.2}/.planning/milestones/v2.0-phases/07-sync-diffing-engine-dry-run-only/07-DISCUSSION-LOG.md +0 -0
  121. {pushframe-5.1.0 → pushframe-5.1.2}/.planning/milestones/v2.0-phases/07-sync-diffing-engine-dry-run-only/07-PATTERNS.md +0 -0
  122. {pushframe-5.1.0 → pushframe-5.1.2}/.planning/milestones/v2.0-phases/07-sync-diffing-engine-dry-run-only/07-REVIEW-FIX.md +0 -0
  123. {pushframe-5.1.0 → pushframe-5.1.2}/.planning/milestones/v2.0-phases/07-sync-diffing-engine-dry-run-only/07-REVIEW.md +0 -0
  124. {pushframe-5.1.0 → pushframe-5.1.2}/.planning/milestones/v2.0-phases/07-sync-diffing-engine-dry-run-only/07-SECURITY.md +0 -0
  125. {pushframe-5.1.0 → pushframe-5.1.2}/.planning/milestones/v2.0-phases/07-sync-diffing-engine-dry-run-only/07-VERIFICATION.md +0 -0
  126. {pushframe-5.1.0 → pushframe-5.1.2}/.planning/milestones/v2.0-phases/08-destructive-execution-upload-delete-verification/08-01-PLAN.md +0 -0
  127. {pushframe-5.1.0 → pushframe-5.1.2}/.planning/milestones/v2.0-phases/08-destructive-execution-upload-delete-verification/08-01-SUMMARY.md +0 -0
  128. {pushframe-5.1.0 → pushframe-5.1.2}/.planning/milestones/v2.0-phases/08-destructive-execution-upload-delete-verification/08-02-PLAN.md +0 -0
  129. {pushframe-5.1.0 → pushframe-5.1.2}/.planning/milestones/v2.0-phases/08-destructive-execution-upload-delete-verification/08-02-SUMMARY.md +0 -0
  130. {pushframe-5.1.0 → pushframe-5.1.2}/.planning/milestones/v2.0-phases/08-destructive-execution-upload-delete-verification/08-03-PLAN.md +0 -0
  131. {pushframe-5.1.0 → pushframe-5.1.2}/.planning/milestones/v2.0-phases/08-destructive-execution-upload-delete-verification/08-03-SUMMARY.md +0 -0
  132. {pushframe-5.1.0 → pushframe-5.1.2}/.planning/milestones/v2.0-phases/08-destructive-execution-upload-delete-verification/08-04-PLAN.md +0 -0
  133. {pushframe-5.1.0 → pushframe-5.1.2}/.planning/milestones/v2.0-phases/08-destructive-execution-upload-delete-verification/08-04-SUMMARY.md +0 -0
  134. {pushframe-5.1.0 → pushframe-5.1.2}/.planning/milestones/v2.0-phases/08-destructive-execution-upload-delete-verification/08-CONTEXT.md +0 -0
  135. {pushframe-5.1.0 → pushframe-5.1.2}/.planning/milestones/v2.0-phases/08-destructive-execution-upload-delete-verification/08-DISCUSSION-LOG.md +0 -0
  136. {pushframe-5.1.0 → pushframe-5.1.2}/.planning/milestones/v2.0-phases/08-destructive-execution-upload-delete-verification/08-LIVE-FINDINGS.md +0 -0
  137. {pushframe-5.1.0 → pushframe-5.1.2}/.planning/milestones/v2.0-phases/08-destructive-execution-upload-delete-verification/08-PATTERNS.md +0 -0
  138. {pushframe-5.1.0 → pushframe-5.1.2}/.planning/milestones/v2.0-phases/08-destructive-execution-upload-delete-verification/08-RESEARCH.md +0 -0
  139. {pushframe-5.1.0 → pushframe-5.1.2}/.planning/milestones/v2.0-phases/08-destructive-execution-upload-delete-verification/08-REVIEW-FIX.md +0 -0
  140. {pushframe-5.1.0 → pushframe-5.1.2}/.planning/milestones/v2.0-phases/08-destructive-execution-upload-delete-verification/08-REVIEW.md +0 -0
  141. {pushframe-5.1.0 → pushframe-5.1.2}/.planning/milestones/v2.0-phases/08-destructive-execution-upload-delete-verification/08-VERIFICATION.md +0 -0
  142. {pushframe-5.1.0 → pushframe-5.1.2}/.planning/milestones/v2.0-phases/09-proactive-write-rate-limiter-geo-guard/.gitkeep +0 -0
  143. {pushframe-5.1.0 → pushframe-5.1.2}/.planning/milestones/v2.0-phases/09-proactive-write-rate-limiter-geo-guard/09-01-PLAN.md +0 -0
  144. {pushframe-5.1.0 → pushframe-5.1.2}/.planning/milestones/v2.0-phases/09-proactive-write-rate-limiter-geo-guard/09-01-SUMMARY.md +0 -0
  145. {pushframe-5.1.0 → pushframe-5.1.2}/.planning/milestones/v2.0-phases/09-proactive-write-rate-limiter-geo-guard/09-02-PLAN.md +0 -0
  146. {pushframe-5.1.0 → pushframe-5.1.2}/.planning/milestones/v2.0-phases/09-proactive-write-rate-limiter-geo-guard/09-02-SUMMARY.md +0 -0
  147. {pushframe-5.1.0 → pushframe-5.1.2}/.planning/milestones/v2.0-phases/09-proactive-write-rate-limiter-geo-guard/09-CONTEXT.md +0 -0
  148. {pushframe-5.1.0 → pushframe-5.1.2}/.planning/milestones/v2.0-phases/09-proactive-write-rate-limiter-geo-guard/09-RESEARCH.md +0 -0
  149. {pushframe-5.1.0 → pushframe-5.1.2}/.planning/milestones/v2.0-phases/09-proactive-write-rate-limiter-geo-guard/09-REVIEW-FIX.md +0 -0
  150. {pushframe-5.1.0 → pushframe-5.1.2}/.planning/milestones/v2.0-phases/09-proactive-write-rate-limiter-geo-guard/09-REVIEW.md +0 -0
  151. {pushframe-5.1.0 → pushframe-5.1.2}/.planning/milestones/v2.0-phases/09-proactive-write-rate-limiter-geo-guard/09-VERIFICATION.md +0 -0
  152. {pushframe-5.1.0 → pushframe-5.1.2}/.planning/milestones/v2.0-phases/10-hide-instead-of-delete-sync-mode/.gitkeep +0 -0
  153. {pushframe-5.1.0 → pushframe-5.1.2}/.planning/milestones/v2.0-phases/10-hide-instead-of-delete-sync-mode/10-01-PLAN.md +0 -0
  154. {pushframe-5.1.0 → pushframe-5.1.2}/.planning/milestones/v2.0-phases/10-hide-instead-of-delete-sync-mode/10-01-SUMMARY.md +0 -0
  155. {pushframe-5.1.0 → pushframe-5.1.2}/.planning/milestones/v2.0-phases/10-hide-instead-of-delete-sync-mode/10-02-PLAN.md +0 -0
  156. {pushframe-5.1.0 → pushframe-5.1.2}/.planning/milestones/v2.0-phases/10-hide-instead-of-delete-sync-mode/10-02-SUMMARY.md +0 -0
  157. {pushframe-5.1.0 → pushframe-5.1.2}/.planning/milestones/v2.0-phases/10-hide-instead-of-delete-sync-mode/10-03-PLAN.md +0 -0
  158. {pushframe-5.1.0 → pushframe-5.1.2}/.planning/milestones/v2.0-phases/10-hide-instead-of-delete-sync-mode/10-03-SUMMARY.md +0 -0
  159. {pushframe-5.1.0 → pushframe-5.1.2}/.planning/milestones/v2.0-phases/10-hide-instead-of-delete-sync-mode/10-04-PLAN.md +0 -0
  160. {pushframe-5.1.0 → pushframe-5.1.2}/.planning/milestones/v2.0-phases/10-hide-instead-of-delete-sync-mode/10-04-SUMMARY.md +0 -0
  161. {pushframe-5.1.0 → pushframe-5.1.2}/.planning/milestones/v2.0-phases/10-hide-instead-of-delete-sync-mode/10-CONTEXT.md +0 -0
  162. {pushframe-5.1.0 → pushframe-5.1.2}/.planning/milestones/v2.0-phases/10-hide-instead-of-delete-sync-mode/10-DISCUSSION-LOG.md +0 -0
  163. {pushframe-5.1.0 → pushframe-5.1.2}/.planning/milestones/v2.0-phases/10-hide-instead-of-delete-sync-mode/10-LIVE-FINDINGS.md +0 -0
  164. {pushframe-5.1.0 → pushframe-5.1.2}/.planning/milestones/v2.0-phases/10-hide-instead-of-delete-sync-mode/10-PATTERNS.md +0 -0
  165. {pushframe-5.1.0 → pushframe-5.1.2}/.planning/milestones/v2.0-phases/10-hide-instead-of-delete-sync-mode/10-RESEARCH.md +0 -0
  166. {pushframe-5.1.0 → pushframe-5.1.2}/.planning/milestones/v2.0-phases/10-hide-instead-of-delete-sync-mode/10-SECURITY.md +0 -0
  167. {pushframe-5.1.0 → pushframe-5.1.2}/.planning/milestones/v2.0-phases/10-hide-instead-of-delete-sync-mode/10-UAT.md +0 -0
  168. {pushframe-5.1.0 → pushframe-5.1.2}/.planning/milestones/v2.0-phases/10-hide-instead-of-delete-sync-mode/10-VERIFICATION.md +0 -0
  169. {pushframe-5.1.0 → pushframe-5.1.2}/.planning/milestones/v2.0-phases/10-hide-instead-of-delete-sync-mode/COVERAGE.md +0 -0
  170. {pushframe-5.1.0 → pushframe-5.1.2}/.planning/milestones/v2.0-quick/260630-qs8-make-main-py-load-a-local-env-file-at-st/260630-qs8-PLAN.md +0 -0
  171. {pushframe-5.1.0 → pushframe-5.1.2}/.planning/milestones/v2.0-quick/260630-qs8-make-main-py-load-a-local-env-file-at-st/260630-qs8-SUMMARY.md +0 -0
  172. {pushframe-5.1.0 → pushframe-5.1.2}/.planning/milestones/v2.0-quick/260708-dt9-add-progress-feedback-to-aura-cli-sync-a/260708-dt9-PLAN.md +0 -0
  173. {pushframe-5.1.0 → pushframe-5.1.2}/.planning/milestones/v2.0-quick/260708-dt9-add-progress-feedback-to-aura-cli-sync-a/260708-dt9-SUMMARY.md +0 -0
  174. {pushframe-5.1.0 → pushframe-5.1.2}/.planning/milestones/v2.0-quick/260708-dt9-add-progress-feedback-to-aura-cli-sync-a/deferred-items.md +0 -0
  175. {pushframe-5.1.0 → pushframe-5.1.2}/.planning/milestones/v2.0-quick/260708-fyr-batch-refactor-of-sync-apply-write-path-/260708-fyr-PLAN.md +0 -0
  176. {pushframe-5.1.0 → pushframe-5.1.2}/.planning/milestones/v2.0-quick/260708-fyr-batch-refactor-of-sync-apply-write-path-/260708-fyr-SUMMARY.md +0 -0
  177. {pushframe-5.1.0 → pushframe-5.1.2}/.planning/milestones/v2.0-quick/260708-fyr-batch-refactor-of-sync-apply-write-path-/260708-fyr-VERIFICATION.md +0 -0
  178. {pushframe-5.1.0 → pushframe-5.1.2}/.planning/milestones/v2.0-quick/README.md +0 -0
  179. {pushframe-5.1.0 → pushframe-5.1.2}/.planning/milestones/v3.0-REQUIREMENTS.md +0 -0
  180. {pushframe-5.1.0 → pushframe-5.1.2}/.planning/milestones/v3.0-ROADMAP.md +0 -0
  181. {pushframe-5.1.0 → pushframe-5.1.2}/.planning/milestones/v3.0-phases/11-write-path-reliability-format-support/11-01-PLAN.md +0 -0
  182. {pushframe-5.1.0 → pushframe-5.1.2}/.planning/milestones/v3.0-phases/11-write-path-reliability-format-support/11-01-SUMMARY.md +0 -0
  183. {pushframe-5.1.0 → pushframe-5.1.2}/.planning/milestones/v3.0-phases/11-write-path-reliability-format-support/11-02-PLAN.md +0 -0
  184. {pushframe-5.1.0 → pushframe-5.1.2}/.planning/milestones/v3.0-phases/11-write-path-reliability-format-support/11-02-SUMMARY.md +0 -0
  185. {pushframe-5.1.0 → pushframe-5.1.2}/.planning/milestones/v3.0-phases/11-write-path-reliability-format-support/11-03-PLAN.md +0 -0
  186. {pushframe-5.1.0 → pushframe-5.1.2}/.planning/milestones/v3.0-phases/11-write-path-reliability-format-support/11-03-SUMMARY.md +0 -0
  187. {pushframe-5.1.0 → pushframe-5.1.2}/.planning/milestones/v3.0-phases/11-write-path-reliability-format-support/11-04-PLAN.md +0 -0
  188. {pushframe-5.1.0 → pushframe-5.1.2}/.planning/milestones/v3.0-phases/11-write-path-reliability-format-support/11-04-SUMMARY.md +0 -0
  189. {pushframe-5.1.0 → pushframe-5.1.2}/.planning/milestones/v3.0-phases/11-write-path-reliability-format-support/11-05-PLAN.md +0 -0
  190. {pushframe-5.1.0 → pushframe-5.1.2}/.planning/milestones/v3.0-phases/11-write-path-reliability-format-support/11-05-SUMMARY.md +0 -0
  191. {pushframe-5.1.0 → pushframe-5.1.2}/.planning/milestones/v3.0-phases/11-write-path-reliability-format-support/11-06-PLAN.md +0 -0
  192. {pushframe-5.1.0 → pushframe-5.1.2}/.planning/milestones/v3.0-phases/11-write-path-reliability-format-support/11-06-SUMMARY.md +0 -0
  193. {pushframe-5.1.0 → pushframe-5.1.2}/.planning/milestones/v3.0-phases/11-write-path-reliability-format-support/11-CONTEXT.md +0 -0
  194. {pushframe-5.1.0 → pushframe-5.1.2}/.planning/milestones/v3.0-phases/11-write-path-reliability-format-support/11-DISCUSSION-LOG.md +0 -0
  195. {pushframe-5.1.0 → pushframe-5.1.2}/.planning/milestones/v3.0-phases/11-write-path-reliability-format-support/11-LIVE-FINDINGS.md +0 -0
  196. {pushframe-5.1.0 → pushframe-5.1.2}/.planning/milestones/v3.0-phases/11-write-path-reliability-format-support/11-PATTERNS.md +0 -0
  197. {pushframe-5.1.0 → pushframe-5.1.2}/.planning/milestones/v3.0-phases/11-write-path-reliability-format-support/11-REVIEW.md +0 -0
  198. {pushframe-5.1.0 → pushframe-5.1.2}/.planning/milestones/v3.0-phases/11-write-path-reliability-format-support/11-VERIFICATION.md +0 -0
  199. {pushframe-5.1.0 → pushframe-5.1.2}/.planning/milestones/v4.0-REQUIREMENTS.md +0 -0
  200. {pushframe-5.1.0 → pushframe-5.1.2}/.planning/milestones/v4.0-ROADMAP.md +0 -0
  201. {pushframe-5.1.0 → pushframe-5.1.2}/.planning/milestones/v4.0-phases/16-local-mechanism-spike-decision/16-01-PLAN.md +0 -0
  202. {pushframe-5.1.0 → pushframe-5.1.2}/.planning/milestones/v4.0-phases/16-local-mechanism-spike-decision/16-01-SUMMARY.md +0 -0
  203. {pushframe-5.1.0 → pushframe-5.1.2}/.planning/milestones/v4.0-phases/16-local-mechanism-spike-decision/16-02-PLAN.md +0 -0
  204. {pushframe-5.1.0 → pushframe-5.1.2}/.planning/milestones/v4.0-phases/16-local-mechanism-spike-decision/16-02-SUMMARY.md +0 -0
  205. {pushframe-5.1.0 → pushframe-5.1.2}/.planning/milestones/v4.0-phases/16-local-mechanism-spike-decision/16-03-PLAN.md +0 -0
  206. {pushframe-5.1.0 → pushframe-5.1.2}/.planning/milestones/v4.0-phases/16-local-mechanism-spike-decision/16-03-SUMMARY.md +0 -0
  207. {pushframe-5.1.0 → pushframe-5.1.2}/.planning/milestones/v4.0-phases/16-local-mechanism-spike-decision/16-CONTEXT.md +0 -0
  208. {pushframe-5.1.0 → pushframe-5.1.2}/.planning/milestones/v4.0-phases/16-local-mechanism-spike-decision/16-DECISION-RECORD.md +0 -0
  209. {pushframe-5.1.0 → pushframe-5.1.2}/.planning/milestones/v4.0-phases/16-local-mechanism-spike-decision/16-DISCUSSION-LOG.md +0 -0
  210. {pushframe-5.1.0 → pushframe-5.1.2}/.planning/milestones/v4.0-phases/16-local-mechanism-spike-decision/16-LIVE-FINDINGS.md +0 -0
  211. {pushframe-5.1.0 → pushframe-5.1.2}/.planning/milestones/v4.0-phases/16-local-mechanism-spike-decision/COVERAGE.md +0 -0
  212. {pushframe-5.1.0 → pushframe-5.1.2}/.planning/milestones/v4.0-phases/17-google-link-album-selection/17-01-PLAN.md +0 -0
  213. {pushframe-5.1.0 → pushframe-5.1.2}/.planning/milestones/v4.0-phases/17-google-link-album-selection/17-01-SUMMARY.md +0 -0
  214. {pushframe-5.1.0 → pushframe-5.1.2}/.planning/milestones/v4.0-phases/17-google-link-album-selection/17-02-PLAN.md +0 -0
  215. {pushframe-5.1.0 → pushframe-5.1.2}/.planning/milestones/v4.0-phases/17-google-link-album-selection/17-02-SUMMARY.md +0 -0
  216. {pushframe-5.1.0 → pushframe-5.1.2}/.planning/milestones/v4.0-phases/17-google-link-album-selection/17-CONTEXT.md +0 -0
  217. {pushframe-5.1.0 → pushframe-5.1.2}/.planning/milestones/v4.0-phases/17-google-link-album-selection/17-DISCUSS-CHECKPOINT.json +0 -0
  218. {pushframe-5.1.0 → pushframe-5.1.2}/.planning/milestones/v4.0-phases/17-google-link-album-selection/17-VERIFICATION.md +0 -0
  219. {pushframe-5.1.0 → pushframe-5.1.2}/.planning/milestones/v4.0-phases/17-google-link-album-selection/COVERAGE.md +0 -0
  220. {pushframe-5.1.0 → pushframe-5.1.2}/.planning/milestones/v4.0-phases/18-album-frame-mirror-sync-single-pair/18-01-PLAN.md +0 -0
  221. {pushframe-5.1.0 → pushframe-5.1.2}/.planning/milestones/v4.0-phases/18-album-frame-mirror-sync-single-pair/18-01-SUMMARY.md +0 -0
  222. {pushframe-5.1.0 → pushframe-5.1.2}/.planning/milestones/v4.0-phases/18-album-frame-mirror-sync-single-pair/18-02-PLAN.md +0 -0
  223. {pushframe-5.1.0 → pushframe-5.1.2}/.planning/milestones/v4.0-phases/18-album-frame-mirror-sync-single-pair/18-02-SUMMARY.md +0 -0
  224. {pushframe-5.1.0 → pushframe-5.1.2}/.planning/milestones/v4.0-phases/18-album-frame-mirror-sync-single-pair/18-03-PLAN.md +0 -0
  225. {pushframe-5.1.0 → pushframe-5.1.2}/.planning/milestones/v4.0-phases/18-album-frame-mirror-sync-single-pair/18-03-SUMMARY.md +0 -0
  226. {pushframe-5.1.0 → pushframe-5.1.2}/.planning/milestones/v4.0-phases/18-album-frame-mirror-sync-single-pair/18-CONTEXT.md +0 -0
  227. {pushframe-5.1.0 → pushframe-5.1.2}/.planning/milestones/v4.0-phases/18-album-frame-mirror-sync-single-pair/18-RESEARCH.md +0 -0
  228. {pushframe-5.1.0 → pushframe-5.1.2}/.planning/milestones/v4.0-phases/18-album-frame-mirror-sync-single-pair/18-UAT.md +0 -0
  229. {pushframe-5.1.0 → pushframe-5.1.2}/.planning/milestones/v4.0-phases/18-album-frame-mirror-sync-single-pair/18-VERIFICATION.md +0 -0
  230. {pushframe-5.1.0 → pushframe-5.1.2}/.planning/milestones/v4.0-phases/19-debt-closeout/19-01-PLAN.md +0 -0
  231. {pushframe-5.1.0 → pushframe-5.1.2}/.planning/milestones/v4.0-phases/19-debt-closeout/19-01-SUMMARY.md +0 -0
  232. {pushframe-5.1.0 → pushframe-5.1.2}/.planning/milestones/v4.0-phases/19-debt-closeout/19-02-PLAN.md +0 -0
  233. {pushframe-5.1.0 → pushframe-5.1.2}/.planning/milestones/v4.0-phases/19-debt-closeout/19-02-SUMMARY.md +0 -0
  234. {pushframe-5.1.0 → pushframe-5.1.2}/.planning/milestones/v4.0-phases/19-debt-closeout/19-CONTEXT.md +0 -0
  235. {pushframe-5.1.0 → pushframe-5.1.2}/.planning/milestones/v4.0-phases/19-debt-closeout/19-RESEARCH.md +0 -0
  236. {pushframe-5.1.0 → pushframe-5.1.2}/.planning/milestones/v4.0-phases/19-debt-closeout/19-VERIFICATION.md +0 -0
  237. {pushframe-5.1.0 → pushframe-5.1.2}/.planning/milestones/v5.0-REQUIREMENTS.md +0 -0
  238. {pushframe-5.1.0 → pushframe-5.1.2}/.planning/milestones/v5.0-ROADMAP.md +0 -0
  239. {pushframe-5.1.0 → pushframe-5.1.2}/.planning/milestones/v5.0-phases/20-pushframe-rename-migration-repo-switch/20-SUMMARY.md +0 -0
  240. {pushframe-5.1.0 → pushframe-5.1.2}/.planning/milestones/v5.0-phases/21-debian-package-apt-repo/21-01-PLAN.md +0 -0
  241. {pushframe-5.1.0 → pushframe-5.1.2}/.planning/milestones/v5.0-phases/21-debian-package-apt-repo/21-01-SUMMARY.md +0 -0
  242. {pushframe-5.1.0 → pushframe-5.1.2}/.planning/milestones/v5.0-phases/21-debian-package-apt-repo/21-02-PLAN.md +0 -0
  243. {pushframe-5.1.0 → pushframe-5.1.2}/.planning/milestones/v5.0-phases/21-debian-package-apt-repo/21-02-SUMMARY.md +0 -0
  244. {pushframe-5.1.0 → pushframe-5.1.2}/.planning/milestones/v5.0-phases/21-debian-package-apt-repo/21-CONTEXT.md +0 -0
  245. {pushframe-5.1.0 → pushframe-5.1.2}/.planning/milestones/v5.0-phases/21-debian-package-apt-repo/21-RESEARCH.md +0 -0
  246. {pushframe-5.1.0 → pushframe-5.1.2}/.planning/milestones/v5.0-phases/21-debian-package-apt-repo/21-VERIFICATION.md +0 -0
  247. {pushframe-5.1.0 → pushframe-5.1.2}/.planning/milestones/v5.0-phases/22-pypi-release-engineering/22-01-PLAN.md +0 -0
  248. {pushframe-5.1.0 → pushframe-5.1.2}/.planning/milestones/v5.0-phases/22-pypi-release-engineering/22-01-SUMMARY.md +0 -0
  249. {pushframe-5.1.0 → pushframe-5.1.2}/.planning/milestones/v5.0-phases/22-pypi-release-engineering/22-02-PLAN.md +0 -0
  250. {pushframe-5.1.0 → pushframe-5.1.2}/.planning/milestones/v5.0-phases/22-pypi-release-engineering/22-02-SUMMARY.md +0 -0
  251. {pushframe-5.1.0 → pushframe-5.1.2}/.planning/milestones/v5.0-phases/22-pypi-release-engineering/22-CONTEXT.md +0 -0
  252. {pushframe-5.1.0 → pushframe-5.1.2}/.planning/milestones/v5.0-phases/22-pypi-release-engineering/22-RESEARCH.md +0 -0
  253. {pushframe-5.1.0 → pushframe-5.1.2}/.planning/milestones/v5.0-phases/22-pypi-release-engineering/22-VERIFICATION.md +0 -0
  254. {pushframe-5.1.0 → pushframe-5.1.2}/.planning/milestones/v5.0-phases/22-pypi-release-engineering/OPERATOR-STEPS.md +0 -0
  255. {pushframe-5.1.0 → pushframe-5.1.2}/.planning/phases/23-config-wizard-precedence/23-01-PLAN.md +0 -0
  256. {pushframe-5.1.0 → pushframe-5.1.2}/.planning/phases/23-config-wizard-precedence/23-01-SUMMARY.md +0 -0
  257. {pushframe-5.1.0 → pushframe-5.1.2}/.planning/phases/23-config-wizard-precedence/23-CONTEXT.md +0 -0
  258. {pushframe-5.1.0 → pushframe-5.1.2}/.planning/phases/23-config-wizard-precedence/23-RESEARCH.md +0 -0
  259. {pushframe-5.1.0 → pushframe-5.1.2}/.planning/phases/23-config-wizard-precedence/23-VERIFICATION.md +0 -0
  260. {pushframe-5.1.0 → pushframe-5.1.2}/.planning/phases/24-token-first-sessions-preflight-sweep-sec-01-03-prf-01-02/24-01-PLAN.md +0 -0
  261. {pushframe-5.1.0 → pushframe-5.1.2}/.planning/phases/24-token-first-sessions-preflight-sweep-sec-01-03-prf-01-02/24-01-SUMMARY.md +0 -0
  262. {pushframe-5.1.0 → pushframe-5.1.2}/.planning/phases/24-token-first-sessions-preflight-sweep-sec-01-03-prf-01-02/24-CONTEXT.md +0 -0
  263. {pushframe-5.1.0 → pushframe-5.1.2}/.planning/phases/24-token-first-sessions-preflight-sweep-sec-01-03-prf-01-02/24-RESEARCH.md +0 -0
  264. {pushframe-5.1.0 → pushframe-5.1.2}/.planning/phases/24-token-first-sessions-preflight-sweep-sec-01-03-prf-01-02/24-VERIFICATION.md +0 -0
  265. {pushframe-5.1.0 → pushframe-5.1.2}/.planning/phases/25-multi-frame-mappings-systemd-user-scheduling-mtf-01-03-tmr-01-03/25-01-PLAN.md +0 -0
  266. {pushframe-5.1.0 → pushframe-5.1.2}/.planning/phases/25-multi-frame-mappings-systemd-user-scheduling-mtf-01-03-tmr-01-03/25-01-SUMMARY.md +0 -0
  267. {pushframe-5.1.0 → pushframe-5.1.2}/.planning/phases/25-multi-frame-mappings-systemd-user-scheduling-mtf-01-03-tmr-01-03/25-CONTEXT.md +0 -0
  268. {pushframe-5.1.0 → pushframe-5.1.2}/.planning/phases/25-multi-frame-mappings-systemd-user-scheduling-mtf-01-03-tmr-01-03/25-RESEARCH.md +0 -0
  269. {pushframe-5.1.0 → pushframe-5.1.2}/.planning/phases/25-multi-frame-mappings-systemd-user-scheduling-mtf-01-03-tmr-01-03/25-VERIFICATION.md +0 -0
  270. {pushframe-5.1.0 → pushframe-5.1.2}/.planning/research/ALBUM-ACCESS-V4-ADDENDUM.md +0 -0
  271. {pushframe-5.1.0 → pushframe-5.1.2}/.planning/research/ALBUM-ACCESS.md +0 -0
  272. {pushframe-5.1.0 → pushframe-5.1.2}/.planning/research/ARCHITECTURE.md +0 -0
  273. {pushframe-5.1.0 → pushframe-5.1.2}/.planning/research/BROWSER-AUTOMATION.md +0 -0
  274. {pushframe-5.1.0 → pushframe-5.1.2}/.planning/research/FEATURES.md +0 -0
  275. {pushframe-5.1.0 → pushframe-5.1.2}/.planning/research/PITFALLS.md +0 -0
  276. {pushframe-5.1.0 → pushframe-5.1.2}/.planning/research/STACK.md +0 -0
  277. {pushframe-5.1.0 → pushframe-5.1.2}/.planning/research/SUMMARY.md +0 -0
  278. {pushframe-5.1.0 → pushframe-5.1.2}/.planning/state.json +0 -0
  279. {pushframe-5.1.0 → pushframe-5.1.2}/.planning/todos/completed/2026-07-06-promote-debug-flag-to-a-global-cli-convention.md +0 -0
  280. {pushframe-5.1.0 → pushframe-5.1.2}/.python-version +0 -0
  281. {pushframe-5.1.0 → pushframe-5.1.2}/CLAUDE.md +0 -0
  282. {pushframe-5.1.0 → pushframe-5.1.2}/LICENSE +0 -0
  283. {pushframe-5.1.0 → pushframe-5.1.2}/README.md +0 -0
  284. {pushframe-5.1.0 → pushframe-5.1.2}/SECURITY.md +0 -0
  285. {pushframe-5.1.0 → pushframe-5.1.2}/VERIFICATION-REPORT.md +0 -0
  286. {pushframe-5.1.0 → pushframe-5.1.2}/docs/agents/domain.md +0 -0
  287. {pushframe-5.1.0 → pushframe-5.1.2}/docs/agents/issue-tracker.md +0 -0
  288. {pushframe-5.1.0 → pushframe-5.1.2}/docs/agents/triage-labels.md +0 -0
  289. {pushframe-5.1.0 → pushframe-5.1.2}/docs/superpowers/specs/2026-07-09-write-rate-limiter-design.md +0 -0
  290. {pushframe-5.1.0 → pushframe-5.1.2}/main.py +0 -0
  291. {pushframe-5.1.0 → pushframe-5.1.2}/packaging/control-template +0 -0
  292. {pushframe-5.1.0 → pushframe-5.1.2}/packaging/lintian-overrides +0 -0
  293. {pushframe-5.1.0 → pushframe-5.1.2}/packaging/postinst +0 -0
  294. {pushframe-5.1.0 → pushframe-5.1.2}/packaging/pushframe.copyright +0 -0
  295. {pushframe-5.1.0 → pushframe-5.1.2}/probes/.gitignore +0 -0
  296. {pushframe-5.1.0 → pushframe-5.1.2}/probes/README.md +0 -0
  297. {pushframe-5.1.0 → pushframe-5.1.2}/probes/browser_bootstrap.py +0 -0
  298. {pushframe-5.1.0 → pushframe-5.1.2}/probes/common.py +0 -0
  299. {pushframe-5.1.0 → pushframe-5.1.2}/probes/cookie_vault.py +0 -0
  300. {pushframe-5.1.0 → pushframe-5.1.2}/probes/fidelity_check.py +0 -0
  301. {pushframe-5.1.0 → pushframe-5.1.2}/probes/rpc_capture.py +0 -0
  302. {pushframe-5.1.0 → pushframe-5.1.2}/probes/shared_link_probe.py +0 -0
  303. {pushframe-5.1.0 → pushframe-5.1.2}/pushframe/api/__init__.py +0 -0
  304. {pushframe-5.1.0 → pushframe-5.1.2}/pushframe/api/accountApi.py +0 -0
  305. {pushframe-5.1.0 → pushframe-5.1.2}/pushframe/api/activityApi.py +0 -0
  306. {pushframe-5.1.0 → pushframe-5.1.2}/pushframe/api/assetApi.py +0 -0
  307. {pushframe-5.1.0 → pushframe-5.1.2}/pushframe/api/baseApi.py +0 -0
  308. {pushframe-5.1.0 → pushframe-5.1.2}/pushframe/api/frameApi.py +0 -0
  309. {pushframe-5.1.0 → pushframe-5.1.2}/pushframe/api/notificationApi.py +0 -0
  310. {pushframe-5.1.0 → pushframe-5.1.2}/pushframe/api/peopleApi.py +0 -0
  311. {pushframe-5.1.0 → pushframe-5.1.2}/pushframe/api/playlistApi.py +0 -0
  312. {pushframe-5.1.0 → pushframe-5.1.2}/pushframe/aura.py +0 -0
  313. {pushframe-5.1.0 → pushframe-5.1.2}/pushframe/aws/__init__.py +0 -0
  314. {pushframe-5.1.0 → pushframe-5.1.2}/pushframe/aws/awsclient.py +0 -0
  315. {pushframe-5.1.0 → pushframe-5.1.2}/pushframe/aws/s3client.py +0 -0
  316. {pushframe-5.1.0 → pushframe-5.1.2}/pushframe/aws/sqsclient.py +0 -0
  317. {pushframe-5.1.0 → pushframe-5.1.2}/pushframe/cache.py +0 -0
  318. {pushframe-5.1.0 → pushframe-5.1.2}/pushframe/config_store.py +0 -0
  319. {pushframe-5.1.0 → pushframe-5.1.2}/pushframe/doctor.py +0 -0
  320. {pushframe-5.1.0 → pushframe-5.1.2}/pushframe/exif.py +0 -0
  321. {pushframe-5.1.0 → pushframe-5.1.2}/pushframe/export.py +0 -0
  322. {pushframe-5.1.0 → pushframe-5.1.2}/pushframe/google/__init__.py +0 -0
  323. {pushframe-5.1.0 → pushframe-5.1.2}/pushframe/google/bootstrap.py +0 -0
  324. {pushframe-5.1.0 → pushframe-5.1.2}/pushframe/google/cache.py +0 -0
  325. {pushframe-5.1.0 → pushframe-5.1.2}/pushframe/google/client.py +0 -0
  326. {pushframe-5.1.0 → pushframe-5.1.2}/pushframe/google/enumerate.py +0 -0
  327. {pushframe-5.1.0 → pushframe-5.1.2}/pushframe/google/manifest.py +0 -0
  328. {pushframe-5.1.0 → pushframe-5.1.2}/pushframe/google/parsers.py +0 -0
  329. {pushframe-5.1.0 → pushframe-5.1.2}/pushframe/google/redaction.py +0 -0
  330. {pushframe-5.1.0 → pushframe-5.1.2}/pushframe/google/vault.py +0 -0
  331. {pushframe-5.1.0 → pushframe-5.1.2}/pushframe/gsync.py +0 -0
  332. {pushframe-5.1.0 → pushframe-5.1.2}/pushframe/migration.py +0 -0
  333. {pushframe-5.1.0 → pushframe-5.1.2}/pushframe/models/__init__.py +0 -0
  334. {pushframe-5.1.0 → pushframe-5.1.2}/pushframe/models/activity.py +0 -0
  335. {pushframe-5.1.0 → pushframe-5.1.2}/pushframe/models/asset.py +0 -0
  336. {pushframe-5.1.0 → pushframe-5.1.2}/pushframe/models/frame.py +0 -0
  337. {pushframe-5.1.0 → pushframe-5.1.2}/pushframe/models/meta.py +0 -0
  338. {pushframe-5.1.0 → pushframe-5.1.2}/pushframe/models/person.py +0 -0
  339. {pushframe-5.1.0 → pushframe-5.1.2}/pushframe/models/user.py +0 -0
  340. {pushframe-5.1.0 → pushframe-5.1.2}/pushframe/pairs.py +0 -0
  341. {pushframe-5.1.0 → pushframe-5.1.2}/pushframe/preflight.py +0 -0
  342. {pushframe-5.1.0 → pushframe-5.1.2}/pushframe/ratelimit.py +0 -0
  343. {pushframe-5.1.0 → pushframe-5.1.2}/pushframe/reconcile.py +0 -0
  344. {pushframe-5.1.0 → pushframe-5.1.2}/pushframe/schedule.py +0 -0
  345. {pushframe-5.1.0 → pushframe-5.1.2}/pushframe/session.py +0 -0
  346. {pushframe-5.1.0 → pushframe-5.1.2}/pushframe/sync.py +0 -0
  347. {pushframe-5.1.0 → pushframe-5.1.2}/pushframe/utils/dt.py +0 -0
  348. {pushframe-5.1.0 → pushframe-5.1.2}/pushframe/utils/io.py +0 -0
  349. {pushframe-5.1.0 → pushframe-5.1.2}/scripts/build-deb.sh +0 -0
  350. {pushframe-5.1.0 → pushframe-5.1.2}/scripts/make-apt-key.sh +0 -0
  351. {pushframe-5.1.0 → pushframe-5.1.2}/scripts/publish-apt-repo.sh +0 -0
  352. {pushframe-5.1.0 → pushframe-5.1.2}/scripts/test-apt-journey-container.sh +0 -0
  353. {pushframe-5.1.0 → pushframe-5.1.2}/scripts/test-config-journey-container.sh +0 -0
  354. {pushframe-5.1.0 → pushframe-5.1.2}/scripts/test-deb-container.sh +0 -0
  355. {pushframe-5.1.0 → pushframe-5.1.2}/scripts/test-install-journey-container.sh +0 -0
  356. {pushframe-5.1.0 → pushframe-5.1.2}/scripts/test-multipair-journey-container.sh +0 -0
  357. {pushframe-5.1.0 → pushframe-5.1.2}/tests/fixtures/asset_unprocessed.json +0 -0
  358. {pushframe-5.1.0 → pushframe-5.1.2}/tests/fixtures/assets_page1.json +0 -0
  359. {pushframe-5.1.0 → pushframe-5.1.2}/tests/fixtures/assets_page2.json +0 -0
  360. {pushframe-5.1.0 → pushframe-5.1.2}/tests/fixtures/assets_placeholders.json +0 -0
  361. {pushframe-5.1.0 → pushframe-5.1.2}/tests/fixtures/error_envelope.json +0 -0
  362. {pushframe-5.1.0 → pushframe-5.1.2}/tests/fixtures/frame_detail.json +0 -0
  363. {pushframe-5.1.0 → pushframe-5.1.2}/tests/fixtures/frames.json +0 -0
  364. {pushframe-5.1.0 → pushframe-5.1.2}/tests/fixtures/google_rpc_snackc_page.json +0 -0
  365. {pushframe-5.1.0 → pushframe-5.1.2}/tests/fixtures/google_share_page_sample.html +0 -0
  366. {pushframe-5.1.0 → pushframe-5.1.2}/tests/fixtures/login.json +0 -0
  367. {pushframe-5.1.0 → pushframe-5.1.2}/tests/fixtures/probe_af_initdata_sample.html +0 -0
  368. {pushframe-5.1.0 → pushframe-5.1.2}/tests/offline.py +0 -0
  369. {pushframe-5.1.0 → pushframe-5.1.2}/tests/test_asset_partial.py +0 -0
  370. {pushframe-5.1.0 → pushframe-5.1.2}/tests/test_asset_unprocessed.py +0 -0
  371. {pushframe-5.1.0 → pushframe-5.1.2}/tests/test_aura_logger.py +0 -0
  372. {pushframe-5.1.0 → pushframe-5.1.2}/tests/test_batch_update_contract.py +0 -0
  373. {pushframe-5.1.0 → pushframe-5.1.2}/tests/test_cli_apply.py +0 -0
  374. {pushframe-5.1.0 → pushframe-5.1.2}/tests/test_cli_google_album.py +0 -0
  375. {pushframe-5.1.0 → pushframe-5.1.2}/tests/test_cli_google_link.py +0 -0
  376. {pushframe-5.1.0 → pushframe-5.1.2}/tests/test_cli_inspect.py +0 -0
  377. {pushframe-5.1.0 → pushframe-5.1.2}/tests/test_cli_push_budget_geo.py +0 -0
  378. {pushframe-5.1.0 → pushframe-5.1.2}/tests/test_cli_reconcile.py +0 -0
  379. {pushframe-5.1.0 → pushframe-5.1.2}/tests/test_cli_status.py +0 -0
  380. {pushframe-5.1.0 → pushframe-5.1.2}/tests/test_cli_status_google.py +0 -0
  381. {pushframe-5.1.0 → pushframe-5.1.2}/tests/test_cli_sync.py +0 -0
  382. {pushframe-5.1.0 → pushframe-5.1.2}/tests/test_client_rate_limit.py +0 -0
  383. {pushframe-5.1.0 → pushframe-5.1.2}/tests/test_doctor.py +0 -0
  384. {pushframe-5.1.0 → pushframe-5.1.2}/tests/test_execute_plan.py +0 -0
  385. {pushframe-5.1.0 → pushframe-5.1.2}/tests/test_execute_plan_budget_geo.py +0 -0
  386. {pushframe-5.1.0 → pushframe-5.1.2}/tests/test_fixtures_validity.py +0 -0
  387. {pushframe-5.1.0 → pushframe-5.1.2}/tests/test_google_cache.py +0 -0
  388. {pushframe-5.1.0 → pushframe-5.1.2}/tests/test_google_client.py +0 -0
  389. {pushframe-5.1.0 → pushframe-5.1.2}/tests/test_google_enumerate.py +0 -0
  390. {pushframe-5.1.0 → pushframe-5.1.2}/tests/test_google_manifest.py +0 -0
  391. {pushframe-5.1.0 → pushframe-5.1.2}/tests/test_google_parsers.py +0 -0
  392. {pushframe-5.1.0 → pushframe-5.1.2}/tests/test_google_vault.py +0 -0
  393. {pushframe-5.1.0 → pushframe-5.1.2}/tests/test_gsync_all.py +0 -0
  394. {pushframe-5.1.0 → pushframe-5.1.2}/tests/test_gsync_engine.py +0 -0
  395. {pushframe-5.1.0 → pushframe-5.1.2}/tests/test_gsync_execute.py +0 -0
  396. {pushframe-5.1.0 → pushframe-5.1.2}/tests/test_imports.py +0 -0
  397. {pushframe-5.1.0 → pushframe-5.1.2}/tests/test_logout.py +0 -0
  398. {pushframe-5.1.0 → pushframe-5.1.2}/tests/test_migration.py +0 -0
  399. {pushframe-5.1.0 → pushframe-5.1.2}/tests/test_offline_read_path.py +0 -0
  400. {pushframe-5.1.0 → pushframe-5.1.2}/tests/test_pairs.py +0 -0
  401. {pushframe-5.1.0 → pushframe-5.1.2}/tests/test_preflight_sweep.py +0 -0
  402. {pushframe-5.1.0 → pushframe-5.1.2}/tests/test_prep_upload_formats.py +0 -0
  403. {pushframe-5.1.0 → pushframe-5.1.2}/tests/test_probe_browser.py +0 -0
  404. {pushframe-5.1.0 → pushframe-5.1.2}/tests/test_probe_shared_link.py +0 -0
  405. {pushframe-5.1.0 → pushframe-5.1.2}/tests/test_ratelimit.py +0 -0
  406. {pushframe-5.1.0 → pushframe-5.1.2}/tests/test_read_path.py +0 -0
  407. {pushframe-5.1.0 → pushframe-5.1.2}/tests/test_reconcile.py +0 -0
  408. {pushframe-5.1.0 → pushframe-5.1.2}/tests/test_schedule.py +0 -0
  409. {pushframe-5.1.0 → pushframe-5.1.2}/tests/test_session.py +0 -0
  410. {pushframe-5.1.0 → pushframe-5.1.2}/tests/test_settings_budget.py +0 -0
  411. {pushframe-5.1.0 → pushframe-5.1.2}/tests/test_sync_engine.py +0 -0
  412. {pushframe-5.1.0 → pushframe-5.1.2}/tests/test_write_endpoints_failloud.py +0 -0
  413. {pushframe-5.1.0 → pushframe-5.1.2}/tests/test_write_formats_live.py +0 -0
@@ -14,7 +14,49 @@ frames; it is not affiliated with Aura Frames Inc.
14
14
 
15
15
  ## [Unreleased]
16
16
 
17
- - (v5.1 work will land here)
17
+ ## [5.1.2] — 2026-09-30
18
+
19
+ ### Changed (tests)
20
+
21
+ - **Network guard**: `httpx.Client` is patched for the whole test session
22
+ so any client built WITHOUT an explicit transport gets a blocking one —
23
+ except when built inside a live-marked test (the only legitimate
24
+ real-network shape). An offline test that reaches the real network now
25
+ fails loudly instead of silently depending on a local .env (the relogin
26
+ masking that kept the tests workflow red from v5.1.0 to 5.1.1), and a
27
+ local .env can no longer masquerade as a live posture.
28
+
29
+ ### Fixed (tests)
30
+
31
+ - **The `tests` workflow is green again** (red on master since v5.1.0 —
32
+ not a 5.1.1 regression): the 9 offline tests of the 401-retry and
33
+ write-throttling suites relied on the pre-phase-24 re-login default
34
+ (`aura.login`); phase 24 made the default token-aware, which fails
35
+ named off-TTY without env credentials — exactly the CI case. A local
36
+ `.env` with real credentials masked it locally. All 9 `execute_plan`
37
+ call sites now pin `relogin=aura.login`; zero product code changed.
38
+
39
+ ## [5.1.1] — 2026-09-30
40
+
41
+ ### Changed
42
+
43
+ - **Credible client identity** (venus 2026-09-30 anti-abuse lesson): the
44
+ user agent is now a setting — `USER_AGENT` (env `PUSHFRAME_USER_AGENT`,
45
+ or `pushframe config set USER_AGENT …`) — whose default tracks a
46
+ current Play build (`Aura/4.7.4271 (Android 36; Client)`) instead of
47
+ the years-stale `4.7.790`; combined with a per-install
48
+ `config set DEVICE_IDENTIFIER "$(uuidgen)"`, an installation stops
49
+ presenting the shared all-zeros fingerprint that kept reads green
50
+ (like the real phone app) while writes were 401-refused for months.
51
+ - **Automatic identity provisioning**: the `pushframe config` wizard now
52
+ generates a unique `DEVICE_IDENTIFIER` (uuid4) on a fresh install
53
+ BEFORE its first API call, so the very first login already presents a
54
+ per-install fingerprint — no installation ever ships the all-zeros id
55
+ again. Explicit identities (env or config file) are never overwritten,
56
+ and the generated id is stable across wizard re-runs.
57
+ - `config show` warns when the effective `DEVICE_IDENTIFIER` is still the
58
+ shared all-zeros default, naming the anti-abuse rationale and the
59
+ `config set DEVICE_IDENTIFIER "$(uuidgen)"` remedy.
18
60
 
19
61
  ## [5.1.0] — 2026-09-29
20
62
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: pushframe
3
- Version: 5.1.0
3
+ Version: 5.1.2
4
4
  Summary: Unofficial community CLI for Aura Frames digital photo frames (sync a local folder or a Google Photos album; not affiliated with Aura Frames Inc.)
5
5
  Project-URL: Homepage, https://github.com/coredmp95/pushframe
6
6
  Project-URL: Repository, https://github.com/coredmp95/pushframe
@@ -123,6 +123,20 @@ retry, no re-login — both feed the trip). The drill:
123
123
  pushframe google-sync Cadre --frame "Cadre de Fabrice" --apply --yes --batch-size 10
124
124
  ```
125
125
 
126
+ **Client identity matters.** Pushd's anti-abuse layer also fingerprints
127
+ the client itself: a years-stale `Aura/4.7.790` user agent and the
128
+ all-zeros `0000000000000000` device identifier shared by every pushframe
129
+ installation read as "not a phone" (2026-09-30: reads stayed green for
130
+ the real phone app while writes were 401-refused for months). Give each
131
+ install its own identity once:
132
+
133
+ ```bash
134
+ pushframe config set DEVICE_IDENTIFIER "$(uuidgen)"
135
+ ```
136
+
137
+ The user agent defaults to a current Play build (a setting since 5.1.1);
138
+ bump it with `pushframe config set USER_AGENT 'Aura/…'` if it ages again.
139
+
126
140
  Progress is never lost: only confirmed writes are remembered, and the next
127
141
  run uploads the remainder once — never twice.
128
142
 
@@ -730,6 +744,7 @@ to adopt one.
730
744
  | `AURA_LOCALE` | `en-US` |
731
745
  | `AURA_APP_IDENTIFIER` | `com.pushd.client` |
732
746
  | `AURA_DEVICE_IDENTIFIER` | `0000000000000000` |
747
+ | `AURA_USER_AGENT` | `Aura/4.7.4271 (Android 36; Client)` |
733
748
 
734
749
  Booleans accept `1`, `true`, `yes`, `on` (case-insensitive); anything else is false.
735
750
 
@@ -1,4 +1,4 @@
1
1
  # pushframe — unofficial community CLI for Aura Frames (not affiliated).
2
2
  # Version single source for the CLI surface (REL-02); pyproject keeps the
3
3
  # packaging metadata version and tests/test_version.py enforces lockstep.
4
- __version__ = "5.1.0"
4
+ __version__ = "5.1.2"
@@ -358,6 +358,27 @@ def run_config(wizard_args=None, stdin_isatty: bool | None = None) -> int:
358
358
  '(use config set / config import for scripts).')
359
359
  return 1
360
360
 
361
+ # Identity provisioning (5.1.1, venus anti-abuse lesson): before the
362
+ # FIRST API call of a fresh install, give it a unique device id so its
363
+ # very first login already presents a credible per-install fingerprint
364
+ # instead of the all-zeros id every pushframe install used to share.
365
+ # Only the absence of one triggers a write; an explicit setting (env or
366
+ # file) is never second-guessed. The block contains no account data —
367
+ # D-03's "nothing written before a successful login" keeps its meaning
368
+ # for session facts (email/token/frame/debug).
369
+ if not os.getenv('PUSHFRAME_DEVICE_IDENTIFIER') \
370
+ and not os.getenv('AURA_DEVICE_IDENTIFIER') \
371
+ and config_store.setting('DEVICE_IDENTIFIER') is None:
372
+ import uuid
373
+ # load→mutate→save (NOT update(settings=...), which would REPLACE the
374
+ # settings map and drop any sibling file settings like LOCALE).
375
+ data = config_store.load()
376
+ identity = str(uuid.uuid4())
377
+ data.setdefault('settings', {})['DEVICE_IDENTIFIER'] = identity
378
+ config_store.save(data)
379
+ print(f'device identity provisioned: DEVICE_IDENTIFIER = {identity}'
380
+ ' (unique to this install)')
381
+
361
382
  existing = config_store.load()
362
383
  if existing.get('email'):
363
384
  print(f"configuring pushframe (current email: {existing['email']} — Enter keeps it)")
@@ -437,6 +458,15 @@ def _config_show() -> int:
437
458
  print(f' {key} = {shown!r} (file)')
438
459
  if data.get('auth_token'):
439
460
  print(' auth_token = *** (file)')
461
+ # Identity hygiene (5.1.1, venus anti-abuse lesson): the all-zeros id is
462
+ # shared by every unprovisioned pushframe install and reads as a
463
+ # non-phone client — name it and hand the remedy.
464
+ if settings.DEVICE_IDENTIFIER == settings.DEFAULTS['DEVICE_IDENTIFIER']['default']:
465
+ print(' WARNING: DEVICE_IDENTIFIER is still the shared all-zeros '
466
+ 'default — pushd reads it as a non-phone client (venus '
467
+ '2026-09-30: reads stayed green while writes were 401-refused '
468
+ 'for months). Give this install a unique identity: '
469
+ 'pushframe config set DEVICE_IDENTIFIER "$(uuidgen)"')
440
470
  return 0
441
471
 
442
472
 
@@ -16,7 +16,15 @@ from pushframe.utils import settings as _settings
16
16
 
17
17
  AURA_API_BASE_URL = _settings.AURA_API_BASE_URL
18
18
  AURA_API_VERSION = _settings.AURA_API_VERSION
19
- USER_AGENT = 'Aura/4.7.790 (Android 30; Client)'
19
+ # Client identity is part of pushd's anti-abuse fingerprint (venus
20
+ # 2026-09-30: the years-stale '4.7.790' UA plus the shared all-zeros
21
+ # device id read as "not a phone" — reads stayed green for the real app
22
+ # while writes 401'd; see docs/CLI.md "Client identity matters"). The UA
23
+ # is therefore a setting (env PUSHFRAME_USER_AGENT → config file →
24
+ # default), resolved at import like the two settings above; the default
25
+ # tracks a current Play build, and DEVICE_IDENTIFIER should be a
26
+ # per-install UUID (`pushframe config set DEVICE_IDENTIFIER "$(uuidgen)"`).
27
+ USER_AGENT = _settings.USER_AGENT
20
28
 
21
29
  # Keys whose values are secrets and must never reach the on-disk logs (D-07).
22
30
  _REDACT_KEYS = {'password', 'auth_token', 'x-token-auth'}
@@ -20,11 +20,15 @@ CONFIG_PATH = Path(os.getenv('PUSHFRAME_CONFIG_FILE',
20
20
 
21
21
  # The single table: settings name -> (env spellings, default, caster).
22
22
  # Defaults ARE the literals that shipped for years (MOD-02 posture), so an
23
- # empty config + empty environment behaves byte-identically to 5.0.x.
23
+ # empty config + empty environment behaves byte-identically to 5.0.x —
24
+ # sole deliberate exception: USER_AGENT's default was bumped to a current
25
+ # Play build in 5.1.1 because the years-stale identity feeds the
26
+ # anti-abuse trip (venus lesson; see docs/CLI.md "Client identity").
24
27
  DEFAULTS: dict[str, dict] = {
25
28
  'LOCALE': {'env': ('PUSHFRAME_LOCALE', 'AURA_LOCALE'), 'default': 'en-US'},
26
29
  'AURA_APP_IDENTIFIER': {'env': ('PUSHFRAME_APP_IDENTIFIER', 'AURA_APP_IDENTIFIER'), 'default': 'com.pushd.client'},
27
30
  'DEVICE_IDENTIFIER': {'env': ('PUSHFRAME_DEVICE_IDENTIFIER', 'AURA_DEVICE_IDENTIFIER'), 'default': '0000000000000000'},
31
+ 'USER_AGENT': {'env': ('PUSHFRAME_USER_AGENT', 'AURA_USER_AGENT'), 'default': 'Aura/4.7.4271 (Android 36; Client)'},
28
32
  'IMAGE_PROXY_BASE_URL': {'env': (), 'default': 'https://imgproxy.pushd.com'},
29
33
  'AURA_API_BASE_URL': {'env': ('PUSHFRAME_API_BASE_URL',), 'default': 'https://api.pushd.com/v5'},
30
34
  'AURA_API_VERSION': {'env': ('PUSHFRAME_API_VERSION',), 'default': 'v5'},
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "pushframe"
3
- version = "5.1.0"
3
+ version = "5.1.2"
4
4
  description = "Unofficial community CLI for Aura Frames digital photo frames (sync a local folder or a Google Photos album; not affiliated with Aura Frames Inc.)"
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.14"
@@ -0,0 +1,100 @@
1
+ import os
2
+
3
+ import pytest
4
+ from dotenv import load_dotenv
5
+
6
+ # Load AURA_EMAIL / AURA_PASSWORD from a local .env so the live read-path tests
7
+ # can run without exporting shell vars. Shell-exported vars still win (override
8
+ # defaults to False), and a missing .env is a no-op — so a credential-less
9
+ # checkout still skips the live suite cleanly (D-02).
10
+ load_dotenv()
11
+
12
+
13
+ # ---------------------------------------------------------------------------
14
+ # Network guard (CI hardening, 2026-09-30). An OFFLINE test must never reach
15
+ # the real pushd API. Phase 24's token-aware relogin default let 9 offline
16
+ # tests silently depend on a local .env (the env-password branch) and fail in
17
+ # CI — the mock was bypassed because nothing blocked the DEFAULT transport.
18
+ # While no live test is actually going to run, patch httpx.Client so any
19
+ # client built WITHOUT an explicit transport (the real-network shape) gets a
20
+ # blocking one: every request fails loudly. offline_aura-style tests are
21
+ # unaffected (they pass transport= explicitly); `live` runs opt out.
22
+ # ---------------------------------------------------------------------------
23
+ _NETWORK_GUARD_ACTIVE = False
24
+ _CURRENT_ITEM = None
25
+
26
+
27
+ def pytest_runtest_setup(item):
28
+ """Track the current item so the guard can allow live-marked tests."""
29
+ global _CURRENT_ITEM
30
+ _CURRENT_ITEM = item
31
+
32
+
33
+ def pytest_runtest_teardown(item):
34
+ global _CURRENT_ITEM
35
+ _CURRENT_ITEM = None
36
+
37
+
38
+ def _blocking_transport():
39
+ import httpx
40
+
41
+ def handler(request: httpx.Request) -> httpx.Response:
42
+ raise RuntimeError(
43
+ 'NETWORK GUARD: an offline test reached the real network at '
44
+ f'{request.url}. Offline tests must go through '
45
+ 'tests.offline.offline_aura (explicit transport) or an injected '
46
+ 'seam. If a product default now escapes the seam, fix the TEST '
47
+ '(e.g. pin relogin=aura.login), not the product.'
48
+ )
49
+
50
+ return httpx.MockTransport(handler)
51
+
52
+
53
+ def pytest_collection_modifyitems(config, items):
54
+ """Install the network guard for the whole session. It is ALWAYS active:
55
+ every transport-less httpx.Client gets a blocking transport, EXCEPT when
56
+ built DURING a live-marked test (the only legitimate real-network shape:
57
+ the session `aura` fixture constructs the client inside the live test,
58
+ so the marker is checked at construction time — not at collection, which
59
+ would let a local .env masquerade as a live posture for offline tests)."""
60
+ global _NETWORK_GUARD_ACTIVE
61
+ import httpx
62
+
63
+ original = httpx.Client.__init__
64
+
65
+ def guarded(self, *args, **kwargs):
66
+ live_running = _CURRENT_ITEM is not None and \
67
+ _CURRENT_ITEM.get_closest_marker('live') is not None
68
+ # httpx.Client's signature is keyword-only; `app` implies transport.
69
+ if not live_running and 'transport' not in kwargs and 'app' not in kwargs:
70
+ kwargs['transport'] = _blocking_transport()
71
+ return original(self, *args, **kwargs)
72
+
73
+ # Detection lives ON the patched __init__ (NOT an importable conftest
74
+ # flag): pytest registers conftest under its own module name, so a test
75
+ # importing tests.conftest would see a second, never-mutated copy.
76
+ guarded._network_guard = True
77
+ httpx.Client.__init__ = guarded
78
+ _NETWORK_GUARD_ACTIVE = True
79
+
80
+
81
+ @pytest.fixture(scope="session")
82
+ def aura():
83
+ """Authenticated Aura session shared by all live read-path tests.
84
+
85
+ Reads AURA_EMAIL/AURA_PASSWORD from the environment and skips the whole
86
+ live suite cleanly when either is unset (D-02), so a credential-less
87
+ checkout stays green. When credentials are present it instantiates Aura()
88
+ and performs the login (READ-01's act, D-03), returning the authenticated
89
+ instance so all live tests reuse one session.
90
+ """
91
+ email = os.getenv("AURA_EMAIL")
92
+ password = os.getenv("AURA_PASSWORD")
93
+ if not email or not password:
94
+ pytest.skip("AURA_EMAIL/AURA_PASSWORD not set; skipping live Aura API tests")
95
+
96
+ from pushframe.aura import Aura
97
+
98
+ instance = Aura()
99
+ instance.login()
100
+ return instance
@@ -57,17 +57,85 @@ def test_wizard_success_writes_0600_config(cfg_path, monkeypatch, fake_login_ok)
57
57
  assert data["auth_token"] == "tok-abc-123" # noqa: S105
58
58
  assert data["default_frame"] == "Cadre de Fabrice"
59
59
  assert data["debug"] is False
60
+ # Identity provisioning (5.1.1): the first wizard run on a fresh store
61
+ # must persist a unique device id — never the shared all-zeros default.
62
+ identity = data["settings"]["DEVICE_IDENTIFIER"]
63
+ assert identity != "0000000000000000"
64
+ assert identity.count("-") == 4 and len(identity) == 36
60
65
  mode = stat.S_IMODE(cfg_path.stat().st_mode)
61
66
  assert mode == 0o600
62
67
 
63
68
 
69
+ def test_wizard_provisioned_identity_survives_save_and_rerun(cfg_path, monkeypatch, fake_login_ok):
70
+ """The provisioned id is written BEFORE the login and must survive the
71
+ wizard's final save (no settings-key clobber), and a second run must
72
+ keep it byte-identical (no re-roll — device ids are stable)."""
73
+ _feed(monkeypatch, ["coredmp95@gmail.com", "s3cret", "1", "n"]) # noqa: S105
74
+ from pushframe.cli import run_config
75
+ assert run_config(wizard_args=[], stdin_isatty=True) == 0
76
+ first = json.loads(cfg_path.read_text())["settings"]["DEVICE_IDENTIFIER"]
77
+
78
+ _feed(monkeypatch, ["coredmp95@gmail.com", "", "", "", ""]) # noqa: S105
79
+ assert run_config(wizard_args=[], stdin_isatty=True) == 0
80
+ second = json.loads(cfg_path.read_text())["settings"]["DEVICE_IDENTIFIER"]
81
+ assert first == second
82
+
83
+
84
+ def test_wizard_respects_explicit_device_identity(cfg_path, monkeypatch, fake_login_ok):
85
+ """An explicit id (file or env) is never second-guessed — provisioning
86
+ only fills the ABSENCE of one."""
87
+ config_store.update(settings={"DEVICE_IDENTIFIER": "my-explicit-id"})
88
+ _feed(monkeypatch, ["coredmp95@gmail.com", "s3cret", "1", "n"]) # noqa: S105
89
+ from pushframe.cli import run_config
90
+ assert run_config(wizard_args=[], stdin_isatty=True) == 0
91
+ assert json.loads(cfg_path.read_text())["settings"]["DEVICE_IDENTIFIER"] \
92
+ == "my-explicit-id"
93
+
94
+
95
+ def test_wizard_provisioning_keeps_sibling_settings(cfg_path, monkeypatch, fake_login_ok):
96
+ """Provisioning must not clobber sibling file settings (update(
97
+ settings=...) REPLACES the map; the load→mutate→save path preserves
98
+ them)."""
99
+ config_store.update(settings={"LOCALE": "fr-FR"})
100
+ _feed(monkeypatch, ["coredmp95@gmail.com", "s3cret", "1", "n"]) # noqa: S105
101
+ from pushframe.cli import run_config
102
+ assert run_config(wizard_args=[], stdin_isatty=True) == 0
103
+ st = json.loads(cfg_path.read_text())["settings"]
104
+ assert st["LOCALE"] == "fr-FR"
105
+ assert st["DEVICE_IDENTIFIER"] != "0000000000000000"
106
+
107
+
108
+ def test_show_warns_on_all_zeros_device_identity(cfg_path, capsys, monkeypatch):
109
+ """Identity hygiene (5.1.1): config show names the shared all-zeros
110
+ device id with its remedy; a provisioned or explicit id silences it."""
111
+ from pushframe.cli import run_config
112
+ monkeypatch.delenv("PUSHFRAME_DEVICE_IDENTIFIER", raising=False)
113
+ monkeypatch.delenv("AURA_DEVICE_IDENTIFIER", raising=False)
114
+
115
+ assert run_config(wizard_args=["show"]) == 0
116
+ out = capsys.readouterr().out
117
+ assert "WARNING" in out and "uuidgen" in out
118
+
119
+ config_store.update(
120
+ settings={"DEVICE_IDENTIFIER": "45ce0cfa-fe12-4850-b8de-f3ca56fbb36d"})
121
+ assert run_config(wizard_args=["show"]) == 0
122
+ assert "WARNING" not in capsys.readouterr().out
123
+
124
+
64
125
  def test_wizard_failed_login_writes_nothing(cfg_path, monkeypatch, fake_login_fail, capsys):
126
+ """D-03, amended for 5.1.1 identity provisioning: a failed login
127
+ persists NO session facts and NO default_frame — the only pre-login
128
+ write allowed is the account-independent device identity block."""
65
129
  _feed(monkeypatch, ["coredmp95@gmail.com", "wrong-pass"]) # noqa: S105
66
130
  from pushframe.cli import run_config
67
131
  rc = run_config(wizard_args=[], stdin_isatty=True)
68
132
  assert rc == 1
69
- assert not cfg_path.exists()
70
133
  assert "invalid credentials" in capsys.readouterr().out
134
+ assert cfg_path.exists() # identity block only
135
+ data = json.loads(cfg_path.read_text())
136
+ assert "email" not in data and "auth_token" not in data # noqa: S105
137
+ assert "default_frame" not in data
138
+ assert "DEVICE_IDENTIFIER" in data.get("settings", {})
71
139
 
72
140
 
73
141
  def test_wizard_non_tty_fails_loud(cfg_path, monkeypatch):
@@ -0,0 +1,41 @@
1
+ """The network guard itself (2026-09-30 CI hardening).
2
+
3
+ Meta-test: when the guard is installed, a client built WITHOUT an explicit
4
+ transport — the real-network shape — must fail loudly instead of reaching
5
+ pushd. When it is not installed (a genuine live posture), there is nothing
6
+ to assert and the test skips.
7
+ """
8
+ import httpx
9
+ import pytest
10
+
11
+
12
+ def _guard_installed() -> bool:
13
+ """True when conftest's blocking transport is patched onto httpx.Client.
14
+
15
+ Detection reads the attribute the hook sets on the patched __init__
16
+ itself — importing the flag from conftest would read a second module
17
+ copy (pytest registers conftest under its own name).
18
+ """
19
+ return getattr(httpx.Client.__init__, '_network_guard', False)
20
+
21
+
22
+ def test_guard_blocks_transportless_clients_in_offline_posture():
23
+ if not _guard_installed():
24
+ pytest.skip('guard not installed — a genuine live posture is selected')
25
+ client = httpx.Client(base_url='https://api.pushd.com/v5')
26
+ with pytest.raises(RuntimeError, match='NETWORK GUARD'):
27
+ client.get('/frames.json') # any request: the transport itself blocks
28
+
29
+
30
+ def test_guard_never_blocks_explicit_transports():
31
+ """offline_aura's shape: transport= passes through untouched."""
32
+ calls = []
33
+
34
+ def handler(request: httpx.Request) -> httpx.Response:
35
+ calls.append(request.url.path)
36
+ return httpx.Response(200, json={'ok': True})
37
+
38
+ client = httpx.Client(transport=httpx.MockTransport(handler))
39
+ r = client.get('https://api.pushd.com/v5/frames.json')
40
+ assert r.status_code == 200
41
+ assert calls == ['/v5/frames.json']
@@ -169,6 +169,7 @@ def test_401_on_select_asset_recovers_after_relogin_and_resend(tmp_path):
169
169
  plan, aura, FRAME_ID, s3_client=_FakeS3Client(), sqs_client=_FakeSQSClient(),
170
170
  sleep=lambda *_: None, throttle_seconds=0, chunk_delay_seconds=0,
171
171
  asset_probe=_ordered_probe(['absent']),
172
+ relogin=aura.login, # pinned: this test encodes the PRE-phase-24 password default
172
173
  )
173
174
 
174
175
  assert result.upload_succeeded == 1
@@ -197,6 +198,7 @@ def test_two_item_chunk_401_resends_only_the_genuinely_absent_item(tmp_path):
197
198
  plan, aura, FRAME_ID, s3_client=_FakeS3Client(), sqs_client=_FakeSQSClient(),
198
199
  sleep=lambda *_: None, throttle_seconds=0, chunk_delay_seconds=0,
199
200
  asset_probe=_ordered_probe(['landed', 'absent']),
201
+ relogin=aura.login, # pinned: pre-phase-24 password default
200
202
  )
201
203
 
202
204
  assert result.items_already_landed == 1
@@ -364,6 +366,7 @@ def test_retried_upload_chunk_consumes_5_budget_tokens_total(tmp_path):
364
366
  plan, aura, FRAME_ID, s3_client=_FakeS3Client(), sqs_client=_FakeSQSClient(),
365
367
  sleep=lambda *_: None, throttle_seconds=0, chunk_delay_seconds=0,
366
368
  asset_probe=_ordered_probe(['absent']),
369
+ relogin=aura.login, # pinned: pre-phase-24 password default
367
370
  budget=budget, clock=lambda: T0, wait_on_budget=False,
368
371
  )
369
372
 
@@ -391,6 +394,7 @@ def test_all_items_already_landed_consumes_3_tokens_no_resend_charge(tmp_path):
391
394
  plan, aura, FRAME_ID, s3_client=_FakeS3Client(), sqs_client=_FakeSQSClient(),
392
395
  sleep=lambda *_: None, throttle_seconds=0, chunk_delay_seconds=0,
393
396
  asset_probe=_ordered_probe(['landed', 'landed']),
397
+ relogin=aura.login, # pinned: pre-phase-24 password default
394
398
  budget=budget, clock=lambda: T0, wait_on_budget=False,
395
399
  )
396
400
 
@@ -419,6 +423,7 @@ def test_probe_itself_never_charges_budget_regardless_of_chunk_size(tmp_path):
419
423
  plan, aura, FRAME_ID, s3_client=_FakeS3Client(), sqs_client=_FakeSQSClient(),
420
424
  sleep=lambda *_: None, throttle_seconds=0, chunk_delay_seconds=0,
421
425
  asset_probe=_ordered_probe(['landed', 'landed', 'landed']),
426
+ relogin=aura.login, # pinned: pre-phase-24 password default
422
427
  budget=budget, clock=lambda: T0, wait_on_budget=False,
423
428
  )
424
429
 
@@ -453,6 +458,7 @@ def test_budget_exhausted_propagates_from_retry_acquire_with_no_bypass(tmp_path)
453
458
  plan, aura, FRAME_ID, s3_client=_FakeS3Client(), sqs_client=_FakeSQSClient(),
454
459
  sleep=lambda *_: None, throttle_seconds=0, chunk_delay_seconds=0,
455
460
  asset_probe=_ordered_probe(['absent']),
461
+ relogin=aura.login, # pinned: pre-phase-24 password default
456
462
  budget=budget, clock=lambda: T0, wait_on_budget=False,
457
463
  )
458
464
 
@@ -70,6 +70,26 @@ def test_legacy_aura_env_still_read(config_file, monkeypatch):
70
70
  assert settings.LOCALE == "es-ES"
71
71
 
72
72
 
73
+ def test_user_agent_default_tracks_a_current_play_build(config_file, monkeypatch):
74
+ """venus 2026-09-30: the years-stale 4.7.790 build fingerprinted
75
+ pushframe as a non-phone client. The default must stay contemporary."""
76
+ monkeypatch.delenv("PUSHFRAME_USER_AGENT", raising=False)
77
+ monkeypatch.delenv("AURA_USER_AGENT", raising=False)
78
+ ua = settings.USER_AGENT
79
+ assert ua.startswith("Aura/4.7.")
80
+ build = int(ua.split("/")[1].split(" ")[0].split(".")[2])
81
+ assert build >= 4000
82
+
83
+
84
+ def test_user_agent_resolves_from_file_then_env(config_file, monkeypatch):
85
+ _write(config_file, {"version": 1, "settings": {
86
+ "USER_AGENT": "Aura/4.7.5000 (Android 36; Client)"}})
87
+ assert settings.USER_AGENT == "Aura/4.7.5000 (Android 36; Client)"
88
+ monkeypatch.setenv("PUSHFRAME_USER_AGENT",
89
+ "Aura/4.7.5001 (Android 36; Client)")
90
+ assert settings.USER_AGENT == "Aura/4.7.5001 (Android 36; Client)"
91
+
92
+
73
93
  def test_numeric_and_bool_settings_resolve(config_file, monkeypatch):
74
94
  monkeypatch.delenv("PUSHFRAME_WRITE_BUDGET_CAPACITY", raising=False)
75
95
  monkeypatch.delenv("AURA_WRITE_BUDGET_CAPACITY", raising=False)
@@ -19,7 +19,7 @@ PYPROJECT = Path(__file__).resolve().parent.parent / "pyproject.toml"
19
19
  def test_version_is_first_distribution_release():
20
20
  """The current distribution release (moved off 0.1.0 at 5.0.0; page
21
21
  metadata fix shipped in 5.0.4)."""
22
- assert __version__ == "5.1.0"
22
+ assert __version__ == "5.1.2"
23
23
 
24
24
 
25
25
  def test_version_matches_pyproject_single_source():
@@ -304,11 +304,13 @@ def test_run_of_plain_401_write_failures_aborts_batch(tmp_path):
304
304
  overrides[SELECT_ASSET_PATH] = httpx.Response(401, json={'error': 'unauthorized'})
305
305
  s3 = _FakeS3Client()
306
306
 
307
+ aura = offline_aura(overrides=overrides)
307
308
  with pytest.raises(ConsecutiveWriteFailureError) as exc_info:
308
309
  execute_plan(
309
- plan, offline_aura(overrides=overrides), FRAME_ID,
310
+ plan, aura, FRAME_ID,
310
311
  s3_client=s3, sqs_client=_FakeSQSClient(),
311
312
  throttle_seconds=0, sleep=lambda *_: None,
313
+ relogin=aura.login, # pinned: pre-phase-24 password default
312
314
  )
313
315
 
314
316
  err = exc_info.value
@@ -351,12 +353,13 @@ def test_interspersed_failures_do_not_trip_the_backstop(tmp_path, monkeypatch):
351
353
 
352
354
  monkeypatch.setattr(aura.asset_api, 'batch_update', _flaky_batch_update)
353
355
 
354
- result = execute_plan(
355
- plan, aura, FRAME_ID,
356
- s3_client=_FakeS3Client(), sqs_client=_FakeSQSClient(),
357
- throttle_seconds=0, sleep=lambda *_: None,
358
- batch_size=1,
359
- )
356
+ result = execute_plan(
357
+ plan, aura, FRAME_ID,
358
+ s3_client=_FakeS3Client(), sqs_client=_FakeSQSClient(),
359
+ throttle_seconds=0, sleep=lambda *_: None,
360
+ batch_size=1,
361
+ relogin=aura.login, # pinned: pre-phase-24 password default
362
+ )
360
363
 
361
364
  # No abort: all 8 attempted, 4 succeeded, 4 recorded per-item (D-08 intact).
362
365
  assert result.upload_succeeded == 4
@@ -390,12 +393,13 @@ def test_a_success_resets_the_consecutive_run(tmp_path, monkeypatch):
390
393
 
391
394
  monkeypatch.setattr(aura.asset_api, 'batch_update', _flaky_batch_update)
392
395
 
393
- result = execute_plan(
394
- plan, aura, FRAME_ID,
395
- s3_client=_FakeS3Client(), sqs_client=_FakeSQSClient(),
396
- throttle_seconds=0, sleep=lambda *_: None,
397
- batch_size=1,
398
- )
396
+ result = execute_plan(
397
+ plan, aura, FRAME_ID,
398
+ s3_client=_FakeS3Client(), sqs_client=_FakeSQSClient(),
399
+ throttle_seconds=0, sleep=lambda *_: None,
400
+ batch_size=1,
401
+ relogin=aura.login, # pinned: pre-phase-24 password default
402
+ )
399
403
 
400
404
  assert result.upload_succeeded == 1
401
405
  assert len(result.upload_failures) == 8
@@ -444,11 +448,13 @@ def test_max_consecutive_failures_zero_disables_the_backstop(tmp_path):
444
448
  overrides = _ok_overrides()
445
449
  overrides[SELECT_ASSET_PATH] = httpx.Response(401, json={'error': 'unauthorized'})
446
450
 
451
+ aura = offline_aura(overrides=overrides)
447
452
  result = execute_plan(
448
- plan, offline_aura(overrides=overrides), FRAME_ID,
453
+ plan, aura, FRAME_ID,
449
454
  s3_client=_FakeS3Client(), sqs_client=_FakeSQSClient(),
450
455
  throttle_seconds=0, sleep=lambda *_: None,
451
456
  max_consecutive_failures=0,
457
+ relogin=aura.login, # pinned: pre-phase-24 password default
452
458
  )
453
459
 
454
460
  assert result.upload_succeeded == 0
@@ -469,12 +475,14 @@ def test_consecutive_run_spans_upload_and_delete_phases(tmp_path):
469
475
  overrides[SELECT_ASSET_PATH] = httpx.Response(401, json={'error': 'unauthorized'})
470
476
  overrides[REMOVE_ASSET_PATH] = httpx.Response(401, json={'error': 'unauthorized'})
471
477
 
478
+ aura = offline_aura(overrides=overrides)
472
479
  with pytest.raises(ConsecutiveWriteFailureError) as exc_info:
473
480
  execute_plan(
474
- plan, offline_aura(overrides=overrides), FRAME_ID,
481
+ plan, aura, FRAME_ID,
475
482
  s3_client=_FakeS3Client(), sqs_client=_FakeSQSClient(),
476
483
  throttle_seconds=0, sleep=lambda *_: None,
477
484
  batch_size=1,
485
+ relogin=aura.login, # pinned: pre-phase-24 password default
478
486
  )
479
487
 
480
488
  err = exc_info.value
@@ -432,7 +432,7 @@ wheels = [
432
432
 
433
433
  [[package]]
434
434
  name = "pushframe"
435
- version = "5.1.0"
435
+ version = "5.1.2"
436
436
  source = { editable = "." }
437
437
  dependencies = [
438
438
  { name = "boto3" },
@@ -1,32 +0,0 @@
1
- import os
2
-
3
- import pytest
4
- from dotenv import load_dotenv
5
-
6
- # Load AURA_EMAIL / AURA_PASSWORD from a local .env so the live read-path tests
7
- # can run without exporting shell vars. Shell-exported vars still win (override
8
- # defaults to False), and a missing .env is a no-op — so a credential-less
9
- # checkout still skips the live suite cleanly (D-02).
10
- load_dotenv()
11
-
12
-
13
- @pytest.fixture(scope="session")
14
- def aura():
15
- """Authenticated Aura session shared by all live read-path tests.
16
-
17
- Reads AURA_EMAIL/AURA_PASSWORD from the environment and skips the whole
18
- live suite cleanly when either is unset (D-02), so a credential-less
19
- checkout stays green. When credentials are present it instantiates Aura()
20
- and performs the login (READ-01's act, D-03), returning the authenticated
21
- instance so all live tests reuse one session.
22
- """
23
- email = os.getenv("AURA_EMAIL")
24
- password = os.getenv("AURA_PASSWORD")
25
- if not email or not password:
26
- pytest.skip("AURA_EMAIL/AURA_PASSWORD not set; skipping live Aura API tests")
27
-
28
- from pushframe.aura import Aura
29
-
30
- instance = Aura()
31
- instance.login()
32
- return instance
File without changes
File without changes