pushframe 5.0.6__tar.gz → 5.0.7__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 (400) hide show
  1. {pushframe-5.0.6 → pushframe-5.0.7}/.planning/STATE.md +5 -5
  2. pushframe-5.0.7/.planning/phases/24-token-first-sessions-preflight-sweep-sec-01-03-prf-01-02/24-01-PLAN.md +77 -0
  3. pushframe-5.0.7/.planning/phases/24-token-first-sessions-preflight-sweep-sec-01-03-prf-01-02/24-01-SUMMARY.md +56 -0
  4. pushframe-5.0.7/.planning/phases/24-token-first-sessions-preflight-sweep-sec-01-03-prf-01-02/24-CONTEXT.md +74 -0
  5. pushframe-5.0.7/.planning/phases/24-token-first-sessions-preflight-sweep-sec-01-03-prf-01-02/24-RESEARCH.md +83 -0
  6. pushframe-5.0.7/.planning/phases/24-token-first-sessions-preflight-sweep-sec-01-03-prf-01-02/24-VERIFICATION.md +50 -0
  7. {pushframe-5.0.6 → pushframe-5.0.7}/CHANGELOG.md +50 -0
  8. {pushframe-5.0.6 → pushframe-5.0.7}/PKG-INFO +1 -1
  9. {pushframe-5.0.6 → pushframe-5.0.7}/docs/CLI.md +36 -0
  10. {pushframe-5.0.6 → pushframe-5.0.7}/pushframe/__init__.py +1 -1
  11. {pushframe-5.0.6 → pushframe-5.0.7}/pushframe/cli.py +53 -10
  12. {pushframe-5.0.6 → pushframe-5.0.7}/pushframe/config_store.py +8 -2
  13. {pushframe-5.0.6 → pushframe-5.0.7}/pushframe/gsync.py +43 -7
  14. pushframe-5.0.7/pushframe/preflight.py +53 -0
  15. pushframe-5.0.7/pushframe/session.py +144 -0
  16. {pushframe-5.0.6 → pushframe-5.0.7}/pushframe/sync.py +74 -5
  17. {pushframe-5.0.6 → pushframe-5.0.7}/pyproject.toml +1 -1
  18. {pushframe-5.0.6 → pushframe-5.0.7}/tests/test_cli_apply.py +8 -3
  19. {pushframe-5.0.6 → pushframe-5.0.7}/tests/test_cli_inspect.py +13 -9
  20. {pushframe-5.0.6 → pushframe-5.0.7}/tests/test_cli_reconcile.py +10 -8
  21. {pushframe-5.0.6 → pushframe-5.0.7}/tests/test_cli_sync.py +11 -8
  22. {pushframe-5.0.6 → pushframe-5.0.7}/tests/test_execute_plan.py +60 -0
  23. {pushframe-5.0.6 → pushframe-5.0.7}/tests/test_gsync_execute.py +63 -0
  24. pushframe-5.0.7/tests/test_logout.py +66 -0
  25. pushframe-5.0.7/tests/test_preflight_sweep.py +116 -0
  26. pushframe-5.0.7/tests/test_session.py +173 -0
  27. {pushframe-5.0.6 → pushframe-5.0.7}/tests/test_version.py +1 -1
  28. {pushframe-5.0.6 → pushframe-5.0.7}/uv.lock +1 -1
  29. {pushframe-5.0.6 → pushframe-5.0.7}/.env.sample +0 -0
  30. {pushframe-5.0.6 → pushframe-5.0.7}/.freebuff/project-id +0 -0
  31. {pushframe-5.0.6 → pushframe-5.0.7}/.github/workflows/release.yml +0 -0
  32. {pushframe-5.0.6 → pushframe-5.0.7}/.github/workflows/tests.yml +0 -0
  33. {pushframe-5.0.6 → pushframe-5.0.7}/.gitignore +0 -0
  34. {pushframe-5.0.6 → pushframe-5.0.7}/.gsd/dispatch-isolation-sentinel.json +0 -0
  35. {pushframe-5.0.6 → pushframe-5.0.7}/.planning/MILESTONES.md +0 -0
  36. {pushframe-5.0.6 → pushframe-5.0.7}/.planning/PROJECT.md +0 -0
  37. {pushframe-5.0.6 → pushframe-5.0.7}/.planning/REQUIREMENTS.md +0 -0
  38. {pushframe-5.0.6 → pushframe-5.0.7}/.planning/RETROSPECTIVE.md +0 -0
  39. {pushframe-5.0.6 → pushframe-5.0.7}/.planning/ROADMAP.md +0 -0
  40. {pushframe-5.0.6 → pushframe-5.0.7}/.planning/WINDOWS.md +0 -0
  41. {pushframe-5.0.6 → pushframe-5.0.7}/.planning/codebase/ARCHITECTURE.md +0 -0
  42. {pushframe-5.0.6 → pushframe-5.0.7}/.planning/codebase/CONCERNS.md +0 -0
  43. {pushframe-5.0.6 → pushframe-5.0.7}/.planning/codebase/CONVENTIONS.md +0 -0
  44. {pushframe-5.0.6 → pushframe-5.0.7}/.planning/codebase/INTEGRATIONS.md +0 -0
  45. {pushframe-5.0.6 → pushframe-5.0.7}/.planning/codebase/STACK.md +0 -0
  46. {pushframe-5.0.6 → pushframe-5.0.7}/.planning/codebase/STRUCTURE.md +0 -0
  47. {pushframe-5.0.6 → pushframe-5.0.7}/.planning/codebase/TESTING.md +0 -0
  48. {pushframe-5.0.6 → pushframe-5.0.7}/.planning/config.json +0 -0
  49. {pushframe-5.0.6 → pushframe-5.0.7}/.planning/debug/knowledge-base.md +0 -0
  50. {pushframe-5.0.6 → pushframe-5.0.7}/.planning/debug/login-475-null-creds.md +0 -0
  51. {pushframe-5.0.6 → pushframe-5.0.7}/.planning/debug/resolved/cli-status-verbose-logging.md +0 -0
  52. {pushframe-5.0.6 → pushframe-5.0.7}/.planning/debug/resolved/inspect-asset-null-fields.md +0 -0
  53. {pushframe-5.0.6 → pushframe-5.0.7}/.planning/debug/resolved/select-asset-401-unauthorized.md +0 -0
  54. {pushframe-5.0.6 → pushframe-5.0.7}/.planning/milestones/v1.0-REQUIREMENTS.md +0 -0
  55. {pushframe-5.0.6 → pushframe-5.0.7}/.planning/milestones/v1.0-ROADMAP.md +0 -0
  56. {pushframe-5.0.6 → pushframe-5.0.7}/.planning/milestones/v1.0-phases/01-toolchain-revival/01-01-PLAN.md +0 -0
  57. {pushframe-5.0.6 → pushframe-5.0.7}/.planning/milestones/v1.0-phases/01-toolchain-revival/01-01-SUMMARY.md +0 -0
  58. {pushframe-5.0.6 → pushframe-5.0.7}/.planning/milestones/v1.0-phases/01-toolchain-revival/01-02-PLAN.md +0 -0
  59. {pushframe-5.0.6 → pushframe-5.0.7}/.planning/milestones/v1.0-phases/01-toolchain-revival/01-02-SUMMARY.md +0 -0
  60. {pushframe-5.0.6 → pushframe-5.0.7}/.planning/milestones/v1.0-phases/01-toolchain-revival/01-CONTEXT.md +0 -0
  61. {pushframe-5.0.6 → pushframe-5.0.7}/.planning/milestones/v1.0-phases/01-toolchain-revival/01-DISCUSSION-LOG.md +0 -0
  62. {pushframe-5.0.6 → pushframe-5.0.7}/.planning/milestones/v1.0-phases/01-toolchain-revival/01-PATTERNS.md +0 -0
  63. {pushframe-5.0.6 → pushframe-5.0.7}/.planning/milestones/v1.0-phases/01-toolchain-revival/01-RESEARCH.md +0 -0
  64. {pushframe-5.0.6 → pushframe-5.0.7}/.planning/milestones/v1.0-phases/01-toolchain-revival/01-REVIEW.md +0 -0
  65. {pushframe-5.0.6 → pushframe-5.0.7}/.planning/milestones/v1.0-phases/01-toolchain-revival/01-VERIFICATION.md +0 -0
  66. {pushframe-5.0.6 → pushframe-5.0.7}/.planning/milestones/v1.0-phases/02-live-read-path-verification/02-01-PLAN.md +0 -0
  67. {pushframe-5.0.6 → pushframe-5.0.7}/.planning/milestones/v1.0-phases/02-live-read-path-verification/02-01-SUMMARY.md +0 -0
  68. {pushframe-5.0.6 → pushframe-5.0.7}/.planning/milestones/v1.0-phases/02-live-read-path-verification/02-02-PLAN.md +0 -0
  69. {pushframe-5.0.6 → pushframe-5.0.7}/.planning/milestones/v1.0-phases/02-live-read-path-verification/02-02-SUMMARY.md +0 -0
  70. {pushframe-5.0.6 → pushframe-5.0.7}/.planning/milestones/v1.0-phases/02-live-read-path-verification/02-CONTEXT.md +0 -0
  71. {pushframe-5.0.6 → pushframe-5.0.7}/.planning/milestones/v1.0-phases/02-live-read-path-verification/02-DISCUSSION-LOG.md +0 -0
  72. {pushframe-5.0.6 → pushframe-5.0.7}/.planning/milestones/v1.0-phases/02-live-read-path-verification/02-LIVE-EVIDENCE.md +0 -0
  73. {pushframe-5.0.6 → pushframe-5.0.7}/.planning/milestones/v1.0-phases/02-live-read-path-verification/02-PATTERNS.md +0 -0
  74. {pushframe-5.0.6 → pushframe-5.0.7}/.planning/milestones/v1.0-phases/02-live-read-path-verification/02-RESEARCH.md +0 -0
  75. {pushframe-5.0.6 → pushframe-5.0.7}/.planning/milestones/v1.0-phases/02-live-read-path-verification/02-REVIEW.md +0 -0
  76. {pushframe-5.0.6 → pushframe-5.0.7}/.planning/milestones/v1.0-phases/02-live-read-path-verification/02-VERIFICATION.md +0 -0
  77. {pushframe-5.0.6 → pushframe-5.0.7}/.planning/milestones/v1.0-phases/03-run-docs-verification-report/03-01-PLAN.md +0 -0
  78. {pushframe-5.0.6 → pushframe-5.0.7}/.planning/milestones/v1.0-phases/03-run-docs-verification-report/03-01-SUMMARY.md +0 -0
  79. {pushframe-5.0.6 → pushframe-5.0.7}/.planning/milestones/v1.0-phases/03-run-docs-verification-report/03-CONTEXT.md +0 -0
  80. {pushframe-5.0.6 → pushframe-5.0.7}/.planning/milestones/v1.0-phases/03-run-docs-verification-report/03-DISCUSSION-LOG.md +0 -0
  81. {pushframe-5.0.6 → pushframe-5.0.7}/.planning/milestones/v1.0-phases/03-run-docs-verification-report/03-PATTERNS.md +0 -0
  82. {pushframe-5.0.6 → pushframe-5.0.7}/.planning/milestones/v1.0-phases/03-run-docs-verification-report/03-RESEARCH.md +0 -0
  83. {pushframe-5.0.6 → pushframe-5.0.7}/.planning/milestones/v1.0-phases/03-run-docs-verification-report/03-REVIEW.md +0 -0
  84. {pushframe-5.0.6 → pushframe-5.0.7}/.planning/milestones/v1.0-phases/03-run-docs-verification-report/03-VERIFICATION.md +0 -0
  85. {pushframe-5.0.6 → pushframe-5.0.7}/.planning/milestones/v1.1-ROADMAP.md +0 -0
  86. {pushframe-5.0.6 → pushframe-5.0.7}/.planning/milestones/v1.1-phases/04-client-transport-seam-for-offline-testability/.gitkeep +0 -0
  87. {pushframe-5.0.6 → pushframe-5.0.7}/.planning/milestones/v1.1-phases/04-client-transport-seam-for-offline-testability/04-01-PLAN.md +0 -0
  88. {pushframe-5.0.6 → pushframe-5.0.7}/.planning/milestones/v1.1-phases/04-client-transport-seam-for-offline-testability/04-01-SUMMARY.md +0 -0
  89. {pushframe-5.0.6 → pushframe-5.0.7}/.planning/milestones/v1.1-phases/04-client-transport-seam-for-offline-testability/04-02-PLAN.md +0 -0
  90. {pushframe-5.0.6 → pushframe-5.0.7}/.planning/milestones/v1.1-phases/04-client-transport-seam-for-offline-testability/04-02-SUMMARY.md +0 -0
  91. {pushframe-5.0.6 → pushframe-5.0.7}/.planning/milestones/v1.1-phases/04-client-transport-seam-for-offline-testability/04-03-PLAN.md +0 -0
  92. {pushframe-5.0.6 → pushframe-5.0.7}/.planning/milestones/v1.1-phases/04-client-transport-seam-for-offline-testability/04-03-SUMMARY.md +0 -0
  93. {pushframe-5.0.6 → pushframe-5.0.7}/.planning/milestones/v1.1-phases/04-client-transport-seam-for-offline-testability/04-PATTERNS.md +0 -0
  94. {pushframe-5.0.6 → pushframe-5.0.7}/.planning/milestones/v1.1-phases/04-client-transport-seam-for-offline-testability/04-RESEARCH.md +0 -0
  95. {pushframe-5.0.6 → pushframe-5.0.7}/.planning/milestones/v1.1-phases/04-client-transport-seam-for-offline-testability/04-REVIEW.md +0 -0
  96. {pushframe-5.0.6 → pushframe-5.0.7}/.planning/milestones/v1.1-phases/04-client-transport-seam-for-offline-testability/04-SECURITY.md +0 -0
  97. {pushframe-5.0.6 → pushframe-5.0.7}/.planning/milestones/v1.1-phases/04-client-transport-seam-for-offline-testability/04-UAT.md +0 -0
  98. {pushframe-5.0.6 → pushframe-5.0.7}/.planning/milestones/v1.1-phases/04-client-transport-seam-for-offline-testability/04-VERIFICATION.md +0 -0
  99. {pushframe-5.0.6 → pushframe-5.0.7}/.planning/milestones/v2.0-REQUIREMENTS.md +0 -0
  100. {pushframe-5.0.6 → pushframe-5.0.7}/.planning/milestones/v2.0-ROADMAP.md +0 -0
  101. {pushframe-5.0.6 → pushframe-5.0.7}/.planning/milestones/v2.0-phases/05-cli-skeleton-status/05-01-PLAN.md +0 -0
  102. {pushframe-5.0.6 → pushframe-5.0.7}/.planning/milestones/v2.0-phases/05-cli-skeleton-status/05-01-SUMMARY.md +0 -0
  103. {pushframe-5.0.6 → pushframe-5.0.7}/.planning/milestones/v2.0-phases/05-cli-skeleton-status/05-02-PLAN.md +0 -0
  104. {pushframe-5.0.6 → pushframe-5.0.7}/.planning/milestones/v2.0-phases/05-cli-skeleton-status/05-02-SUMMARY.md +0 -0
  105. {pushframe-5.0.6 → pushframe-5.0.7}/.planning/milestones/v2.0-phases/05-cli-skeleton-status/05-CONTEXT.md +0 -0
  106. {pushframe-5.0.6 → pushframe-5.0.7}/.planning/milestones/v2.0-phases/05-cli-skeleton-status/05-DISCUSSION-LOG.md +0 -0
  107. {pushframe-5.0.6 → pushframe-5.0.7}/.planning/milestones/v2.0-phases/05-cli-skeleton-status/05-PATTERNS.md +0 -0
  108. {pushframe-5.0.6 → pushframe-5.0.7}/.planning/milestones/v2.0-phases/05-cli-skeleton-status/05-REVIEW.md +0 -0
  109. {pushframe-5.0.6 → pushframe-5.0.7}/.planning/milestones/v2.0-phases/05-cli-skeleton-status/05-SECURITY.md +0 -0
  110. {pushframe-5.0.6 → pushframe-5.0.7}/.planning/milestones/v2.0-phases/05-cli-skeleton-status/05-UAT.md +0 -0
  111. {pushframe-5.0.6 → pushframe-5.0.7}/.planning/milestones/v2.0-phases/05-cli-skeleton-status/05-VERIFICATION.md +0 -0
  112. {pushframe-5.0.6 → pushframe-5.0.7}/.planning/milestones/v2.0-phases/06-inspect-frame-resolution/06-01-PLAN.md +0 -0
  113. {pushframe-5.0.6 → pushframe-5.0.7}/.planning/milestones/v2.0-phases/06-inspect-frame-resolution/06-01-SUMMARY.md +0 -0
  114. {pushframe-5.0.6 → pushframe-5.0.7}/.planning/milestones/v2.0-phases/06-inspect-frame-resolution/06-02-PLAN.md +0 -0
  115. {pushframe-5.0.6 → pushframe-5.0.7}/.planning/milestones/v2.0-phases/06-inspect-frame-resolution/06-02-SUMMARY.md +0 -0
  116. {pushframe-5.0.6 → pushframe-5.0.7}/.planning/milestones/v2.0-phases/06-inspect-frame-resolution/06-CONTEXT.md +0 -0
  117. {pushframe-5.0.6 → pushframe-5.0.7}/.planning/milestones/v2.0-phases/06-inspect-frame-resolution/06-DISCUSSION-LOG.md +0 -0
  118. {pushframe-5.0.6 → pushframe-5.0.7}/.planning/milestones/v2.0-phases/06-inspect-frame-resolution/06-PATTERNS.md +0 -0
  119. {pushframe-5.0.6 → pushframe-5.0.7}/.planning/milestones/v2.0-phases/06-inspect-frame-resolution/06-RESEARCH.md +0 -0
  120. {pushframe-5.0.6 → pushframe-5.0.7}/.planning/milestones/v2.0-phases/06-inspect-frame-resolution/06-REVIEW.md +0 -0
  121. {pushframe-5.0.6 → pushframe-5.0.7}/.planning/milestones/v2.0-phases/06-inspect-frame-resolution/06-SECURITY.md +0 -0
  122. {pushframe-5.0.6 → pushframe-5.0.7}/.planning/milestones/v2.0-phases/06-inspect-frame-resolution/06-VERIFICATION.md +0 -0
  123. {pushframe-5.0.6 → pushframe-5.0.7}/.planning/milestones/v2.0-phases/07-sync-diffing-engine-dry-run-only/07-01-PLAN.md +0 -0
  124. {pushframe-5.0.6 → pushframe-5.0.7}/.planning/milestones/v2.0-phases/07-sync-diffing-engine-dry-run-only/07-01-SUMMARY.md +0 -0
  125. {pushframe-5.0.6 → pushframe-5.0.7}/.planning/milestones/v2.0-phases/07-sync-diffing-engine-dry-run-only/07-02-PLAN.md +0 -0
  126. {pushframe-5.0.6 → pushframe-5.0.7}/.planning/milestones/v2.0-phases/07-sync-diffing-engine-dry-run-only/07-02-SUMMARY.md +0 -0
  127. {pushframe-5.0.6 → pushframe-5.0.7}/.planning/milestones/v2.0-phases/07-sync-diffing-engine-dry-run-only/07-03-PLAN.md +0 -0
  128. {pushframe-5.0.6 → pushframe-5.0.7}/.planning/milestones/v2.0-phases/07-sync-diffing-engine-dry-run-only/07-03-SUMMARY.md +0 -0
  129. {pushframe-5.0.6 → pushframe-5.0.7}/.planning/milestones/v2.0-phases/07-sync-diffing-engine-dry-run-only/07-CONTEXT.md +0 -0
  130. {pushframe-5.0.6 → pushframe-5.0.7}/.planning/milestones/v2.0-phases/07-sync-diffing-engine-dry-run-only/07-DISCUSSION-LOG.md +0 -0
  131. {pushframe-5.0.6 → pushframe-5.0.7}/.planning/milestones/v2.0-phases/07-sync-diffing-engine-dry-run-only/07-PATTERNS.md +0 -0
  132. {pushframe-5.0.6 → pushframe-5.0.7}/.planning/milestones/v2.0-phases/07-sync-diffing-engine-dry-run-only/07-REVIEW-FIX.md +0 -0
  133. {pushframe-5.0.6 → pushframe-5.0.7}/.planning/milestones/v2.0-phases/07-sync-diffing-engine-dry-run-only/07-REVIEW.md +0 -0
  134. {pushframe-5.0.6 → pushframe-5.0.7}/.planning/milestones/v2.0-phases/07-sync-diffing-engine-dry-run-only/07-SECURITY.md +0 -0
  135. {pushframe-5.0.6 → pushframe-5.0.7}/.planning/milestones/v2.0-phases/07-sync-diffing-engine-dry-run-only/07-VERIFICATION.md +0 -0
  136. {pushframe-5.0.6 → pushframe-5.0.7}/.planning/milestones/v2.0-phases/08-destructive-execution-upload-delete-verification/08-01-PLAN.md +0 -0
  137. {pushframe-5.0.6 → pushframe-5.0.7}/.planning/milestones/v2.0-phases/08-destructive-execution-upload-delete-verification/08-01-SUMMARY.md +0 -0
  138. {pushframe-5.0.6 → pushframe-5.0.7}/.planning/milestones/v2.0-phases/08-destructive-execution-upload-delete-verification/08-02-PLAN.md +0 -0
  139. {pushframe-5.0.6 → pushframe-5.0.7}/.planning/milestones/v2.0-phases/08-destructive-execution-upload-delete-verification/08-02-SUMMARY.md +0 -0
  140. {pushframe-5.0.6 → pushframe-5.0.7}/.planning/milestones/v2.0-phases/08-destructive-execution-upload-delete-verification/08-03-PLAN.md +0 -0
  141. {pushframe-5.0.6 → pushframe-5.0.7}/.planning/milestones/v2.0-phases/08-destructive-execution-upload-delete-verification/08-03-SUMMARY.md +0 -0
  142. {pushframe-5.0.6 → pushframe-5.0.7}/.planning/milestones/v2.0-phases/08-destructive-execution-upload-delete-verification/08-04-PLAN.md +0 -0
  143. {pushframe-5.0.6 → pushframe-5.0.7}/.planning/milestones/v2.0-phases/08-destructive-execution-upload-delete-verification/08-04-SUMMARY.md +0 -0
  144. {pushframe-5.0.6 → pushframe-5.0.7}/.planning/milestones/v2.0-phases/08-destructive-execution-upload-delete-verification/08-CONTEXT.md +0 -0
  145. {pushframe-5.0.6 → pushframe-5.0.7}/.planning/milestones/v2.0-phases/08-destructive-execution-upload-delete-verification/08-DISCUSSION-LOG.md +0 -0
  146. {pushframe-5.0.6 → pushframe-5.0.7}/.planning/milestones/v2.0-phases/08-destructive-execution-upload-delete-verification/08-LIVE-FINDINGS.md +0 -0
  147. {pushframe-5.0.6 → pushframe-5.0.7}/.planning/milestones/v2.0-phases/08-destructive-execution-upload-delete-verification/08-PATTERNS.md +0 -0
  148. {pushframe-5.0.6 → pushframe-5.0.7}/.planning/milestones/v2.0-phases/08-destructive-execution-upload-delete-verification/08-RESEARCH.md +0 -0
  149. {pushframe-5.0.6 → pushframe-5.0.7}/.planning/milestones/v2.0-phases/08-destructive-execution-upload-delete-verification/08-REVIEW-FIX.md +0 -0
  150. {pushframe-5.0.6 → pushframe-5.0.7}/.planning/milestones/v2.0-phases/08-destructive-execution-upload-delete-verification/08-REVIEW.md +0 -0
  151. {pushframe-5.0.6 → pushframe-5.0.7}/.planning/milestones/v2.0-phases/08-destructive-execution-upload-delete-verification/08-VERIFICATION.md +0 -0
  152. {pushframe-5.0.6 → pushframe-5.0.7}/.planning/milestones/v2.0-phases/09-proactive-write-rate-limiter-geo-guard/.gitkeep +0 -0
  153. {pushframe-5.0.6 → pushframe-5.0.7}/.planning/milestones/v2.0-phases/09-proactive-write-rate-limiter-geo-guard/09-01-PLAN.md +0 -0
  154. {pushframe-5.0.6 → pushframe-5.0.7}/.planning/milestones/v2.0-phases/09-proactive-write-rate-limiter-geo-guard/09-01-SUMMARY.md +0 -0
  155. {pushframe-5.0.6 → pushframe-5.0.7}/.planning/milestones/v2.0-phases/09-proactive-write-rate-limiter-geo-guard/09-02-PLAN.md +0 -0
  156. {pushframe-5.0.6 → pushframe-5.0.7}/.planning/milestones/v2.0-phases/09-proactive-write-rate-limiter-geo-guard/09-02-SUMMARY.md +0 -0
  157. {pushframe-5.0.6 → pushframe-5.0.7}/.planning/milestones/v2.0-phases/09-proactive-write-rate-limiter-geo-guard/09-CONTEXT.md +0 -0
  158. {pushframe-5.0.6 → pushframe-5.0.7}/.planning/milestones/v2.0-phases/09-proactive-write-rate-limiter-geo-guard/09-RESEARCH.md +0 -0
  159. {pushframe-5.0.6 → pushframe-5.0.7}/.planning/milestones/v2.0-phases/09-proactive-write-rate-limiter-geo-guard/09-REVIEW-FIX.md +0 -0
  160. {pushframe-5.0.6 → pushframe-5.0.7}/.planning/milestones/v2.0-phases/09-proactive-write-rate-limiter-geo-guard/09-REVIEW.md +0 -0
  161. {pushframe-5.0.6 → pushframe-5.0.7}/.planning/milestones/v2.0-phases/09-proactive-write-rate-limiter-geo-guard/09-VERIFICATION.md +0 -0
  162. {pushframe-5.0.6 → pushframe-5.0.7}/.planning/milestones/v2.0-phases/10-hide-instead-of-delete-sync-mode/.gitkeep +0 -0
  163. {pushframe-5.0.6 → pushframe-5.0.7}/.planning/milestones/v2.0-phases/10-hide-instead-of-delete-sync-mode/10-01-PLAN.md +0 -0
  164. {pushframe-5.0.6 → pushframe-5.0.7}/.planning/milestones/v2.0-phases/10-hide-instead-of-delete-sync-mode/10-01-SUMMARY.md +0 -0
  165. {pushframe-5.0.6 → pushframe-5.0.7}/.planning/milestones/v2.0-phases/10-hide-instead-of-delete-sync-mode/10-02-PLAN.md +0 -0
  166. {pushframe-5.0.6 → pushframe-5.0.7}/.planning/milestones/v2.0-phases/10-hide-instead-of-delete-sync-mode/10-02-SUMMARY.md +0 -0
  167. {pushframe-5.0.6 → pushframe-5.0.7}/.planning/milestones/v2.0-phases/10-hide-instead-of-delete-sync-mode/10-03-PLAN.md +0 -0
  168. {pushframe-5.0.6 → pushframe-5.0.7}/.planning/milestones/v2.0-phases/10-hide-instead-of-delete-sync-mode/10-03-SUMMARY.md +0 -0
  169. {pushframe-5.0.6 → pushframe-5.0.7}/.planning/milestones/v2.0-phases/10-hide-instead-of-delete-sync-mode/10-04-PLAN.md +0 -0
  170. {pushframe-5.0.6 → pushframe-5.0.7}/.planning/milestones/v2.0-phases/10-hide-instead-of-delete-sync-mode/10-04-SUMMARY.md +0 -0
  171. {pushframe-5.0.6 → pushframe-5.0.7}/.planning/milestones/v2.0-phases/10-hide-instead-of-delete-sync-mode/10-CONTEXT.md +0 -0
  172. {pushframe-5.0.6 → pushframe-5.0.7}/.planning/milestones/v2.0-phases/10-hide-instead-of-delete-sync-mode/10-DISCUSSION-LOG.md +0 -0
  173. {pushframe-5.0.6 → pushframe-5.0.7}/.planning/milestones/v2.0-phases/10-hide-instead-of-delete-sync-mode/10-LIVE-FINDINGS.md +0 -0
  174. {pushframe-5.0.6 → pushframe-5.0.7}/.planning/milestones/v2.0-phases/10-hide-instead-of-delete-sync-mode/10-PATTERNS.md +0 -0
  175. {pushframe-5.0.6 → pushframe-5.0.7}/.planning/milestones/v2.0-phases/10-hide-instead-of-delete-sync-mode/10-RESEARCH.md +0 -0
  176. {pushframe-5.0.6 → pushframe-5.0.7}/.planning/milestones/v2.0-phases/10-hide-instead-of-delete-sync-mode/10-SECURITY.md +0 -0
  177. {pushframe-5.0.6 → pushframe-5.0.7}/.planning/milestones/v2.0-phases/10-hide-instead-of-delete-sync-mode/10-UAT.md +0 -0
  178. {pushframe-5.0.6 → pushframe-5.0.7}/.planning/milestones/v2.0-phases/10-hide-instead-of-delete-sync-mode/10-VERIFICATION.md +0 -0
  179. {pushframe-5.0.6 → pushframe-5.0.7}/.planning/milestones/v2.0-phases/10-hide-instead-of-delete-sync-mode/COVERAGE.md +0 -0
  180. {pushframe-5.0.6 → pushframe-5.0.7}/.planning/milestones/v2.0-quick/260630-qs8-make-main-py-load-a-local-env-file-at-st/260630-qs8-PLAN.md +0 -0
  181. {pushframe-5.0.6 → pushframe-5.0.7}/.planning/milestones/v2.0-quick/260630-qs8-make-main-py-load-a-local-env-file-at-st/260630-qs8-SUMMARY.md +0 -0
  182. {pushframe-5.0.6 → pushframe-5.0.7}/.planning/milestones/v2.0-quick/260708-dt9-add-progress-feedback-to-aura-cli-sync-a/260708-dt9-PLAN.md +0 -0
  183. {pushframe-5.0.6 → pushframe-5.0.7}/.planning/milestones/v2.0-quick/260708-dt9-add-progress-feedback-to-aura-cli-sync-a/260708-dt9-SUMMARY.md +0 -0
  184. {pushframe-5.0.6 → pushframe-5.0.7}/.planning/milestones/v2.0-quick/260708-dt9-add-progress-feedback-to-aura-cli-sync-a/deferred-items.md +0 -0
  185. {pushframe-5.0.6 → pushframe-5.0.7}/.planning/milestones/v2.0-quick/260708-fyr-batch-refactor-of-sync-apply-write-path-/260708-fyr-PLAN.md +0 -0
  186. {pushframe-5.0.6 → pushframe-5.0.7}/.planning/milestones/v2.0-quick/260708-fyr-batch-refactor-of-sync-apply-write-path-/260708-fyr-SUMMARY.md +0 -0
  187. {pushframe-5.0.6 → pushframe-5.0.7}/.planning/milestones/v2.0-quick/260708-fyr-batch-refactor-of-sync-apply-write-path-/260708-fyr-VERIFICATION.md +0 -0
  188. {pushframe-5.0.6 → pushframe-5.0.7}/.planning/milestones/v2.0-quick/README.md +0 -0
  189. {pushframe-5.0.6 → pushframe-5.0.7}/.planning/milestones/v3.0-REQUIREMENTS.md +0 -0
  190. {pushframe-5.0.6 → pushframe-5.0.7}/.planning/milestones/v3.0-ROADMAP.md +0 -0
  191. {pushframe-5.0.6 → pushframe-5.0.7}/.planning/milestones/v3.0-phases/11-write-path-reliability-format-support/11-01-PLAN.md +0 -0
  192. {pushframe-5.0.6 → pushframe-5.0.7}/.planning/milestones/v3.0-phases/11-write-path-reliability-format-support/11-01-SUMMARY.md +0 -0
  193. {pushframe-5.0.6 → pushframe-5.0.7}/.planning/milestones/v3.0-phases/11-write-path-reliability-format-support/11-02-PLAN.md +0 -0
  194. {pushframe-5.0.6 → pushframe-5.0.7}/.planning/milestones/v3.0-phases/11-write-path-reliability-format-support/11-02-SUMMARY.md +0 -0
  195. {pushframe-5.0.6 → pushframe-5.0.7}/.planning/milestones/v3.0-phases/11-write-path-reliability-format-support/11-03-PLAN.md +0 -0
  196. {pushframe-5.0.6 → pushframe-5.0.7}/.planning/milestones/v3.0-phases/11-write-path-reliability-format-support/11-03-SUMMARY.md +0 -0
  197. {pushframe-5.0.6 → pushframe-5.0.7}/.planning/milestones/v3.0-phases/11-write-path-reliability-format-support/11-04-PLAN.md +0 -0
  198. {pushframe-5.0.6 → pushframe-5.0.7}/.planning/milestones/v3.0-phases/11-write-path-reliability-format-support/11-04-SUMMARY.md +0 -0
  199. {pushframe-5.0.6 → pushframe-5.0.7}/.planning/milestones/v3.0-phases/11-write-path-reliability-format-support/11-05-PLAN.md +0 -0
  200. {pushframe-5.0.6 → pushframe-5.0.7}/.planning/milestones/v3.0-phases/11-write-path-reliability-format-support/11-05-SUMMARY.md +0 -0
  201. {pushframe-5.0.6 → pushframe-5.0.7}/.planning/milestones/v3.0-phases/11-write-path-reliability-format-support/11-06-PLAN.md +0 -0
  202. {pushframe-5.0.6 → pushframe-5.0.7}/.planning/milestones/v3.0-phases/11-write-path-reliability-format-support/11-06-SUMMARY.md +0 -0
  203. {pushframe-5.0.6 → pushframe-5.0.7}/.planning/milestones/v3.0-phases/11-write-path-reliability-format-support/11-CONTEXT.md +0 -0
  204. {pushframe-5.0.6 → pushframe-5.0.7}/.planning/milestones/v3.0-phases/11-write-path-reliability-format-support/11-DISCUSSION-LOG.md +0 -0
  205. {pushframe-5.0.6 → pushframe-5.0.7}/.planning/milestones/v3.0-phases/11-write-path-reliability-format-support/11-LIVE-FINDINGS.md +0 -0
  206. {pushframe-5.0.6 → pushframe-5.0.7}/.planning/milestones/v3.0-phases/11-write-path-reliability-format-support/11-PATTERNS.md +0 -0
  207. {pushframe-5.0.6 → pushframe-5.0.7}/.planning/milestones/v3.0-phases/11-write-path-reliability-format-support/11-REVIEW.md +0 -0
  208. {pushframe-5.0.6 → pushframe-5.0.7}/.planning/milestones/v3.0-phases/11-write-path-reliability-format-support/11-VERIFICATION.md +0 -0
  209. {pushframe-5.0.6 → pushframe-5.0.7}/.planning/milestones/v4.0-REQUIREMENTS.md +0 -0
  210. {pushframe-5.0.6 → pushframe-5.0.7}/.planning/milestones/v4.0-ROADMAP.md +0 -0
  211. {pushframe-5.0.6 → pushframe-5.0.7}/.planning/milestones/v4.0-phases/16-local-mechanism-spike-decision/16-01-PLAN.md +0 -0
  212. {pushframe-5.0.6 → pushframe-5.0.7}/.planning/milestones/v4.0-phases/16-local-mechanism-spike-decision/16-01-SUMMARY.md +0 -0
  213. {pushframe-5.0.6 → pushframe-5.0.7}/.planning/milestones/v4.0-phases/16-local-mechanism-spike-decision/16-02-PLAN.md +0 -0
  214. {pushframe-5.0.6 → pushframe-5.0.7}/.planning/milestones/v4.0-phases/16-local-mechanism-spike-decision/16-02-SUMMARY.md +0 -0
  215. {pushframe-5.0.6 → pushframe-5.0.7}/.planning/milestones/v4.0-phases/16-local-mechanism-spike-decision/16-03-PLAN.md +0 -0
  216. {pushframe-5.0.6 → pushframe-5.0.7}/.planning/milestones/v4.0-phases/16-local-mechanism-spike-decision/16-03-SUMMARY.md +0 -0
  217. {pushframe-5.0.6 → pushframe-5.0.7}/.planning/milestones/v4.0-phases/16-local-mechanism-spike-decision/16-CONTEXT.md +0 -0
  218. {pushframe-5.0.6 → pushframe-5.0.7}/.planning/milestones/v4.0-phases/16-local-mechanism-spike-decision/16-DECISION-RECORD.md +0 -0
  219. {pushframe-5.0.6 → pushframe-5.0.7}/.planning/milestones/v4.0-phases/16-local-mechanism-spike-decision/16-DISCUSSION-LOG.md +0 -0
  220. {pushframe-5.0.6 → pushframe-5.0.7}/.planning/milestones/v4.0-phases/16-local-mechanism-spike-decision/16-LIVE-FINDINGS.md +0 -0
  221. {pushframe-5.0.6 → pushframe-5.0.7}/.planning/milestones/v4.0-phases/16-local-mechanism-spike-decision/COVERAGE.md +0 -0
  222. {pushframe-5.0.6 → pushframe-5.0.7}/.planning/milestones/v4.0-phases/17-google-link-album-selection/17-01-PLAN.md +0 -0
  223. {pushframe-5.0.6 → pushframe-5.0.7}/.planning/milestones/v4.0-phases/17-google-link-album-selection/17-01-SUMMARY.md +0 -0
  224. {pushframe-5.0.6 → pushframe-5.0.7}/.planning/milestones/v4.0-phases/17-google-link-album-selection/17-02-PLAN.md +0 -0
  225. {pushframe-5.0.6 → pushframe-5.0.7}/.planning/milestones/v4.0-phases/17-google-link-album-selection/17-02-SUMMARY.md +0 -0
  226. {pushframe-5.0.6 → pushframe-5.0.7}/.planning/milestones/v4.0-phases/17-google-link-album-selection/17-CONTEXT.md +0 -0
  227. {pushframe-5.0.6 → pushframe-5.0.7}/.planning/milestones/v4.0-phases/17-google-link-album-selection/17-DISCUSS-CHECKPOINT.json +0 -0
  228. {pushframe-5.0.6 → pushframe-5.0.7}/.planning/milestones/v4.0-phases/17-google-link-album-selection/17-VERIFICATION.md +0 -0
  229. {pushframe-5.0.6 → pushframe-5.0.7}/.planning/milestones/v4.0-phases/17-google-link-album-selection/COVERAGE.md +0 -0
  230. {pushframe-5.0.6 → pushframe-5.0.7}/.planning/milestones/v4.0-phases/18-album-frame-mirror-sync-single-pair/18-01-PLAN.md +0 -0
  231. {pushframe-5.0.6 → pushframe-5.0.7}/.planning/milestones/v4.0-phases/18-album-frame-mirror-sync-single-pair/18-01-SUMMARY.md +0 -0
  232. {pushframe-5.0.6 → pushframe-5.0.7}/.planning/milestones/v4.0-phases/18-album-frame-mirror-sync-single-pair/18-02-PLAN.md +0 -0
  233. {pushframe-5.0.6 → pushframe-5.0.7}/.planning/milestones/v4.0-phases/18-album-frame-mirror-sync-single-pair/18-02-SUMMARY.md +0 -0
  234. {pushframe-5.0.6 → pushframe-5.0.7}/.planning/milestones/v4.0-phases/18-album-frame-mirror-sync-single-pair/18-03-PLAN.md +0 -0
  235. {pushframe-5.0.6 → pushframe-5.0.7}/.planning/milestones/v4.0-phases/18-album-frame-mirror-sync-single-pair/18-03-SUMMARY.md +0 -0
  236. {pushframe-5.0.6 → pushframe-5.0.7}/.planning/milestones/v4.0-phases/18-album-frame-mirror-sync-single-pair/18-CONTEXT.md +0 -0
  237. {pushframe-5.0.6 → pushframe-5.0.7}/.planning/milestones/v4.0-phases/18-album-frame-mirror-sync-single-pair/18-RESEARCH.md +0 -0
  238. {pushframe-5.0.6 → pushframe-5.0.7}/.planning/milestones/v4.0-phases/18-album-frame-mirror-sync-single-pair/18-UAT.md +0 -0
  239. {pushframe-5.0.6 → pushframe-5.0.7}/.planning/milestones/v4.0-phases/18-album-frame-mirror-sync-single-pair/18-VERIFICATION.md +0 -0
  240. {pushframe-5.0.6 → pushframe-5.0.7}/.planning/milestones/v4.0-phases/19-debt-closeout/19-01-PLAN.md +0 -0
  241. {pushframe-5.0.6 → pushframe-5.0.7}/.planning/milestones/v4.0-phases/19-debt-closeout/19-01-SUMMARY.md +0 -0
  242. {pushframe-5.0.6 → pushframe-5.0.7}/.planning/milestones/v4.0-phases/19-debt-closeout/19-02-PLAN.md +0 -0
  243. {pushframe-5.0.6 → pushframe-5.0.7}/.planning/milestones/v4.0-phases/19-debt-closeout/19-02-SUMMARY.md +0 -0
  244. {pushframe-5.0.6 → pushframe-5.0.7}/.planning/milestones/v4.0-phases/19-debt-closeout/19-CONTEXT.md +0 -0
  245. {pushframe-5.0.6 → pushframe-5.0.7}/.planning/milestones/v4.0-phases/19-debt-closeout/19-RESEARCH.md +0 -0
  246. {pushframe-5.0.6 → pushframe-5.0.7}/.planning/milestones/v4.0-phases/19-debt-closeout/19-VERIFICATION.md +0 -0
  247. {pushframe-5.0.6 → pushframe-5.0.7}/.planning/milestones/v5.0-REQUIREMENTS.md +0 -0
  248. {pushframe-5.0.6 → pushframe-5.0.7}/.planning/milestones/v5.0-ROADMAP.md +0 -0
  249. {pushframe-5.0.6 → pushframe-5.0.7}/.planning/milestones/v5.0-phases/20-pushframe-rename-migration-repo-switch/20-SUMMARY.md +0 -0
  250. {pushframe-5.0.6 → pushframe-5.0.7}/.planning/milestones/v5.0-phases/21-debian-package-apt-repo/21-01-PLAN.md +0 -0
  251. {pushframe-5.0.6 → pushframe-5.0.7}/.planning/milestones/v5.0-phases/21-debian-package-apt-repo/21-01-SUMMARY.md +0 -0
  252. {pushframe-5.0.6 → pushframe-5.0.7}/.planning/milestones/v5.0-phases/21-debian-package-apt-repo/21-02-PLAN.md +0 -0
  253. {pushframe-5.0.6 → pushframe-5.0.7}/.planning/milestones/v5.0-phases/21-debian-package-apt-repo/21-02-SUMMARY.md +0 -0
  254. {pushframe-5.0.6 → pushframe-5.0.7}/.planning/milestones/v5.0-phases/21-debian-package-apt-repo/21-CONTEXT.md +0 -0
  255. {pushframe-5.0.6 → pushframe-5.0.7}/.planning/milestones/v5.0-phases/21-debian-package-apt-repo/21-RESEARCH.md +0 -0
  256. {pushframe-5.0.6 → pushframe-5.0.7}/.planning/milestones/v5.0-phases/21-debian-package-apt-repo/21-VERIFICATION.md +0 -0
  257. {pushframe-5.0.6 → pushframe-5.0.7}/.planning/milestones/v5.0-phases/22-pypi-release-engineering/22-01-PLAN.md +0 -0
  258. {pushframe-5.0.6 → pushframe-5.0.7}/.planning/milestones/v5.0-phases/22-pypi-release-engineering/22-01-SUMMARY.md +0 -0
  259. {pushframe-5.0.6 → pushframe-5.0.7}/.planning/milestones/v5.0-phases/22-pypi-release-engineering/22-02-PLAN.md +0 -0
  260. {pushframe-5.0.6 → pushframe-5.0.7}/.planning/milestones/v5.0-phases/22-pypi-release-engineering/22-02-SUMMARY.md +0 -0
  261. {pushframe-5.0.6 → pushframe-5.0.7}/.planning/milestones/v5.0-phases/22-pypi-release-engineering/22-CONTEXT.md +0 -0
  262. {pushframe-5.0.6 → pushframe-5.0.7}/.planning/milestones/v5.0-phases/22-pypi-release-engineering/22-RESEARCH.md +0 -0
  263. {pushframe-5.0.6 → pushframe-5.0.7}/.planning/milestones/v5.0-phases/22-pypi-release-engineering/22-VERIFICATION.md +0 -0
  264. {pushframe-5.0.6 → pushframe-5.0.7}/.planning/milestones/v5.0-phases/22-pypi-release-engineering/OPERATOR-STEPS.md +0 -0
  265. {pushframe-5.0.6 → pushframe-5.0.7}/.planning/phases/23-config-wizard-precedence/23-01-PLAN.md +0 -0
  266. {pushframe-5.0.6 → pushframe-5.0.7}/.planning/phases/23-config-wizard-precedence/23-01-SUMMARY.md +0 -0
  267. {pushframe-5.0.6 → pushframe-5.0.7}/.planning/phases/23-config-wizard-precedence/23-CONTEXT.md +0 -0
  268. {pushframe-5.0.6 → pushframe-5.0.7}/.planning/phases/23-config-wizard-precedence/23-RESEARCH.md +0 -0
  269. {pushframe-5.0.6 → pushframe-5.0.7}/.planning/phases/23-config-wizard-precedence/23-VERIFICATION.md +0 -0
  270. {pushframe-5.0.6 → pushframe-5.0.7}/.planning/research/ALBUM-ACCESS-V4-ADDENDUM.md +0 -0
  271. {pushframe-5.0.6 → pushframe-5.0.7}/.planning/research/ALBUM-ACCESS.md +0 -0
  272. {pushframe-5.0.6 → pushframe-5.0.7}/.planning/research/ARCHITECTURE.md +0 -0
  273. {pushframe-5.0.6 → pushframe-5.0.7}/.planning/research/BROWSER-AUTOMATION.md +0 -0
  274. {pushframe-5.0.6 → pushframe-5.0.7}/.planning/research/FEATURES.md +0 -0
  275. {pushframe-5.0.6 → pushframe-5.0.7}/.planning/research/PITFALLS.md +0 -0
  276. {pushframe-5.0.6 → pushframe-5.0.7}/.planning/research/STACK.md +0 -0
  277. {pushframe-5.0.6 → pushframe-5.0.7}/.planning/research/SUMMARY.md +0 -0
  278. {pushframe-5.0.6 → pushframe-5.0.7}/.planning/state.json +0 -0
  279. {pushframe-5.0.6 → pushframe-5.0.7}/.planning/todos/completed/2026-07-06-promote-debug-flag-to-a-global-cli-convention.md +0 -0
  280. {pushframe-5.0.6 → pushframe-5.0.7}/.python-version +0 -0
  281. {pushframe-5.0.6 → pushframe-5.0.7}/CLAUDE.md +0 -0
  282. {pushframe-5.0.6 → pushframe-5.0.7}/LICENSE +0 -0
  283. {pushframe-5.0.6 → pushframe-5.0.7}/README.md +0 -0
  284. {pushframe-5.0.6 → pushframe-5.0.7}/SECURITY.md +0 -0
  285. {pushframe-5.0.6 → pushframe-5.0.7}/VERIFICATION-REPORT.md +0 -0
  286. {pushframe-5.0.6 → pushframe-5.0.7}/docs/agents/domain.md +0 -0
  287. {pushframe-5.0.6 → pushframe-5.0.7}/docs/agents/issue-tracker.md +0 -0
  288. {pushframe-5.0.6 → pushframe-5.0.7}/docs/agents/triage-labels.md +0 -0
  289. {pushframe-5.0.6 → pushframe-5.0.7}/docs/superpowers/specs/2026-07-09-write-rate-limiter-design.md +0 -0
  290. {pushframe-5.0.6 → pushframe-5.0.7}/main.py +0 -0
  291. {pushframe-5.0.6 → pushframe-5.0.7}/packaging/control-template +0 -0
  292. {pushframe-5.0.6 → pushframe-5.0.7}/packaging/lintian-overrides +0 -0
  293. {pushframe-5.0.6 → pushframe-5.0.7}/packaging/postinst +0 -0
  294. {pushframe-5.0.6 → pushframe-5.0.7}/packaging/pushframe.copyright +0 -0
  295. {pushframe-5.0.6 → pushframe-5.0.7}/probes/.gitignore +0 -0
  296. {pushframe-5.0.6 → pushframe-5.0.7}/probes/README.md +0 -0
  297. {pushframe-5.0.6 → pushframe-5.0.7}/probes/browser_bootstrap.py +0 -0
  298. {pushframe-5.0.6 → pushframe-5.0.7}/probes/common.py +0 -0
  299. {pushframe-5.0.6 → pushframe-5.0.7}/probes/cookie_vault.py +0 -0
  300. {pushframe-5.0.6 → pushframe-5.0.7}/probes/fidelity_check.py +0 -0
  301. {pushframe-5.0.6 → pushframe-5.0.7}/probes/rpc_capture.py +0 -0
  302. {pushframe-5.0.6 → pushframe-5.0.7}/probes/shared_link_probe.py +0 -0
  303. {pushframe-5.0.6 → pushframe-5.0.7}/pushframe/api/__init__.py +0 -0
  304. {pushframe-5.0.6 → pushframe-5.0.7}/pushframe/api/accountApi.py +0 -0
  305. {pushframe-5.0.6 → pushframe-5.0.7}/pushframe/api/activityApi.py +0 -0
  306. {pushframe-5.0.6 → pushframe-5.0.7}/pushframe/api/assetApi.py +0 -0
  307. {pushframe-5.0.6 → pushframe-5.0.7}/pushframe/api/baseApi.py +0 -0
  308. {pushframe-5.0.6 → pushframe-5.0.7}/pushframe/api/frameApi.py +0 -0
  309. {pushframe-5.0.6 → pushframe-5.0.7}/pushframe/api/notificationApi.py +0 -0
  310. {pushframe-5.0.6 → pushframe-5.0.7}/pushframe/api/peopleApi.py +0 -0
  311. {pushframe-5.0.6 → pushframe-5.0.7}/pushframe/api/playlistApi.py +0 -0
  312. {pushframe-5.0.6 → pushframe-5.0.7}/pushframe/aura.py +0 -0
  313. {pushframe-5.0.6 → pushframe-5.0.7}/pushframe/aws/__init__.py +0 -0
  314. {pushframe-5.0.6 → pushframe-5.0.7}/pushframe/aws/awsclient.py +0 -0
  315. {pushframe-5.0.6 → pushframe-5.0.7}/pushframe/aws/s3client.py +0 -0
  316. {pushframe-5.0.6 → pushframe-5.0.7}/pushframe/aws/sqsclient.py +0 -0
  317. {pushframe-5.0.6 → pushframe-5.0.7}/pushframe/cache.py +0 -0
  318. {pushframe-5.0.6 → pushframe-5.0.7}/pushframe/client.py +0 -0
  319. {pushframe-5.0.6 → pushframe-5.0.7}/pushframe/doctor.py +0 -0
  320. {pushframe-5.0.6 → pushframe-5.0.7}/pushframe/exif.py +0 -0
  321. {pushframe-5.0.6 → pushframe-5.0.7}/pushframe/export.py +0 -0
  322. {pushframe-5.0.6 → pushframe-5.0.7}/pushframe/google/__init__.py +0 -0
  323. {pushframe-5.0.6 → pushframe-5.0.7}/pushframe/google/bootstrap.py +0 -0
  324. {pushframe-5.0.6 → pushframe-5.0.7}/pushframe/google/cache.py +0 -0
  325. {pushframe-5.0.6 → pushframe-5.0.7}/pushframe/google/client.py +0 -0
  326. {pushframe-5.0.6 → pushframe-5.0.7}/pushframe/google/enumerate.py +0 -0
  327. {pushframe-5.0.6 → pushframe-5.0.7}/pushframe/google/manifest.py +0 -0
  328. {pushframe-5.0.6 → pushframe-5.0.7}/pushframe/google/parsers.py +0 -0
  329. {pushframe-5.0.6 → pushframe-5.0.7}/pushframe/google/redaction.py +0 -0
  330. {pushframe-5.0.6 → pushframe-5.0.7}/pushframe/google/vault.py +0 -0
  331. {pushframe-5.0.6 → pushframe-5.0.7}/pushframe/migration.py +0 -0
  332. {pushframe-5.0.6 → pushframe-5.0.7}/pushframe/models/__init__.py +0 -0
  333. {pushframe-5.0.6 → pushframe-5.0.7}/pushframe/models/activity.py +0 -0
  334. {pushframe-5.0.6 → pushframe-5.0.7}/pushframe/models/asset.py +0 -0
  335. {pushframe-5.0.6 → pushframe-5.0.7}/pushframe/models/frame.py +0 -0
  336. {pushframe-5.0.6 → pushframe-5.0.7}/pushframe/models/meta.py +0 -0
  337. {pushframe-5.0.6 → pushframe-5.0.7}/pushframe/models/person.py +0 -0
  338. {pushframe-5.0.6 → pushframe-5.0.7}/pushframe/models/user.py +0 -0
  339. {pushframe-5.0.6 → pushframe-5.0.7}/pushframe/ratelimit.py +0 -0
  340. {pushframe-5.0.6 → pushframe-5.0.7}/pushframe/reconcile.py +0 -0
  341. {pushframe-5.0.6 → pushframe-5.0.7}/pushframe/utils/dt.py +0 -0
  342. {pushframe-5.0.6 → pushframe-5.0.7}/pushframe/utils/io.py +0 -0
  343. {pushframe-5.0.6 → pushframe-5.0.7}/pushframe/utils/settings.py +0 -0
  344. {pushframe-5.0.6 → pushframe-5.0.7}/scripts/build-deb.sh +0 -0
  345. {pushframe-5.0.6 → pushframe-5.0.7}/scripts/make-apt-key.sh +0 -0
  346. {pushframe-5.0.6 → pushframe-5.0.7}/scripts/publish-apt-repo.sh +0 -0
  347. {pushframe-5.0.6 → pushframe-5.0.7}/scripts/test-apt-journey-container.sh +0 -0
  348. {pushframe-5.0.6 → pushframe-5.0.7}/scripts/test-config-journey-container.sh +0 -0
  349. {pushframe-5.0.6 → pushframe-5.0.7}/scripts/test-deb-container.sh +0 -0
  350. {pushframe-5.0.6 → pushframe-5.0.7}/scripts/test-install-journey-container.sh +0 -0
  351. {pushframe-5.0.6 → pushframe-5.0.7}/tests/conftest.py +0 -0
  352. {pushframe-5.0.6 → pushframe-5.0.7}/tests/fixtures/asset_unprocessed.json +0 -0
  353. {pushframe-5.0.6 → pushframe-5.0.7}/tests/fixtures/assets_page1.json +0 -0
  354. {pushframe-5.0.6 → pushframe-5.0.7}/tests/fixtures/assets_page2.json +0 -0
  355. {pushframe-5.0.6 → pushframe-5.0.7}/tests/fixtures/assets_placeholders.json +0 -0
  356. {pushframe-5.0.6 → pushframe-5.0.7}/tests/fixtures/error_envelope.json +0 -0
  357. {pushframe-5.0.6 → pushframe-5.0.7}/tests/fixtures/frame_detail.json +0 -0
  358. {pushframe-5.0.6 → pushframe-5.0.7}/tests/fixtures/frames.json +0 -0
  359. {pushframe-5.0.6 → pushframe-5.0.7}/tests/fixtures/google_rpc_snackc_page.json +0 -0
  360. {pushframe-5.0.6 → pushframe-5.0.7}/tests/fixtures/google_share_page_sample.html +0 -0
  361. {pushframe-5.0.6 → pushframe-5.0.7}/tests/fixtures/login.json +0 -0
  362. {pushframe-5.0.6 → pushframe-5.0.7}/tests/fixtures/probe_af_initdata_sample.html +0 -0
  363. {pushframe-5.0.6 → pushframe-5.0.7}/tests/offline.py +0 -0
  364. {pushframe-5.0.6 → pushframe-5.0.7}/tests/test_asset_partial.py +0 -0
  365. {pushframe-5.0.6 → pushframe-5.0.7}/tests/test_asset_unprocessed.py +0 -0
  366. {pushframe-5.0.6 → pushframe-5.0.7}/tests/test_aura_logger.py +0 -0
  367. {pushframe-5.0.6 → pushframe-5.0.7}/tests/test_batch_update_contract.py +0 -0
  368. {pushframe-5.0.6 → pushframe-5.0.7}/tests/test_cli_google_album.py +0 -0
  369. {pushframe-5.0.6 → pushframe-5.0.7}/tests/test_cli_google_link.py +0 -0
  370. {pushframe-5.0.6 → pushframe-5.0.7}/tests/test_cli_push_budget_geo.py +0 -0
  371. {pushframe-5.0.6 → pushframe-5.0.7}/tests/test_cli_status.py +0 -0
  372. {pushframe-5.0.6 → pushframe-5.0.7}/tests/test_cli_status_google.py +0 -0
  373. {pushframe-5.0.6 → pushframe-5.0.7}/tests/test_client_rate_limit.py +0 -0
  374. {pushframe-5.0.6 → pushframe-5.0.7}/tests/test_config_wizard.py +0 -0
  375. {pushframe-5.0.6 → pushframe-5.0.7}/tests/test_doctor.py +0 -0
  376. {pushframe-5.0.6 → pushframe-5.0.7}/tests/test_execute_plan_budget_geo.py +0 -0
  377. {pushframe-5.0.6 → pushframe-5.0.7}/tests/test_fixtures_validity.py +0 -0
  378. {pushframe-5.0.6 → pushframe-5.0.7}/tests/test_google_cache.py +0 -0
  379. {pushframe-5.0.6 → pushframe-5.0.7}/tests/test_google_client.py +0 -0
  380. {pushframe-5.0.6 → pushframe-5.0.7}/tests/test_google_enumerate.py +0 -0
  381. {pushframe-5.0.6 → pushframe-5.0.7}/tests/test_google_manifest.py +0 -0
  382. {pushframe-5.0.6 → pushframe-5.0.7}/tests/test_google_parsers.py +0 -0
  383. {pushframe-5.0.6 → pushframe-5.0.7}/tests/test_google_vault.py +0 -0
  384. {pushframe-5.0.6 → pushframe-5.0.7}/tests/test_gsync_engine.py +0 -0
  385. {pushframe-5.0.6 → pushframe-5.0.7}/tests/test_imports.py +0 -0
  386. {pushframe-5.0.6 → pushframe-5.0.7}/tests/test_migration.py +0 -0
  387. {pushframe-5.0.6 → pushframe-5.0.7}/tests/test_offline_read_path.py +0 -0
  388. {pushframe-5.0.6 → pushframe-5.0.7}/tests/test_prep_upload_formats.py +0 -0
  389. {pushframe-5.0.6 → pushframe-5.0.7}/tests/test_probe_browser.py +0 -0
  390. {pushframe-5.0.6 → pushframe-5.0.7}/tests/test_probe_shared_link.py +0 -0
  391. {pushframe-5.0.6 → pushframe-5.0.7}/tests/test_ratelimit.py +0 -0
  392. {pushframe-5.0.6 → pushframe-5.0.7}/tests/test_read_path.py +0 -0
  393. {pushframe-5.0.6 → pushframe-5.0.7}/tests/test_reconcile.py +0 -0
  394. {pushframe-5.0.6 → pushframe-5.0.7}/tests/test_retry_401.py +0 -0
  395. {pushframe-5.0.6 → pushframe-5.0.7}/tests/test_settings_budget.py +0 -0
  396. {pushframe-5.0.6 → pushframe-5.0.7}/tests/test_settings_resolution.py +0 -0
  397. {pushframe-5.0.6 → pushframe-5.0.7}/tests/test_sync_engine.py +0 -0
  398. {pushframe-5.0.6 → pushframe-5.0.7}/tests/test_write_endpoints_failloud.py +0 -0
  399. {pushframe-5.0.6 → pushframe-5.0.7}/tests/test_write_formats_live.py +0 -0
  400. {pushframe-5.0.6 → pushframe-5.0.7}/tests/test_write_throttling.py +0 -0
