pushframe 5.1.4__tar.gz → 5.1.6__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 (418) hide show
  1. pushframe-5.1.6/.github/workflows/tests.yml +50 -0
  2. pushframe-5.1.6/.planning/debug/gsync-apply-budget-none-crash.md +51 -0
  3. {pushframe-5.1.4 → pushframe-5.1.6}/CHANGELOG.md +54 -1
  4. {pushframe-5.1.4 → pushframe-5.1.6}/PKG-INFO +1 -1
  5. {pushframe-5.1.4 → pushframe-5.1.6}/pushframe/__init__.py +1 -1
  6. {pushframe-5.1.4 → pushframe-5.1.6}/pushframe/cli.py +15 -2
  7. {pushframe-5.1.4 → pushframe-5.1.6}/pushframe/gsync.py +15 -4
  8. {pushframe-5.1.4 → pushframe-5.1.6}/pushframe/preflight.py +28 -4
  9. {pushframe-5.1.4 → pushframe-5.1.6}/pushframe/session.py +20 -0
  10. {pushframe-5.1.4 → pushframe-5.1.6}/pyproject.toml +1 -1
  11. pushframe-5.1.6/tests/test_budget_identity.py +89 -0
  12. {pushframe-5.1.4 → pushframe-5.1.6}/tests/test_preflight_sweep.py +30 -15
  13. pushframe-5.1.6/tests/test_preflight_vault.py +78 -0
  14. {pushframe-5.1.4 → pushframe-5.1.6}/tests/test_version.py +1 -1
  15. {pushframe-5.1.4 → pushframe-5.1.6}/uv.lock +4 -4
  16. pushframe-5.1.4/.github/workflows/tests.yml +0 -20
  17. {pushframe-5.1.4 → pushframe-5.1.6}/.env.sample +0 -0
  18. {pushframe-5.1.4 → pushframe-5.1.6}/.freebuff/project-id +0 -0
  19. {pushframe-5.1.4 → pushframe-5.1.6}/.github/workflows/release.yml +0 -0
  20. {pushframe-5.1.4 → pushframe-5.1.6}/.gitignore +0 -0
  21. {pushframe-5.1.4 → pushframe-5.1.6}/.gsd/dispatch-isolation-sentinel.json +0 -0
  22. {pushframe-5.1.4 → pushframe-5.1.6}/.planning/MILESTONES.md +0 -0
  23. {pushframe-5.1.4 → pushframe-5.1.6}/.planning/PROJECT.md +0 -0
  24. {pushframe-5.1.4 → pushframe-5.1.6}/.planning/REQUIREMENTS.md +0 -0
  25. {pushframe-5.1.4 → pushframe-5.1.6}/.planning/RETROSPECTIVE.md +0 -0
  26. {pushframe-5.1.4 → pushframe-5.1.6}/.planning/ROADMAP.md +0 -0
  27. {pushframe-5.1.4 → pushframe-5.1.6}/.planning/STATE.md +0 -0
  28. {pushframe-5.1.4 → pushframe-5.1.6}/.planning/WINDOWS.md +0 -0
  29. {pushframe-5.1.4 → pushframe-5.1.6}/.planning/codebase/ARCHITECTURE.md +0 -0
  30. {pushframe-5.1.4 → pushframe-5.1.6}/.planning/codebase/CONCERNS.md +0 -0
  31. {pushframe-5.1.4 → pushframe-5.1.6}/.planning/codebase/CONVENTIONS.md +0 -0
  32. {pushframe-5.1.4 → pushframe-5.1.6}/.planning/codebase/INTEGRATIONS.md +0 -0
  33. {pushframe-5.1.4 → pushframe-5.1.6}/.planning/codebase/STACK.md +0 -0
  34. {pushframe-5.1.4 → pushframe-5.1.6}/.planning/codebase/STRUCTURE.md +0 -0
  35. {pushframe-5.1.4 → pushframe-5.1.6}/.planning/codebase/TESTING.md +0 -0
  36. {pushframe-5.1.4 → pushframe-5.1.6}/.planning/config.json +0 -0
  37. {pushframe-5.1.4 → pushframe-5.1.6}/.planning/debug/knowledge-base.md +0 -0
  38. {pushframe-5.1.4 → pushframe-5.1.6}/.planning/debug/login-475-null-creds.md +0 -0
  39. {pushframe-5.1.4 → pushframe-5.1.6}/.planning/debug/resolved/cli-status-verbose-logging.md +0 -0
  40. {pushframe-5.1.4 → pushframe-5.1.6}/.planning/debug/resolved/inspect-asset-null-fields.md +0 -0
  41. {pushframe-5.1.4 → pushframe-5.1.6}/.planning/debug/resolved/select-asset-401-unauthorized.md +0 -0
  42. {pushframe-5.1.4 → pushframe-5.1.6}/.planning/debug/status-crash-401-trip.md +0 -0
  43. {pushframe-5.1.4 → pushframe-5.1.6}/.planning/milestones/v1.0-REQUIREMENTS.md +0 -0
  44. {pushframe-5.1.4 → pushframe-5.1.6}/.planning/milestones/v1.0-ROADMAP.md +0 -0
  45. {pushframe-5.1.4 → pushframe-5.1.6}/.planning/milestones/v1.0-phases/01-toolchain-revival/01-01-PLAN.md +0 -0
  46. {pushframe-5.1.4 → pushframe-5.1.6}/.planning/milestones/v1.0-phases/01-toolchain-revival/01-01-SUMMARY.md +0 -0
  47. {pushframe-5.1.4 → pushframe-5.1.6}/.planning/milestones/v1.0-phases/01-toolchain-revival/01-02-PLAN.md +0 -0
  48. {pushframe-5.1.4 → pushframe-5.1.6}/.planning/milestones/v1.0-phases/01-toolchain-revival/01-02-SUMMARY.md +0 -0
  49. {pushframe-5.1.4 → pushframe-5.1.6}/.planning/milestones/v1.0-phases/01-toolchain-revival/01-CONTEXT.md +0 -0
  50. {pushframe-5.1.4 → pushframe-5.1.6}/.planning/milestones/v1.0-phases/01-toolchain-revival/01-DISCUSSION-LOG.md +0 -0
  51. {pushframe-5.1.4 → pushframe-5.1.6}/.planning/milestones/v1.0-phases/01-toolchain-revival/01-PATTERNS.md +0 -0
  52. {pushframe-5.1.4 → pushframe-5.1.6}/.planning/milestones/v1.0-phases/01-toolchain-revival/01-RESEARCH.md +0 -0
  53. {pushframe-5.1.4 → pushframe-5.1.6}/.planning/milestones/v1.0-phases/01-toolchain-revival/01-REVIEW.md +0 -0
  54. {pushframe-5.1.4 → pushframe-5.1.6}/.planning/milestones/v1.0-phases/01-toolchain-revival/01-VERIFICATION.md +0 -0
  55. {pushframe-5.1.4 → pushframe-5.1.6}/.planning/milestones/v1.0-phases/02-live-read-path-verification/02-01-PLAN.md +0 -0
  56. {pushframe-5.1.4 → pushframe-5.1.6}/.planning/milestones/v1.0-phases/02-live-read-path-verification/02-01-SUMMARY.md +0 -0
  57. {pushframe-5.1.4 → pushframe-5.1.6}/.planning/milestones/v1.0-phases/02-live-read-path-verification/02-02-PLAN.md +0 -0
  58. {pushframe-5.1.4 → pushframe-5.1.6}/.planning/milestones/v1.0-phases/02-live-read-path-verification/02-02-SUMMARY.md +0 -0
  59. {pushframe-5.1.4 → pushframe-5.1.6}/.planning/milestones/v1.0-phases/02-live-read-path-verification/02-CONTEXT.md +0 -0
  60. {pushframe-5.1.4 → pushframe-5.1.6}/.planning/milestones/v1.0-phases/02-live-read-path-verification/02-DISCUSSION-LOG.md +0 -0
  61. {pushframe-5.1.4 → pushframe-5.1.6}/.planning/milestones/v1.0-phases/02-live-read-path-verification/02-LIVE-EVIDENCE.md +0 -0
  62. {pushframe-5.1.4 → pushframe-5.1.6}/.planning/milestones/v1.0-phases/02-live-read-path-verification/02-PATTERNS.md +0 -0
  63. {pushframe-5.1.4 → pushframe-5.1.6}/.planning/milestones/v1.0-phases/02-live-read-path-verification/02-RESEARCH.md +0 -0
  64. {pushframe-5.1.4 → pushframe-5.1.6}/.planning/milestones/v1.0-phases/02-live-read-path-verification/02-REVIEW.md +0 -0
  65. {pushframe-5.1.4 → pushframe-5.1.6}/.planning/milestones/v1.0-phases/02-live-read-path-verification/02-VERIFICATION.md +0 -0
  66. {pushframe-5.1.4 → pushframe-5.1.6}/.planning/milestones/v1.0-phases/03-run-docs-verification-report/03-01-PLAN.md +0 -0
  67. {pushframe-5.1.4 → pushframe-5.1.6}/.planning/milestones/v1.0-phases/03-run-docs-verification-report/03-01-SUMMARY.md +0 -0
  68. {pushframe-5.1.4 → pushframe-5.1.6}/.planning/milestones/v1.0-phases/03-run-docs-verification-report/03-CONTEXT.md +0 -0
  69. {pushframe-5.1.4 → pushframe-5.1.6}/.planning/milestones/v1.0-phases/03-run-docs-verification-report/03-DISCUSSION-LOG.md +0 -0
  70. {pushframe-5.1.4 → pushframe-5.1.6}/.planning/milestones/v1.0-phases/03-run-docs-verification-report/03-PATTERNS.md +0 -0
  71. {pushframe-5.1.4 → pushframe-5.1.6}/.planning/milestones/v1.0-phases/03-run-docs-verification-report/03-RESEARCH.md +0 -0
  72. {pushframe-5.1.4 → pushframe-5.1.6}/.planning/milestones/v1.0-phases/03-run-docs-verification-report/03-REVIEW.md +0 -0
  73. {pushframe-5.1.4 → pushframe-5.1.6}/.planning/milestones/v1.0-phases/03-run-docs-verification-report/03-VERIFICATION.md +0 -0
  74. {pushframe-5.1.4 → pushframe-5.1.6}/.planning/milestones/v1.1-ROADMAP.md +0 -0
  75. {pushframe-5.1.4 → pushframe-5.1.6}/.planning/milestones/v1.1-phases/04-client-transport-seam-for-offline-testability/.gitkeep +0 -0
  76. {pushframe-5.1.4 → pushframe-5.1.6}/.planning/milestones/v1.1-phases/04-client-transport-seam-for-offline-testability/04-01-PLAN.md +0 -0
  77. {pushframe-5.1.4 → pushframe-5.1.6}/.planning/milestones/v1.1-phases/04-client-transport-seam-for-offline-testability/04-01-SUMMARY.md +0 -0
  78. {pushframe-5.1.4 → pushframe-5.1.6}/.planning/milestones/v1.1-phases/04-client-transport-seam-for-offline-testability/04-02-PLAN.md +0 -0
  79. {pushframe-5.1.4 → pushframe-5.1.6}/.planning/milestones/v1.1-phases/04-client-transport-seam-for-offline-testability/04-02-SUMMARY.md +0 -0
  80. {pushframe-5.1.4 → pushframe-5.1.6}/.planning/milestones/v1.1-phases/04-client-transport-seam-for-offline-testability/04-03-PLAN.md +0 -0
  81. {pushframe-5.1.4 → pushframe-5.1.6}/.planning/milestones/v1.1-phases/04-client-transport-seam-for-offline-testability/04-03-SUMMARY.md +0 -0
  82. {pushframe-5.1.4 → pushframe-5.1.6}/.planning/milestones/v1.1-phases/04-client-transport-seam-for-offline-testability/04-PATTERNS.md +0 -0
  83. {pushframe-5.1.4 → pushframe-5.1.6}/.planning/milestones/v1.1-phases/04-client-transport-seam-for-offline-testability/04-RESEARCH.md +0 -0
  84. {pushframe-5.1.4 → pushframe-5.1.6}/.planning/milestones/v1.1-phases/04-client-transport-seam-for-offline-testability/04-REVIEW.md +0 -0
  85. {pushframe-5.1.4 → pushframe-5.1.6}/.planning/milestones/v1.1-phases/04-client-transport-seam-for-offline-testability/04-SECURITY.md +0 -0
  86. {pushframe-5.1.4 → pushframe-5.1.6}/.planning/milestones/v1.1-phases/04-client-transport-seam-for-offline-testability/04-UAT.md +0 -0
  87. {pushframe-5.1.4 → pushframe-5.1.6}/.planning/milestones/v1.1-phases/04-client-transport-seam-for-offline-testability/04-VERIFICATION.md +0 -0
  88. {pushframe-5.1.4 → pushframe-5.1.6}/.planning/milestones/v2.0-REQUIREMENTS.md +0 -0
  89. {pushframe-5.1.4 → pushframe-5.1.6}/.planning/milestones/v2.0-ROADMAP.md +0 -0
  90. {pushframe-5.1.4 → pushframe-5.1.6}/.planning/milestones/v2.0-phases/05-cli-skeleton-status/05-01-PLAN.md +0 -0
  91. {pushframe-5.1.4 → pushframe-5.1.6}/.planning/milestones/v2.0-phases/05-cli-skeleton-status/05-01-SUMMARY.md +0 -0
  92. {pushframe-5.1.4 → pushframe-5.1.6}/.planning/milestones/v2.0-phases/05-cli-skeleton-status/05-02-PLAN.md +0 -0
  93. {pushframe-5.1.4 → pushframe-5.1.6}/.planning/milestones/v2.0-phases/05-cli-skeleton-status/05-02-SUMMARY.md +0 -0
  94. {pushframe-5.1.4 → pushframe-5.1.6}/.planning/milestones/v2.0-phases/05-cli-skeleton-status/05-CONTEXT.md +0 -0
  95. {pushframe-5.1.4 → pushframe-5.1.6}/.planning/milestones/v2.0-phases/05-cli-skeleton-status/05-DISCUSSION-LOG.md +0 -0
  96. {pushframe-5.1.4 → pushframe-5.1.6}/.planning/milestones/v2.0-phases/05-cli-skeleton-status/05-PATTERNS.md +0 -0
  97. {pushframe-5.1.4 → pushframe-5.1.6}/.planning/milestones/v2.0-phases/05-cli-skeleton-status/05-REVIEW.md +0 -0
  98. {pushframe-5.1.4 → pushframe-5.1.6}/.planning/milestones/v2.0-phases/05-cli-skeleton-status/05-SECURITY.md +0 -0
  99. {pushframe-5.1.4 → pushframe-5.1.6}/.planning/milestones/v2.0-phases/05-cli-skeleton-status/05-UAT.md +0 -0
  100. {pushframe-5.1.4 → pushframe-5.1.6}/.planning/milestones/v2.0-phases/05-cli-skeleton-status/05-VERIFICATION.md +0 -0
  101. {pushframe-5.1.4 → pushframe-5.1.6}/.planning/milestones/v2.0-phases/06-inspect-frame-resolution/06-01-PLAN.md +0 -0
  102. {pushframe-5.1.4 → pushframe-5.1.6}/.planning/milestones/v2.0-phases/06-inspect-frame-resolution/06-01-SUMMARY.md +0 -0
  103. {pushframe-5.1.4 → pushframe-5.1.6}/.planning/milestones/v2.0-phases/06-inspect-frame-resolution/06-02-PLAN.md +0 -0
  104. {pushframe-5.1.4 → pushframe-5.1.6}/.planning/milestones/v2.0-phases/06-inspect-frame-resolution/06-02-SUMMARY.md +0 -0
  105. {pushframe-5.1.4 → pushframe-5.1.6}/.planning/milestones/v2.0-phases/06-inspect-frame-resolution/06-CONTEXT.md +0 -0
  106. {pushframe-5.1.4 → pushframe-5.1.6}/.planning/milestones/v2.0-phases/06-inspect-frame-resolution/06-DISCUSSION-LOG.md +0 -0
  107. {pushframe-5.1.4 → pushframe-5.1.6}/.planning/milestones/v2.0-phases/06-inspect-frame-resolution/06-PATTERNS.md +0 -0
  108. {pushframe-5.1.4 → pushframe-5.1.6}/.planning/milestones/v2.0-phases/06-inspect-frame-resolution/06-RESEARCH.md +0 -0
  109. {pushframe-5.1.4 → pushframe-5.1.6}/.planning/milestones/v2.0-phases/06-inspect-frame-resolution/06-REVIEW.md +0 -0
  110. {pushframe-5.1.4 → pushframe-5.1.6}/.planning/milestones/v2.0-phases/06-inspect-frame-resolution/06-SECURITY.md +0 -0
  111. {pushframe-5.1.4 → pushframe-5.1.6}/.planning/milestones/v2.0-phases/06-inspect-frame-resolution/06-VERIFICATION.md +0 -0
  112. {pushframe-5.1.4 → pushframe-5.1.6}/.planning/milestones/v2.0-phases/07-sync-diffing-engine-dry-run-only/07-01-PLAN.md +0 -0
  113. {pushframe-5.1.4 → pushframe-5.1.6}/.planning/milestones/v2.0-phases/07-sync-diffing-engine-dry-run-only/07-01-SUMMARY.md +0 -0
  114. {pushframe-5.1.4 → pushframe-5.1.6}/.planning/milestones/v2.0-phases/07-sync-diffing-engine-dry-run-only/07-02-PLAN.md +0 -0
  115. {pushframe-5.1.4 → pushframe-5.1.6}/.planning/milestones/v2.0-phases/07-sync-diffing-engine-dry-run-only/07-02-SUMMARY.md +0 -0
  116. {pushframe-5.1.4 → pushframe-5.1.6}/.planning/milestones/v2.0-phases/07-sync-diffing-engine-dry-run-only/07-03-PLAN.md +0 -0
  117. {pushframe-5.1.4 → pushframe-5.1.6}/.planning/milestones/v2.0-phases/07-sync-diffing-engine-dry-run-only/07-03-SUMMARY.md +0 -0
  118. {pushframe-5.1.4 → pushframe-5.1.6}/.planning/milestones/v2.0-phases/07-sync-diffing-engine-dry-run-only/07-CONTEXT.md +0 -0
  119. {pushframe-5.1.4 → pushframe-5.1.6}/.planning/milestones/v2.0-phases/07-sync-diffing-engine-dry-run-only/07-DISCUSSION-LOG.md +0 -0
  120. {pushframe-5.1.4 → pushframe-5.1.6}/.planning/milestones/v2.0-phases/07-sync-diffing-engine-dry-run-only/07-PATTERNS.md +0 -0
  121. {pushframe-5.1.4 → pushframe-5.1.6}/.planning/milestones/v2.0-phases/07-sync-diffing-engine-dry-run-only/07-REVIEW-FIX.md +0 -0
  122. {pushframe-5.1.4 → pushframe-5.1.6}/.planning/milestones/v2.0-phases/07-sync-diffing-engine-dry-run-only/07-REVIEW.md +0 -0
  123. {pushframe-5.1.4 → pushframe-5.1.6}/.planning/milestones/v2.0-phases/07-sync-diffing-engine-dry-run-only/07-SECURITY.md +0 -0
  124. {pushframe-5.1.4 → pushframe-5.1.6}/.planning/milestones/v2.0-phases/07-sync-diffing-engine-dry-run-only/07-VERIFICATION.md +0 -0
  125. {pushframe-5.1.4 → pushframe-5.1.6}/.planning/milestones/v2.0-phases/08-destructive-execution-upload-delete-verification/08-01-PLAN.md +0 -0
  126. {pushframe-5.1.4 → pushframe-5.1.6}/.planning/milestones/v2.0-phases/08-destructive-execution-upload-delete-verification/08-01-SUMMARY.md +0 -0
  127. {pushframe-5.1.4 → pushframe-5.1.6}/.planning/milestones/v2.0-phases/08-destructive-execution-upload-delete-verification/08-02-PLAN.md +0 -0
  128. {pushframe-5.1.4 → pushframe-5.1.6}/.planning/milestones/v2.0-phases/08-destructive-execution-upload-delete-verification/08-02-SUMMARY.md +0 -0
  129. {pushframe-5.1.4 → pushframe-5.1.6}/.planning/milestones/v2.0-phases/08-destructive-execution-upload-delete-verification/08-03-PLAN.md +0 -0
  130. {pushframe-5.1.4 → pushframe-5.1.6}/.planning/milestones/v2.0-phases/08-destructive-execution-upload-delete-verification/08-03-SUMMARY.md +0 -0
  131. {pushframe-5.1.4 → pushframe-5.1.6}/.planning/milestones/v2.0-phases/08-destructive-execution-upload-delete-verification/08-04-PLAN.md +0 -0
  132. {pushframe-5.1.4 → pushframe-5.1.6}/.planning/milestones/v2.0-phases/08-destructive-execution-upload-delete-verification/08-04-SUMMARY.md +0 -0
  133. {pushframe-5.1.4 → pushframe-5.1.6}/.planning/milestones/v2.0-phases/08-destructive-execution-upload-delete-verification/08-CONTEXT.md +0 -0
  134. {pushframe-5.1.4 → pushframe-5.1.6}/.planning/milestones/v2.0-phases/08-destructive-execution-upload-delete-verification/08-DISCUSSION-LOG.md +0 -0
  135. {pushframe-5.1.4 → pushframe-5.1.6}/.planning/milestones/v2.0-phases/08-destructive-execution-upload-delete-verification/08-LIVE-FINDINGS.md +0 -0
  136. {pushframe-5.1.4 → pushframe-5.1.6}/.planning/milestones/v2.0-phases/08-destructive-execution-upload-delete-verification/08-PATTERNS.md +0 -0
  137. {pushframe-5.1.4 → pushframe-5.1.6}/.planning/milestones/v2.0-phases/08-destructive-execution-upload-delete-verification/08-RESEARCH.md +0 -0
  138. {pushframe-5.1.4 → pushframe-5.1.6}/.planning/milestones/v2.0-phases/08-destructive-execution-upload-delete-verification/08-REVIEW-FIX.md +0 -0
  139. {pushframe-5.1.4 → pushframe-5.1.6}/.planning/milestones/v2.0-phases/08-destructive-execution-upload-delete-verification/08-REVIEW.md +0 -0
  140. {pushframe-5.1.4 → pushframe-5.1.6}/.planning/milestones/v2.0-phases/08-destructive-execution-upload-delete-verification/08-VERIFICATION.md +0 -0
  141. {pushframe-5.1.4 → pushframe-5.1.6}/.planning/milestones/v2.0-phases/09-proactive-write-rate-limiter-geo-guard/.gitkeep +0 -0
  142. {pushframe-5.1.4 → pushframe-5.1.6}/.planning/milestones/v2.0-phases/09-proactive-write-rate-limiter-geo-guard/09-01-PLAN.md +0 -0
  143. {pushframe-5.1.4 → pushframe-5.1.6}/.planning/milestones/v2.0-phases/09-proactive-write-rate-limiter-geo-guard/09-01-SUMMARY.md +0 -0
  144. {pushframe-5.1.4 → pushframe-5.1.6}/.planning/milestones/v2.0-phases/09-proactive-write-rate-limiter-geo-guard/09-02-PLAN.md +0 -0
  145. {pushframe-5.1.4 → pushframe-5.1.6}/.planning/milestones/v2.0-phases/09-proactive-write-rate-limiter-geo-guard/09-02-SUMMARY.md +0 -0
  146. {pushframe-5.1.4 → pushframe-5.1.6}/.planning/milestones/v2.0-phases/09-proactive-write-rate-limiter-geo-guard/09-CONTEXT.md +0 -0
  147. {pushframe-5.1.4 → pushframe-5.1.6}/.planning/milestones/v2.0-phases/09-proactive-write-rate-limiter-geo-guard/09-RESEARCH.md +0 -0
  148. {pushframe-5.1.4 → pushframe-5.1.6}/.planning/milestones/v2.0-phases/09-proactive-write-rate-limiter-geo-guard/09-REVIEW-FIX.md +0 -0
  149. {pushframe-5.1.4 → pushframe-5.1.6}/.planning/milestones/v2.0-phases/09-proactive-write-rate-limiter-geo-guard/09-REVIEW.md +0 -0
  150. {pushframe-5.1.4 → pushframe-5.1.6}/.planning/milestones/v2.0-phases/09-proactive-write-rate-limiter-geo-guard/09-VERIFICATION.md +0 -0
  151. {pushframe-5.1.4 → pushframe-5.1.6}/.planning/milestones/v2.0-phases/10-hide-instead-of-delete-sync-mode/.gitkeep +0 -0
  152. {pushframe-5.1.4 → pushframe-5.1.6}/.planning/milestones/v2.0-phases/10-hide-instead-of-delete-sync-mode/10-01-PLAN.md +0 -0
  153. {pushframe-5.1.4 → pushframe-5.1.6}/.planning/milestones/v2.0-phases/10-hide-instead-of-delete-sync-mode/10-01-SUMMARY.md +0 -0
  154. {pushframe-5.1.4 → pushframe-5.1.6}/.planning/milestones/v2.0-phases/10-hide-instead-of-delete-sync-mode/10-02-PLAN.md +0 -0
  155. {pushframe-5.1.4 → pushframe-5.1.6}/.planning/milestones/v2.0-phases/10-hide-instead-of-delete-sync-mode/10-02-SUMMARY.md +0 -0
  156. {pushframe-5.1.4 → pushframe-5.1.6}/.planning/milestones/v2.0-phases/10-hide-instead-of-delete-sync-mode/10-03-PLAN.md +0 -0
  157. {pushframe-5.1.4 → pushframe-5.1.6}/.planning/milestones/v2.0-phases/10-hide-instead-of-delete-sync-mode/10-03-SUMMARY.md +0 -0
  158. {pushframe-5.1.4 → pushframe-5.1.6}/.planning/milestones/v2.0-phases/10-hide-instead-of-delete-sync-mode/10-04-PLAN.md +0 -0
  159. {pushframe-5.1.4 → pushframe-5.1.6}/.planning/milestones/v2.0-phases/10-hide-instead-of-delete-sync-mode/10-04-SUMMARY.md +0 -0
  160. {pushframe-5.1.4 → pushframe-5.1.6}/.planning/milestones/v2.0-phases/10-hide-instead-of-delete-sync-mode/10-CONTEXT.md +0 -0
  161. {pushframe-5.1.4 → pushframe-5.1.6}/.planning/milestones/v2.0-phases/10-hide-instead-of-delete-sync-mode/10-DISCUSSION-LOG.md +0 -0
  162. {pushframe-5.1.4 → pushframe-5.1.6}/.planning/milestones/v2.0-phases/10-hide-instead-of-delete-sync-mode/10-LIVE-FINDINGS.md +0 -0
  163. {pushframe-5.1.4 → pushframe-5.1.6}/.planning/milestones/v2.0-phases/10-hide-instead-of-delete-sync-mode/10-PATTERNS.md +0 -0
  164. {pushframe-5.1.4 → pushframe-5.1.6}/.planning/milestones/v2.0-phases/10-hide-instead-of-delete-sync-mode/10-RESEARCH.md +0 -0
  165. {pushframe-5.1.4 → pushframe-5.1.6}/.planning/milestones/v2.0-phases/10-hide-instead-of-delete-sync-mode/10-SECURITY.md +0 -0
  166. {pushframe-5.1.4 → pushframe-5.1.6}/.planning/milestones/v2.0-phases/10-hide-instead-of-delete-sync-mode/10-UAT.md +0 -0
  167. {pushframe-5.1.4 → pushframe-5.1.6}/.planning/milestones/v2.0-phases/10-hide-instead-of-delete-sync-mode/10-VERIFICATION.md +0 -0
  168. {pushframe-5.1.4 → pushframe-5.1.6}/.planning/milestones/v2.0-phases/10-hide-instead-of-delete-sync-mode/COVERAGE.md +0 -0
  169. {pushframe-5.1.4 → pushframe-5.1.6}/.planning/milestones/v2.0-quick/260630-qs8-make-main-py-load-a-local-env-file-at-st/260630-qs8-PLAN.md +0 -0
  170. {pushframe-5.1.4 → pushframe-5.1.6}/.planning/milestones/v2.0-quick/260630-qs8-make-main-py-load-a-local-env-file-at-st/260630-qs8-SUMMARY.md +0 -0
  171. {pushframe-5.1.4 → pushframe-5.1.6}/.planning/milestones/v2.0-quick/260708-dt9-add-progress-feedback-to-aura-cli-sync-a/260708-dt9-PLAN.md +0 -0
  172. {pushframe-5.1.4 → pushframe-5.1.6}/.planning/milestones/v2.0-quick/260708-dt9-add-progress-feedback-to-aura-cli-sync-a/260708-dt9-SUMMARY.md +0 -0
  173. {pushframe-5.1.4 → pushframe-5.1.6}/.planning/milestones/v2.0-quick/260708-dt9-add-progress-feedback-to-aura-cli-sync-a/deferred-items.md +0 -0
  174. {pushframe-5.1.4 → pushframe-5.1.6}/.planning/milestones/v2.0-quick/260708-fyr-batch-refactor-of-sync-apply-write-path-/260708-fyr-PLAN.md +0 -0
  175. {pushframe-5.1.4 → pushframe-5.1.6}/.planning/milestones/v2.0-quick/260708-fyr-batch-refactor-of-sync-apply-write-path-/260708-fyr-SUMMARY.md +0 -0
  176. {pushframe-5.1.4 → pushframe-5.1.6}/.planning/milestones/v2.0-quick/260708-fyr-batch-refactor-of-sync-apply-write-path-/260708-fyr-VERIFICATION.md +0 -0
  177. {pushframe-5.1.4 → pushframe-5.1.6}/.planning/milestones/v2.0-quick/README.md +0 -0
  178. {pushframe-5.1.4 → pushframe-5.1.6}/.planning/milestones/v3.0-REQUIREMENTS.md +0 -0
  179. {pushframe-5.1.4 → pushframe-5.1.6}/.planning/milestones/v3.0-ROADMAP.md +0 -0
  180. {pushframe-5.1.4 → pushframe-5.1.6}/.planning/milestones/v3.0-phases/11-write-path-reliability-format-support/11-01-PLAN.md +0 -0
  181. {pushframe-5.1.4 → pushframe-5.1.6}/.planning/milestones/v3.0-phases/11-write-path-reliability-format-support/11-01-SUMMARY.md +0 -0
  182. {pushframe-5.1.4 → pushframe-5.1.6}/.planning/milestones/v3.0-phases/11-write-path-reliability-format-support/11-02-PLAN.md +0 -0
  183. {pushframe-5.1.4 → pushframe-5.1.6}/.planning/milestones/v3.0-phases/11-write-path-reliability-format-support/11-02-SUMMARY.md +0 -0
  184. {pushframe-5.1.4 → pushframe-5.1.6}/.planning/milestones/v3.0-phases/11-write-path-reliability-format-support/11-03-PLAN.md +0 -0
  185. {pushframe-5.1.4 → pushframe-5.1.6}/.planning/milestones/v3.0-phases/11-write-path-reliability-format-support/11-03-SUMMARY.md +0 -0
  186. {pushframe-5.1.4 → pushframe-5.1.6}/.planning/milestones/v3.0-phases/11-write-path-reliability-format-support/11-04-PLAN.md +0 -0
  187. {pushframe-5.1.4 → pushframe-5.1.6}/.planning/milestones/v3.0-phases/11-write-path-reliability-format-support/11-04-SUMMARY.md +0 -0
  188. {pushframe-5.1.4 → pushframe-5.1.6}/.planning/milestones/v3.0-phases/11-write-path-reliability-format-support/11-05-PLAN.md +0 -0
  189. {pushframe-5.1.4 → pushframe-5.1.6}/.planning/milestones/v3.0-phases/11-write-path-reliability-format-support/11-05-SUMMARY.md +0 -0
  190. {pushframe-5.1.4 → pushframe-5.1.6}/.planning/milestones/v3.0-phases/11-write-path-reliability-format-support/11-06-PLAN.md +0 -0
  191. {pushframe-5.1.4 → pushframe-5.1.6}/.planning/milestones/v3.0-phases/11-write-path-reliability-format-support/11-06-SUMMARY.md +0 -0
  192. {pushframe-5.1.4 → pushframe-5.1.6}/.planning/milestones/v3.0-phases/11-write-path-reliability-format-support/11-CONTEXT.md +0 -0
  193. {pushframe-5.1.4 → pushframe-5.1.6}/.planning/milestones/v3.0-phases/11-write-path-reliability-format-support/11-DISCUSSION-LOG.md +0 -0
  194. {pushframe-5.1.4 → pushframe-5.1.6}/.planning/milestones/v3.0-phases/11-write-path-reliability-format-support/11-LIVE-FINDINGS.md +0 -0
  195. {pushframe-5.1.4 → pushframe-5.1.6}/.planning/milestones/v3.0-phases/11-write-path-reliability-format-support/11-PATTERNS.md +0 -0
  196. {pushframe-5.1.4 → pushframe-5.1.6}/.planning/milestones/v3.0-phases/11-write-path-reliability-format-support/11-REVIEW.md +0 -0
  197. {pushframe-5.1.4 → pushframe-5.1.6}/.planning/milestones/v3.0-phases/11-write-path-reliability-format-support/11-VERIFICATION.md +0 -0
  198. {pushframe-5.1.4 → pushframe-5.1.6}/.planning/milestones/v4.0-REQUIREMENTS.md +0 -0
  199. {pushframe-5.1.4 → pushframe-5.1.6}/.planning/milestones/v4.0-ROADMAP.md +0 -0
  200. {pushframe-5.1.4 → pushframe-5.1.6}/.planning/milestones/v4.0-phases/16-local-mechanism-spike-decision/16-01-PLAN.md +0 -0
  201. {pushframe-5.1.4 → pushframe-5.1.6}/.planning/milestones/v4.0-phases/16-local-mechanism-spike-decision/16-01-SUMMARY.md +0 -0
  202. {pushframe-5.1.4 → pushframe-5.1.6}/.planning/milestones/v4.0-phases/16-local-mechanism-spike-decision/16-02-PLAN.md +0 -0
  203. {pushframe-5.1.4 → pushframe-5.1.6}/.planning/milestones/v4.0-phases/16-local-mechanism-spike-decision/16-02-SUMMARY.md +0 -0
  204. {pushframe-5.1.4 → pushframe-5.1.6}/.planning/milestones/v4.0-phases/16-local-mechanism-spike-decision/16-03-PLAN.md +0 -0
  205. {pushframe-5.1.4 → pushframe-5.1.6}/.planning/milestones/v4.0-phases/16-local-mechanism-spike-decision/16-03-SUMMARY.md +0 -0
  206. {pushframe-5.1.4 → pushframe-5.1.6}/.planning/milestones/v4.0-phases/16-local-mechanism-spike-decision/16-CONTEXT.md +0 -0
  207. {pushframe-5.1.4 → pushframe-5.1.6}/.planning/milestones/v4.0-phases/16-local-mechanism-spike-decision/16-DECISION-RECORD.md +0 -0
  208. {pushframe-5.1.4 → pushframe-5.1.6}/.planning/milestones/v4.0-phases/16-local-mechanism-spike-decision/16-DISCUSSION-LOG.md +0 -0
  209. {pushframe-5.1.4 → pushframe-5.1.6}/.planning/milestones/v4.0-phases/16-local-mechanism-spike-decision/16-LIVE-FINDINGS.md +0 -0
  210. {pushframe-5.1.4 → pushframe-5.1.6}/.planning/milestones/v4.0-phases/16-local-mechanism-spike-decision/COVERAGE.md +0 -0
  211. {pushframe-5.1.4 → pushframe-5.1.6}/.planning/milestones/v4.0-phases/17-google-link-album-selection/17-01-PLAN.md +0 -0
  212. {pushframe-5.1.4 → pushframe-5.1.6}/.planning/milestones/v4.0-phases/17-google-link-album-selection/17-01-SUMMARY.md +0 -0
  213. {pushframe-5.1.4 → pushframe-5.1.6}/.planning/milestones/v4.0-phases/17-google-link-album-selection/17-02-PLAN.md +0 -0
  214. {pushframe-5.1.4 → pushframe-5.1.6}/.planning/milestones/v4.0-phases/17-google-link-album-selection/17-02-SUMMARY.md +0 -0
  215. {pushframe-5.1.4 → pushframe-5.1.6}/.planning/milestones/v4.0-phases/17-google-link-album-selection/17-CONTEXT.md +0 -0
  216. {pushframe-5.1.4 → pushframe-5.1.6}/.planning/milestones/v4.0-phases/17-google-link-album-selection/17-DISCUSS-CHECKPOINT.json +0 -0
  217. {pushframe-5.1.4 → pushframe-5.1.6}/.planning/milestones/v4.0-phases/17-google-link-album-selection/17-VERIFICATION.md +0 -0
  218. {pushframe-5.1.4 → pushframe-5.1.6}/.planning/milestones/v4.0-phases/17-google-link-album-selection/COVERAGE.md +0 -0
  219. {pushframe-5.1.4 → pushframe-5.1.6}/.planning/milestones/v4.0-phases/18-album-frame-mirror-sync-single-pair/18-01-PLAN.md +0 -0
  220. {pushframe-5.1.4 → pushframe-5.1.6}/.planning/milestones/v4.0-phases/18-album-frame-mirror-sync-single-pair/18-01-SUMMARY.md +0 -0
  221. {pushframe-5.1.4 → pushframe-5.1.6}/.planning/milestones/v4.0-phases/18-album-frame-mirror-sync-single-pair/18-02-PLAN.md +0 -0
  222. {pushframe-5.1.4 → pushframe-5.1.6}/.planning/milestones/v4.0-phases/18-album-frame-mirror-sync-single-pair/18-02-SUMMARY.md +0 -0
  223. {pushframe-5.1.4 → pushframe-5.1.6}/.planning/milestones/v4.0-phases/18-album-frame-mirror-sync-single-pair/18-03-PLAN.md +0 -0
  224. {pushframe-5.1.4 → pushframe-5.1.6}/.planning/milestones/v4.0-phases/18-album-frame-mirror-sync-single-pair/18-03-SUMMARY.md +0 -0
  225. {pushframe-5.1.4 → pushframe-5.1.6}/.planning/milestones/v4.0-phases/18-album-frame-mirror-sync-single-pair/18-CONTEXT.md +0 -0
  226. {pushframe-5.1.4 → pushframe-5.1.6}/.planning/milestones/v4.0-phases/18-album-frame-mirror-sync-single-pair/18-RESEARCH.md +0 -0
  227. {pushframe-5.1.4 → pushframe-5.1.6}/.planning/milestones/v4.0-phases/18-album-frame-mirror-sync-single-pair/18-UAT.md +0 -0
  228. {pushframe-5.1.4 → pushframe-5.1.6}/.planning/milestones/v4.0-phases/18-album-frame-mirror-sync-single-pair/18-VERIFICATION.md +0 -0
  229. {pushframe-5.1.4 → pushframe-5.1.6}/.planning/milestones/v4.0-phases/19-debt-closeout/19-01-PLAN.md +0 -0
  230. {pushframe-5.1.4 → pushframe-5.1.6}/.planning/milestones/v4.0-phases/19-debt-closeout/19-01-SUMMARY.md +0 -0
  231. {pushframe-5.1.4 → pushframe-5.1.6}/.planning/milestones/v4.0-phases/19-debt-closeout/19-02-PLAN.md +0 -0
  232. {pushframe-5.1.4 → pushframe-5.1.6}/.planning/milestones/v4.0-phases/19-debt-closeout/19-02-SUMMARY.md +0 -0
  233. {pushframe-5.1.4 → pushframe-5.1.6}/.planning/milestones/v4.0-phases/19-debt-closeout/19-CONTEXT.md +0 -0
  234. {pushframe-5.1.4 → pushframe-5.1.6}/.planning/milestones/v4.0-phases/19-debt-closeout/19-RESEARCH.md +0 -0
  235. {pushframe-5.1.4 → pushframe-5.1.6}/.planning/milestones/v4.0-phases/19-debt-closeout/19-VERIFICATION.md +0 -0
  236. {pushframe-5.1.4 → pushframe-5.1.6}/.planning/milestones/v5.0-REQUIREMENTS.md +0 -0
  237. {pushframe-5.1.4 → pushframe-5.1.6}/.planning/milestones/v5.0-ROADMAP.md +0 -0
  238. {pushframe-5.1.4 → pushframe-5.1.6}/.planning/milestones/v5.0-phases/20-pushframe-rename-migration-repo-switch/20-SUMMARY.md +0 -0
  239. {pushframe-5.1.4 → pushframe-5.1.6}/.planning/milestones/v5.0-phases/21-debian-package-apt-repo/21-01-PLAN.md +0 -0
  240. {pushframe-5.1.4 → pushframe-5.1.6}/.planning/milestones/v5.0-phases/21-debian-package-apt-repo/21-01-SUMMARY.md +0 -0
  241. {pushframe-5.1.4 → pushframe-5.1.6}/.planning/milestones/v5.0-phases/21-debian-package-apt-repo/21-02-PLAN.md +0 -0
  242. {pushframe-5.1.4 → pushframe-5.1.6}/.planning/milestones/v5.0-phases/21-debian-package-apt-repo/21-02-SUMMARY.md +0 -0
  243. {pushframe-5.1.4 → pushframe-5.1.6}/.planning/milestones/v5.0-phases/21-debian-package-apt-repo/21-CONTEXT.md +0 -0
  244. {pushframe-5.1.4 → pushframe-5.1.6}/.planning/milestones/v5.0-phases/21-debian-package-apt-repo/21-RESEARCH.md +0 -0
  245. {pushframe-5.1.4 → pushframe-5.1.6}/.planning/milestones/v5.0-phases/21-debian-package-apt-repo/21-VERIFICATION.md +0 -0
  246. {pushframe-5.1.4 → pushframe-5.1.6}/.planning/milestones/v5.0-phases/22-pypi-release-engineering/22-01-PLAN.md +0 -0
  247. {pushframe-5.1.4 → pushframe-5.1.6}/.planning/milestones/v5.0-phases/22-pypi-release-engineering/22-01-SUMMARY.md +0 -0
  248. {pushframe-5.1.4 → pushframe-5.1.6}/.planning/milestones/v5.0-phases/22-pypi-release-engineering/22-02-PLAN.md +0 -0
  249. {pushframe-5.1.4 → pushframe-5.1.6}/.planning/milestones/v5.0-phases/22-pypi-release-engineering/22-02-SUMMARY.md +0 -0
  250. {pushframe-5.1.4 → pushframe-5.1.6}/.planning/milestones/v5.0-phases/22-pypi-release-engineering/22-CONTEXT.md +0 -0
  251. {pushframe-5.1.4 → pushframe-5.1.6}/.planning/milestones/v5.0-phases/22-pypi-release-engineering/22-RESEARCH.md +0 -0
  252. {pushframe-5.1.4 → pushframe-5.1.6}/.planning/milestones/v5.0-phases/22-pypi-release-engineering/22-VERIFICATION.md +0 -0
  253. {pushframe-5.1.4 → pushframe-5.1.6}/.planning/milestones/v5.0-phases/22-pypi-release-engineering/OPERATOR-STEPS.md +0 -0
  254. {pushframe-5.1.4 → pushframe-5.1.6}/.planning/phases/23-config-wizard-precedence/23-01-PLAN.md +0 -0
  255. {pushframe-5.1.4 → pushframe-5.1.6}/.planning/phases/23-config-wizard-precedence/23-01-SUMMARY.md +0 -0
  256. {pushframe-5.1.4 → pushframe-5.1.6}/.planning/phases/23-config-wizard-precedence/23-CONTEXT.md +0 -0
  257. {pushframe-5.1.4 → pushframe-5.1.6}/.planning/phases/23-config-wizard-precedence/23-RESEARCH.md +0 -0
  258. {pushframe-5.1.4 → pushframe-5.1.6}/.planning/phases/23-config-wizard-precedence/23-VERIFICATION.md +0 -0
  259. {pushframe-5.1.4 → pushframe-5.1.6}/.planning/phases/24-token-first-sessions-preflight-sweep-sec-01-03-prf-01-02/24-01-PLAN.md +0 -0
  260. {pushframe-5.1.4 → pushframe-5.1.6}/.planning/phases/24-token-first-sessions-preflight-sweep-sec-01-03-prf-01-02/24-01-SUMMARY.md +0 -0
  261. {pushframe-5.1.4 → pushframe-5.1.6}/.planning/phases/24-token-first-sessions-preflight-sweep-sec-01-03-prf-01-02/24-CONTEXT.md +0 -0
  262. {pushframe-5.1.4 → pushframe-5.1.6}/.planning/phases/24-token-first-sessions-preflight-sweep-sec-01-03-prf-01-02/24-RESEARCH.md +0 -0
  263. {pushframe-5.1.4 → pushframe-5.1.6}/.planning/phases/24-token-first-sessions-preflight-sweep-sec-01-03-prf-01-02/24-VERIFICATION.md +0 -0
  264. {pushframe-5.1.4 → pushframe-5.1.6}/.planning/phases/25-multi-frame-mappings-systemd-user-scheduling-mtf-01-03-tmr-01-03/25-01-PLAN.md +0 -0
  265. {pushframe-5.1.4 → pushframe-5.1.6}/.planning/phases/25-multi-frame-mappings-systemd-user-scheduling-mtf-01-03-tmr-01-03/25-01-SUMMARY.md +0 -0
  266. {pushframe-5.1.4 → pushframe-5.1.6}/.planning/phases/25-multi-frame-mappings-systemd-user-scheduling-mtf-01-03-tmr-01-03/25-CONTEXT.md +0 -0
  267. {pushframe-5.1.4 → pushframe-5.1.6}/.planning/phases/25-multi-frame-mappings-systemd-user-scheduling-mtf-01-03-tmr-01-03/25-RESEARCH.md +0 -0
  268. {pushframe-5.1.4 → pushframe-5.1.6}/.planning/phases/25-multi-frame-mappings-systemd-user-scheduling-mtf-01-03-tmr-01-03/25-VERIFICATION.md +0 -0
  269. {pushframe-5.1.4 → pushframe-5.1.6}/.planning/research/ALBUM-ACCESS-V4-ADDENDUM.md +0 -0
  270. {pushframe-5.1.4 → pushframe-5.1.6}/.planning/research/ALBUM-ACCESS.md +0 -0
  271. {pushframe-5.1.4 → pushframe-5.1.6}/.planning/research/ARCHITECTURE.md +0 -0
  272. {pushframe-5.1.4 → pushframe-5.1.6}/.planning/research/BROWSER-AUTOMATION.md +0 -0
  273. {pushframe-5.1.4 → pushframe-5.1.6}/.planning/research/FEATURES.md +0 -0
  274. {pushframe-5.1.4 → pushframe-5.1.6}/.planning/research/PITFALLS.md +0 -0
  275. {pushframe-5.1.4 → pushframe-5.1.6}/.planning/research/STACK.md +0 -0
  276. {pushframe-5.1.4 → pushframe-5.1.6}/.planning/research/SUMMARY.md +0 -0
  277. {pushframe-5.1.4 → pushframe-5.1.6}/.planning/state.json +0 -0
  278. {pushframe-5.1.4 → pushframe-5.1.6}/.planning/todos/completed/2026-07-06-promote-debug-flag-to-a-global-cli-convention.md +0 -0
  279. {pushframe-5.1.4 → pushframe-5.1.6}/.python-version +0 -0
  280. {pushframe-5.1.4 → pushframe-5.1.6}/CLAUDE.md +0 -0
  281. {pushframe-5.1.4 → pushframe-5.1.6}/LICENSE +0 -0
  282. {pushframe-5.1.4 → pushframe-5.1.6}/README.md +0 -0
  283. {pushframe-5.1.4 → pushframe-5.1.6}/SECURITY.md +0 -0
  284. {pushframe-5.1.4 → pushframe-5.1.6}/VERIFICATION-REPORT.md +0 -0
  285. {pushframe-5.1.4 → pushframe-5.1.6}/docs/CLI.md +0 -0
  286. {pushframe-5.1.4 → pushframe-5.1.6}/docs/agents/domain.md +0 -0
  287. {pushframe-5.1.4 → pushframe-5.1.6}/docs/agents/issue-tracker.md +0 -0
  288. {pushframe-5.1.4 → pushframe-5.1.6}/docs/agents/triage-labels.md +0 -0
  289. {pushframe-5.1.4 → pushframe-5.1.6}/docs/superpowers/specs/2026-07-09-write-rate-limiter-design.md +0 -0
  290. {pushframe-5.1.4 → pushframe-5.1.6}/main.py +0 -0
  291. {pushframe-5.1.4 → pushframe-5.1.6}/packaging/control-template +0 -0
  292. {pushframe-5.1.4 → pushframe-5.1.6}/packaging/lintian-overrides +0 -0
  293. {pushframe-5.1.4 → pushframe-5.1.6}/packaging/postinst +0 -0
  294. {pushframe-5.1.4 → pushframe-5.1.6}/packaging/pushframe.copyright +0 -0
  295. {pushframe-5.1.4 → pushframe-5.1.6}/probes/.gitignore +0 -0
  296. {pushframe-5.1.4 → pushframe-5.1.6}/probes/README.md +0 -0
  297. {pushframe-5.1.4 → pushframe-5.1.6}/probes/browser_bootstrap.py +0 -0
  298. {pushframe-5.1.4 → pushframe-5.1.6}/probes/common.py +0 -0
  299. {pushframe-5.1.4 → pushframe-5.1.6}/probes/cookie_vault.py +0 -0
  300. {pushframe-5.1.4 → pushframe-5.1.6}/probes/fidelity_check.py +0 -0
  301. {pushframe-5.1.4 → pushframe-5.1.6}/probes/rpc_capture.py +0 -0
  302. {pushframe-5.1.4 → pushframe-5.1.6}/probes/shared_link_probe.py +0 -0
  303. {pushframe-5.1.4 → pushframe-5.1.6}/pushframe/api/__init__.py +0 -0
  304. {pushframe-5.1.4 → pushframe-5.1.6}/pushframe/api/accountApi.py +0 -0
  305. {pushframe-5.1.4 → pushframe-5.1.6}/pushframe/api/activityApi.py +0 -0
  306. {pushframe-5.1.4 → pushframe-5.1.6}/pushframe/api/assetApi.py +0 -0
  307. {pushframe-5.1.4 → pushframe-5.1.6}/pushframe/api/baseApi.py +0 -0
  308. {pushframe-5.1.4 → pushframe-5.1.6}/pushframe/api/frameApi.py +0 -0
  309. {pushframe-5.1.4 → pushframe-5.1.6}/pushframe/api/notificationApi.py +0 -0
  310. {pushframe-5.1.4 → pushframe-5.1.6}/pushframe/api/peopleApi.py +0 -0
  311. {pushframe-5.1.4 → pushframe-5.1.6}/pushframe/api/playlistApi.py +0 -0
  312. {pushframe-5.1.4 → pushframe-5.1.6}/pushframe/aura.py +0 -0
  313. {pushframe-5.1.4 → pushframe-5.1.6}/pushframe/aws/__init__.py +0 -0
  314. {pushframe-5.1.4 → pushframe-5.1.6}/pushframe/aws/awsclient.py +0 -0
  315. {pushframe-5.1.4 → pushframe-5.1.6}/pushframe/aws/s3client.py +0 -0
  316. {pushframe-5.1.4 → pushframe-5.1.6}/pushframe/aws/sqsclient.py +0 -0
  317. {pushframe-5.1.4 → pushframe-5.1.6}/pushframe/cache.py +0 -0
  318. {pushframe-5.1.4 → pushframe-5.1.6}/pushframe/client.py +0 -0
  319. {pushframe-5.1.4 → pushframe-5.1.6}/pushframe/config_store.py +0 -0
  320. {pushframe-5.1.4 → pushframe-5.1.6}/pushframe/doctor.py +0 -0
  321. {pushframe-5.1.4 → pushframe-5.1.6}/pushframe/exif.py +0 -0
  322. {pushframe-5.1.4 → pushframe-5.1.6}/pushframe/export.py +0 -0
  323. {pushframe-5.1.4 → pushframe-5.1.6}/pushframe/google/__init__.py +0 -0
  324. {pushframe-5.1.4 → pushframe-5.1.6}/pushframe/google/bootstrap.py +0 -0
  325. {pushframe-5.1.4 → pushframe-5.1.6}/pushframe/google/cache.py +0 -0
  326. {pushframe-5.1.4 → pushframe-5.1.6}/pushframe/google/client.py +0 -0
  327. {pushframe-5.1.4 → pushframe-5.1.6}/pushframe/google/enumerate.py +0 -0
  328. {pushframe-5.1.4 → pushframe-5.1.6}/pushframe/google/manifest.py +0 -0
  329. {pushframe-5.1.4 → pushframe-5.1.6}/pushframe/google/parsers.py +0 -0
  330. {pushframe-5.1.4 → pushframe-5.1.6}/pushframe/google/redaction.py +0 -0
  331. {pushframe-5.1.4 → pushframe-5.1.6}/pushframe/google/vault.py +0 -0
  332. {pushframe-5.1.4 → pushframe-5.1.6}/pushframe/migration.py +0 -0
  333. {pushframe-5.1.4 → pushframe-5.1.6}/pushframe/models/__init__.py +0 -0
  334. {pushframe-5.1.4 → pushframe-5.1.6}/pushframe/models/activity.py +0 -0
  335. {pushframe-5.1.4 → pushframe-5.1.6}/pushframe/models/asset.py +0 -0
  336. {pushframe-5.1.4 → pushframe-5.1.6}/pushframe/models/frame.py +0 -0
  337. {pushframe-5.1.4 → pushframe-5.1.6}/pushframe/models/meta.py +0 -0
  338. {pushframe-5.1.4 → pushframe-5.1.6}/pushframe/models/person.py +0 -0
  339. {pushframe-5.1.4 → pushframe-5.1.6}/pushframe/models/user.py +0 -0
  340. {pushframe-5.1.4 → pushframe-5.1.6}/pushframe/pairs.py +0 -0
  341. {pushframe-5.1.4 → pushframe-5.1.6}/pushframe/ratelimit.py +0 -0
  342. {pushframe-5.1.4 → pushframe-5.1.6}/pushframe/reconcile.py +0 -0
  343. {pushframe-5.1.4 → pushframe-5.1.6}/pushframe/schedule.py +0 -0
  344. {pushframe-5.1.4 → pushframe-5.1.6}/pushframe/sync.py +0 -0
  345. {pushframe-5.1.4 → pushframe-5.1.6}/pushframe/utils/dt.py +0 -0
  346. {pushframe-5.1.4 → pushframe-5.1.6}/pushframe/utils/io.py +0 -0
  347. {pushframe-5.1.4 → pushframe-5.1.6}/pushframe/utils/settings.py +0 -0
  348. {pushframe-5.1.4 → pushframe-5.1.6}/scripts/build-deb.sh +0 -0
  349. {pushframe-5.1.4 → pushframe-5.1.6}/scripts/make-apt-key.sh +0 -0
  350. {pushframe-5.1.4 → pushframe-5.1.6}/scripts/publish-apt-repo.sh +0 -0
  351. {pushframe-5.1.4 → pushframe-5.1.6}/scripts/test-apt-journey-container.sh +0 -0
  352. {pushframe-5.1.4 → pushframe-5.1.6}/scripts/test-config-journey-container.sh +0 -0
  353. {pushframe-5.1.4 → pushframe-5.1.6}/scripts/test-deb-container.sh +0 -0
  354. {pushframe-5.1.4 → pushframe-5.1.6}/scripts/test-install-journey-container.sh +0 -0
  355. {pushframe-5.1.4 → pushframe-5.1.6}/scripts/test-multipair-journey-container.sh +0 -0
  356. {pushframe-5.1.4 → pushframe-5.1.6}/tests/conftest.py +0 -0
  357. {pushframe-5.1.4 → pushframe-5.1.6}/tests/fixtures/asset_unprocessed.json +0 -0
  358. {pushframe-5.1.4 → pushframe-5.1.6}/tests/fixtures/assets_page1.json +0 -0
  359. {pushframe-5.1.4 → pushframe-5.1.6}/tests/fixtures/assets_page2.json +0 -0
  360. {pushframe-5.1.4 → pushframe-5.1.6}/tests/fixtures/assets_placeholders.json +0 -0
  361. {pushframe-5.1.4 → pushframe-5.1.6}/tests/fixtures/error_envelope.json +0 -0
  362. {pushframe-5.1.4 → pushframe-5.1.6}/tests/fixtures/frame_detail.json +0 -0
  363. {pushframe-5.1.4 → pushframe-5.1.6}/tests/fixtures/frames.json +0 -0
  364. {pushframe-5.1.4 → pushframe-5.1.6}/tests/fixtures/google_rpc_snackc_page.json +0 -0
  365. {pushframe-5.1.4 → pushframe-5.1.6}/tests/fixtures/google_share_page_sample.html +0 -0
  366. {pushframe-5.1.4 → pushframe-5.1.6}/tests/fixtures/login.json +0 -0
  367. {pushframe-5.1.4 → pushframe-5.1.6}/tests/fixtures/probe_af_initdata_sample.html +0 -0
  368. {pushframe-5.1.4 → pushframe-5.1.6}/tests/offline.py +0 -0
  369. {pushframe-5.1.4 → pushframe-5.1.6}/tests/test_asset_partial.py +0 -0
  370. {pushframe-5.1.4 → pushframe-5.1.6}/tests/test_asset_unprocessed.py +0 -0
  371. {pushframe-5.1.4 → pushframe-5.1.6}/tests/test_aura_logger.py +0 -0
  372. {pushframe-5.1.4 → pushframe-5.1.6}/tests/test_batch_update_contract.py +0 -0
  373. {pushframe-5.1.4 → pushframe-5.1.6}/tests/test_cli_apply.py +0 -0
  374. {pushframe-5.1.4 → pushframe-5.1.6}/tests/test_cli_google_album.py +0 -0
  375. {pushframe-5.1.4 → pushframe-5.1.6}/tests/test_cli_google_link.py +0 -0
  376. {pushframe-5.1.4 → pushframe-5.1.6}/tests/test_cli_inspect.py +0 -0
  377. {pushframe-5.1.4 → pushframe-5.1.6}/tests/test_cli_push_budget_geo.py +0 -0
  378. {pushframe-5.1.4 → pushframe-5.1.6}/tests/test_cli_reconcile.py +0 -0
  379. {pushframe-5.1.4 → pushframe-5.1.6}/tests/test_cli_status.py +0 -0
  380. {pushframe-5.1.4 → pushframe-5.1.6}/tests/test_cli_status_google.py +0 -0
  381. {pushframe-5.1.4 → pushframe-5.1.6}/tests/test_cli_sync.py +0 -0
  382. {pushframe-5.1.4 → pushframe-5.1.6}/tests/test_client_rate_limit.py +0 -0
  383. {pushframe-5.1.4 → pushframe-5.1.6}/tests/test_config_wizard.py +0 -0
  384. {pushframe-5.1.4 → pushframe-5.1.6}/tests/test_doctor.py +0 -0
  385. {pushframe-5.1.4 → pushframe-5.1.6}/tests/test_execute_plan.py +0 -0
  386. {pushframe-5.1.4 → pushframe-5.1.6}/tests/test_execute_plan_budget_geo.py +0 -0
  387. {pushframe-5.1.4 → pushframe-5.1.6}/tests/test_fixtures_validity.py +0 -0
  388. {pushframe-5.1.4 → pushframe-5.1.6}/tests/test_google_cache.py +0 -0
  389. {pushframe-5.1.4 → pushframe-5.1.6}/tests/test_google_client.py +0 -0
  390. {pushframe-5.1.4 → pushframe-5.1.6}/tests/test_google_enumerate.py +0 -0
  391. {pushframe-5.1.4 → pushframe-5.1.6}/tests/test_google_manifest.py +0 -0
  392. {pushframe-5.1.4 → pushframe-5.1.6}/tests/test_google_parsers.py +0 -0
  393. {pushframe-5.1.4 → pushframe-5.1.6}/tests/test_google_vault.py +0 -0
  394. {pushframe-5.1.4 → pushframe-5.1.6}/tests/test_gsync_all.py +0 -0
  395. {pushframe-5.1.4 → pushframe-5.1.6}/tests/test_gsync_engine.py +0 -0
  396. {pushframe-5.1.4 → pushframe-5.1.6}/tests/test_gsync_execute.py +0 -0
  397. {pushframe-5.1.4 → pushframe-5.1.6}/tests/test_imports.py +0 -0
  398. {pushframe-5.1.4 → pushframe-5.1.6}/tests/test_logout.py +0 -0
  399. {pushframe-5.1.4 → pushframe-5.1.6}/tests/test_migration.py +0 -0
  400. {pushframe-5.1.4 → pushframe-5.1.6}/tests/test_network_guard.py +0 -0
  401. {pushframe-5.1.4 → pushframe-5.1.6}/tests/test_offline_read_path.py +0 -0
  402. {pushframe-5.1.4 → pushframe-5.1.6}/tests/test_pairs.py +0 -0
  403. {pushframe-5.1.4 → pushframe-5.1.6}/tests/test_prep_upload_formats.py +0 -0
  404. {pushframe-5.1.4 → pushframe-5.1.6}/tests/test_probe_browser.py +0 -0
  405. {pushframe-5.1.4 → pushframe-5.1.6}/tests/test_probe_shared_link.py +0 -0
  406. {pushframe-5.1.4 → pushframe-5.1.6}/tests/test_ratelimit.py +0 -0
  407. {pushframe-5.1.4 → pushframe-5.1.6}/tests/test_read_path.py +0 -0
  408. {pushframe-5.1.4 → pushframe-5.1.6}/tests/test_reconcile.py +0 -0
  409. {pushframe-5.1.4 → pushframe-5.1.6}/tests/test_retry_401.py +0 -0
  410. {pushframe-5.1.4 → pushframe-5.1.6}/tests/test_schedule.py +0 -0
  411. {pushframe-5.1.4 → pushframe-5.1.6}/tests/test_session.py +0 -0
  412. {pushframe-5.1.4 → pushframe-5.1.6}/tests/test_settings_budget.py +0 -0
  413. {pushframe-5.1.4 → pushframe-5.1.6}/tests/test_settings_resolution.py +0 -0
  414. {pushframe-5.1.4 → pushframe-5.1.6}/tests/test_sync_engine.py +0 -0
  415. {pushframe-5.1.4 → pushframe-5.1.6}/tests/test_token_refresh_extension.py +0 -0
  416. {pushframe-5.1.4 → pushframe-5.1.6}/tests/test_write_endpoints_failloud.py +0 -0
  417. {pushframe-5.1.4 → pushframe-5.1.6}/tests/test_write_formats_live.py +0 -0
  418. {pushframe-5.1.4 → pushframe-5.1.6}/tests/test_write_throttling.py +0 -0
