agentevo-cloud 0.2.7__tar.gz → 0.2.9__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.
- {agentevo_cloud-0.2.7 → agentevo_cloud-0.2.9}/LICENSE +1 -1
- {agentevo_cloud-0.2.7 → agentevo_cloud-0.2.9}/PKG-INFO +6 -6
- {agentevo_cloud-0.2.7 → agentevo_cloud-0.2.9}/README.md +4 -4
- {agentevo_cloud-0.2.7 → agentevo_cloud-0.2.9}/pyproject.toml +2 -2
- {agentevo_cloud-0.2.7 → agentevo_cloud-0.2.9}/src/agentevo_cloud/community.py +5 -1
- {agentevo_cloud-0.2.7 → agentevo_cloud-0.2.9}/src/agentevo_cloud/health_receipts.py +33 -7
- {agentevo_cloud-0.2.7 → agentevo_cloud-0.2.9}/tests/test_community.py +12 -0
- {agentevo_cloud-0.2.7 → agentevo_cloud-0.2.9}/tests/test_health_freshness.py +48 -2
- {agentevo_cloud-0.2.7 → agentevo_cloud-0.2.9}/.gitignore +0 -0
- {agentevo_cloud-0.2.7 → agentevo_cloud-0.2.9}/openapi/openapi.v1.json +0 -0
- {agentevo_cloud-0.2.7 → agentevo_cloud-0.2.9}/src/agentevo_cloud/__init__.py +0 -0
- {agentevo_cloud-0.2.7 → agentevo_cloud-0.2.9}/src/agentevo_cloud/analytics.py +0 -0
- {agentevo_cloud-0.2.7 → agentevo_cloud-0.2.9}/src/agentevo_cloud/app.py +0 -0
- {agentevo_cloud-0.2.7 → agentevo_cloud-0.2.9}/src/agentevo_cloud/application_alerts.py +0 -0
- {agentevo_cloud-0.2.7 → agentevo_cloud-0.2.9}/src/agentevo_cloud/audit_activation.py +0 -0
- {agentevo_cloud-0.2.7 → agentevo_cloud-0.2.9}/src/agentevo_cloud/audit_admission.py +0 -0
- {agentevo_cloud-0.2.7 → agentevo_cloud-0.2.9}/src/agentevo_cloud/audit_authority.py +0 -0
- {agentevo_cloud-0.2.7 → agentevo_cloud-0.2.9}/src/agentevo_cloud/audit_bootstrap.py +0 -0
- {agentevo_cloud-0.2.7 → agentevo_cloud-0.2.9}/src/agentevo_cloud/audit_continuity.py +0 -0
- {agentevo_cloud-0.2.7 → agentevo_cloud-0.2.9}/src/agentevo_cloud/audit_epoch.py +0 -0
- {agentevo_cloud-0.2.7 → agentevo_cloud-0.2.9}/src/agentevo_cloud/audit_legacy.py +0 -0
- {agentevo_cloud-0.2.7 → agentevo_cloud-0.2.9}/src/agentevo_cloud/audit_mode.py +0 -0
- {agentevo_cloud-0.2.7 → agentevo_cloud-0.2.9}/src/agentevo_cloud/audit_retained.py +0 -0
- {agentevo_cloud-0.2.7 → agentevo_cloud-0.2.9}/src/agentevo_cloud/audit_rows.py +0 -0
- {agentevo_cloud-0.2.7 → agentevo_cloud-0.2.9}/src/agentevo_cloud/audit_schema.py +0 -0
- {agentevo_cloud-0.2.7 → agentevo_cloud-0.2.9}/src/agentevo_cloud/audit_writer.py +0 -0
- {agentevo_cloud-0.2.7 → agentevo_cloud-0.2.9}/src/agentevo_cloud/backup_archive.py +0 -0
- {agentevo_cloud-0.2.7 → agentevo_cloud-0.2.9}/src/agentevo_cloud/backup_workflows.py +0 -0
- {agentevo_cloud-0.2.7 → agentevo_cloud-0.2.9}/src/agentevo_cloud/billing.py +0 -0
- {agentevo_cloud-0.2.7 → agentevo_cloud-0.2.9}/src/agentevo_cloud/bounded_dns.py +0 -0
- {agentevo_cloud-0.2.7 → agentevo_cloud-0.2.9}/src/agentevo_cloud/clerk.py +0 -0
- {agentevo_cloud-0.2.7 → agentevo_cloud-0.2.9}/src/agentevo_cloud/clerk_session.py +0 -0
- {agentevo_cloud-0.2.7 → agentevo_cloud-0.2.9}/src/agentevo_cloud/cli.py +0 -0
- {agentevo_cloud-0.2.7 → agentevo_cloud-0.2.9}/src/agentevo_cloud/config.py +0 -0
- {agentevo_cloud-0.2.7 → agentevo_cloud-0.2.9}/src/agentevo_cloud/data_exports.py +0 -0
- {agentevo_cloud-0.2.7 → agentevo_cloud-0.2.9}/src/agentevo_cloud/db.py +0 -0
- {agentevo_cloud-0.2.7 → agentevo_cloud-0.2.9}/src/agentevo_cloud/demo.py +0 -0
- {agentevo_cloud-0.2.7 → agentevo_cloud-0.2.9}/src/agentevo_cloud/device_grants.py +0 -0
- {agentevo_cloud-0.2.7 → agentevo_cloud-0.2.9}/src/agentevo_cloud/devices.py +0 -0
- {agentevo_cloud-0.2.7 → agentevo_cloud-0.2.9}/src/agentevo_cloud/entitlement_recovery.py +0 -0
- {agentevo_cloud-0.2.7 → agentevo_cloud-0.2.9}/src/agentevo_cloud/erasure_journal.py +0 -0
- {agentevo_cloud-0.2.7 → agentevo_cloud-0.2.9}/src/agentevo_cloud/event_evidence.py +0 -0
- {agentevo_cloud-0.2.7 → agentevo_cloud-0.2.9}/src/agentevo_cloud/event_retirement.py +0 -0
- {agentevo_cloud-0.2.7 → agentevo_cloud-0.2.9}/src/agentevo_cloud/events.py +0 -0
- {agentevo_cloud-0.2.7 → agentevo_cloud-0.2.9}/src/agentevo_cloud/handoffs.py +0 -0
- {agentevo_cloud-0.2.7 → agentevo_cloud-0.2.9}/src/agentevo_cloud/health.py +0 -0
- {agentevo_cloud-0.2.7 → agentevo_cloud-0.2.9}/src/agentevo_cloud/housekeeping.py +0 -0
- {agentevo_cloud-0.2.7 → agentevo_cloud-0.2.9}/src/agentevo_cloud/http_deadline.py +0 -0
- {agentevo_cloud-0.2.7 → agentevo_cloud-0.2.9}/src/agentevo_cloud/job_authority.py +0 -0
- {agentevo_cloud-0.2.7 → agentevo_cloud-0.2.9}/src/agentevo_cloud/jobs.py +0 -0
- {agentevo_cloud-0.2.7 → agentevo_cloud-0.2.9}/src/agentevo_cloud/live.py +0 -0
- {agentevo_cloud-0.2.7 → agentevo_cloud-0.2.9}/src/agentevo_cloud/managed.py +0 -0
- {agentevo_cloud-0.2.7 → agentevo_cloud-0.2.9}/src/agentevo_cloud/mcp.py +0 -0
- {agentevo_cloud-0.2.7 → agentevo_cloud-0.2.9}/src/agentevo_cloud/mcp_delivery.py +0 -0
- {agentevo_cloud-0.2.7 → agentevo_cloud-0.2.9}/src/agentevo_cloud/mcp_delivery_asgi.py +0 -0
- {agentevo_cloud-0.2.7 → agentevo_cloud-0.2.9}/src/agentevo_cloud/metering.py +0 -0
- {agentevo_cloud-0.2.7 → agentevo_cloud-0.2.9}/src/agentevo_cloud/migration_lifecycle.py +0 -0
- {agentevo_cloud-0.2.7 → agentevo_cloud-0.2.9}/src/agentevo_cloud/observability.py +0 -0
- {agentevo_cloud-0.2.7 → agentevo_cloud-0.2.9}/src/agentevo_cloud/oidc.py +0 -0
- {agentevo_cloud-0.2.7 → agentevo_cloud-0.2.9}/src/agentevo_cloud/openapi.py +0 -0
- {agentevo_cloud-0.2.7 → agentevo_cloud-0.2.9}/src/agentevo_cloud/operator_privacy.py +0 -0
- {agentevo_cloud-0.2.7 → agentevo_cloud-0.2.9}/src/agentevo_cloud/privacy_boundary.py +0 -0
- {agentevo_cloud-0.2.7 → agentevo_cloud-0.2.9}/src/agentevo_cloud/promote.py +0 -0
- {agentevo_cloud-0.2.7 → agentevo_cloud-0.2.9}/src/agentevo_cloud/py.typed +0 -0
- {agentevo_cloud-0.2.7 → agentevo_cloud-0.2.9}/src/agentevo_cloud/recall.py +0 -0
- {agentevo_cloud-0.2.7 → agentevo_cloud-0.2.9}/src/agentevo_cloud/records.py +0 -0
- {agentevo_cloud-0.2.7 → agentevo_cloud-0.2.9}/src/agentevo_cloud/recovery_authority.py +0 -0
- {agentevo_cloud-0.2.7 → agentevo_cloud-0.2.9}/src/agentevo_cloud/relationships.py +0 -0
- {agentevo_cloud-0.2.7 → agentevo_cloud-0.2.9}/src/agentevo_cloud/retention.py +0 -0
- {agentevo_cloud-0.2.7 → agentevo_cloud-0.2.9}/src/agentevo_cloud/revoke.py +0 -0
- {agentevo_cloud-0.2.7 → agentevo_cloud-0.2.9}/src/agentevo_cloud/screen.py +0 -0
- {agentevo_cloud-0.2.7 → agentevo_cloud-0.2.9}/src/agentevo_cloud/session_closure.py +0 -0
- {agentevo_cloud-0.2.7 → agentevo_cloud-0.2.9}/src/agentevo_cloud/sqlite_engine.py +0 -0
- {agentevo_cloud-0.2.7 → agentevo_cloud-0.2.9}/src/agentevo_cloud/storage_conversion.py +0 -0
- {agentevo_cloud-0.2.7 → agentevo_cloud-0.2.9}/src/agentevo_cloud/summaries.py +0 -0
- {agentevo_cloud-0.2.7 → agentevo_cloud-0.2.9}/src/agentevo_cloud/sync.py +0 -0
- {agentevo_cloud-0.2.7 → agentevo_cloud-0.2.9}/src/agentevo_cloud/tenant_guard.py +0 -0
- {agentevo_cloud-0.2.7 → agentevo_cloud-0.2.9}/src/agentevo_cloud/tenants.py +0 -0
- {agentevo_cloud-0.2.7 → agentevo_cloud-0.2.9}/src/agentevo_cloud/tokens.py +0 -0
- {agentevo_cloud-0.2.7 → agentevo_cloud-0.2.9}/src/agentevo_cloud/webhooks.py +0 -0
- {agentevo_cloud-0.2.7 → agentevo_cloud-0.2.9}/src/agentevo_cloud/workers.py +0 -0
- {agentevo_cloud-0.2.7 → agentevo_cloud-0.2.9}/tests/mcp_delivery_fixtures.py +0 -0
- {agentevo_cloud-0.2.7 → agentevo_cloud-0.2.9}/tests/mcp_delivery_support.py +0 -0
- {agentevo_cloud-0.2.7 → agentevo_cloud-0.2.9}/tests/test_acceptance_m12.py +0 -0
- {agentevo_cloud-0.2.7 → agentevo_cloud-0.2.9}/tests/test_acceptance_m8.py +0 -0
- {agentevo_cloud-0.2.7 → agentevo_cloud-0.2.9}/tests/test_account_export.py +0 -0
- {agentevo_cloud-0.2.7 → agentevo_cloud-0.2.9}/tests/test_analytics.py +0 -0
- {agentevo_cloud-0.2.7 → agentevo_cloud-0.2.9}/tests/test_app.py +0 -0
- {agentevo_cloud-0.2.7 → agentevo_cloud-0.2.9}/tests/test_app_auth.py +0 -0
- {agentevo_cloud-0.2.7 → agentevo_cloud-0.2.9}/tests/test_app_jobs.py +0 -0
- {agentevo_cloud-0.2.7 → agentevo_cloud-0.2.9}/tests/test_app_managed.py +0 -0
- {agentevo_cloud-0.2.7 → agentevo_cloud-0.2.9}/tests/test_app_objects.py +0 -0
- {agentevo_cloud-0.2.7 → agentevo_cloud-0.2.9}/tests/test_app_sync.py +0 -0
- {agentevo_cloud-0.2.7 → agentevo_cloud-0.2.9}/tests/test_app_tenant_export.py +0 -0
- {agentevo_cloud-0.2.7 → agentevo_cloud-0.2.9}/tests/test_audit_admission_slice2.py +0 -0
- {agentevo_cloud-0.2.7 → agentevo_cloud-0.2.9}/tests/test_audit_bootstrap_slice1.py +0 -0
- {agentevo_cloud-0.2.7 → agentevo_cloud-0.2.9}/tests/test_audit_configured_commit.py +0 -0
- {agentevo_cloud-0.2.7 → agentevo_cloud-0.2.9}/tests/test_audit_cutover_context.py +0 -0
- {agentevo_cloud-0.2.7 → agentevo_cloud-0.2.9}/tests/test_audit_details.py +0 -0
- {agentevo_cloud-0.2.7 → agentevo_cloud-0.2.9}/tests/test_audit_epoch.py +0 -0
- {agentevo_cloud-0.2.7 → agentevo_cloud-0.2.9}/tests/test_audit_legacy_retained.py +0 -0
- {agentevo_cloud-0.2.7 → agentevo_cloud-0.2.9}/tests/test_audit_negative_mode.py +0 -0
- {agentevo_cloud-0.2.7 → agentevo_cloud-0.2.9}/tests/test_audit_order.py +0 -0
- {agentevo_cloud-0.2.7 → agentevo_cloud-0.2.9}/tests/test_audit_preparation.py +0 -0
- {agentevo_cloud-0.2.7 → agentevo_cloud-0.2.9}/tests/test_audit_producer_d1.py +0 -0
- {agentevo_cloud-0.2.7 → agentevo_cloud-0.2.9}/tests/test_audit_rows.py +0 -0
- {agentevo_cloud-0.2.7 → agentevo_cloud-0.2.9}/tests/test_audit_schema.py +0 -0
- {agentevo_cloud-0.2.7 → agentevo_cloud-0.2.9}/tests/test_audit_sequence_boundary.py +0 -0
- {agentevo_cloud-0.2.7 → agentevo_cloud-0.2.9}/tests/test_audit_spending.py +0 -0
- {agentevo_cloud-0.2.7 → agentevo_cloud-0.2.9}/tests/test_audit_sql_lifecycle.py +0 -0
- {agentevo_cloud-0.2.7 → agentevo_cloud-0.2.9}/tests/test_audit_v18_lineage_graph.py +0 -0
- {agentevo_cloud-0.2.7 → agentevo_cloud-0.2.9}/tests/test_audit_v18_preparation.py +0 -0
- {agentevo_cloud-0.2.7 → agentevo_cloud-0.2.9}/tests/test_authz_b5.py +0 -0
- {agentevo_cloud-0.2.7 → agentevo_cloud-0.2.9}/tests/test_backup_archive.py +0 -0
- {agentevo_cloud-0.2.7 → agentevo_cloud-0.2.9}/tests/test_backup_script.py +0 -0
- {agentevo_cloud-0.2.7 → agentevo_cloud-0.2.9}/tests/test_backup_workflows.py +0 -0
- {agentevo_cloud-0.2.7 → agentevo_cloud-0.2.9}/tests/test_billing.py +0 -0
- {agentevo_cloud-0.2.7 → agentevo_cloud-0.2.9}/tests/test_body_limit.py +0 -0
- {agentevo_cloud-0.2.7 → agentevo_cloud-0.2.9}/tests/test_caddy_edge.py +0 -0
- {agentevo_cloud-0.2.7 → agentevo_cloud-0.2.9}/tests/test_clerk.py +0 -0
- {agentevo_cloud-0.2.7 → agentevo_cloud-0.2.9}/tests/test_clerk_online.py +0 -0
- {agentevo_cloud-0.2.7 → agentevo_cloud-0.2.9}/tests/test_clerk_session_transport.py +0 -0
- {agentevo_cloud-0.2.7 → agentevo_cloud-0.2.9}/tests/test_clerk_transactions.py +0 -0
- {agentevo_cloud-0.2.7 → agentevo_cloud-0.2.9}/tests/test_cli.py +0 -0
- {agentevo_cloud-0.2.7 → agentevo_cloud-0.2.9}/tests/test_compile.py +0 -0
- {agentevo_cloud-0.2.7 → agentevo_cloud-0.2.9}/tests/test_console_gate.py +0 -0
- {agentevo_cloud-0.2.7 → agentevo_cloud-0.2.9}/tests/test_console_titles.py +0 -0
- {agentevo_cloud-0.2.7 → agentevo_cloud-0.2.9}/tests/test_containment_at_quota.py +0 -0
- {agentevo_cloud-0.2.7 → agentevo_cloud-0.2.9}/tests/test_contributors.py +0 -0
- {agentevo_cloud-0.2.7 → agentevo_cloud-0.2.9}/tests/test_cov_app.py +0 -0
- {agentevo_cloud-0.2.7 → agentevo_cloud-0.2.9}/tests/test_cov_application_alerts.py +0 -0
- {agentevo_cloud-0.2.7 → agentevo_cloud-0.2.9}/tests/test_cov_audit_activation.py +0 -0
- {agentevo_cloud-0.2.7 → agentevo_cloud-0.2.9}/tests/test_cov_audit_admission.py +0 -0
- {agentevo_cloud-0.2.7 → agentevo_cloud-0.2.9}/tests/test_cov_audit_authority.py +0 -0
- {agentevo_cloud-0.2.7 → agentevo_cloud-0.2.9}/tests/test_cov_audit_bootstrap.py +0 -0
- {agentevo_cloud-0.2.7 → agentevo_cloud-0.2.9}/tests/test_cov_audit_continuity.py +0 -0
- {agentevo_cloud-0.2.7 → agentevo_cloud-0.2.9}/tests/test_cov_audit_epoch.py +0 -0
- {agentevo_cloud-0.2.7 → agentevo_cloud-0.2.9}/tests/test_cov_audit_legacy.py +0 -0
- {agentevo_cloud-0.2.7 → agentevo_cloud-0.2.9}/tests/test_cov_audit_mode.py +0 -0
- {agentevo_cloud-0.2.7 → agentevo_cloud-0.2.9}/tests/test_cov_audit_retained.py +0 -0
- {agentevo_cloud-0.2.7 → agentevo_cloud-0.2.9}/tests/test_cov_audit_rows.py +0 -0
- {agentevo_cloud-0.2.7 → agentevo_cloud-0.2.9}/tests/test_cov_audit_schema.py +0 -0
- {agentevo_cloud-0.2.7 → agentevo_cloud-0.2.9}/tests/test_cov_audit_writer.py +0 -0
- {agentevo_cloud-0.2.7 → agentevo_cloud-0.2.9}/tests/test_cov_backup_workflows.py +0 -0
- {agentevo_cloud-0.2.7 → agentevo_cloud-0.2.9}/tests/test_cov_billing.py +0 -0
- {agentevo_cloud-0.2.7 → agentevo_cloud-0.2.9}/tests/test_cov_clerk_session.py +0 -0
- {agentevo_cloud-0.2.7 → agentevo_cloud-0.2.9}/tests/test_cov_cli.py +0 -0
- {agentevo_cloud-0.2.7 → agentevo_cloud-0.2.9}/tests/test_cov_config.py +0 -0
- {agentevo_cloud-0.2.7 → agentevo_cloud-0.2.9}/tests/test_cov_data_exports.py +0 -0
- {agentevo_cloud-0.2.7 → agentevo_cloud-0.2.9}/tests/test_cov_db.py +0 -0
- {agentevo_cloud-0.2.7 → agentevo_cloud-0.2.9}/tests/test_cov_device_grants.py +0 -0
- {agentevo_cloud-0.2.7 → agentevo_cloud-0.2.9}/tests/test_cov_devices.py +0 -0
- {agentevo_cloud-0.2.7 → agentevo_cloud-0.2.9}/tests/test_cov_entitlement_recovery.py +0 -0
- {agentevo_cloud-0.2.7 → agentevo_cloud-0.2.9}/tests/test_cov_erasure_journal.py +0 -0
- {agentevo_cloud-0.2.7 → agentevo_cloud-0.2.9}/tests/test_cov_event_evidence.py +0 -0
- {agentevo_cloud-0.2.7 → agentevo_cloud-0.2.9}/tests/test_cov_event_retirement.py +0 -0
- {agentevo_cloud-0.2.7 → agentevo_cloud-0.2.9}/tests/test_cov_events.py +0 -0
- {agentevo_cloud-0.2.7 → agentevo_cloud-0.2.9}/tests/test_cov_health_receipts.py +0 -0
- {agentevo_cloud-0.2.7 → agentevo_cloud-0.2.9}/tests/test_cov_http_deadline.py +0 -0
- {agentevo_cloud-0.2.7 → agentevo_cloud-0.2.9}/tests/test_cov_job_authority.py +0 -0
- {agentevo_cloud-0.2.7 → agentevo_cloud-0.2.9}/tests/test_cov_jobs.py +0 -0
- {agentevo_cloud-0.2.7 → agentevo_cloud-0.2.9}/tests/test_cov_mcp.py +0 -0
- {agentevo_cloud-0.2.7 → agentevo_cloud-0.2.9}/tests/test_cov_mcp_delivery.py +0 -0
- {agentevo_cloud-0.2.7 → agentevo_cloud-0.2.9}/tests/test_cov_mcp_delivery_asgi.py +0 -0
- {agentevo_cloud-0.2.7 → agentevo_cloud-0.2.9}/tests/test_cov_migration_lifecycle.py +0 -0
- {agentevo_cloud-0.2.7 → agentevo_cloud-0.2.9}/tests/test_cov_observability.py +0 -0
- {agentevo_cloud-0.2.7 → agentevo_cloud-0.2.9}/tests/test_cov_oidc.py +0 -0
- {agentevo_cloud-0.2.7 → agentevo_cloud-0.2.9}/tests/test_cov_operator_privacy.py +0 -0
- {agentevo_cloud-0.2.7 → agentevo_cloud-0.2.9}/tests/test_cov_privacy_boundary.py +0 -0
- {agentevo_cloud-0.2.7 → agentevo_cloud-0.2.9}/tests/test_cov_recovery_authority.py +0 -0
- {agentevo_cloud-0.2.7 → agentevo_cloud-0.2.9}/tests/test_cov_retention.py +0 -0
- {agentevo_cloud-0.2.7 → agentevo_cloud-0.2.9}/tests/test_cov_revoke.py +0 -0
- {agentevo_cloud-0.2.7 → agentevo_cloud-0.2.9}/tests/test_cov_session_closure.py +0 -0
- {agentevo_cloud-0.2.7 → agentevo_cloud-0.2.9}/tests/test_cov_sqlite_engine.py +0 -0
- {agentevo_cloud-0.2.7 → agentevo_cloud-0.2.9}/tests/test_cov_storage_conversion.py +0 -0
- {agentevo_cloud-0.2.7 → agentevo_cloud-0.2.9}/tests/test_cov_sync.py +0 -0
- {agentevo_cloud-0.2.7 → agentevo_cloud-0.2.9}/tests/test_cov_tenant_guard.py +0 -0
- {agentevo_cloud-0.2.7 → agentevo_cloud-0.2.9}/tests/test_cov_tenants.py +0 -0
- {agentevo_cloud-0.2.7 → agentevo_cloud-0.2.9}/tests/test_cov_tokens.py +0 -0
- {agentevo_cloud-0.2.7 → agentevo_cloud-0.2.9}/tests/test_cov_webhooks.py +0 -0
- {agentevo_cloud-0.2.7 → agentevo_cloud-0.2.9}/tests/test_cov_workers.py +0 -0
- {agentevo_cloud-0.2.7 → agentevo_cloud-0.2.9}/tests/test_database_publication.py +0 -0
- {agentevo_cloud-0.2.7 → agentevo_cloud-0.2.9}/tests/test_deletion_backup_receipts.py +0 -0
- {agentevo_cloud-0.2.7 → agentevo_cloud-0.2.9}/tests/test_demo.py +0 -0
- {agentevo_cloud-0.2.7 → agentevo_cloud-0.2.9}/tests/test_deploy_build_context.py +0 -0
- {agentevo_cloud-0.2.7 → agentevo_cloud-0.2.9}/tests/test_deploy_image_lock.py +0 -0
- {agentevo_cloud-0.2.7 → agentevo_cloud-0.2.9}/tests/test_device_grant_lifecycle.py +0 -0
- {agentevo_cloud-0.2.7 → agentevo_cloud-0.2.9}/tests/test_device_grants.py +0 -0
- {agentevo_cloud-0.2.7 → agentevo_cloud-0.2.9}/tests/test_devices_ci.py +0 -0
- {agentevo_cloud-0.2.7 → agentevo_cloud-0.2.9}/tests/test_encrypted_backup_workflows.py +0 -0
- {agentevo_cloud-0.2.7 → agentevo_cloud-0.2.9}/tests/test_encrypted_service.py +0 -0
- {agentevo_cloud-0.2.7 → agentevo_cloud-0.2.9}/tests/test_entitlement_mixed_live.py +0 -0
- {agentevo_cloud-0.2.7 → agentevo_cloud-0.2.9}/tests/test_entitlement_recovery.py +0 -0
- {agentevo_cloud-0.2.7 → agentevo_cloud-0.2.9}/tests/test_erasure_http.py +0 -0
- {agentevo_cloud-0.2.7 → agentevo_cloud-0.2.9}/tests/test_erasure_journal.py +0 -0
- {agentevo_cloud-0.2.7 → agentevo_cloud-0.2.9}/tests/test_event_evidence.py +0 -0
- {agentevo_cloud-0.2.7 → agentevo_cloud-0.2.9}/tests/test_event_loop_blocking.py +0 -0
- {agentevo_cloud-0.2.7 → agentevo_cloud-0.2.9}/tests/test_event_retirement.py +0 -0
- {agentevo_cloud-0.2.7 → agentevo_cloud-0.2.9}/tests/test_events.py +0 -0
- {agentevo_cloud-0.2.7 → agentevo_cloud-0.2.9}/tests/test_feed.py +0 -0
- {agentevo_cloud-0.2.7 → agentevo_cloud-0.2.9}/tests/test_foundation.py +0 -0
- {agentevo_cloud-0.2.7 → agentevo_cloud-0.2.9}/tests/test_handoffs.py +0 -0
- {agentevo_cloud-0.2.7 → agentevo_cloud-0.2.9}/tests/test_housekeeping.py +0 -0
- {agentevo_cloud-0.2.7 → agentevo_cloud-0.2.9}/tests/test_idempotency_scope.py +0 -0
- {agentevo_cloud-0.2.7 → agentevo_cloud-0.2.9}/tests/test_identity_privacy_hops.py +0 -0
- {agentevo_cloud-0.2.7 → agentevo_cloud-0.2.9}/tests/test_int_bounds.py +0 -0
- {agentevo_cloud-0.2.7 → agentevo_cloud-0.2.9}/tests/test_isolation_matrix.py +0 -0
- {agentevo_cloud-0.2.7 → agentevo_cloud-0.2.9}/tests/test_job_authority_migration.py +0 -0
- {agentevo_cloud-0.2.7 → agentevo_cloud-0.2.9}/tests/test_job_submitter_authority.py +0 -0
- {agentevo_cloud-0.2.7 → agentevo_cloud-0.2.9}/tests/test_jobs.py +0 -0
- {agentevo_cloud-0.2.7 → agentevo_cloud-0.2.9}/tests/test_license.py +0 -0
- {agentevo_cloud-0.2.7 → agentevo_cloud-0.2.9}/tests/test_live.py +0 -0
- {agentevo_cloud-0.2.7 → agentevo_cloud-0.2.9}/tests/test_lone_surrogates.py +0 -0
- {agentevo_cloud-0.2.7 → agentevo_cloud-0.2.9}/tests/test_maintenance.py +0 -0
- {agentevo_cloud-0.2.7 → agentevo_cloud-0.2.9}/tests/test_managed.py +0 -0
- {agentevo_cloud-0.2.7 → agentevo_cloud-0.2.9}/tests/test_mcp_delivery_authority.py +0 -0
- {agentevo_cloud-0.2.7 → agentevo_cloud-0.2.9}/tests/test_mcp_delivery_leases.py +0 -0
- {agentevo_cloud-0.2.7 → agentevo_cloud-0.2.9}/tests/test_mcp_server_send_boundary.py +0 -0
- {agentevo_cloud-0.2.7 → agentevo_cloud-0.2.9}/tests/test_mcp_sse.py +0 -0
- {agentevo_cloud-0.2.7 → agentevo_cloud-0.2.9}/tests/test_metered_write_lock.py +0 -0
- {agentevo_cloud-0.2.7 → agentevo_cloud-0.2.9}/tests/test_metering_records.py +0 -0
- {agentevo_cloud-0.2.7 → agentevo_cloud-0.2.9}/tests/test_migration_atomicity.py +0 -0
- {agentevo_cloud-0.2.7 → agentevo_cloud-0.2.9}/tests/test_migration_lifecycle.py +0 -0
- {agentevo_cloud-0.2.7 → agentevo_cloud-0.2.9}/tests/test_migration_lock.py +0 -0
- {agentevo_cloud-0.2.7 → agentevo_cloud-0.2.9}/tests/test_migration_minimum_bootstrap.py +0 -0
- {agentevo_cloud-0.2.7 → agentevo_cloud-0.2.9}/tests/test_object_workspace_ownership.py +0 -0
- {agentevo_cloud-0.2.7 → agentevo_cloud-0.2.9}/tests/test_observability.py +0 -0
- {agentevo_cloud-0.2.7 → agentevo_cloud-0.2.9}/tests/test_oidc.py +0 -0
- {agentevo_cloud-0.2.7 → agentevo_cloud-0.2.9}/tests/test_openapi.py +0 -0
- {agentevo_cloud-0.2.7 → agentevo_cloud-0.2.9}/tests/test_operator_privacy.py +0 -0
- {agentevo_cloud-0.2.7 → agentevo_cloud-0.2.9}/tests/test_perf_b6.py +0 -0
- {agentevo_cloud-0.2.7 → agentevo_cloud-0.2.9}/tests/test_privacy_boundary.py +0 -0
- {agentevo_cloud-0.2.7 → agentevo_cloud-0.2.9}/tests/test_privileged_integrity.py +0 -0
- {agentevo_cloud-0.2.7 → agentevo_cloud-0.2.9}/tests/test_promote.py +0 -0
- {agentevo_cloud-0.2.7 → agentevo_cloud-0.2.9}/tests/test_protected_backup_scripts.py +0 -0
- {agentevo_cloud-0.2.7 → agentevo_cloud-0.2.9}/tests/test_proxy_ip.py +0 -0
- {agentevo_cloud-0.2.7 → agentevo_cloud-0.2.9}/tests/test_prune_foreign.py +0 -0
- {agentevo_cloud-0.2.7 → agentevo_cloud-0.2.9}/tests/test_readiness.py +0 -0
- {agentevo_cloud-0.2.7 → agentevo_cloud-0.2.9}/tests/test_recall.py +0 -0
- {agentevo_cloud-0.2.7 → agentevo_cloud-0.2.9}/tests/test_recovery_schema.py +0 -0
- {agentevo_cloud-0.2.7 → agentevo_cloud-0.2.9}/tests/test_relationship_integrity.py +0 -0
- {agentevo_cloud-0.2.7 → agentevo_cloud-0.2.9}/tests/test_relationship_parent_report.py +0 -0
- {agentevo_cloud-0.2.7 → agentevo_cloud-0.2.9}/tests/test_reserved_key_ids.py +0 -0
- {agentevo_cloud-0.2.7 → agentevo_cloud-0.2.9}/tests/test_restore_cutover.py +0 -0
- {agentevo_cloud-0.2.7 → agentevo_cloud-0.2.9}/tests/test_restore_script.py +0 -0
- {agentevo_cloud-0.2.7 → agentevo_cloud-0.2.9}/tests/test_retention.py +0 -0
- {agentevo_cloud-0.2.7 → agentevo_cloud-0.2.9}/tests/test_revoke.py +0 -0
- {agentevo_cloud-0.2.7 → agentevo_cloud-0.2.9}/tests/test_runbook_schema.py +0 -0
- {agentevo_cloud-0.2.7 → agentevo_cloud-0.2.9}/tests/test_scope_hardening.py +0 -0
- {agentevo_cloud-0.2.7 → agentevo_cloud-0.2.9}/tests/test_screen.py +0 -0
- {agentevo_cloud-0.2.7 → agentevo_cloud-0.2.9}/tests/test_screening_events.py +0 -0
- {agentevo_cloud-0.2.7 → agentevo_cloud-0.2.9}/tests/test_sdist_layout.py +0 -0
- {agentevo_cloud-0.2.7 → agentevo_cloud-0.2.9}/tests/test_session_closure.py +0 -0
- {agentevo_cloud-0.2.7 → agentevo_cloud-0.2.9}/tests/test_shared_proxy_guard.py +0 -0
- {agentevo_cloud-0.2.7 → agentevo_cloud-0.2.9}/tests/test_smoke.py +0 -0
- {agentevo_cloud-0.2.7 → agentevo_cloud-0.2.9}/tests/test_snapshot_current_authority.py +0 -0
- {agentevo_cloud-0.2.7 → agentevo_cloud-0.2.9}/tests/test_snapshot_evidence.py +0 -0
- {agentevo_cloud-0.2.7 → agentevo_cloud-0.2.9}/tests/test_sql_tenant_scoping.py +0 -0
- {agentevo_cloud-0.2.7 → agentevo_cloud-0.2.9}/tests/test_sqlite_engine.py +0 -0
- {agentevo_cloud-0.2.7 → agentevo_cloud-0.2.9}/tests/test_storage_conversion.py +0 -0
- {agentevo_cloud-0.2.7 → agentevo_cloud-0.2.9}/tests/test_storage_operator.py +0 -0
- {agentevo_cloud-0.2.7 → agentevo_cloud-0.2.9}/tests/test_summaries.py +0 -0
- {agentevo_cloud-0.2.7 → agentevo_cloud-0.2.9}/tests/test_sync_mode.py +0 -0
- {agentevo_cloud-0.2.7 → agentevo_cloud-0.2.9}/tests/test_sync_push_pull.py +0 -0
- {agentevo_cloud-0.2.7 → agentevo_cloud-0.2.9}/tests/test_tenant_binding_contract.py +0 -0
- {agentevo_cloud-0.2.7 → agentevo_cloud-0.2.9}/tests/test_tenant_delete.py +0 -0
- {agentevo_cloud-0.2.7 → agentevo_cloud-0.2.9}/tests/test_tenant_guard.py +0 -0
- {agentevo_cloud-0.2.7 → agentevo_cloud-0.2.9}/tests/test_tenant_guard_adversarial.py +0 -0
- {agentevo_cloud-0.2.7 → agentevo_cloud-0.2.9}/tests/test_token_ancestry.py +0 -0
- {agentevo_cloud-0.2.7 → agentevo_cloud-0.2.9}/tests/test_webhook_dns_deadline.py +0 -0
- {agentevo_cloud-0.2.7 → agentevo_cloud-0.2.9}/tests/test_webhook_ssrf.py +0 -0
- {agentevo_cloud-0.2.7 → agentevo_cloud-0.2.9}/tests/test_webhook_write_lock.py +0 -0
- {agentevo_cloud-0.2.7 → agentevo_cloud-0.2.9}/tests/test_webhooks.py +0 -0
- {agentevo_cloud-0.2.7 → agentevo_cloud-0.2.9}/tests/test_worker_privacy.py +0 -0
- {agentevo_cloud-0.2.7 → agentevo_cloud-0.2.9}/tests/test_workers.py +0 -0
- {agentevo_cloud-0.2.7 → agentevo_cloud-0.2.9}/tests/test_workspace_delete_jobs.py +0 -0
- {agentevo_cloud-0.2.7 → agentevo_cloud-0.2.9}/tests/test_workspace_export_handoffs.py +0 -0
- {agentevo_cloud-0.2.7 → agentevo_cloud-0.2.9}/tests/test_write_lock_convoy.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: agentevo-cloud
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.9
|
|
4
4
|
Summary: Agentevo SaaS (TZ-3 control plane, TZ-4 managed intelligence): auth, metering, C2 APIs, workers.
|
|
5
5
|
Project-URL: Homepage, https://agentevo.sh
|
|
6
6
|
Project-URL: Documentation, https://agentevo.sh/#setup
|
|
@@ -20,7 +20,7 @@ Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
|
|
|
20
20
|
Classifier: Typing :: Typed
|
|
21
21
|
Requires-Python: >=3.10
|
|
22
22
|
Requires-Dist: agentevo-core==0.1.6
|
|
23
|
-
Requires-Dist: agentevo-local==0.1.
|
|
23
|
+
Requires-Dist: agentevo-local==0.1.7
|
|
24
24
|
Requires-Dist: anyio>=4
|
|
25
25
|
Requires-Dist: cryptography>=44
|
|
26
26
|
Requires-Dist: fastapi>=0.115
|
|
@@ -51,16 +51,16 @@ Part of [Agentevo](https://agentevo.sh).
|
|
|
51
51
|
|
|
52
52
|
## Encrypted runtime release
|
|
53
53
|
|
|
54
|
-
Cloud **0.2.
|
|
54
|
+
Cloud **0.2.9** is the pending source version for authenticated accepted event
|
|
55
55
|
context, reviewed legacy transitions and the existing encrypted storage,
|
|
56
|
-
provider and backup controls.
|
|
57
|
-
preserved;0.2.
|
|
56
|
+
provider and backup controls. Verified 0.2.8 artifacts and consumer seal remain
|
|
57
|
+
preserved; 0.2.9 is not built/sealed/cold-installed yet. A local passing test alone
|
|
58
58
|
does not establish publication, deployment or complete operating acceptance.
|
|
59
59
|
|
|
60
60
|
The optional `encrypted` extra pins `sqlcipher3==0.6.3`. For source development,
|
|
61
61
|
install it with `uv sync --frozen --package agentevo-cloud --extra encrypted`.
|
|
62
62
|
After final publication, the package selection is
|
|
63
|
-
`agentevo-cloud[encrypted]==0.2.
|
|
63
|
+
`agentevo-cloud[encrypted]==0.2.9`; use the approved hashed, wheel-only consumer
|
|
64
64
|
lock for a hosted image. Installation alone does not activate encryption.
|
|
65
65
|
|
|
66
66
|
Select `AGENTEVO_DB_STORAGE=sqlcipher-v1`, an explicit public
|
|
@@ -19,16 +19,16 @@ Part of [Agentevo](https://agentevo.sh).
|
|
|
19
19
|
|
|
20
20
|
## Encrypted runtime release
|
|
21
21
|
|
|
22
|
-
Cloud **0.2.
|
|
22
|
+
Cloud **0.2.9** is the pending source version for authenticated accepted event
|
|
23
23
|
context, reviewed legacy transitions and the existing encrypted storage,
|
|
24
|
-
provider and backup controls.
|
|
25
|
-
preserved;0.2.
|
|
24
|
+
provider and backup controls. Verified 0.2.8 artifacts and consumer seal remain
|
|
25
|
+
preserved; 0.2.9 is not built/sealed/cold-installed yet. A local passing test alone
|
|
26
26
|
does not establish publication, deployment or complete operating acceptance.
|
|
27
27
|
|
|
28
28
|
The optional `encrypted` extra pins `sqlcipher3==0.6.3`. For source development,
|
|
29
29
|
install it with `uv sync --frozen --package agentevo-cloud --extra encrypted`.
|
|
30
30
|
After final publication, the package selection is
|
|
31
|
-
`agentevo-cloud[encrypted]==0.2.
|
|
31
|
+
`agentevo-cloud[encrypted]==0.2.9`; use the approved hashed, wheel-only consumer
|
|
32
32
|
lock for a hosted image. Installation alone does not activate encryption.
|
|
33
33
|
|
|
34
34
|
Select `AGENTEVO_DB_STORAGE=sqlcipher-v1`, an explicit public
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "agentevo-cloud"
|
|
3
|
-
version = "0.2.
|
|
3
|
+
version = "0.2.9"
|
|
4
4
|
description = "Agentevo SaaS (TZ-3 control plane, TZ-4 managed intelligence): auth, metering, C2 APIs, workers."
|
|
5
5
|
readme = "README.md"
|
|
6
6
|
requires-python = ">=3.10"
|
|
@@ -22,7 +22,7 @@ classifiers = [
|
|
|
22
22
|
]
|
|
23
23
|
dependencies = [
|
|
24
24
|
"agentevo-core==0.1.6",
|
|
25
|
-
"agentevo-local==0.1.
|
|
25
|
+
"agentevo-local==0.1.7",
|
|
26
26
|
"cryptography>=44",
|
|
27
27
|
"fastapi>=0.115",
|
|
28
28
|
"anyio>=4",
|
|
@@ -46,6 +46,9 @@ SIGNING_KEY_ENV = "AGENTEVO_FEED_SIGNING_KEY"
|
|
|
46
46
|
#: Key id of the manifest signature (rotation adds ids; clients pin the map).
|
|
47
47
|
SIGNING_KEY_ID = "vendor-feed-v1"
|
|
48
48
|
|
|
49
|
+
#: Env overriding the manifest key id (the deploy sets it when it rotates).
|
|
50
|
+
SIGNING_KEY_ID_ENV = "AGENTEVO_FEED_SIGNING_KEY_ID"
|
|
51
|
+
|
|
49
52
|
|
|
50
53
|
@dataclass(frozen=True)
|
|
51
54
|
class CommunityGuard:
|
|
@@ -187,7 +190,8 @@ def feed(*, since: str | None = None) -> dict[str, Any]:
|
|
|
187
190
|
}
|
|
188
191
|
key = os.environ.get(SIGNING_KEY_ENV, "").strip()
|
|
189
192
|
if key:
|
|
190
|
-
|
|
193
|
+
key_id = os.environ.get(SIGNING_KEY_ID_ENV, "").strip() or SIGNING_KEY_ID
|
|
194
|
+
return sign_feed(envelope, key, key_id=key_id)
|
|
191
195
|
return envelope
|
|
192
196
|
|
|
193
197
|
|
|
@@ -8,11 +8,13 @@ These files are application observations, not backup-restorability proofs.
|
|
|
8
8
|
|
|
9
9
|
from __future__ import annotations
|
|
10
10
|
|
|
11
|
+
import errno
|
|
11
12
|
import json
|
|
12
13
|
import os
|
|
13
14
|
import stat
|
|
14
15
|
import sys
|
|
15
16
|
import tempfile
|
|
17
|
+
import time
|
|
16
18
|
from collections.abc import Iterator
|
|
17
19
|
from contextlib import contextmanager
|
|
18
20
|
from dataclasses import dataclass
|
|
@@ -32,6 +34,13 @@ COMPONENTS = frozenset({"jobs", "webhooks", "backup"})
|
|
|
32
34
|
_EVENTS = frozenset({"attempt", "completed", "success", "item_success", "failure"})
|
|
33
35
|
_MAX_MARKER_BYTES = 1024
|
|
34
36
|
|
|
37
|
+
#: Contended receipt writes wait this long in total (sibling passes hold
|
|
38
|
+
#: the lock for milliseconds; a lock stuck past this is a real fault).
|
|
39
|
+
_WRITER_LOCK_DEADLINE_SECONDS = 5.0
|
|
40
|
+
|
|
41
|
+
#: Pause between writer-lock attempts (mirrors the migration lock poll).
|
|
42
|
+
_WRITER_LOCK_POLL_SECONDS = 0.05
|
|
43
|
+
|
|
35
44
|
|
|
36
45
|
def receipt_directory(settings: Settings) -> Path:
|
|
37
46
|
"""A dedicated private directory, normally beside the shared database."""
|
|
@@ -165,18 +174,35 @@ def _writer_lock(directory: Path) -> Iterator[None]:
|
|
|
165
174
|
try:
|
|
166
175
|
if not stat.S_ISREG(os.fstat(fd).st_mode):
|
|
167
176
|
raise ValueError("invalid receipt lock file")
|
|
168
|
-
if sys.platform == "win32":
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
177
|
+
if sys.platform == "win32" and os.fstat(fd).st_size == 0:
|
|
178
|
+
os.write(fd, b"0")
|
|
179
|
+
deadline = time.monotonic() + _WRITER_LOCK_DEADLINE_SECONDS
|
|
180
|
+
while True:
|
|
181
|
+
try:
|
|
182
|
+
_lock_writer_file(fd)
|
|
183
|
+
break
|
|
184
|
+
except OSError as exc:
|
|
185
|
+
if exc.errno not in (errno.EACCES, errno.EAGAIN):
|
|
186
|
+
raise
|
|
187
|
+
remaining = deadline - time.monotonic()
|
|
188
|
+
if remaining <= 0:
|
|
189
|
+
raise TimeoutError("timed out waiting for the receipt writer lock") from None
|
|
190
|
+
time.sleep(min(_WRITER_LOCK_POLL_SECONDS, remaining))
|
|
175
191
|
yield
|
|
176
192
|
finally:
|
|
177
193
|
os.close(fd) # Also releases the kernel lock after a crashed writer.
|
|
178
194
|
|
|
179
195
|
|
|
196
|
+
def _lock_writer_file(fd: int) -> None:
|
|
197
|
+
"""Try kernel ownership once (the caller retries contention)."""
|
|
198
|
+
if sys.platform == "win32":
|
|
199
|
+
os.lseek(fd, 0, os.SEEK_SET)
|
|
200
|
+
# Windows permits the locked byte range to extend beyond EOF.
|
|
201
|
+
msvcrt.locking(fd, msvcrt.LK_NBLCK, 1)
|
|
202
|
+
else:
|
|
203
|
+
fcntl.flock(fd, fcntl.LOCK_EX | fcntl.LOCK_NB)
|
|
204
|
+
|
|
205
|
+
|
|
180
206
|
def _write_marker(directory: Path, component: str, event: str, moment: datetime) -> None:
|
|
181
207
|
"""Publish only after a complete file has reached the filesystem."""
|
|
182
208
|
payload = {"version": 1, "at": moment.astimezone(UTC).isoformat()}
|
|
@@ -18,6 +18,8 @@ from agentevo_cloud.community import (
|
|
|
18
18
|
FEED_VERSION,
|
|
19
19
|
FORMAT,
|
|
20
20
|
SIGNING_KEY_ENV,
|
|
21
|
+
SIGNING_KEY_ID,
|
|
22
|
+
SIGNING_KEY_ID_ENV,
|
|
21
23
|
CommunityGuard,
|
|
22
24
|
feed,
|
|
23
25
|
sign_feed,
|
|
@@ -348,6 +350,16 @@ def test_feed_signs_when_env_set(monkeypatch: pytest.MonkeyPatch) -> None:
|
|
|
348
350
|
assert "manifest" not in feed()
|
|
349
351
|
|
|
350
352
|
|
|
353
|
+
def test_feed_honors_key_id_env(monkeypatch: pytest.MonkeyPatch) -> None:
|
|
354
|
+
private_pem, _ = _ed25519_pem()
|
|
355
|
+
monkeypatch.setenv(SIGNING_KEY_ENV, private_pem)
|
|
356
|
+
assert feed()["manifest"]["key_id"] == SIGNING_KEY_ID
|
|
357
|
+
monkeypatch.setenv(SIGNING_KEY_ID_ENV, "vendor-feed-v2")
|
|
358
|
+
assert feed()["manifest"]["key_id"] == "vendor-feed-v2"
|
|
359
|
+
monkeypatch.setenv(SIGNING_KEY_ID_ENV, " ")
|
|
360
|
+
assert feed()["manifest"]["key_id"] == SIGNING_KEY_ID
|
|
361
|
+
|
|
362
|
+
|
|
351
363
|
def test_route_since_filter_and_no_billing(client: Any) -> None:
|
|
352
364
|
secret = client.provisioned["secret"]
|
|
353
365
|
before = client.get("/v1/usage", headers=auth(secret)).json()["used"]
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
|
|
4
4
|
from __future__ import annotations
|
|
5
5
|
|
|
6
|
+
import errno
|
|
6
7
|
import gzip
|
|
7
8
|
import json
|
|
8
9
|
import os
|
|
@@ -94,7 +95,9 @@ def test_independent_freshness_boundaries_and_failure_recovery(
|
|
|
94
95
|
assert component_status(settings, other, now=STAMP)["last_successful_pass_at"] is None
|
|
95
96
|
|
|
96
97
|
|
|
97
|
-
def test_receipts_are_private_atomic_and_do_not_regress(
|
|
98
|
+
def test_receipts_are_private_atomic_and_do_not_regress(
|
|
99
|
+
tmp_path: Path, monkeypatch: pytest.MonkeyPatch
|
|
100
|
+
) -> None:
|
|
98
101
|
settings = _settings(tmp_path, "jobs")
|
|
99
102
|
_success(settings, "jobs")
|
|
100
103
|
directory = receipt_directory(settings)
|
|
@@ -106,13 +109,56 @@ def test_receipts_are_private_atomic_and_do_not_regress(tmp_path: Path) -> None:
|
|
|
106
109
|
assert component_status(settings, "jobs", now=STAMP)["last_successful_pass_at"] == (
|
|
107
110
|
STAMP.isoformat()
|
|
108
111
|
)
|
|
109
|
-
|
|
112
|
+
# A lock stuck past the deadline still surfaces (TimeoutError is an
|
|
113
|
+
# OSError) and the timestamp never regresses; production showed the
|
|
114
|
+
# old fail-on-first-contention dropping worker passes.
|
|
115
|
+
monkeypatch.setattr(health_receipts, "_WRITER_LOCK_DEADLINE_SECONDS", 0.01)
|
|
116
|
+
with health_receipts._writer_lock(directory), pytest.raises(TimeoutError):
|
|
110
117
|
record_event(settings, "jobs", "success", now=STAMP + timedelta(seconds=1))
|
|
111
118
|
assert component_status(settings, "jobs", now=STAMP)["last_successful_pass_at"] == (
|
|
112
119
|
STAMP.isoformat()
|
|
113
120
|
)
|
|
114
121
|
|
|
115
122
|
|
|
123
|
+
def test_writer_lock_retries_transient_contention(
|
|
124
|
+
tmp_path: Path, monkeypatch: pytest.MonkeyPatch
|
|
125
|
+
) -> None:
|
|
126
|
+
settings = _settings(tmp_path, "jobs")
|
|
127
|
+
calls = 0
|
|
128
|
+
real_flock = health_receipts.fcntl.flock
|
|
129
|
+
|
|
130
|
+
def _flaky(fd: int, operation: int) -> None:
|
|
131
|
+
nonlocal calls
|
|
132
|
+
calls += 1
|
|
133
|
+
if calls <= 2:
|
|
134
|
+
raise OSError(errno.EAGAIN, "contended")
|
|
135
|
+
real_flock(fd, operation)
|
|
136
|
+
|
|
137
|
+
monkeypatch.setattr(health_receipts.fcntl, "flock", _flaky)
|
|
138
|
+
record_event(settings, "jobs", "success", now=STAMP)
|
|
139
|
+
assert calls == 3
|
|
140
|
+
assert component_status(settings, "jobs", now=STAMP)["last_successful_pass_at"] == (
|
|
141
|
+
STAMP.isoformat()
|
|
142
|
+
)
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
def test_writer_lock_rejects_non_contention_errors_without_retry(
|
|
146
|
+
tmp_path: Path, monkeypatch: pytest.MonkeyPatch
|
|
147
|
+
) -> None:
|
|
148
|
+
settings = _settings(tmp_path, "jobs")
|
|
149
|
+
calls = 0
|
|
150
|
+
|
|
151
|
+
def _boom(fd: int, operation: int) -> None:
|
|
152
|
+
nonlocal calls
|
|
153
|
+
calls += 1
|
|
154
|
+
raise OSError(errno.EBADF, "synthetic")
|
|
155
|
+
|
|
156
|
+
monkeypatch.setattr(health_receipts.fcntl, "flock", _boom)
|
|
157
|
+
with pytest.raises(OSError, match="synthetic"):
|
|
158
|
+
record_event(settings, "jobs", "success", now=STAMP)
|
|
159
|
+
assert calls == 1
|
|
160
|
+
|
|
161
|
+
|
|
116
162
|
@pytest.mark.parametrize(
|
|
117
163
|
"payload",
|
|
118
164
|
[
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|