@@ -5,15 +5,15 @@
5
5
 
6
6
  ## Current Position
7
7
 
8
- Phase: 23 — VERIFIED (passed)
9
- Current Plan: 23-01 complete
10
- Total Plans in Phase: 1
11
- Status: Verified — next: phase 24 (Token-First Sessions & Preflight Sweep)
8
+ Phase: 24 — VERIFIED (passed)
9
+ Current Plan: 24-01 complete
10
+ Total Plans in Phase: 1 (phase 24)
11
+ Status: Verified — next: phase 25 (Multi-frame + systemd)
12
12
 
13
13
  ## Milestone Roadmap (v5.1, Phases 23-25)
14
14
 
15
15
  - [x] Phase 23 — Config Wizard & Precedence (CFG-01..04)
16
- - [ ] Phase 24 — Token-First Sessions & Preflight Sweep (SEC-01..03, PRF-01..02)
16
+ - [x] Phase 24 — Token-First Sessions & Preflight Sweep (SEC-01..03, PRF-01..02)
17
17
  - [ ] Phase 25 — Multi-frame Mappings & systemd User Scheduling (MTF-01..03, TMR-01..03)
18
18
 
19
19
  ## Completed Milestones
@@ -0,0 +1,77 @@
1
+ ---
2
+ phase: 24-token-first-sessions-preflight-sweep-sec-01-03-prf-01-02
3
+ plan: 01
4
+ type: execute
5
+ wave: 1
6
+ depends_on: []
7
+ files_modified:
8
+ - pushframe/session.py
9
+ - pushframe/cli.py
10
+ - pushframe/gsync.py
11
+ - pushframe/sync.py
12
+ - pushframe/config_store.py
13
+ - pushframe/preflight.py
14
+ - tests/test_session.py
15
+ - tests/test_logout.py
16
+ - tests/test_preflight_sweep.py
17
+ - tests/test_execute_plan.py
18
+ - docs/CLI.md
19
+ - README.md
20
+ - CHANGELOG.md
21
+ autonomous: true
22
+ requirements: [SEC-01, SEC-02, SEC-03, PRF-01, PRF-02]
23
+
24
+ estimate:
25
+ tokens: 60000
26
+ raw_tokens: 48000
27
+ tasks: 4
28
+ confidence: high
29
+
30
+ must_haves:
31
+ truths:
32
+ - "config wizard + login leaves a token, NO password, in ~/.config/pushframe/config.json at 0600 (file-content test)"
33
+ - "every command (inspect, reconcile, sync, push, google-sync, status) runs from the stored token session when no env password is set"
34
+ - "a mocked 401 on session establishment triggers exactly ONE re-login then succeeds; a second failure surfaces the named error with remedy — never a loop"
35
+ - "non-TTY + expired token = SessionExpiredError with remedy, no prompt, no hang"
36
+ - "pushframe logout removes auth_token/user_id, keeps email/settings, stays 0600, idempotent"
37
+ - "sync/push fail named+prework on a nonexistent source dir; empty dir stays a non-error"
38
+ - "the PRF-02 sweep proves every inventoried foreseeable mode ends named, with remedy, traceback-free"
39
+ artifacts:
40
+ - path: pushframe/session.py
41
+ provides: establish_session + SessionExpiredError + the one TTY prompt path
42
+ - path: pushframe/preflight.py
43
+ provides: preflight helpers (vault, source dir) used by gsync/sync/push
44
+ key_links:
45
+ - from: establish_session
46
+ to: config_store (persist token after TTY re-login)
47
+ - from: execute_plan relogin default
48
+ to: session.py token-aware relogin (env password → login; TTY → prompt; else named error)
49
+
50
+ sequential_tasks:
51
+ - id: "Task 1: establish_session + call-site conversion (SEC-01/02)"
52
+ files: [pushframe/session.py, pushframe/cli.py, pushframe/gsync.py]
53
+ tests: [tests/test_session.py]
54
+ done_when:
55
+ - "red→green: run_inspect/reconcile/sync/google-sync work with ONLY a stored session (no env)"
56
+ - "env password still wins (override test)"
57
+ - "no password ever persisted (file-content assertion)"
58
+ - id: "Task 2: expiry flow — one re-login then surface (criterion 2)"
59
+ files: [pushframe/session.py, pushframe/sync.py]
60
+ tests: [tests/test_session.py, tests/test_execute_plan.py]
61
+ done_when:
62
+ - "mocked 401 → TTY prompt once → new token persisted → command continues"
63
+ - "mocked 401 → non-TTY → SessionExpiredError, remedy, rc=1"
64
+ - "execute_plan relogin default is token-aware; second consecutive failure surfaces"
65
+ - id: "Task 3: pushframe logout (SEC-03, D-02)"
66
+ files: [pushframe/config_store.py, pushframe/cli.py]
67
+ tests: [tests/test_logout.py]
68
+ done_when:
69
+ - "token+user_id gone, email+settings kept, 0600 preserved, idempotent, no token in output"
70
+ - id: "Task 4: preflights + the traceback-free sweep (PRF-01/02, D-03)"
71
+ files: [pushframe/preflight.py, pushframe/gsync.py, pushframe/cli.py]
72
+ tests: [tests/test_preflight_sweep.py]
73
+ done_when:
74
+ - "google-sync without vault = named error + google-link remedy (before any Google work)"
75
+ - "sync/push nonexistent dir = named error; empty dir = nothing-to-do"
76
+ - "sweep: every inventoried mode parametrized — named error, remedy substring, zero Traceback"
77
+ - "docs (README/CLI.md) + CHANGELOG"
@@ -0,0 +1,56 @@
1
+ # Plan 24-01 Execution Summary — Token-First Sessions & Preflight Sweep
2
+
3
+ **Phase:** 24-token-first-sessions-preflight-sweep-sec-01-03-prf-01-02
4
+ **Date:** 2026-09-29
5
+ **Status:** Complete — suite **459 passed** (was 443; +16: session 7, logout 4, sweep 5)
6
+ **Branch:** `gsd/phase-24-token-first-preflight`
7
+
8
+ ## What Shipped
9
+
10
+ - **`pushframe/session.py`** — `establish_session`, the ONE path: env
11
+ password override → stored token resume (no login call) → ONE TTY prompt
12
+ (token persisted, SEC-01 file-content tested) → `NoCredentialsError`
13
+ named. Expiry (D-01): a 401 on resume = TTY one-prompt-and-continue /
14
+ non-TTY `SessionExpiredError`; `_is_auth_failure` keeps corrupt-config
15
+ errors from masquerading as expiry.
16
+ - **Call-site conversion** — `run_inspect`, `run_reconcile`, `run_sync`
17
+ (sync+push), `gsync` all funnel through establish_session. DI contract
18
+ now enforced at every site: an **injected Aura is never re-authenticated**
19
+ (tests, doctor, pipelines manage their own auth).
20
+ - **Token-aware relogin** — `execute_plan`'s 401-retry seam no longer
21
+ assumes `aura.login`: env password → real login; TTY → the one prompt
22
+ (budget-charged as before); non-TTY → `AuthenticationError` named, the
23
+ chunk fails, the breaker still guards. No loops, no hangs.
24
+ - **`pushframe logout`** (D-02) — token+user_id removed, email/settings/
25
+ default_frame kept, 0600 preserved, idempotent, token never printed.
26
+ `config_store.update` learns None-means-remove.
27
+ - **`pushframe/preflight.py`** — `require_google_vault` (google-sync, with
28
+ the ssh -X headless remedy) and `require_source_dir` (D-03: nonexistent =
29
+ hard named error BEFORE any network; empty = the friendly nothing-to-do).
30
+ Wired: gsync vault check precedes session; run_sync dir check precedes
31
+ login entirely.
32
+ - **The PRF-02 sweep** — `tests/test_preflight_sweep.py` parametrized over
33
+ the 24-CONTEXT failure-mode inventory (5 cases live; the remaining 4 are
34
+ already covered by the phase-23.5 google-link tests, referenced in the
35
+ inventory). Contract per case: rc≠0, named marker, remedy substring,
36
+ zero "Traceback".
37
+
38
+ ## Roadmap §24 criteria check
39
+
40
+ | # | Criterion | Evidence |
41
+ |---|---|---|
42
+ | 1 | config+login → token, no password, 0600 (file-content test) | `test_tty_prompt_logs_in_and_persists_token_never_password` (asserts the password string is absent from the file) |
43
+ | 2 | mocked 401 → transparent re-login once, then surface | `test_expired_token_tty_prompts_once_and_persists_new_token` (exactly one prompt, new token persisted) + `test_expired_token_non_tty_named_error_no_prompt` + the relogin seam's named AuthenticationError |
44
+ | 3 | traceback-free sweep passes, parametrized | `test_preflight_sweep.py` — 5 modes, zero tracebacks |
45
+
46
+ ## Broken-and-fixed along the way
47
+
48
+ - The sweep caught the preflight ORDER bug: run_sync checked the source
49
+ dir AFTER login — machine prerequisites now precede any network call
50
+ (that ordering is the PRF-01 point).
51
+ - Three tests encoded the old "call sites re-login an injected aura"
52
+ contract; converted to the DI contract (no-credentials named path), and
53
+ the 475 test now exercises the establish_session env path where that
54
+ behavior actually lives.
55
+
56
+ **Commits:** this branch (session + call sites + logout + preflights + sweep)
@@ -0,0 +1,74 @@
1
+ # Phase 24: Token-First Sessions & Preflight Sweep — Context
2
+
3
+ **Gathered:** 2026-09-29
4
+ **Status:** Ready for planning
5
+ **Provenance:** compiled from REQUIREMENTS SEC-01..03 / PRF-01..02 verbatim,
6
+ ROADMAP §24 success criteria, a full read of the session plumbing after
7
+ phase 23 (which already delivered the stored-session foundation), and the
8
+ operator's three design decisions taken at the discuss checkpoint
9
+ (2026-09-29).
10
+
11
+ ## Operator decisions (locked at discuss)
12
+
13
+ - **D-01 Expiry in an interactive session**: on a 401 while establishing or
14
+ using the stored session, a **TTY session prompts once for the password
15
+ and continues** (token persisted after re-login); a non-TTY (scheduled)
16
+ run fails with a **named error and the remedy**, never a hanging prompt.
17
+ - **D-02 `pushframe logout`**: deletes **only the stored token** — email,
18
+ settings and (future) pairs stay. The next `pushframe config` proposes
19
+ the stored email as the default. Conforms to SEC-03 verbatim.
20
+ - **D-03 sync/push source-dir preflight**: a **nonexistent directory is a
21
+ hard, named error before any work**; an existing-but-empty directory
22
+ stays the current friendly "nothing to do" (not an error) — looping
23
+ scripts over sometimes-empty dirs must not break.
24
+
25
+ ## Current state after phase 23 (probe facts)
26
+
27
+ - `config_store` already persists `{email, auth_token, user_id}` 0600; the
28
+ wizard writes it after a live login (D-03 of phase 23).
29
+ - `run_status` (only) resumes the stored session when no password env is
30
+ set — `Aura.resume_session(email, auth_token, user_id)` exists and is
31
+ tested. All other commands (`inspect`, `reconcile`, `sync`/`push`,
32
+ `google-sync`) still call `aura.login()` (env-password path) — the
33
+ env-only path is kept (SEC-02 override) but stored sessions are not
34
+ consulted there yet.
35
+ - `execute_plan(relogin=...)` defaults to `aura.login` — **wrong seam for a
36
+ token session** (no password in config, by design). The 401-retry path
37
+ (REL-01/02) needs a token-aware relogin that can: try a silent token
38
+ refresh (re-POST login only if a password is available via env/`--password`),
39
+ else surface the named error.
40
+ - `google-sync` calls `aura.login()` unconditionally at its start
41
+ (`gsync.py` ~line 306).
42
+ - The wizard's `_wizard_login` seam returns `{email, auth_token, user_id,
43
+ frames}` — reuse for the TTY prompt-and-continue path (D-01).
44
+ - Preflight pattern to generalize lives in `google/bootstrap.py`
45
+ (`_require_profile`, prerequisite checks with install-shape-aware
46
+ remedies) + `cli.py` headless detection — proven pattern, phase 23.5.
47
+
48
+ ## Failure modes inventoried for the PRF-02 sweep (traceback-free contract)
49
+
50
+ 1. missing `playwright` (google-link) — done, keep the test
51
+ 2. missing Chrome/Chromium — done, keep
52
+ 3. headless `$DISPLAY` (google-link) — done, keep
53
+ 4. near-miss env var name — done, keep
54
+ 5. missing/blank vault on `google-sync` — named error, remedy `google-link`
55
+ 6. ambiguous/absent `--frame` — already resolve_frame; assert in sweep
56
+ 7. sync/push source dir nonexistent — NEW (D-03 hard error)
57
+ 8. no credentials at all (no env, no config) — status has it; sweep the rest
58
+ 9. corrupt config.json — status has it; sweep the rest
59
+ 10. systemd user session absent (`schedule`, phase 25) — placeholder
60
+ 11. multi-arch apt notice — docs, N/A in-code
61
+ 12. token expired + non-TTY — NEW (D-01 named error)
62
+
63
+ ## Constraints & invariants
64
+
65
+ - The password must NEVER be written to config.json (SEC-01) — wizard and
66
+ prompt-and-continue paths both persist **token only**.
67
+ - Env-password path (`PUSHFRAME_PASSWORD`, `--password` in a future CLI)
68
+ stays the override and is documented discouraged-for-humans (SEC-02).
69
+ - Token material never in logs/output (existing `_REDACT_KEYS` enforced by
70
+ tests; extend sweep to assert on new paths).
71
+ - Suite is offline-first: every new behavior gets a mock-transport test;
72
+ the real-API live tests stay `@live`.
73
+ - Roadmap §24 criteria are the acceptance gate (file-content test, mocked
74
+ 401 expiry re-login once-then-surface, parametrized traceback-free sweep).
@@ -0,0 +1,83 @@
1
+ # Phase 24 — Research
2
+
3
+ **Researched:** 2026-09-29 (code reads + venus regressions 23.5 as evidence)
4
+
5
+ ## 1. The unified session establishment (SEC-01/02)
6
+
7
+ Target: one helper every command funnels through, replacing the four copies
8
+ of `aura = aura or Aura(); aura.login()`.
9
+
10
+ ```
11
+ establish_session(aura=None, *, tty_ok: bool, password_override=None) -> Aura
12
+ 1. env password present (PUSHFRAME_/AURA_PASSWORD) → aura.login() (override, discouraged)
13
+ 2. config has auth_token → resume_session(email, token, uid)
14
+ 3. else TTY and tty_ok → wizard-style prompt, login, PERSIST token (never the password)
15
+ 4. else → NamedError: remedy = pushframe config
16
+ ```
17
+
18
+ - Call sites: run_inspect, run_reconcile, run_sync, gsync (google-sync),
19
+ run_status (already done — reuse). `_wizard_login` reused as the login
20
+ seam; persistence via `config_store.update`.
21
+ - 401 DURING establishment with a stored token (expired/revoked): D-01 —
22
+ TTY: one password prompt, re-login, persist new token, continue;
23
+ non-TTY: `SessionExpiredError` (named), remedy text, no prompt.
24
+
25
+ ## 2. Token-aware relogin for execute_plan (the 401-retry seam)
26
+
27
+ `execute_plan(relogin=...)` default is `aura.login` — breaks under
28
+ token-first (no password). New default behavior, injected at the
29
+ establish_session boundary:
30
+
31
+ - If env password exists → `aura.login` as today.
32
+ - Else TTY → the D-01 one-prompt path (with budget charge as today).
33
+ - Else → raise `AuthenticationError` immediately with the named remedy;
34
+ the chunk's items are attributed as failures and the breaker may abort —
35
+ identical semantics to a failed relogin today (no new failure shape).
36
+
37
+ ## 3. `pushframe logout` (SEC-03, D-02)
38
+
39
+ - `config_store.update(auth_token=None, user_id=None)` — atomic rewrite,
40
+ email/settings intact. Mode stays 0600.
41
+ - Output: what was removed, what was kept, remedy (`pushframe config`).
42
+ - Exit 0 also when nothing was stored (idempotent).
43
+ - Sweep asserts: no token material in output; file afterwards contains no
44
+ `auth_token` key (file-content assertion, mirrors roadmap criterion 1).
45
+
46
+ ## 4. Preflight inventory (PRF-01) with remedies
47
+
48
+ | Command | Check | Remedy text points to |
49
+ |---|---|---|
50
+ | google-sync | vault `~/.config/pushframe/google-cookies.json` present + parseable | `pushframe google-link` (+ ssh -X recipe for headless) |
51
+ | google-sync | playwright package importable (it imports the google stack) | install-shape-aware line (uv tool / pip --user) |
52
+ | sync/push | source dir exists (D-03 hard) | the exact path echoed back |
53
+ | sync/push | source dir empty → current "nothing to do", NOT an error | — |
54
+ | status/doctor | — | already green paths |
55
+ | schedule (25) | systemd user session + linger | placeholder assertion in sweep (phase 25 fills) |
56
+
57
+ ## 5. The PRF-02 traceback-free sweep
58
+
59
+ Parametrized test driving each command's handler with the failing
60
+ dependency injected (mock transports / monkeypatched builders) — asserting
61
+ `rc != 0`, **no `Traceback` in captured stderr**, and the named remedy
62
+ substring present. Failure-mode list lives in 24-CONTEXT §Failure-modes;
63
+ new modes must be added there (the sweep iterates that inventory).
64
+
65
+ ## 6. Test strategy
66
+
67
+ - Session flows: fake config path (tmp), fake `_wizard_login`, MockTransport
68
+ Aura asserting **login called exactly once on expiry then success**
69
+ (criterion 2's mocked-401 simulation), non-TTY raises SessionExpiredError.
70
+ - execute_plan relogin: existing offline harness; relogin stub asserting
71
+ one call then surface (no loops) — criterion 2, second half.
72
+ - logout: file-content assertion + idempotency.
73
+ - Sweep: `tests/test_preflight_sweep.py`, parametrized over the inventory.
74
+
75
+ ## 7. Risks / open points
76
+
77
+ - `google-sync` resolves frames AFTER login; with no session AND no env it
78
+ must fail preflight-style before any Google work — ordering in the plan.
79
+ - The wizard currently only runs interactively; the D-01 prompt reuses it —
80
+ keep the tty-guard one guard, not two diverging ones.
81
+ - Budget charging for the TTY relogin mid-run: follow the existing
82
+ RETRY_RELOGIN_REQUEST_COST shape (D-06 lineage) — one acquire, no bespoke
83
+ path.
@@ -0,0 +1,50 @@
1
+ ---
2
+ phase: "24"
3
+ name: "Token-First Sessions & Preflight Sweep"
4
+ created: 2026-09-29
5
+ status: passed
6
+ ---
7
+
8
+ # Phase 24 Verification — Token-First Sessions & Preflight Sweep
9
+
10
+ **Date:** 2026-09-29
11
+ **Status:** PASSED — suite 459 passed; the three ROADMAP §24 success
12
+ criteria evidenced by file-content tests, mocked-401 flows, and the
13
+ parametrized sweep.
14
+
15
+ ## Criteria → Evidence
16
+
17
+ | # | Criterion (ROADMAP §24) | Evidence | Result |
18
+ |---|--------------------------|----------|--------|
19
+ | 1 | `pushframe config` + login → `~/.config/pushframe/` contains a token, no password, mode 0600 (file-content test) | `tests/test_session.py::test_tty_prompt_logs_in_and_persists_token_never_password` — parses the stored JSON, asserts email/token/user_id present, the password STRING absent from the raw file, and no `password` key at all; 0600 covered by the phase-23 wizard test (same writer) | ✅ PASSED |
20
+ | 2 | Token-expiry simulation (mocked 401): transparent re-login ONCE and retry; a second failure surfaces the login error (no loops) | Establishment: `test_expired_token_tty_prompts_once_and_persists_new_token` (resume 401 → exactly ONE prompt → new token persisted → session continues) and `test_expired_token_non_tty_named_error_no_prompt` (SessionExpiredError, prompt asserted impossible). Mid-run: `execute_plan`'s relogin seam is token-aware — env login / TTY one-prompt / else `AuthenticationError` named (chunk fails, breaker guards; the one-retry-then-surface shape is the pre-existing REL-02 contract, still tested in `test_execute_plan.py`) | ✅ PASSED |
21
+ | 3 | The traceback-free sweep: every documented foreseeable failure mode maps to a named error with a remedy (parametrized test) | `tests/test_preflight_sweep.py` — parametrized over the 24-CONTEXT inventory (vault missing, dir nonexistent, no-credentials ×3 commands); per case: rc≠0 + named marker + remedy substring + zero `Traceback`. Modes 1–4 (playwright/Chrome/headless/near-miss env) are the phase-23.5 google-link tests, referenced in the same inventory | ✅ PASSED |
22
+
23
+ ## Requirement traceability (SEC-01..03, PRF-01..02)
24
+
25
+ - **SEC-01**: password never persisted — wizard test (phase 23) + the new
26
+ prompt-persist test both assert raw-file content; `_store_token` writes
27
+ only `{email, auth_token, user_id}`.
28
+ - **SEC-02**: all five commands run from the stored token; env stays the
29
+ override (`test_env_password_still_wins_the_override`); discouraged-for-
30
+ humans wording in docs/CLI.md.
31
+ - **SEC-03**: `logout` tests — token-only removal, idempotent, no token in
32
+ output; redaction regression suite unchanged and green.
33
+ - **PRF-01**: vault preflight (google-sync, remedy includes the headless
34
+ ssh -X recipe) + source-dir preflight (D-03 severity split).
35
+ - **PRF-02**: the sweep itself + the preflight-ORDER fix it caught (machine
36
+ checks precede every network call).
37
+
38
+ ## Scope honesty
39
+
40
+ - The token-expiry REFRESH is a re-login (credentials in memory), not a
41
+ server-side refresh token — Pushd offers none; that is the phase-22
42
+ decision, unchanged.
43
+ - `schedule`'s systemd preflight is a phase-25 item; the inventory already
44
+ lists it so the sweep grows by one case then.
45
+ - `--password` CLI flag (vs env) was deferred: the env override satisfies
46
+ SEC-02's "documented, discouraged" bar; the flag lands with phase 25's
47
+ scheduled-jobs ergonomics if wanted.
48
+
49
+ **Commits:** `9a84480` (session + call sites + logout + preflights + sweep)
50
+ on `gsd/phase-24-token-first-preflight`
@@ -16,6 +16,36 @@ frames; it is not affiliated with Aura Frames Inc.
16
16
 
17
17
  - (v5.1 work will land here)
18
18
 
19
+ ## [5.0.7] — 2026-09-29
20
+
21
+ ### Added (phase 24: Token-First Sessions & Preflight Sweep)
22
+
23
+ - **One session path for every command** (SEC-01/02): `inspect`,
24
+ `reconcile`, `sync`, `push` and `google-sync` now run from the stored
25
+ token session (0600) with no password and no login call; env vars stay
26
+ the override; a terminal session gets ONE password prompt whose token is
27
+ persisted (SEC-01: the password is never written anywhere — file-content
28
+ tested); non-interactive runs without credentials fail named.
29
+ - **Token expiry** (D-01): a 401 while resuming the stored session prompts
30
+ once in a terminal (new token persisted, command continues) and raises
31
+ `SessionExpiredError` with the remedy in scheduled runs. `execute_plan`'s
32
+ 401-retry relogin is token-aware (it previously assumed a password).
33
+ - **`pushframe logout`** (SEC-03, D-02): deletes the stored token (and only
34
+ the token) — email and settings survive, mode stays 0600, idempotent.
35
+ - **Preflights** (PRF-01, D-03): `google-sync` checks the vault up front
36
+ (remedy: `google-link` + the headless ssh -X recipe); `sync`/`push` fail
37
+ named on a nonexistent source directory BEFORE any network call (an
38
+ empty directory remains a friendly "nothing to do").
39
+ - **The traceback-free sweep** (PRF-02): a parametrized test over the
40
+ failure-mode inventory proves every foreseeable mode ends named, with a
41
+ remedy, and never a traceback.
42
+
43
+ ### Changed
44
+
45
+ - DI contract, now enforced everywhere: a call site NEVER re-authenticates
46
+ an injected Aura (tests, `doctor`, pipelines). Login-failure shapes are
47
+ owned by the session path.
48
+
19
49
  ## [5.0.6] — 2026-09-29
20
50
  - **`pushframe doctor`** — the field write-probe the venus regressions
21
51
  demanded: one 4×4 test image through the REAL write path (S3 +
@@ -64,6 +94,26 @@ frames; it is not affiliated with Aura Frames Inc.
64
94
  `asset_for_local_identifier` verify-probe read) while login/frames keep
65
95
  answering. The abort message no longer suggests status as an all-clear;
66
96
  it prescribes 60+ min from the abort and a single, solitary retry call.
97
+ - **"Request Unauthenticated" + logout:true identified as the trip's
98
+ disguise** (fourth venus regression, decisive capture): the body arrived
99
+ on a FRESH token immediately after a successful re-login, one item after
100
+ a doctor probe had WRITTEN fine — so Pushd's "Request Unauthenticated"
101
+ here means "we refuse your writes (trip)", not "your token is bad". The
102
+ classifier now pins this exact body to the wait-60+-min verdict and
103
+ warns against a pointless re-login.
104
+ - **First-occurrence trip stop**: a write 401 carrying that proven body
105
+ aborts the run IMMEDIATELY (`TripDetectedError`) — no iterating to the
106
+ 5-failure backstop, no re-login, no resend (all feed the trip). Budget
107
+ force-reconciled; the message states what is confirmed on the frame and
108
+ that the next run resumes cleanly. (Also fixed en route: TripDetected
109
+ propagates through every chunk-level handler — the generic per-chunk
110
+ attribution branch would otherwise swallow it as ordinary failures.)
111
+ - **`google-sync --batch-size N`**: the trip keys on per-batch volume (a
112
+ 1-item doctor write passed seconds before a 50-item chunk was refused),
113
+ so an operator can stay under the detection threshold (e.g. 10).
114
+ Forwarded only when supplied — the historical default (50) otherwise
115
+ rules. The anti-trip drill (doctor → wait → batch-size) is documented in
116
+ docs/CLI.md.
67
117
  - **401 bodies are captured and classified** (third venus regression): an
68
118
  HTTP 401 exception now carries the server's response body (redacted
69
119
  through the same filter as request logs, truncated to 300 chars, also
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: pushframe
3
- Version: 5.0.6
3
+ Version: 5.0.7
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
@@ -26,6 +26,22 @@ talks to the frame over your local network — everything goes through your Aura
26
26
 
27
27
  ## Global usage
28
28
 
29
+ One session path (phase 24): every command authenticates the same way —
30
+ `PUSHFRAME_EMAIL`/`PUSHFRAME_PASSWORD` env (override; discouraged for
31
+ humans), else the **stored token session** from `pushframe config` (no
32
+ login call, no password), else ONE interactive password prompt whose token
33
+ is persisted (the password is never written anywhere). Non-interactive runs
34
+ never prompt: without any credential they fail named with the remedy.
35
+
36
+ On an expired token: a terminal session prompts once and continues; a
37
+ scheduled run reports `SessionExpiredError` with the remedy.
38
+
39
+ ### `pushframe logout`
40
+
41
+ Deletes the stored session token — and only the token. Your email and
42
+ settings stay (the next `pushframe config` proposes your email). Idempotent;
43
+ token material is never printed.
44
+
29
45
  ```
