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