@@ -0,0 +1,50 @@
1
+ name: tests
2
+
3
+ on:
4
+ push:
5
+ branches: [master]
6
+ pull_request:
7
+
8
+ jobs:
9
+ offline-suite:
10
+ runs-on: ubuntu-latest
11
+ steps:
12
+ - uses: actions/checkout@v7
13
+
14
+ - name: Install uv
15
+ uses: astral-sh/setup-uv@v10.2.0
16
+ with:
17
+ enable-cache: true
18
+
19
+ - name: Run the offline test suite (no credentials, no network)
20
+ run: uv run --extra dev pytest -q -m "not live"
21
+
22
+ dependency-audit:
23
+ runs-on: ubuntu-latest
24
+ steps:
25
+ - uses: actions/checkout@v7
26
+
27
+ - name: Install uv
28
+ uses: astral-sh/setup-uv@v10.2.0
29
+ with:
30
+ enable-cache: true
31
+
32
+ # FAIL THE BUILD on any known CVE in the LOCKED dependency tree
33
+ # (uv.lock — the exact set users install, not pyproject's open
34
+ # ranges). pip-audit reads the OSV/PyPI advisory DBs. Dependabot
35
+ # flags and PRs stay the discovery side; this gate makes a known-
36
+ # vulnerable lockfile unshippable on master, independent of scan
37
+ # timing (2026-09-30: the push banner fired on a tree that the
38
+ # merged urllib3 bump had already fixed — stale, but nothing
39
+ # enforced it). Fails loudly on ANY confirmed vulnerability;
40
+ #GHSA-withdrawn advisories vanish on their own. If a vulnerability
41
+ # lands with NO patched release yet, this job blocks master until
42
+ # one exists or the dep is pinned/removed — that visibility is the
43
+ # point; use `continue-on-error: true` temporarily ONLY with a
44
+ # linked issue.
45
+ - name: Audit the locked dependency tree (pip-audit)
46
+ run: |
47
+ set -euo pipefail
48
+ uv export --format requirements-txt --no-hashes --no-emit-project -o /tmp/reqs.txt
49
+ grep -v '^#' /tmp/reqs.txt | grep -v '^$' | head -3
50
+ uvx pip-audit --requirement /tmp/reqs.txt --no-deps --strict
@@ -0,0 +1,51 @@
1
+ ---
2
+ slug: gsync-apply-budget-none-crash
3
+ status: resolved
4
+ created: 2026-09-30T23:05:00+02:00
5
+ updated: 2026-09-30T23:50:00+02:00
6
+ trigger: pushframe google-sync --apply crashes with AttributeError 'NoneType' object has no attribute 'encode' in _build_write_budget after the y confirmation — operator framing: "la gestion de la dualité config env et fichier de conf ne fonctionne pas" (the env-vs-config-file duality is broken)
7
+ ---
8
+
9
+ # Debug: google-sync --apply crashes building the write budget (email None)
10
+
11
+ ## Symptoms
12
+
13
+ - expected: after the `y` confirmation, the plan applies at write-budget pace (or runs budgetless when the account is not identified), then prints the apply report — never a traceback (PRF-02).
14
+ - actual: raw AttributeError traceback from cli._build_write_budget via gsync.py line 491.
15
+ - error: `AttributeError: 'NoneType' object has no attribute 'encode'` at `budget-{sha1(email.encode())…}` — the email passed is None.
16
+
17
+ ## Environment
18
+
19
+ - venus (headless server, ssh), pushframe 5.1.5 (installed this evening)
20
+ - config.json: stored token session (email coredmp95@gmail.com) — NO PUSHFRAME_EMAIL/AURA_EMAIL/PASSWORD env in the operator shell
21
+ - command: `pushframe google-sync Cadre --frame "Cadre de Fabrice"` then `y` at the apply gate
22
+ - the SAME dry-run command earlier tonight printed the plan fine; the crash is strictly past the y gate
23
+
24
+ ## Evidence
25
+
26
+ - 23:10: crash site + mechanism located (this context, no subagents available in this runtime): gsync.py:497 builds the budget from `os.getenv('PUSHFRAME_EMAIL') or os.getenv('AURA_EMAIL')` ONLY — on venus neither env var is set (token-session posture) → email=None → `email.encode()` inside the sha1 → AttributeError. The auth layer (session.establish_session) DOES resolve the stored config email — two different identity resolutions = the operator's "duality" is real.
27
+ - scope audit (this context): same env-only identity read at cli.py:1129 (reconcile — crashes on --remove with no env) and cli.py:1392 (sync/push --apply — crashes with no env). Reconcile has NO --ignore-budget, so it is unreachable on venus until fixed.
28
+ - why tests never caught it: every gsync apply test injects s3_client=…, and the build condition is `budget is None AND s3_client is None` — injection skips the crash line entirely. Same for sync/push tests (exec_kwargs path).
29
+ - why it never crashed before tonight: phase-25 nightly runs were `--pair` DRY-RUN (plan print only — budget built after the gate, never reached). Tonight was the first interactive `--apply` on venus.
30
+ - 23:2x: operator removed ALL env vars from the venus shell (user message) — the crash posture is exactly "stored token session, no env identity". The auth layer resolves that posture (token resumed fine — the plan printed); the budget layer does not (None.encode()). Duality confirmed on both sides.
31
+
32
+ ## Hypotheses
33
+
34
+ - H1 (CONFIRMED): the write-budget identity is resolved env-ONLY while auth resolves env-then-config — two different resolvers for one identity. Mechanism proven by code read + crash line + operator env removal reproducing.
35
+
36
+ ## Root Cause
37
+
38
+ `gsync.py:497` / `cli.py:1129` / `cli.py:1392` build the budget email from `os.getenv('PUSHFRAME_EMAIL') or os.getenv('AURA_EMAIL')` ONLY. On a token-session host (venus's intended posture) that is None and `_build_write_budget` calls `email.encode()` → AttributeError. The config store's email (the one auth uses) is never consulted. Fix: ONE resolver — `session.account_email()` (env override first, then stored session) — wired into all three call sites; `_build_write_budget(None)` now returns None (skip pacing) instead of crashing.
39
+
40
+ ## Resolution
41
+
42
+ root_cause: identity duality — auth resolves env-then-config-store, the write-budget layer resolved env-ONLY (gsync.py:497, cli.py:1129 reconcile, cli.py:1392 sync/push). Token-session host + no env ⇒ email=None ⇒ email.encode() AttributeError AFTER the operator's y confirmation.
43
+ fix: ONE resolver `session.account_email()` (env override, then stored session) wired into all three budget call sites; `_build_write_budget(None)` skips pacing with a named stderr line instead of crashing (a budget is per-account state — with no account named there is nothing to key).
44
+ verification: 6 regression tests (tests/test_budget_identity.py: env-over-store, stored fallback, None never crashes + names the skip, state-file keyed to sha1(email)[:12], direct probe of the gsync crash expression) + sweep case mode-17 reconcile-no-identity (named, never traceback); suite 524 passed, seeded and fixed order. Deployed to venus from the local wheel; existing budget file budget-eeda3bb09fc5.json = sha1('coredmp95@gmail.com')[:12] — continuity preserved.
45
+ files_changed: pushframe/session.py, pushframe/cli.py, pushframe/gsync.py, tests/test_budget_identity.py (new), tests/test_preflight_sweep.py, CHANGELOG.md
46
+
47
+ ## Current Focus
48
+
49
+ next_action: none — resolved
50
+
51
+ Note on process: the orchestrator already located the crash site + mechanism before creating this file (gsync.py:497 → cli._build_write_budget email=None; same latent shape at cli.py:1129 reconcile and cli.py:1392 sync/push when the account is unnamed). No subagent dispatch is available in this runtime (established 2026-09-30, session status-crash-401-trip) — the scientific loop runs in this context.
@@ -12,7 +12,60 @@ every channel.
12
12
  `pushframe` is an unofficial community CLI for Aura Frames digital photo
13
13
  frames; it is not affiliated with Aura Frames Inc.
14
14
 
15
- ## [5.1.4] - 2026-09-30
15
+ ## [5.1.6] - 2026-09-30
16
+
17
+ ### Fixed
18
+
19
+ - **`google-sync --apply` (and `sync --apply` / `push --apply` /
20
+ `reconcile --remove`) no longer crash building the write budget on a
21
+ token-session host** (venus, 2026-09-30, debug session
22
+ gsync-apply-budget-none-crash): the budget's account email was read
23
+ from the ENVIRONMENT only while auth resolves env-then-stored-session
24
+ — the config/env duality was broken at exactly that layer. With a
25
+ stored token session and no env vars (venus's intended posture), the
26
+ run authenticated fine, printed the plan, took the operator's `y`
27
+ confirmation, then died on `email.encode()` inside the budget state
28
+ filename. The identity now resolves through ONE helper
29
+ (`session.account_email()`: env override first, then the stored
30
+ session) at all three call sites, and a fully identity-less run skips
31
+ pacing with a named stderr line instead of crashing (a budget is
32
+ per-account state; with no account named there is nothing to key).
33
+
34
+ - `google-sync`'s new `vault_path` argument now flows END-TO-END: the
35
+ preflight AND the actual `GoogleSession.from_vault()` read both use the
36
+ pinned path (5.1.5 pinned only the preflight — a gap found by CI, whose
37
+ vault-less runner exposed it; harmless on real machines, which use the
38
+ default path).
39
+
40
+ ### Removed
41
+
42
+ - The phase-24 sweep's `gsync_no_creds` case: with the vault gate standing
43
+ BEFORE the Aura gate by design, a form-valid fake vault pushed the run
44
+ past the gate onto the REAL Google network with a bogus cookie (the
45
+ 5.1.2 network guard covers pushd, not google.com). The Aura-side
46
+ no-creds contract stays proven by the `sync`/`inspect` cases; gsync's
47
+ Google-side named shapes by `gsync_no_vault`, `pair_unknown`,
48
+ `all_zero_pairs`.
49
+
50
+ ### Fixed
51
+
52
+ - **`google-sync` no longer claims "no Google session vault" on a healthy
53
+ vault** (venus, 2026-09-30): the vault preflight probed the default path
54
+ WITHOUT `expanduser()` — a literal `~` path never exists, so the check
55
+ fired on every machine whose vault was perfectly healthy (a
56
+ `google-link` seconds earlier had saved it) — and its shape check
57
+ demanded a JSON object while the vault is a JSON list of cookie
58
+ records, so even the right path would have failed. Two stacked
59
+ phase-24 bugs, masked until now because CI has no vault either and the
60
+ scheduled `--pair` branch skips this block entirely. One tell was in
61
+ every transcript: `google-link` prints "existing Google session found"
62
+ only when a vault READ succeeds. The preflight now expands the default
63
+ path, accepts the list shape (dict kept for backward compatibility),
64
+ and takes a `vault_path` argument; `PUSHFRAME_VAULT_PATH` pins the
65
+ path for tests. The preflight-sweep suite is now pinned away from the
66
+ real machine's vault (it silently depended on the bug) and its
67
+ `gsync_no_creds` case asserts what it always meant to (Aura-side named
68
+ auth failure).
16
69
 
17
70
  ### Added
18
71
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: pushframe
3
- Version: 5.1.4
3
+ Version: 5.1.6
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
@@ -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.4"
4
+ __version__ = "5.1.6"
@@ -1126,7 +1126,8 @@ def run_reconcile(frame_arg: str, *, remove: bool = False, yes: bool = False, me
1126
1126
 
1127
1127
  # D-13: the same account-wide budget as sync/push -- reconcile has
1128
1128
  # no --ignore-budget escape hatch, so this is always False.
1129
- write_budget = _build_write_budget(os.getenv('PUSHFRAME_EMAIL') or os.getenv('AURA_EMAIL'), False)
1129
+ from pushframe.session import account_email
1130
+ write_budget = _build_write_budget(account_email(), False)
1130
1131
 
1131
1132
  apply_reconciliation(result, aura, frame.id, mechanism=mechanism, budget=write_budget)
1132
1133
 
@@ -1176,6 +1177,17 @@ def _build_write_budget(email: str, ignore_budget: bool) -> 'WriteBudget | None'
1176
1177
  """
1177
1178
  if ignore_budget:
1178
1179
  return None
1180
+ if not email:
1181
+ # venus 2026-09-30 (debug gsync-apply-budget-none-crash): a run with
1182
+ # no resolvable identity (no env, no stored email) previously died
1183
+ # HERE, on email.encode(), AFTER the operator confirmed the apply.
1184
+ # A budget is per-ACCOUNT state; with no account named there is
1185
+ # nothing to key — skip pacing rather than crash. Named debug
1186
+ # output on stderr keeps this observable, never silent.
1187
+ import sys as _sys
1188
+ print('write-budget: no account identity (env or stored session) '
1189
+ '— pacing skipped for this run', file=_sys.stderr)
1190
+ return None
1179
1191
  state_path = AURA_STATE_DIR / f'budget-{hashlib.sha1(email.encode()).hexdigest()[:12]}.json'
1180
1192
  return WriteBudget.load(
1181
1193
  state_path, capacity=AURA_WRITE_BUDGET_CAPACITY, refill_per_min=AURA_WRITE_BUDGET_REFILL_PER_MIN,
@@ -1389,7 +1401,8 @@ def run_sync(dir_arg: str, frame_arg: str, apply: bool = False, yes: bool = Fals
1389
1401
  # protection by default with zero new flags. Only `--ignore-budget`
1390
1402
  # (push-only) omits `budget`; `write_budget`/`geo_check` being
1391
1403
  # `None` is exec_kwargs's signal to omit the corresponding kwarg.
1392
- write_budget = _build_write_budget(os.getenv('PUSHFRAME_EMAIL') or os.getenv('AURA_EMAIL'), ignore_budget)
1404
+ from pushframe.session import account_email
1405
+ write_budget = _build_write_budget(account_email(), ignore_budget)
1393
1406
  geo_check = _build_geo_check(country)
1394
1407
 
1395
1408
  total = len(plan.to_upload) + len(plan.to_delete)
@@ -215,7 +215,7 @@ def run_google_sync(album_target: str, frame_arg: str, *, apply: bool = False,
215
215
  aura=None, s3_client=None, sqs_client=None, budget=None,
216
216
  workers: int = 4, threshold: float | None = None,
217
217
  input_fn=None, is_interactive: bool | None = None,
218
- list_shared=None, cache_dir=None,
218
+ list_shared=None, cache_dir=None, vault_path=None,
219
219
  manifest_path=None, batch_size: int | None = None,
220
220
  pair: str | None = None, run_all: bool = False,
221
221
  scheduled: bool = False) -> int:
@@ -314,12 +314,18 @@ def run_google_sync(album_target: str, frame_arg: str, *, apply: bool = False,
314
314
  # with the full remedy (google-link + the headless ssh -X recipe).
315
315
  try:
316
316
  from pushframe.preflight import require_google_vault
317
- require_google_vault()
317
+ require_google_vault(vault_path)
318
318
  except Exception as e:
319
319
  print(f'google-sync failed: {e}')
320
320
  return 1
321
321
  try:
322
- session = GoogleSession.from_vault()
322
+ # The pinned path flows END-TO-END (2026-09-30 CI lesson: pinning
323
+ # the preflight alone left this read on the default path — on a
324
+ # vault-less machine the run died 'bootstrap first' before ever
325
+ # reaching its intended failure, and a test pinning only the
326
+ # preflight could push the DEFAULT read onto the network).
327
+ session = (GoogleSession.from_vault(path=str(vault_path))
328
+ if vault_path else GoogleSession.from_vault())
323
329
  except CookieVaultError as e:
324
330
  print(f'google-sync failed: {e}')
325
331
  return 1
@@ -488,7 +494,12 @@ def run_google_sync(album_target: str, frame_arg: str, *, apply: bool = False,
488
494
  if debug:
489
495
  _configure_cli_logging(True)
490
496
  if budget is None and s3_client is None:
491
- budget = _build_write_budget(_os.getenv('PUSHFRAME_EMAIL') or _os.getenv('AURA_EMAIL'), ignore_budget=False)
497
+ # venus 2026-09-30 (debug gsync-apply-budget-none-crash): resolve the
498
+ # account through session.account_email (env override THEN stored
499
+ # session) — the bare getenv read got None on this token-session host
500
+ # and crashed on email.encode() after the y confirmation.
501
+ from pushframe.session import account_email
502
+ budget = _build_write_budget(account_email(), ignore_budget=False)
492
503
 
493
504
  staged_by_id = staged.staged_by_id
494
505
  confirmed_paths: list[str] = []
@@ -6,6 +6,7 @@ Google cookie vault; sync/push require the source directory to EXIST (D-03:
6
6
  hard error) while empty stays the friendly "nothing to do" (not an error —
7
7
  looping scripts over sometimes-empty dirs must not break).
8
8
  """
9
+ import os
9
10
  from pathlib import Path
10
11
 
11
12
 
@@ -14,11 +15,32 @@ class PreflightError(Exception):
14
15
  action; str() renders both."""
15
16
 
16
17
 
18
+ def default_vault_path() -> Path:
19
+ """The vault path google-sync's preflight probes: PUSHFRAME_VAULT_PATH
20
+ when set (tests and power users pin it — a suite must never depend on
21
+ the real machine's vault), else the production path expanded per-user."""
22
+ from pushframe.google.vault import DEFAULT_VAULT_PATH as _default
23
+ override = os.getenv("PUSHFRAME_VAULT_PATH")
24
+ return Path(override).expanduser() if override else _default.expanduser()
25
+
26
+
17
27
  def require_google_vault(vault_path: Path | None = None) -> Path:
18
- """google-sync's vault check (PRF-01): present + non-empty JSON object."""
28
+ """google-sync's vault check (PRF-01): present + parseable JSON holding
29
+ at least one cookie record.
30
+
31
+ Two 2026-09-30 live-proven bugs fixed here (venus: link saves fine,
32
+ google-sync claims 'no vault' anyway):
33
+ - the default path was probed WITHOUT expanduser(): a literal `~` path
34
+ never exists, so this preflight always fired on machines whose vault
35
+ was perfectly healthy (phase-24 regression, masked on CI — CI has no
36
+ vault either — and on scheduled runs, whose --pair branch skips this
37
+ block entirely);
38
+ - the shape check demanded a JSON OBJECT while vault.save() writes a
39
+ JSON LIST of cookie records — a healthy vault would have failed the
40
+ 'unreadable' branch even with the right path.
41
+ """
19
42
  import json
20
- from pushframe.google.vault import DEFAULT_VAULT_PATH as _default
21
- path = vault_path or _default
43
+ path = Path(vault_path).expanduser() if vault_path else default_vault_path()
22
44
  if not path.exists():
23
45
  raise PreflightError(
24
46
  f'no Google session vault at {path} — google-sync needs a linked '
@@ -27,7 +49,9 @@ def require_google_vault(vault_path: Path | None = None) -> Path:
27
49
  f'window can open; the 0600 vault survives logouts).')
28
50
  try:
29
51
  data = json.loads(path.read_text())
30
- if not isinstance(data, dict) or not data:
52
+ healthy = (isinstance(data, list) and len(data) > 0) or \
53
+ (isinstance(data, dict) and len(data) > 0)
54
+ if not healthy:
31
55
  raise ValueError('empty or malformed vault')
32
56
  except (ValueError, OSError) as e:
33
57
  raise PreflightError(
@@ -72,6 +72,26 @@ def auto_refresh_stored():
72
72
  return config_store.load()
73
73
 
74
74
 
75
+ def account_email() -> str | None:
76
+ """THE one account-identity resolver (venus 2026-09-30, debug
77
+ gsync-apply-budget-none-crash): env override first, else the stored
78
+ session's email. Every consumer that only needs the ACCOUNT NAME (the
79
+ budget state-file key, `who` display strings) must resolve through
80
+ here — a bare os.getenv read is the bug that crashed google-sync
81
+ --apply: auth resolved the stored-session email and logged in fine,
82
+ then the budget layer read env-only, got None, and died on
83
+ email.encode() after the operator had already confirmed the plan.
84
+ Returns None only when the run has no identity at all — callers must
85
+ handle that named (skip/budgetless), never crash.
86
+ """
87
+ import os
88
+ env_email = os.getenv('PUSHFRAME_EMAIL') or os.getenv('AURA_EMAIL')
89
+ if env_email:
90
+ return env_email
91
+ stored = auto_refresh_stored()
92
+ return (stored or {}).get('email') or None
93
+
94
+
75
95
  def frames_read_with_refresh(aura, who, stored):
76
96
  """THE shared frames-read gate (status, inspect, sync/push, google-sync):
77
97
  the ONE-shot TTY token refresh plus the named, never-a-traceback failure
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "pushframe"
3
- version = "5.1.4"
3
+ version = "5.1.6"
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,89 @@
1
+ """The account-identity duality (venus 2026-09-30, debug
2
+ gsync-apply-budget-none-crash): auth resolves env-then-stored-session,
3
+ while the write-budget layer read env-ONLY — a token-session host (venus:
4
+ no env vars) crashed `google-sync --apply` on `email.encode()` AFTER the
5
+ operator confirmed the plan. The fix is ONE resolver
6
+ (`session.account_email()`) wired into every budget call site, and a
7
+ never-crash `_build_write_budget(None)`.
8
+
9
+ Regression shape (must NEVER reproduce): the crash happened after the
10
+ interactive confirmation, with a valid stored session and an empty
11
+ environment — the exact intended venus posture.
12
+ """
13
+ import hashlib
14
+ import json
15
+
16
+ import pytest
17
+
18
+ from pushframe.utils import settings
19
+
20
+
21
+ @pytest.fixture
22
+ def token_session_no_env(tmp_path, monkeypatch):
23
+ """The venus posture: stored session, zero env identity, state dir
24
+ pinned under tmp (the budget file must land there, not in $HOME)."""
25
+ monkeypatch.setattr(settings, "CONFIG_PATH", tmp_path / "config.json")
26
+ from pushframe import config_store
27
+ config_store.update(email="coredmp95@gmail.com", auth_token="tok-x",
28
+ user_id="u-1")
29
+ for var in ("PUSHFRAME_EMAIL", "AURA_EMAIL",
30
+ "PUSHFRAME_PASSWORD", "AURA_PASSWORD"):
31
+ monkeypatch.delenv(var, raising=False)
32
+ import pushframe.cli as cli_mod
33
+ monkeypatch.setattr(cli_mod, "AURA_STATE_DIR", tmp_path / "state")
34
+ return tmp_path
35
+
36
+
37
+ def test_account_email_resolves_stored_session(token_session_no_env):
38
+ from pushframe.session import account_email
39
+ assert account_email() == "coredmp95@gmail.com"
40
+
41
+
42
+ def test_account_email_env_overrides_stored(token_session_no_env, monkeypatch):
43
+ monkeypatch.setenv("PUSHFRAME_EMAIL", "override@example.invalid")
44
+ from pushframe.session import account_email
45
+ assert account_email() == "override@example.invalid"
46
+
47
+
48
+ def test_account_email_none_without_any_identity(token_session_no_env, monkeypatch):
49
+ from pushframe import config_store
50
+ import os
51
+ config_store.update(email=None) if hasattr(config_store, "update") else None
52
+ # remove the stored email by rewriting the config without it
53
+ cfg = json.loads((token_session_no_env / "config.json").read_text())
54
+ cfg.pop("email", None)
55
+ (token_session_no_env / "config.json").write_text(json.dumps(cfg))
56
+ from pushframe.session import account_email
57
+ assert account_email() is None
58
+
59
+
60
+ def test_build_write_budget_none_email_skips_never_crashes(token_session_no_env,
61
+ capsys):
62
+ """The crash line itself: email=None must SKIP pacing (named on stderr),
63
+ never raise — the operator has already confirmed the apply at that
64
+ point."""
65
+ from pushframe.cli import _build_write_budget
66
+ budget = _build_write_budget(None, ignore_budget=False)
67
+ assert budget is None
68
+ assert 'no account identity' in capsys.readouterr().err
69
+
70
+
71
+ def test_build_write_budget_email_keys_state_file(token_session_no_env):
72
+ from pushframe.cli import _build_write_budget
73
+ budget = _build_write_budget("coredmp95@gmail.com", ignore_budget=False)
74
+ assert budget is not None
75
+ expected = hashlib.sha1(b"coredmp95@gmail.com").hexdigest()[:12]
76
+ # load() does not create the file until the first save — the PATH is
77
+ # the keyed contract:
78
+ assert budget.path.parent == token_session_no_env / "state"
79
+ assert budget.path.name == f"budget-{expected}.json"
80
+
81
+
82
+ def test_gsync_apply_budget_line_survives_no_env(token_session_no_env):
83
+ """Direct probe of the gsync crash expression, post-fix: the resolver
84
+ feeds a real email from the stored session — no AttributeError path."""
85
+ from pushframe.session import account_email
86
+ from pushframe.cli import _build_write_budget
87
+ # Exactly what gsync.py's apply path now runs:
88
+ budget = _build_write_budget(account_email(), ignore_budget=False)
89
+ assert budget is not None # identity exists → pacing active, keyed
@@ -7,6 +7,16 @@ inventory entry + one parametrize case, nowhere else.
7
7
 
8
8
  The contract per case: handler returns non-zero, captured output contains
9
9
  the named marker AND the remedy substring, and NO 'Traceback' anywhere.
10
+
11
+ 2026-09-30 (venus live + CI): there is deliberately NO `gsync_no_creds`
12
+ case — the Google gate stands BEFORE the Aura gate by design, so a vault-less
13
+ gsync run fails Google-side named long before any Aura credential check,
14
+ and a form-valid fake vault pushes the run past the gate onto the REAL
15
+ Google network with a bogus cookie (the 5.1.2 network guard covers pushd,
16
+ not google.com). The Aura-side no-creds contract is proven by
17
+ `sync_dir_no_creds` / `inspect_no_creds` (same establish_session surface);
18
+ gsync's Google-side named shapes by `gsync_no_vault`, `pair_unknown`,
19
+ `all_zero_pairs`.
10
20
  """
11
21
  import json
12
22
 
@@ -32,10 +42,6 @@ CASES = [
32
42
  "inspect_no_creds", {},
33
43
  "not authenticated", "pushframe config",
34
44
  id="mode-8-inspect-no-creds"),
35
- pytest.param(
36
- "gsync_no_creds", {"album": "Cadre", "frame": "Fabrice"},
37
- "google-sync failed", "google-link",
38
- id="mode-8-gsync-no-creds"),
39
45
  pytest.param(
40
46
  "sync_dir_no_creds", {"dir": "."},
41
47
  "not authenticated", "pushframe config",
@@ -57,6 +63,10 @@ CASES = [
57
63
  "pair_duplicate", {"name": "dup", "album": "A", "frame": "F"},
58
64
  "already exists", "remove it first",
59
65
  id="mode-16-pair-duplicate"),
66
+ pytest.param(
67
+ "reconcile_no_identity", {"frame": "ghost-frame"},
68
+ "not authenticated", "pushframe config",
69
+ id="mode-17-reconcile-no-identity"),
60
70
  ]
61
71
 
62
72
 
@@ -68,6 +78,10 @@ def no_creds(tmp_path, monkeypatch):
68
78
  for var in ("PUSHFRAME_EMAIL", "AURA_EMAIL",
69
79
  "PUSHFRAME_PASSWORD", "AURA_PASSWORD"):
70
80
  monkeypatch.delenv(var, raising=False)
81
+ # The vault preflight is pinned AWAY from the real machine's vault: after
82
+ # the 2026-09-30 path fix this suite would otherwise depend on whether
83
+ # the dev box happens to hold a healthy vault (venus live-proven bug).
84
+ monkeypatch.setenv("PUSHFRAME_VAULT_PATH", str(tmp_path / "no-vault.json"))
71
85
 
72
86
 
73
87
  # --- callables ---------------------------------------------------------------
@@ -104,17 +118,6 @@ def inspect_no_creds(capsys, no_creds):
104
118
  return run_inspect("whatever")
105
119
 
106
120
 
107
- def gsync_no_creds(capsys, no_creds, album, frame):
108
- # vault exists (mode 5 covered separately) — the Aura session then fails
109
- # named because no credentials of any kind are available.
110
- vault = settings.CONFIG_PATH.parent / "google-cookies.json"
111
- vault.parent.mkdir(parents=True, exist_ok=True)
112
- vault.write_text(json.dumps({"SID": "x"})) # minimal-looking vault
113
- from pushframe.gsync import run_google_sync
114
- return run_google_sync(album, frame, apply=False, yes=True,
115
- is_interactive=False)
116
-
117
-
118
121
  class _MuteAura:
119
122
  """Aura stub good enough to reach the dir preflight (which fails first)."""
120
123
 
@@ -145,6 +148,18 @@ def schedule_no_systemd(capsys, no_creds):
145
148
  mp.undo()
146
149
 
147
150
 
151
+ def reconcile_no_identity(capsys, no_creds, frame):
152
+ """venus 2026-09-30 (gsync-apply-budget-none-crash): reconcile --remove
153
+ built the write budget from an env-ONLY email read — on a token-session
154
+ host it crashed on email.encode() AFTER the confirmation. The identity
155
+ resolver now falls back to the stored session, and a fully identity-less
156
+ run must fail NAMED here (no config), never traceback."""
157
+ from pushframe.cli import run_reconcile
158
+ # no injected aura: establish_session must fail NAMED (empty config,
159
+ # no env) before any network — that IS the identity-less path.
160
+ return run_reconcile(frame, remove=True, yes=True)
161
+
162
+
148
163
  def pair_duplicate(capsys, no_creds, name, album, frame):
149
164
  from pushframe import pairs as pairs_mod
150
165
  pairs_mod.pair_add(name, album=album, frame=frame)
@@ -0,0 +1,78 @@
1
+ """google-sync's vault preflight (PRF-01) — the venus live-proven bugs.
2
+
3
+ 2026-09-30, venus: `pushframe google-link` saved a perfectly healthy vault
4
+ (28 cookie records) yet `pushframe google-sync` answered "no Google session
5
+ vault". Two stacked bugs in require_google_vault:
6
+
7
+ 1. the default path was probed WITHOUT expanduser() — `Path("~/.config/…")
8
+ .exists()` compares a literal `~` directory on disk and never matches,
9
+ so the preflight fired on every healthy machine (masked on CI: no vault
10
+ there either; masked on scheduled runs: the --pair branch skips the
11
+ block — exactly the path the nightly uses);
12
+ 2. the shape check demanded a JSON OBJECT while vault.save() writes a JSON
13
+ LIST of cookie records — with the path fixed, a healthy vault would have
14
+ landed in the "unreadable — re-link" branch anyway.
15
+
16
+ The tell was in the transcript: google-link prints "existing Google session
17
+ found" only when from_vault() (a vault READ) succeeds — the vault was
18
+ there; the preflight was the liar.
19
+ """
20
+ import json
21
+
22
+ import pytest
23
+
24
+ from pushframe.preflight import PreflightError, require_google_vault
25
+
26
+
27
+ @pytest.fixture
28
+ def vault_root(tmp_path, monkeypatch):
29
+ """Pinned vault env: default path under tmp, isolated from the machine."""
30
+ monkeypatch.setenv("PUSHFRAME_VAULT_PATH", str(tmp_path / "v.json"))
31
+ return tmp_path / "v.json"
32
+
33
+
34
+ def _healthy_vault(path):
35
+ path.write_text(json.dumps([
36
+ {"name": "SID", "value": "x", "domain": ".google.com"},
37
+ {"name": "SAPISID", "value": "y", "domain": ".google.com"},
38
+ ]))
39
+
40
+
41
+ def test_healthy_list_vault_passes(vault_root):
42
+ """The exact venus shape: vault.save()'s JSON list of records."""
43
+ _healthy_vault(vault_root)
44
+ assert require_google_vault() == vault_root.expanduser()
45
+
46
+
47
+ def test_missing_vault_fails_named_with_remedy(vault_root):
48
+ with pytest.raises(PreflightError) as e:
49
+ require_google_vault()
50
+ assert 'no Google session vault' in str(e.value)
51
+ assert 'google-link' in str(e.value)
52
+
53
+
54
+ def test_literal_tilde_default_path_never_probed(monkeypatch):
55
+ """The regression itself: without PUSHFRAME_VAULT_PATH the default must
56
+ be EXPANDED (probe the real per-user path, not a literal `~` file).
57
+ Pure assertions — this test must never write near the machine's real
58
+ vault (the first draft did a write+unlink there; unacceptable)."""
59
+ monkeypatch.delenv("PUSHFRAME_VAULT_PATH", raising=False)
60
+ from pushframe.preflight import default_vault_path
61
+ from pushframe.google.vault import DEFAULT_VAULT_PATH
62
+ real = default_vault_path()
63
+ assert real == DEFAULT_VAULT_PATH.expanduser()
64
+ assert '~' not in str(real)
65
+
66
+
67
+ def test_dict_vault_still_accepted_backward_compat(vault_root):
68
+ vault_root.write_text(json.dumps({"SID": "x"}))
69
+ assert require_google_vault() == vault_root.expanduser()
70
+
71
+
72
+ def test_empty_shapes_fail_named(vault_root):
73
+ for payload in ("[]", "{}", "not json at all"):
74
+ vault_root.write_text(payload)
75
+ with pytest.raises(PreflightError) as e:
76
+ require_google_vault()
77
+ assert 'unreadable' in str(e.value)
78
+ assert 'google-link' in str(e.value)
@@ -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.4"
22
+ assert __version__ == "5.1.6"
23
23
 
24
24
 
25
25
  def test_version_matches_pyproject_single_source():
@@ -432,7 +432,7 @@ wheels = [
432
432
 
433
433
  [[package]]
434
434
  name = "pushframe"
435
- version = "5.1.4"
435
+ version = "5.1.6"
436
436
  source = { editable = "." }
437
437
  dependencies = [
438
438
  { name = "boto3" },
@@ -663,11 +663,11 @@ wheels = [
663
663
 
664
664
  [[package]]
665
665
  name = "urllib3"
666
- version = "2.7.0"
666
+ version = "2.8.0"
667
667
  source = { registry = "https://pypi.org/simple" }
668
- sdist = { url = "https://files.pythonhosted.org/packages/53/0c/06f8b233b8fd13b9e5ee11424ef85419ba0d8ba0b3138bf360be2ff56953/urllib3-2.7.0.tar.gz", hash = "sha256:231e0ec3b63ceb14667c67be60f2f2c40a518cb38b03af60abc813da26505f4c", size = 433602, upload-time = "2026-05-07T16:13:18.596Z" }
668
+ sdist = { url = "https://files.pythonhosted.org/packages/e3/05/b17359e1cefb4f909b5e40b1b90a496d987258916dbbf88e842c729f510e/urllib3-2.8.0.tar.gz", hash = "sha256:63bf2ead4c879426ebf22ef2a781eeb4aa3b4ae798a0435506f8687fd5bb9b63", size = 458972, upload-time = "2026-09-15T19:29:36.253Z" }
669
669
  wheels = [
670
- { url = "https://files.pythonhosted.org/packages/7f/3e/5db95bcf282c52709639744ca2a8b149baccf648e39c8cc87553df9eae0c/urllib3-2.7.0-py3-none-any.whl", hash = "sha256:9fb4c81ebbb1ce9531cce37674bbc6f1360472bc18ca9a553ede278ef7276897", size = 131087, upload-time = "2026-05-07T16:13:17.151Z" },
670
+ { url = "https://files.pythonhosted.org/packages/92/9d/c4e665119135114480843e7ab388fa94d8480650450e6f8e26b70d323a4c/urllib3-2.8.0-py3-none-any.whl", hash = "sha256:0cf3cae568d36aa9576b28dfb35f11328f1cb974ca7647d9475ebb86c75ac6e3", size = 135717, upload-time = "2026-09-15T19:29:34.577Z" },
671
671
  ]
672
672
 
673
673
  [[package]]