30
46
  usage: pushframe [-h] [--version] [--debug] {status,inspect,sync,push} ...
31
47
 
@@ -90,6 +106,26 @@ launch the sync) or **NO-GO** with the captured 401 server body and its
90
106
  classification (anti-abuse trip vs token problem). Charges ~6 write-budget
91
107
  tokens; safe to repeat; never run it in CI.
92
108
 
109
+ ### When the anti-abuse layer refuses your writes
110
+
111
+ Pushd's anti-abuse layer can refuse writes while reads and login keep
112
+ working (proven venus, 2026-09). The refusal body says
113
+ `"Request Unauthenticated"` — **that is the trip's disguise, not a token
114
+ problem**. pushframe recognizes it and stops on the FIRST refusal (no
115
+ retry, no re-login — both feed the trip). The drill:
116
+
117
+ 1. `pushframe doctor` before any sync (GO = the write surface is open NOW)
118
+ 2. Trip detected → wait **60+ min** from the stop, then `pushframe doctor` again
119
+ 3. If doctor passes but a sync still trips immediately, lower the per-batch
120
+ volume — the trip keys on batch size (a 1-item write may pass where a
121
+ 50-item chunk is refused):
122
+ ```bash
123
+ pushframe google-sync Cadre --frame "Cadre de Fabrice" --apply --yes --batch-size 10
124
+ ```
125
+
126
+ Progress is never lost: only confirmed writes are remembered, and the next
127
+ run uploads the remainder once — never twice.
128
+
93
129
  ## `config` — set up credentials and settings once
