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