94
130
 
95
131
  The conversational alternative to hand-managed environment variables. Every value
@@ -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.0.6"
4
+ __version__ = "5.0.7"
@@ -77,6 +77,7 @@ def build_parser() -> argparse.ArgumentParser:
77
77
  )
78
78
  subparsers = parser.add_subparsers(dest='command', required=True)
79
79
  subparsers.add_parser('status', help='Check config/auth health and list account frames')
80
+ subparsers.add_parser('logout', help='Delete the stored session token (email and settings are kept)')
80
81
  doctor_parser = subparsers.add_parser(
81
82
  'doctor', help='Field write-probe: 1 tiny test image through the real '
82
83
  'write path, then GO/NO-GO for a sync from this machine')
@@ -200,6 +201,11 @@ def build_parser() -> argparse.ArgumentParser:
200
201
  gsync_parser.add_argument(
201
202
  '--debug', action='store_true', default=False,
202
203
  help='Verbose logging')
204
+ gsync_parser.add_argument(
205
+ '--batch-size', type=int, default=None, dest='batch_size',
206
+ help='Assets per select_asset/batch_update call (default 50). '
207
+ 'Lower it (e.g. 10) if the anti-abuse layer trips on batch volume '
208
+ '— a 1-item write may pass where a 50-item chunk is refused')
203
209
  return parser
204
210
 
205
211
 
@@ -447,6 +453,22 @@ def _config_set(argv: list[str]) -> int:
447
453
  return 0
448
454
 
449
455
 
456
+ def run_logout() -> int:
457
+ """pushframe logout (phase 24, SEC-03, D-02): delete the stored TOKEN
458
+ and only the token. Email, settings, default_frame (and future pairs)
459
+ survive; the file stays 0600; idempotent; token material never printed."""
460
+ from pushframe import config_store
461
+ stored = config_store.load()
462
+ if not (stored.get('auth_token') or stored.get('user_id')): # noqa: S105
463
+ print('no stored session — nothing to remove.')
464
+ return 0
465
+ config_store.update(auth_token=None, user_id=None)
466
+ who = stored.get('email') or 'the account'
467
+ print(f'logged out: the stored session for {who} was deleted.')
468
+ print('kept: your email and settings. To sign back in: pushframe config')
469
+ return 0
470
+
471
+
450
472
  def run_status(aura=None, debug: bool = False, google_session=None) -> int:
451
473
  """Status command handler. Returns a process exit code (0 success, 1
452
474
  failure) — never calls sys.exit directly. Accepts an optional injected
@@ -809,13 +831,16 @@ def run_inspect(frame_arg: str, aura=None, debug: bool = False) -> int:
809
831
  `run_status`) there's no config-precheck step before constructing
810
832
  `Aura()`.
811
833
  """
812
- aura = aura or Aura()
813
834
  # Must run after Aura() construction (which registers the noisy sinks)
814
835
  # and before login/get_frames (the HTTP calls that trigger them).
815
836
  _configure_cli_logging(debug)
816
-
837
+ from pushframe.session import establish_session, SessionError
817
838
  try:
818
- aura.login()
839
+ if aura is None: # DI contract: an injected Aura manages its own auth
840
+ aura = establish_session(aura=aura)
841
+ except SessionError as e:
842
+ print(f'not authenticated: {e}')
843
+ return 1
819
844
  except Exception as e:
820
845
  # D-05: bad credentials, network error, or API drift all surface
821
846
  # here — a broad catch at the CLI boundary is correct.
@@ -885,13 +910,16 @@ def run_reconcile(frame_arg: str, *, remove: bool = False, yes: bool = False, me
885
910
  default `False` reproduces today's behaviour exactly (an unresolvable
886
911
  creation time stays in `unknown_age`, never a removal candidate).
887
912
  """
888
- aura = aura or Aura()
889
913
  # Must run after Aura() construction (which registers the noisy sinks)
890
914
  # and before login/get_frames (the HTTP calls that trigger them).
891
915
  _configure_cli_logging(debug)
892
-
916
+ from pushframe.session import establish_session, SessionError
893
917
  try:
894
- aura.login()
918
+ if aura is None: # DI contract: an injected Aura manages its own auth
919
+ aura = establish_session(aura=aura)
920
+ except SessionError as e:
921
+ print(f'not authenticated: {e}')
922
+ return 1
895
923
  except Exception as e:
896
924
  print(f'Login failed: {e}')
897
925
  return 1
@@ -1068,7 +1096,6 @@ def run_sync(dir_arg: str, frame_arg: str, apply: bool = False, yes: bool = Fals
1068
1096
  sys.exit directly. Accepts an optional injected `Aura` (dependency-
1069
1097
  injection seam) so this is testable offline, mirroring `run_inspect`.
1070
1098
  """
1071
- aura = aura or Aura()
1072
1099
  # Must run after Aura() construction (which registers the noisy sinks)
1073
1100
  # and before login/get_frames (the HTTP calls that trigger them).
1074
1101
  _configure_cli_logging(debug)
@@ -1076,8 +1103,22 @@ def run_sync(dir_arg: str, frame_arg: str, apply: bool = False, yes: bool = Fals
1076
1103
  verb_present = _REMOVAL_VERB_PRESENT[removal_mode]
1077
1104
  verb_past = _REMOVAL_VERB_PAST[removal_mode]
1078
1105
 
1106
+ # PRF-01/02, D-03 (phase 24): machine prerequisites BEFORE any network —
1107
+ # a nonexistent source dir is a named error without touching the API.
1108
+ from pushframe.preflight import require_source_dir, PreflightError
1079
1109
  try:
1080
- aura.login()
1110
+ require_source_dir(dir_arg)
1111
+ except PreflightError as e:
1112
+ print(f'sync failed: {e}')
1113
+ return 1
1114
+
1115
+ from pushframe.session import establish_session, SessionError
1116
+ try:
1117
+ if aura is None: # DI contract: an injected Aura manages its own auth
1118
+ aura = establish_session(aura=aura)
1119
+ except SessionError as e:
1120
+ print(f'not authenticated: {e}')
1121
+ return 1
1081
1122
  except RateLimitError as e:
1082
1123
  # Anti-abuse throttle/lockout escalated to reject login (the HTTP 475
1083
1124
  # seen in the select-asset-401-unauthorized session). Surface the
@@ -1113,7 +1154,6 @@ def run_sync(dir_arg: str, frame_arg: str, apply: bool = False, yes: bool = Fals
1113
1154
  # local filesystem error (missing/invalid `dir_arg`, permission
1114
1155
  # error reading a file) is reported distinctly from a remote
1115
1156
  # API/auth failure instead of being collapsed into the same
1116
- # generic "Failed to sync frame" message below.
1117
1157
  try:
1118
1158
  scan = scan_directory(Path(dir_arg))
1119
1159
  except OSError as e:
@@ -1366,6 +1406,8 @@ def main(argv=None) -> int:
1366
1406
  from pushframe.doctor import run_doctor
1367
1407
  return run_doctor(args.frame, do_write=not args.no_write,
1368
1408
  debug=args.debug)
1409
+ if args.command == 'logout':
1410
+ return run_logout()
1369
1411
  if args.command == 'status':
1370
1412
  return run_status(debug=args.debug)
1371
1413
  if args.command == 'google-link':
@@ -1375,7 +1417,8 @@ def main(argv=None) -> int:
1375
1417
  if args.command == 'google-sync':
1376
1418
  from pushframe.gsync import run_google_sync
1377
1419
  return run_google_sync(args.album, args.frame, apply=args.apply,
1378
- yes=args.yes, debug=args.debug)
1420
+ yes=args.yes, debug=args.debug,
1421
+ batch_size=args.batch_size)
1379
1422
  if args.command == 'inspect':
1380
1423
  return run_inspect(args.frame, debug=args.debug)
1381
1424
  if args.command == 'sync':
@@ -87,9 +87,15 @@ def save(data: dict, path: Path | None = None) -> Path:
87
87
 
88
88
 
89
89
  def update(**kwargs) -> Path:
90
- """Merge keys into the existing config and save."""
90
+ """Merge keys into the existing config and save. A None VALUE removes
91
+ the key (phase 24 logout semantics: deleting the token is an update
92
+ whose result is the key's ABSENCE, not a stored null)."""
91
93
  data = load()
92
- data.update(kwargs)
94
+ for key, value in kwargs.items():
95
+ if value is None:
96
+ data.pop(key, None)
97
+ else:
98
+ data[key] = value
93
99
  return save(data)
94
100
 
95
101