diff --git a/scripts/no_magic_numbers_baseline.txt b/scripts/no_magic_numbers_baseline.txt index b5693d5816..efb652654c 100644 --- a/scripts/no_magic_numbers_baseline.txt +++ b/scripts/no_magic_numbers_baseline.txt @@ -9,516 +9,3 @@ # # Regenerate (rare; requires explicit user approval) with: # uv run python scripts/check_no_magic_numbers.py --update -src/synthorg/a2a/connection_types/a2a_peer.py:15:30 -src/synthorg/a2a/gateway.py:75:30 -src/synthorg/a2a/gateway.py:630:27 -src/synthorg/a2a/push_verifier.py:22:30 -src/synthorg/api/auth/middleware.py:48:16 -src/synthorg/api/auth/ticket_store.py:111:29 -src/synthorg/api/boundary.py:46:24 -src/synthorg/api/controllers/activities.py:318:29 -src/synthorg/api/controllers/agent_identity_versions.py:111:29 -src/synthorg/api/controllers/agents.py:66:24 -src/synthorg/api/controllers/agents.py:190:29 -src/synthorg/api/controllers/agents.py:426:29 -src/synthorg/api/controllers/analytics.py:645:12 -src/synthorg/api/controllers/approvals.py:519:29 -src/synthorg/api/controllers/artifacts.py:181:29 -src/synthorg/api/controllers/audit.py:39:19 -src/synthorg/api/controllers/audit.py:104:29 -src/synthorg/api/controllers/backup.py:187:29 -src/synthorg/api/controllers/budget.py:302:29 -src/synthorg/api/controllers/budget_config_versions.py:46:29 -src/synthorg/api/controllers/clients.py:216:29 -src/synthorg/api/controllers/company_versions.py:46:29 -src/synthorg/api/controllers/connections.py:65:20 -src/synthorg/api/controllers/connections.py:66:22 -src/synthorg/api/controllers/connections.py:156:29 -src/synthorg/api/controllers/coordination_metrics.py:34:21 -src/synthorg/api/controllers/coordination_metrics.py:96:29 -src/synthorg/api/controllers/custom_rules.py:205:29 -src/synthorg/api/controllers/custom_rules.py:435:29 -src/synthorg/api/controllers/departments.py:392:29 -src/synthorg/api/controllers/escalations.py:133:29 -src/synthorg/api/controllers/evaluation_config_versions.py:46:29 -src/synthorg/api/controllers/events.py:57:34 -src/synthorg/api/controllers/integration_health.py:77:29 -src/synthorg/api/controllers/meetings.py:244:29 -src/synthorg/api/controllers/messages.py:39:29 -src/synthorg/api/controllers/messages.py:133:29 -src/synthorg/api/controllers/meta_analytics.py:44:33 -src/synthorg/api/controllers/meta_analytics.py:115:29 -src/synthorg/api/controllers/meta_analytics.py:145:29 -src/synthorg/api/controllers/oauth.py:40:27 -src/synthorg/api/controllers/oauth.py:41:17 -src/synthorg/api/controllers/oauth.py:42:22 -src/synthorg/api/controllers/oauth.py:43:23 -src/synthorg/api/controllers/ontology.py:131:29 -src/synthorg/api/controllers/ontology.py:359:29 -src/synthorg/api/controllers/ontology.py:450:29 -src/synthorg/api/controllers/personalities.py:76:29 -src/synthorg/api/controllers/projects.py:71:29 -src/synthorg/api/controllers/reports.py:144:29 -src/synthorg/api/controllers/requests.py:89:29 -src/synthorg/api/controllers/role_versions.py:44:29 -src/synthorg/api/controllers/simulations.py:291:29 -src/synthorg/api/controllers/tasks.py:172:29 -src/synthorg/api/controllers/users.py:255:29 -src/synthorg/api/controllers/workflow_versions.py:197:29 -src/synthorg/api/cursor.py:44:17 -src/synthorg/api/cursor.py:51:18 -src/synthorg/api/cursor.py:238:22 -src/synthorg/api/dto_discovery.py:15:12 -src/synthorg/api/services/org_mutations.py:44:22 -src/synthorg/api/services/ssrf_violation_service.py:141:21 -src/synthorg/backup/service_archive.py:38:23 -src/synthorg/backup/service_archive.py:42:21 -src/synthorg/budget/automated_reports.py:87:21 -src/synthorg/budget/coordination_metrics.py:700:33 -src/synthorg/budget/coordination_store.py:56:45 -src/synthorg/budget/coordination_store.py:92:21 -src/synthorg/budget/enforcer.py:146:49 -src/synthorg/budget/optimizer.py:76:20 -src/synthorg/budget/optimizer.py:118:28 -src/synthorg/budget/quota_tracker.py:519:19 -src/synthorg/budget/rebalance.py:50:24 -src/synthorg/budget/risk_tracker.py:34:21 -src/synthorg/budget/risk_tracker.py:35:24 -src/synthorg/budget/tracker.py:68:21 -src/synthorg/budget/tracker.py:69:24 -src/synthorg/budget/tracker.py:79:30 -src/synthorg/client/feedback/adversarial.py:30:26 -src/synthorg/client/feedback/adversarial.py:31:25 -src/synthorg/client/feedback/scored.py:41:31 -src/synthorg/client/generators/llm.py:56:29 -src/synthorg/client/generators/llm.py:57:26 -src/synthorg/client/generators/procedural.py:66:38 -src/synthorg/communication/bus/_nats_history.py:74:22 -src/synthorg/communication/bus/_nats_history.py:75:35 -src/synthorg/communication/bus/_nats_history.py:157:22 -src/synthorg/communication/bus/_nats_history.py:158:35 -src/synthorg/communication/bus/_nats_history.py:203:22 -src/synthorg/communication/bus/_nats_history.py:204:35 -src/synthorg/communication/conflict_resolution/escalation/config.py:15:26 -src/synthorg/communication/conflict_resolution/escalation/in_memory_store.py:34:17 -src/synthorg/communication/conflict_resolution/escalation/protocol.py:21:17 -src/synthorg/communication/conflict_resolution/escalation/sweeper.py:43:34 -src/synthorg/communication/conflict_resolution/models.py:26:17 -src/synthorg/communication/delegation/record_store.py:29:23 -src/synthorg/communication/event_stream/stream.py:46:26 -src/synthorg/communication/event_stream/stream.py:47:29 -src/synthorg/communication/event_stream/stream.py:48:41 -src/synthorg/communication/event_stream/stream.py:49:39 -src/synthorg/communication/event_stream/stream.py:50:36 -src/synthorg/communication/event_stream/stream.py:51:40 -src/synthorg/communication/loop_prevention/dedup.py:38:30 -src/synthorg/config/rate_limits.py:20:22 -src/synthorg/constants.py:3:28 -src/synthorg/core/auth/config.py:9:20 -src/synthorg/core/personality.py:25:19 -src/synthorg/core/personality.py:26:24 -src/synthorg/core/personality.py:27:19 -src/synthorg/core/personality.py:30:15 -src/synthorg/core/personality.py:31:24 -src/synthorg/core/personality.py:32:19 -src/synthorg/core/personality.py:33:20 -src/synthorg/core/personality.py:34:13 -src/synthorg/engine/agent_engine_post_exec.py:48:34 -src/synthorg/engine/approval_gate.py:73:43 -src/synthorg/engine/classification/detectors.py:36:31 -src/synthorg/engine/classification/detectors.py:37:26 -src/synthorg/engine/classification/detectors.py:38:26 -src/synthorg/engine/classification/detectors.py:39:24 -src/synthorg/engine/classification/detectors.py:276:31 -src/synthorg/engine/classification/loaders.py:30:18 -src/synthorg/engine/classification/loaders.py:31:23 -src/synthorg/engine/classification/semantic_detectors.py:49:23 -src/synthorg/engine/classification/semantic_detectors.py:55:22 -src/synthorg/engine/classification/semantic_detectors.py:221:26 -src/synthorg/engine/classification/sinks.py:268:32 -src/synthorg/engine/classification/taxonomy_store.py:47:23 -src/synthorg/engine/compaction/epistemic.py:58:20 -src/synthorg/engine/compaction/epistemic.py:103:21 -src/synthorg/engine/coordination/attribution.py:51:23 -src/synthorg/engine/decomposition/classifier.py:43:28 -src/synthorg/engine/evolution/guards/rate_limit.py:21:42 -src/synthorg/engine/evolution/guards/rollback.py:28:28 -src/synthorg/engine/evolution/guards/rollback.py:29:38 -src/synthorg/engine/evolution/proposers/composite.py:27:29 -src/synthorg/engine/evolution/proposers/self_report.py:30:26 -src/synthorg/engine/evolution/proposers/self_report.py:53:29 -src/synthorg/engine/evolution/proposers/self_report.py:54:26 -src/synthorg/engine/evolution/proposers/separate_analyzer.py:256:29 -src/synthorg/engine/evolution/proposers/separate_analyzer.py:257:26 -src/synthorg/engine/intake/strategies/agent_intake.py:67:26 -src/synthorg/engine/loop_selector.py:233:34 -src/synthorg/engine/middleware/behavior_tagger.py:60:33 -src/synthorg/engine/middleware/coordination_constraints.py:130:36 -src/synthorg/engine/middleware/coordination_constraints.py:257:31 -src/synthorg/engine/middleware/coordination_constraints.py:258:31 -src/synthorg/engine/middleware/semantic_drift.py:33:22 -src/synthorg/engine/plan_helpers.py:19:27 -src/synthorg/engine/quality/decomposers/llm.py:214:40 -src/synthorg/engine/sanitization.py:62:52 -src/synthorg/engine/shutdown.py:144:31 -src/synthorg/engine/shutdown.py:145:33 -src/synthorg/engine/shutdown_strategies.py:88:51 -src/synthorg/engine/shutdown_strategies.py:190:33 -src/synthorg/engine/shutdown_strategies.py:351:31 -src/synthorg/engine/shutdown_strategies.py:352:33 -src/synthorg/engine/stagnation/quality_erosion_detector.py:50:27 -src/synthorg/engine/stagnation/quality_erosion_detector.py:51:27 -src/synthorg/engine/strategy/consensus.py:85:51 -src/synthorg/engine/trajectory/budget_guard.py:21:20 -src/synthorg/engine/trajectory/structural_erosion.py:34:23 -src/synthorg/engine/trajectory/structural_erosion.py:69:23 -src/synthorg/engine/trajectory/structural_erosion.py:107:23 -src/synthorg/engine/trajectory/structural_erosion.py:147:23 -src/synthorg/engine/workflow/condition_eval.py:50:19 -src/synthorg/engine/workflow/sprint_velocity.py:146:18 -src/synthorg/engine/workflow/validate_edges.py:18:22 -src/synthorg/engine/workflow/validation_types.py:10:22 -src/synthorg/engine/workflow/version_service.py:43:21 -src/synthorg/engine/workspace/disk_quota.py:26:16 -src/synthorg/engine/workspace/semantic_checks.py:74:25 -src/synthorg/engine/workspace/semantic_llm.py:181:27 -src/synthorg/hr/performance/ci_quality_strategy.py:49:29 -src/synthorg/hr/performance/composite_quality_strategy.py:76:27 -src/synthorg/hr/performance/composite_quality_strategy.py:77:28 -src/synthorg/hr/performance/composite_quality_strategy.py:78:37 -src/synthorg/hr/performance/composite_quality_strategy.py:79:45 -src/synthorg/hr/performance/llm_calibration_sampler.py:82:31 -src/synthorg/hr/performance/llm_calibration_sampler.py:83:30 -src/synthorg/hr/performance/multi_window_strategy.py:62:31 -src/synthorg/hr/performance/quality_override_store.py:26:25 -src/synthorg/hr/performance/theil_sen_strategy.py:61:31 -src/synthorg/hr/performance/theil_sen_strategy.py:62:37 -src/synthorg/hr/performance/theil_sen_strategy.py:63:37 -src/synthorg/hr/persistence_protocol.py:43:21 -src/synthorg/hr/scaling/guards/approval_gate.py:49:27 -src/synthorg/hr/scaling/guards/conflict_resolver.py:30:19 -src/synthorg/hr/scaling/guards/cooldown.py:34:32 -src/synthorg/hr/scaling/guards/rate_limit.py:35:33 -src/synthorg/hr/scaling/service.py:63:15 -src/synthorg/hr/scaling/signals/workload.py:33:36 -src/synthorg/hr/scaling/strategies/budget_cap.py:47:31 -src/synthorg/hr/scaling/strategies/budget_cap.py:48:35 -src/synthorg/hr/scaling/strategies/workload.py:41:32 -src/synthorg/hr/scaling/strategies/workload.py:42:33 -src/synthorg/hr/scaling/triggers/batched.py:33:32 -src/synthorg/hr/training/curation/llm_curated.py:69:29 -src/synthorg/hr/training/curation/llm_curated.py:70:21 -src/synthorg/hr/training/curation/relevance.py:23:17 -src/synthorg/hr/training/curation/relevance.py:26:27 -src/synthorg/hr/training/curation/relevance.py:27:20 -src/synthorg/hr/training/curation/relevance.py:31:21 -src/synthorg/hr/training/extractors/procedural.py:27:25 -src/synthorg/hr/training/extractors/semantic.py:27:25 -src/synthorg/hr/training/extractors/tool_patterns.py:29:25 -src/synthorg/hr/training/guards/review_gate.py:31:25 -src/synthorg/hr/training/guards/sanitization.py:25:22 -src/synthorg/hr/training/source_selectors/department_diversity.py:30:31 -src/synthorg/hr/training/source_selectors/department_diversity.py:31:31 -src/synthorg/hr/training/source_selectors/role_top_performers.py:30:17 -src/synthorg/infrastructure/services.py:917:21 -src/synthorg/infrastructure/services.py:957:21 -src/synthorg/integrations/health/checks/generic_http.py:21:11 -src/synthorg/integrations/health/checks/generic_http.py:22:19 -src/synthorg/integrations/health/checks/generic_http.py:23:22 -src/synthorg/integrations/health/checks/generic_http.py:24:19 -src/synthorg/integrations/health/checks/github.py:30:11 -src/synthorg/integrations/health/checks/github.py:31:11 -src/synthorg/integrations/health/checks/slack.py:22:11 -src/synthorg/integrations/health/checks/smtp.py:21:11 -src/synthorg/integrations/health/prober.py:88:32 -src/synthorg/integrations/health/prober.py:89:35 -src/synthorg/integrations/mcp_catalog/in_memory_installations.py:65:21 -src/synthorg/integrations/mcp_catalog/installations.py:53:21 -src/synthorg/integrations/oauth/flows/device_flow.py:56:26 -src/synthorg/integrations/oauth/flows/device_flow.py:282:32 -src/synthorg/integrations/oauth/pkce.py:30:19 -src/synthorg/integrations/oauth/pkce.py:31:23 -src/synthorg/integrations/oauth/pkce.py:32:23 -src/synthorg/integrations/oauth/token_manager.py:60:41 -src/synthorg/integrations/oauth/token_manager.py:61:38 -src/synthorg/integrations/rate_limiting/shared_state.py:47:16 -src/synthorg/integrations/rate_limiting/shared_state.py:65:23 -src/synthorg/integrations/tunnel/ngrok_adapter.py:53:20 -src/synthorg/integrations/webhooks/replay_protection.py:25:26 -src/synthorg/integrations/webhooks/replay_protection.py:26:23 -src/synthorg/integrations/webhooks/verifiers/slack_signing.py:15:18 -src/synthorg/memory/backends/inmemory/adapter.py:58:38 -src/synthorg/memory/backends/mem0/adapter.py:109:38 -src/synthorg/memory/consolidation/abstractive.py:38:19 -src/synthorg/memory/consolidation/abstractive.py:77:34 -src/synthorg/memory/consolidation/abstractive.py:78:29 -src/synthorg/memory/consolidation/config.py:351:27 -src/synthorg/memory/consolidation/density.py:37:15 -src/synthorg/memory/consolidation/density.py:38:21 -src/synthorg/memory/consolidation/density.py:39:22 -src/synthorg/memory/consolidation/density.py:40:18 -src/synthorg/memory/consolidation/density.py:41:25 -src/synthorg/memory/consolidation/density.py:43:24 -src/synthorg/memory/consolidation/dual_mode_strategy.py:40:27 -src/synthorg/memory/consolidation/dual_mode_strategy.py:41:23 -src/synthorg/memory/consolidation/extractive.py:114:25 -src/synthorg/memory/consolidation/extractive.py:115:29 -src/synthorg/memory/consolidation/service.py:50:21 -src/synthorg/memory/consolidation/service.py:51:25 -src/synthorg/memory/consolidation/service.py:52:25 -src/synthorg/memory/consolidation/service.py:58:26 -src/synthorg/memory/consolidation/simple_strategy.py:24:27 -src/synthorg/memory/consolidation/simple_strategy.py:26:27 -src/synthorg/memory/consolidation/simple_strategy.py:28:23 -src/synthorg/memory/consolidation/two_tier_strategy.py:39:23 -src/synthorg/memory/embedding/fine_tune.py:182:30 -src/synthorg/memory/embedding/fine_tune.py:285:17 -src/synthorg/memory/embedding/fine_tune.py:409:18 -src/synthorg/memory/embedding/fine_tune.py:410:27 -src/synthorg/memory/embedding/fine_tune.py:411:25 -src/synthorg/memory/embedding/fine_tune.py:412:22 -src/synthorg/memory/embedding/fine_tune.py:645:13 -src/synthorg/memory/fine_tune_plan.py:40:36 -src/synthorg/memory/procedural/capture/success_capture.py:50:35 -src/synthorg/memory/procedural/propagation/department_scoped.py:31:42 -src/synthorg/memory/procedural/propagation/role_scoped.py:31:42 -src/synthorg/memory/procedural/pruning/pareto_strategy.py:32:42 -src/synthorg/memory/procedural/pruning/ttl_strategy.py:25:43 -src/synthorg/memory/procedural/trajectory_aggregator.py:124:56 -src/synthorg/memory/ranking.py:331:13 -src/synthorg/memory/ranking.py:332:23 -src/synthorg/memory/ranking.py:454:30 -src/synthorg/memory/retrieval/hierarchical/supervisor.py:80:29 -src/synthorg/memory/retrieval/hierarchical/workers.py:42:33 -src/synthorg/memory/retrieval/reranking/cache.py:26:23 -src/synthorg/memory/retrieval/reranking/cache.py:27:20 -src/synthorg/memory/retrieval/reranking/llm_reranker.py:60:31 -src/synthorg/memory/retrieval_config.py:19:24 -src/synthorg/memory/retrieval_config.py:20:17 -src/synthorg/memory/retrieval_config.py:21:28 -src/synthorg/memory/retrieval_config.py:22:37 -src/synthorg/memory/retrieval_config.py:23:33 -src/synthorg/memory/retrieval_config.py:24:36 -src/synthorg/memory/retrieval_config.py:25:27 -src/synthorg/memory/sparse.py:80:30 -src/synthorg/memory/tools/_args.py:52:21 -src/synthorg/meta/analytics/service.py:44:28 -src/synthorg/meta/analytics/service.py:118:28 -src/synthorg/meta/analytics/service.py:186:28 -src/synthorg/meta/appliers/github_client.py:62:19 -src/synthorg/meta/appliers/prompt_applier.py:29:23 -src/synthorg/meta/appliers/prompt_applier.py:30:23 -src/synthorg/meta/chief_of_staff/inflection.py:21:11 -src/synthorg/meta/chief_of_staff/inflection.py:80:35 -src/synthorg/meta/chief_of_staff/inflection.py:81:36 -src/synthorg/meta/chief_of_staff/learning.py:42:41 -src/synthorg/meta/chief_of_staff/learning.py:115:29 -src/synthorg/meta/chief_of_staff/learning.py:116:28 -src/synthorg/meta/chief_of_staff/learning.py:117:23 -src/synthorg/meta/chief_of_staff/monitor.py:68:38 -src/synthorg/meta/chief_of_staff/outcome_store.py:50:28 -src/synthorg/meta/chief_of_staff/outcome_store.py:164:21 -src/synthorg/meta/chief_of_staff/protocol.py:68:21 -src/synthorg/meta/evolution/outcome_store.py:35:23 -src/synthorg/meta/evolution/outcome_store.py:133:26 -src/synthorg/meta/evolution/outcome_store_protocol.py:85:26 -src/synthorg/meta/guards/approval_gate.py:43:23 -src/synthorg/meta/guards/rate_limit.py:39:29 -src/synthorg/meta/guards/rate_limit.py:40:28 -src/synthorg/meta/mcp/handlers/analytics.py:61:20 -src/synthorg/meta/mcp/handlers/common_args.py:322:25 -src/synthorg/meta/reports/service.py:64:27 -src/synthorg/meta/reports/service.py:80:21 -src/synthorg/meta/rollout/ab_comparator.py:34:28 -src/synthorg/meta/rollout/ab_comparator.py:62:32 -src/synthorg/meta/rollout/ab_comparator.py:63:39 -src/synthorg/meta/rollout/ab_comparator.py:64:36 -src/synthorg/meta/rollout/ab_models.py:74:15 -src/synthorg/meta/rollout/ab_test.py:96:34 -src/synthorg/meta/rollout/ab_test.py:97:36 -src/synthorg/meta/rollout/ab_test.py:98:42 -src/synthorg/meta/rollout/ab_test.py:99:39 -src/synthorg/meta/rollout/ab_test.py:100:36 -src/synthorg/meta/rollout/ab_test.py:105:38 -src/synthorg/meta/rollout/before_after.py:69:38 -src/synthorg/meta/rollout/canary.py:54:33 -src/synthorg/meta/rollout/canary.py:58:38 -src/synthorg/meta/rollout/regression/statistical.py:118:31 -src/synthorg/meta/rollout/regression/statistical.py:119:36 -src/synthorg/meta/rollout/regression/welch.py:27:23 -src/synthorg/meta/rollout/regression/welch.py:129:19 -src/synthorg/meta/rollout/regression/welch.py:130:14 -src/synthorg/meta/rollout/regression/welch.py:131:16 -src/synthorg/meta/rules/builtin.py:40:45 -src/synthorg/meta/rules/builtin.py:87:45 -src/synthorg/meta/rules/builtin.py:133:48 -src/synthorg/meta/rules/builtin.py:178:45 -src/synthorg/meta/rules/builtin.py:225:45 -src/synthorg/meta/rules/builtin.py:269:45 -src/synthorg/meta/rules/builtin.py:316:45 -src/synthorg/meta/rules/builtin.py:367:27 -src/synthorg/meta/rules/builtin.py:368:29 -src/synthorg/meta/rules/builtin.py:420:43 -src/synthorg/meta/strategies/code_modification.py:480:22 -src/synthorg/meta/telemetry/aggregator.py:20:27 -src/synthorg/meta/telemetry/collector.py:23:22 -src/synthorg/meta/telemetry/collector.py:85:31 -src/synthorg/meta/telemetry/emitter.py:44:15 -src/synthorg/meta/telemetry/emitter.py:46:23 -src/synthorg/meta/telemetry/emitter.py:47:15 -src/synthorg/meta/telemetry/emitter.py:48:15 -src/synthorg/meta/telemetry/emitter.py:49:20 -src/synthorg/meta/telemetry/emitter.py:50:20 -src/synthorg/meta/telemetry/emitter.py:51:20 -src/synthorg/meta/telemetry/protocol.py:90:31 -src/synthorg/meta/telemetry/recommender.py:113:31 -src/synthorg/meta/telemetry/recommender.py:114:32 -src/synthorg/meta/validation/ci_validator.py:23:27 -src/synthorg/meta/validation/ci_validator.py:39:31 -src/synthorg/notifications/adapters/email.py:66:20 -src/synthorg/notifications/adapters/email.py:72:38 -src/synthorg/notifications/adapters/ntfy.py:102:41 -src/synthorg/notifications/adapters/slack.py:92:41 -src/synthorg/observability/audit_chain/tsa_client.py:162:29 -src/synthorg/observability/background_tasks.py:156:50 -src/synthorg/observability/http_handler.py:46:26 -src/synthorg/observability/http_handler.py:47:32 -src/synthorg/observability/http_handler.py:48:25 -src/synthorg/observability/http_handler.py:49:27 -src/synthorg/observability/otlp_handler.py:89:26 -src/synthorg/observability/otlp_handler.py:90:32 -src/synthorg/observability/otlp_handler.py:91:25 -src/synthorg/observability/otlp_trace_handler.py:147:53 -src/synthorg/observability/tracing/protocol.py:37:53 -src/synthorg/observability/tracing/protocol.py:60:53 -src/synthorg/ontology/drift/active.py:37:27 -src/synthorg/ontology/drift/passive.py:42:20 -src/synthorg/ontology/drift/passive.py:43:21 -src/synthorg/ontology/drift/passive.py:85:27 -src/synthorg/ontology/injection/hybrid.py:47:33 -src/synthorg/ontology/injection/prompt.py:73:33 -src/synthorg/ontology/service.py:242:21 -src/synthorg/persistence/approval_protocol.py:108:21 -src/synthorg/persistence/artifact_protocol.py:53:21 -src/synthorg/persistence/atlas.py:40:36 -src/synthorg/persistence/atlas.py:41:28 -src/synthorg/persistence/audit_protocol.py:47:21 -src/synthorg/persistence/auth_protocol.py:53:21 -src/synthorg/persistence/auth_protocol.py:62:21 -src/synthorg/persistence/connection_protocol.py:34:21 -src/synthorg/persistence/connection_protocol.py:50:21 -src/synthorg/persistence/connection_protocol.py:168:21 -src/synthorg/persistence/cost_record_protocol.py:29:21 -src/synthorg/persistence/decision_protocol.py:101:21 -src/synthorg/persistence/decision_protocol.py:125:21 -src/synthorg/persistence/fine_tune_protocol.py:36:21 -src/synthorg/persistence/fine_tune_protocol.py:80:21 -src/synthorg/persistence/integration_stubs.py:52:21 -src/synthorg/persistence/integration_stubs.py:66:21 -src/synthorg/persistence/integration_stubs.py:145:21 -src/synthorg/persistence/jsonb_capability.py:37:21 -src/synthorg/persistence/jsonb_capability.py:65:21 -src/synthorg/persistence/mcp_protocol.py:33:21 -src/synthorg/persistence/memory_protocol.py:40:21 -src/synthorg/persistence/memory_protocol.py:50:21 -src/synthorg/persistence/message_protocol.py:29:21 -src/synthorg/persistence/ontology_protocol.py:67:21 -src/synthorg/persistence/ontology_protocol.py:77:21 -src/synthorg/persistence/ontology_protocol.py:107:21 -src/synthorg/persistence/ontology_protocol.py:115:21 -src/synthorg/persistence/postgres/artifact_repo.py:202:21 -src/synthorg/persistence/postgres/audit_repository.py:102:21 -src/synthorg/persistence/postgres/audit_repository.py:359:21 -src/synthorg/persistence/postgres/audit_repository.py:384:21 -src/synthorg/persistence/postgres/connection_repo.py:212:21 -src/synthorg/persistence/postgres/connection_repo.py:256:21 -src/synthorg/persistence/postgres/decision_repo.py:438:21 -src/synthorg/persistence/postgres/decision_repo.py:514:21 -src/synthorg/persistence/postgres/escalation_repo.py:42:17 -src/synthorg/persistence/postgres/fine_tune_repo.py:244:21 -src/synthorg/persistence/postgres/fine_tune_repo.py:456:21 -src/synthorg/persistence/postgres/hr_repositories.py:120:21 -src/synthorg/persistence/postgres/mcp_installation_repo.py:134:21 -src/synthorg/persistence/postgres/ontology_drift_repo.py:105:21 -src/synthorg/persistence/postgres/ontology_drift_repo.py:132:21 -src/synthorg/persistence/postgres/ontology_entity_repo.py:226:21 -src/synthorg/persistence/postgres/ontology_entity_repo.py:263:21 -src/synthorg/persistence/postgres/org_fact_repo.py:417:21 -src/synthorg/persistence/postgres/org_fact_repo.py:473:21 -src/synthorg/persistence/postgres/provider_audit_repo.py:108:21 -src/synthorg/persistence/postgres/repositories.py:224:21 -src/synthorg/persistence/postgres/repositories.py:392:21 -src/synthorg/persistence/postgres/repositories.py:620:21 -src/synthorg/persistence/postgres/session_repo.py:127:21 -src/synthorg/persistence/postgres/session_repo.py:155:21 -src/synthorg/persistence/postgres/settings_repo.py:127:21 -src/synthorg/persistence/postgres/ssrf_violation_repo.py:143:21 -src/synthorg/persistence/postgres/user_repo.py:535:21 -src/synthorg/persistence/postgres/version_repo.py:339:21 -src/synthorg/persistence/postgres/webhook_receipt_repo.py:147:21 -src/synthorg/persistence/provider_audit_protocol.py:62:21 -src/synthorg/persistence/settings_protocol.py:56:21 -src/synthorg/persistence/sqlite/artifact_repo.py:269:21 -src/synthorg/persistence/sqlite/audit_repository.py:127:21 -src/synthorg/persistence/sqlite/connection_repo.py:231:21 -src/synthorg/persistence/sqlite/connection_repo.py:268:21 -src/synthorg/persistence/sqlite/decision_repo.py:482:21 -src/synthorg/persistence/sqlite/decision_repo.py:556:21 -src/synthorg/persistence/sqlite/escalation_repo.py:36:17 -src/synthorg/persistence/sqlite/fine_tune_repo.py:212:21 -src/synthorg/persistence/sqlite/fine_tune_repo.py:419:21 -src/synthorg/persistence/sqlite/hr_repositories.py:117:21 -src/synthorg/persistence/sqlite/mcp_installation_repo.py:109:21 -src/synthorg/persistence/sqlite/ontology_drift_repo.py:112:21 -src/synthorg/persistence/sqlite/ontology_drift_repo.py:129:21 -src/synthorg/persistence/sqlite/ontology_entity_repo.py:216:21 -src/synthorg/persistence/sqlite/ontology_entity_repo.py:253:21 -src/synthorg/persistence/sqlite/org_fact_repo.py:439:21 -src/synthorg/persistence/sqlite/org_fact_repo.py:491:21 -src/synthorg/persistence/sqlite/provider_audit_repo.py:116:21 -src/synthorg/persistence/sqlite/repositories.py:215:21 -src/synthorg/persistence/sqlite/repositories.py:373:21 -src/synthorg/persistence/sqlite/repositories.py:627:21 -src/synthorg/persistence/sqlite/session_repo.py:145:21 -src/synthorg/persistence/sqlite/session_repo.py:167:21 -src/synthorg/persistence/sqlite/settings_repo.py:98:21 -src/synthorg/persistence/sqlite/ssrf_violation_repo.py:156:21 -src/synthorg/persistence/sqlite/user_repo.py:695:21 -src/synthorg/persistence/sqlite/version_repo.py:331:21 -src/synthorg/persistence/sqlite/webhook_receipt_repo.py:126:21 -src/synthorg/persistence/ssrf_violation_protocol.py:49:21 -src/synthorg/persistence/task_protocol.py:45:21 -src/synthorg/persistence/user_protocol.py:202:21 -src/synthorg/persistence/version_protocol.py:107:21 -src/synthorg/providers/drivers/litellm_driver.py:97:24 -src/synthorg/providers/health.py:37:23 -src/synthorg/providers/health.py:38:22 -src/synthorg/providers/health.py:39:18 -src/synthorg/providers/health.py:40:24 -src/synthorg/providers/management/_capability_helpers.py:29:26 -src/synthorg/providers/management/audit_service.py:74:21 -src/synthorg/security/audit.py:30:45 -src/synthorg/security/audit.py:99:21 -src/synthorg/security/llm_evaluator.py:81:20 -src/synthorg/security/llm_evaluator.py:85:20 -src/synthorg/security/llm_evaluator.py:90:21 -src/synthorg/security/timeout/factory.py:23:22 -src/synthorg/security/timeout/policies.py:30:22 -src/synthorg/settings/subscribers/per_op_rate_limit_subscriber.py:50:33 -src/synthorg/telemetry/event_counter.py:34:22 -src/synthorg/telemetry/event_counter.py:116:23 -src/synthorg/telemetry/event_counter_protocol.py:61:23 -src/synthorg/templates/_inheritance.py:42:25 -src/synthorg/tools/design/image_generator.py:63:21 -src/synthorg/tools/design/image_generator.py:64:22 -src/synthorg/tools/factory.py:107:30 -src/synthorg/tools/invocation_tracker.py:28:23 -src/synthorg/tools/mcp/cache.py:48:24 -src/synthorg/tools/mcp/cache.py:49:29 -src/synthorg/tools/sandbox/docker_config.py:25:12 -src/synthorg/tools/sandbox/docker_config.py:26:19 -src/synthorg/tools/web/base_web_tool.py:41:33 -src/synthorg/tools/web/http_request.py:65:34 -src/synthorg/tools/web/http_request.py:66:33 -src/synthorg/tools/web/web_search.py:55:27 -src/synthorg/workers/__main__.py:48:24 diff --git a/src/synthorg/a2a/connection_types/a2a_peer.py b/src/synthorg/a2a/connection_types/a2a_peer.py index 2551684bc0..dbafea0b58 100644 --- a/src/synthorg/a2a/connection_types/a2a_peer.py +++ b/src/synthorg/a2a/connection_types/a2a_peer.py @@ -4,15 +4,19 @@ verifier registration for the unified webhook receiver. """ +from typing import Final + from synthorg.a2a.push_verifier import A2APushVerifier from synthorg.integrations.connections.models import ConnectionType from synthorg.integrations.webhooks.verifiers.protocol import ( SignatureVerifier, # noqa: TC001 ) +_DEFAULT_CLOCK_SKEW_SECONDS: Final[int] = 300 + def get_a2a_push_verifier( - clock_skew_seconds: int = 300, + clock_skew_seconds: int = _DEFAULT_CLOCK_SKEW_SECONDS, ) -> SignatureVerifier: """Create an A2A push notification verifier. diff --git a/src/synthorg/a2a/gateway.py b/src/synthorg/a2a/gateway.py index e7cc947ca5..066b617156 100644 --- a/src/synthorg/a2a/gateway.py +++ b/src/synthorg/a2a/gateway.py @@ -9,7 +9,7 @@ import asyncio import hmac import json -from typing import Any, ClassVar +from typing import Any, ClassVar, Final from litestar import Controller, Request, post from litestar.datastructures import State # noqa: TC002 @@ -57,6 +57,7 @@ from synthorg.observability.events.settings import SETTINGS_FETCH_FAILED logger = get_logger(__name__) +_DEFAULT_HTTP_STATUS: Final[int] = 400 _SUPPORTED_METHODS = frozenset( { @@ -72,7 +73,7 @@ # ``a2a.max_message_parts``. This constant mirrors that registry # default so a test harness or a boot path that bypasses # :class:`AppState` still enforces the documented ceiling. -_MAX_MESSAGE_PARTS_FALLBACK = 100 +_MAX_MESSAGE_PARTS_FALLBACK: Final[int] = 100 async def _resolve_max_message_parts(app_state: Any) -> int: @@ -627,7 +628,7 @@ def __init__( code: int, message: str, *, - http_status: int = 400, + http_status: int = _DEFAULT_HTTP_STATUS, ) -> None: super().__init__(message) self.code = code diff --git a/src/synthorg/a2a/push_verifier.py b/src/synthorg/a2a/push_verifier.py index 91c86a9214..3b2578fa60 100644 --- a/src/synthorg/a2a/push_verifier.py +++ b/src/synthorg/a2a/push_verifier.py @@ -8,6 +8,7 @@ import hashlib import hmac import math +from typing import Final from synthorg.core.clock import Clock, SystemClock from synthorg.observability import get_logger @@ -19,7 +20,7 @@ logger = get_logger(__name__) -_DEFAULT_CLOCK_SKEW_SECONDS = 300 +_DEFAULT_CLOCK_SKEW_SECONDS: Final[int] = 300 class A2APushVerifier: diff --git a/src/synthorg/api/app.py b/src/synthorg/api/app.py index f08fc0e9cd..9e526c81d0 100644 --- a/src/synthorg/api/app.py +++ b/src/synthorg/api/app.py @@ -9,7 +9,7 @@ import sys import time from pathlib import Path -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, Final from litestar import Controller, Litestar, Router from litestar.config.compression import CompressionConfig @@ -142,9 +142,7 @@ # Update both sites together if the default ever changes; otherwise a # bootstrap value will silently disagree with operator-editable # overrides resolved through ``ConfigResolver``. -_DEFAULT_TIMEOUT_CHECK_INTERVAL_SECONDS = ( - 60.0 # lint-allow: magic-numbers -- bootstrap default mirrored by ConfigResolver -) +_DEFAULT_TIMEOUT_CHECK_INTERVAL_SECONDS: Final[float] = 60.0 def _build_default_approval_timeout_scheduler( diff --git a/src/synthorg/api/auth/middleware.py b/src/synthorg/api/auth/middleware.py index fa019102a4..9d926a307e 100644 --- a/src/synthorg/api/auth/middleware.py +++ b/src/synthorg/api/auth/middleware.py @@ -3,7 +3,7 @@ import hashlib import hmac as _hmac from datetime import UTC, datetime -from typing import TYPE_CHECKING, Any +from typing import TYPE_CHECKING, Any, Final import jwt from litestar.enums import ScopeType @@ -45,7 +45,7 @@ logger = get_logger(__name__) -_BEARER_PARTS = 2 +_BEARER_PARTS: Final[int] = 2 _DEFAULT_COOKIE_NAME = "session" diff --git a/src/synthorg/api/auth/ticket_store.py b/src/synthorg/api/auth/ticket_store.py index 60562aeb5e..bab5ccebb7 100644 --- a/src/synthorg/api/auth/ticket_store.py +++ b/src/synthorg/api/auth/ticket_store.py @@ -24,7 +24,7 @@ import math import secrets import threading -from typing import ClassVar +from typing import ClassVar, Final from pydantic import BaseModel, ConfigDict @@ -44,6 +44,7 @@ ) logger = get_logger(__name__) +_DEFAULT_TTL_SECONDS: Final[float] = 30.0 class TicketLimitExceededError(PerOperationRateLimitError): @@ -108,7 +109,7 @@ class WsTicketStore: def __init__( self, - ttl_seconds: float = 30.0, + ttl_seconds: float = _DEFAULT_TTL_SECONDS, *, max_pending_per_user: int = _MAX_PENDING_PER_USER, clock: Clock | None = None, diff --git a/src/synthorg/api/boundary.py b/src/synthorg/api/boundary.py index 349dd01be7..5eec8454df 100644 --- a/src/synthorg/api/boundary.py +++ b/src/synthorg/api/boundary.py @@ -34,7 +34,7 @@ from collections.abc import ( Mapping, # noqa: TC003 -- runtime-needed via annotation introspection ) -from typing import LiteralString, overload +from typing import Final, LiteralString, overload from pydantic import BaseModel, TypeAdapter, ValidationError @@ -43,7 +43,7 @@ logger = get_logger(__name__) -_MAX_LOGGED_LOCATIONS = 5 +_MAX_LOGGED_LOCATIONS: Final[int] = 5 @overload diff --git a/src/synthorg/api/bus_bridge.py b/src/synthorg/api/bus_bridge.py index b1c20e009f..38c9ed1203 100644 --- a/src/synthorg/api/bus_bridge.py +++ b/src/synthorg/api/bus_bridge.py @@ -35,9 +35,9 @@ _SUBSCRIBER_ID: Final[str] = "__api_bridge__" _POLL_TIMEOUT: Final[float] = 1.0 """Fallback poll timeout used when no resolver is wired in.""" -_MAX_CONSECUTIVE_ERRORS: Final[int] = 30 # lint-allow: magic-numbers -- bootstrap +_MAX_CONSECUTIVE_ERRORS: Final[int] = 30 """Fallback error budget used when no resolver is wired in.""" -_STOP_DRAIN_TIMEOUT_SECONDS: Final[float] = 10.0 # lint-allow: magic-numbers -- boot +_STOP_DRAIN_TIMEOUT_SECONDS: Final[float] = 10.0 """Fallback hard deadline for the ``stop()`` drain. 10 seconds is the standard graceful-shutdown grace period across diff --git a/src/synthorg/api/controllers/activities.py b/src/synthorg/api/controllers/activities.py index 34113c9790..aeca5c1cc7 100644 --- a/src/synthorg/api/controllers/activities.py +++ b/src/synthorg/api/controllers/activities.py @@ -3,7 +3,7 @@ import asyncio from datetime import UTC, datetime, timedelta from enum import IntEnum -from typing import TYPE_CHECKING, Annotated, Any +from typing import TYPE_CHECKING, Annotated, Any, Final if TYPE_CHECKING: from collections.abc import Awaitable @@ -39,6 +39,7 @@ from synthorg.tools.invocation_record import ToolInvocationRecord # noqa: TC001 logger = get_logger(__name__) +_DEFAULT_LIMIT: Final[int] = 50 # Degraded source names -- used in responses and tests. @@ -315,7 +316,7 @@ async def list_activities( # noqa: PLR0913 request: Request[Any, Any, Any], state: State, cursor: CursorParam = None, - limit: CursorLimit = 50, + limit: CursorLimit = _DEFAULT_LIMIT, event_type: Annotated[ ActivityEventType | None, Parameter( diff --git a/src/synthorg/api/controllers/agent_identity_versions.py b/src/synthorg/api/controllers/agent_identity_versions.py index d1d6e3e766..251b4e7667 100644 --- a/src/synthorg/api/controllers/agent_identity_versions.py +++ b/src/synthorg/api/controllers/agent_identity_versions.py @@ -1,7 +1,7 @@ """Agent identity version history API -- list, get, diff, rollback.""" import asyncio -from typing import Annotated +from typing import Annotated, Final from litestar import Controller, get, post from litestar.datastructures import State # noqa: TC002 @@ -41,6 +41,7 @@ from synthorg.versioning import VersionSnapshot logger = get_logger(__name__) +_DEFAULT_LIMIT: Final[int] = 20 SnapshotT = VersionSnapshot[AgentIdentity] @@ -108,7 +109,7 @@ async def list_versions( state: State, agent_id: PathId, cursor: CursorParam = None, - limit: CursorLimit = 20, + limit: CursorLimit = _DEFAULT_LIMIT, ) -> PaginatedResponse[SnapshotT]: """List version history for an agent identity.""" secret = state.app_state.cursor_secret diff --git a/src/synthorg/api/controllers/agents.py b/src/synthorg/api/controllers/agents.py index 59d8f90179..027d793070 100644 --- a/src/synthorg/api/controllers/agents.py +++ b/src/synthorg/api/controllers/agents.py @@ -1,7 +1,7 @@ """Agent configuration, performance, activity, history, and CRUD mutations.""" import json -from typing import Any, Self +from typing import Any, Final, Self from litestar import Controller, Request, Response, delete, get, patch, post from litestar.datastructures import State # noqa: TC002 @@ -59,11 +59,12 @@ ) logger = get_logger(__name__) +_DEFAULT_LIMIT: Final[int] = 50 # Safety cap for lifecycle event queries to prevent unbounded memory # allocation. The paginate() helper already caps the returned page # to MAX_LIMIT, but the underlying fetch is uncapped without this. -_MAX_LIFECYCLE_EVENTS = 10_000 +_MAX_LIFECYCLE_EVENTS: Final[int] = 10_000 async def _resolve_agent_id( @@ -187,7 +188,7 @@ async def list_agents( self, state: State, cursor: CursorParam = None, - limit: CursorLimit = 50, + limit: CursorLimit = _DEFAULT_LIMIT, ) -> PaginatedResponse[AgentConfig]: """List all configured agents. @@ -423,7 +424,7 @@ async def get_agent_activity( state: State, agent_name: PathName, cursor: CursorParam = None, - limit: CursorLimit = 50, + limit: CursorLimit = _DEFAULT_LIMIT, ) -> PaginatedResponse[ActivityEvent]: """Get an agent's activity timeline (paginated). diff --git a/src/synthorg/api/controllers/analytics.py b/src/synthorg/api/controllers/analytics.py index f3c0398284..31183122f3 100644 --- a/src/synthorg/api/controllers/analytics.py +++ b/src/synthorg/api/controllers/analytics.py @@ -3,7 +3,7 @@ import asyncio from collections import Counter from datetime import UTC, datetime, timedelta -from typing import TYPE_CHECKING, Annotated, NamedTuple +from typing import TYPE_CHECKING, Annotated, Final, NamedTuple from litestar import Controller, get from litestar.datastructures import State # noqa: TC002 @@ -48,6 +48,7 @@ from synthorg.hr.performance.models import TaskMetricRecord logger = get_logger(__name__) +_DEFAULT_HORIZON_DAYS: Final[int] = 14 # ── Response models ──────────────────────────────────────────── @@ -642,7 +643,7 @@ async def get_forecast( le=90, description="Projection horizon in days", ), - ] = 14, + ] = _DEFAULT_HORIZON_DAYS, ) -> ApiResponse[ForecastResponse]: """Return budget spend projection. diff --git a/src/synthorg/api/controllers/approvals.py b/src/synthorg/api/controllers/approvals.py index b01d4f4385..cc2defedef 100644 --- a/src/synthorg/api/controllers/approvals.py +++ b/src/synthorg/api/controllers/approvals.py @@ -4,7 +4,7 @@ import math from datetime import UTC, datetime, timedelta from enum import StrEnum -from typing import Annotated, Any +from typing import Annotated, Any, Final from uuid import uuid4 from litestar import Controller, Request, get, post @@ -70,6 +70,7 @@ from synthorg.settings.enums import SettingNamespace logger = get_logger(__name__) +_DEFAULT_LIMIT: Final[int] = 50 _URGENCY_CRITICAL_FALLBACK_SECONDS: float = 3600.0 _URGENCY_HIGH_FALLBACK_SECONDS: float = 14400.0 @@ -516,7 +517,7 @@ async def list_approvals( # noqa: PLR0913 action_type: Annotated[str, Parameter(max_length=QUERY_MAX_LENGTH)] | None = None, cursor: CursorParam = None, - limit: CursorLimit = 50, + limit: CursorLimit = _DEFAULT_LIMIT, ) -> PaginatedResponse[ApprovalResponse]: """List approval items with optional filters. diff --git a/src/synthorg/api/controllers/artifacts.py b/src/synthorg/api/controllers/artifacts.py index b932fc4add..a03ec79597 100644 --- a/src/synthorg/api/controllers/artifacts.py +++ b/src/synthorg/api/controllers/artifacts.py @@ -1,6 +1,6 @@ """Artifact controller -- endpoints for artifact management, storage, and retrieval.""" -from typing import Annotated, Any +from typing import Annotated, Any, Final from litestar import Controller, Request, Response, delete, get, post, put from litestar.datastructures import State # noqa: TC002 @@ -43,6 +43,7 @@ ) logger = get_logger(__name__) +_DEFAULT_LIMIT: Final[int] = 50 def _service(state: State) -> ArtifactService: @@ -178,7 +179,7 @@ async def list_artifacts( # noqa: PLR0913 self, state: State, cursor: CursorParam = None, - limit: CursorLimit = 50, + limit: CursorLimit = _DEFAULT_LIMIT, task_id: TaskIdFilter = None, created_by: CreatedByFilter = None, type: TypeFilter = None, # noqa: A002 diff --git a/src/synthorg/api/controllers/audit.py b/src/synthorg/api/controllers/audit.py index 5a4ae44fb9..b3b31e48a9 100644 --- a/src/synthorg/api/controllers/audit.py +++ b/src/synthorg/api/controllers/audit.py @@ -10,7 +10,7 @@ import asyncio import json from datetime import datetime # noqa: TC003 -from typing import Annotated +from typing import Annotated, Final from litestar import Controller, get from litestar.datastructures import State # noqa: TC002 @@ -35,8 +35,9 @@ from synthorg.settings.enums import SettingNamespace logger = get_logger(__name__) +_DEFAULT_LIMIT: Final[int] = 50 -_MAX_AUDIT_QUERY = 10_000 +_MAX_AUDIT_QUERY: Final[int] = 10_000 """Fallback cap applied when no settings resolver is wired in.""" # Module-level log-once guard for the settings-resolution fallback: @@ -101,7 +102,7 @@ async def list_audit_entries( # noqa: PLR0913 since: datetime | None = None, until: datetime | None = None, cursor: CursorParam = None, - limit: CursorLimit = 50, + limit: CursorLimit = _DEFAULT_LIMIT, jsonb_contains: Annotated[str, Parameter(max_length=2048)] | None = None, jsonb_key_exists: Annotated[str, Parameter(max_length=256)] | None = None, ) -> PaginatedResponse[AuditEntry]: diff --git a/src/synthorg/api/controllers/backup.py b/src/synthorg/api/controllers/backup.py index f828c254ef..793ed363b5 100644 --- a/src/synthorg/api/controllers/backup.py +++ b/src/synthorg/api/controllers/backup.py @@ -4,7 +4,7 @@ (used by the CLI for ``synthorg backup`` / ``synthorg wipe``). """ -from typing import TYPE_CHECKING, Annotated +from typing import TYPE_CHECKING, Annotated, Final if TYPE_CHECKING: from collections.abc import Awaitable, Callable @@ -54,6 +54,7 @@ from synthorg.observability.events.idempotency import IDEMPOTENCY_CLAIM_IN_FLIGHT logger = get_logger(__name__) +_DEFAULT_LIMIT: Final[int] = 50 async def _do_backup_as_dict( @@ -184,7 +185,7 @@ async def list_backups( self, state: State, cursor: CursorParam = None, - limit: CursorLimit = 50, + limit: CursorLimit = _DEFAULT_LIMIT, ) -> PaginatedResponse[BackupInfo]: """List available backups (paginated, newest first). diff --git a/src/synthorg/api/controllers/budget.py b/src/synthorg/api/controllers/budget.py index a966649df6..9d1593eabd 100644 --- a/src/synthorg/api/controllers/budget.py +++ b/src/synthorg/api/controllers/budget.py @@ -2,7 +2,7 @@ import math from collections import defaultdict -from typing import Annotated, Self +from typing import Annotated, Final, Self from litestar import Controller, get from litestar.datastructures import State # noqa: TC002 @@ -31,6 +31,7 @@ ) logger = get_logger(__name__) +_DEFAULT_LIMIT: Final[int] = 50 class AgentSpending(BaseModel): @@ -299,7 +300,7 @@ async def list_cost_records( agent_id: Annotated[str, Parameter(max_length=QUERY_MAX_LENGTH)] | None = None, task_id: Annotated[str, Parameter(max_length=QUERY_MAX_LENGTH)] | None = None, cursor: CursorParam = None, - limit: CursorLimit = 50, + limit: CursorLimit = _DEFAULT_LIMIT, ) -> CostRecordListResponse: """List cost records with optional filters and summaries. diff --git a/src/synthorg/api/controllers/budget_config_versions.py b/src/synthorg/api/controllers/budget_config_versions.py index 762a83d036..0c8bbdc996 100644 --- a/src/synthorg/api/controllers/budget_config_versions.py +++ b/src/synthorg/api/controllers/budget_config_versions.py @@ -1,7 +1,7 @@ """Budget config version history controller -- list, get.""" import asyncio -from typing import Annotated +from typing import Annotated, Final from litestar import Controller, Response, get from litestar.datastructures import State # noqa: TC002 @@ -25,6 +25,7 @@ from synthorg.versioning import VersionSnapshot logger = get_logger(__name__) +_DEFAULT_LIMIT: Final[int] = 20 SnapshotT = VersionSnapshot[BudgetConfig] @@ -43,7 +44,7 @@ async def list_versions( self, state: State, cursor: CursorParam = None, - limit: CursorLimit = 20, + limit: CursorLimit = _DEFAULT_LIMIT, ) -> Response[PaginatedResponse[SnapshotT]]: """List version history for budget configuration.""" secret = state.app_state.cursor_secret diff --git a/src/synthorg/api/controllers/clients.py b/src/synthorg/api/controllers/clients.py index 9dd475b864..937746b014 100644 --- a/src/synthorg/api/controllers/clients.py +++ b/src/synthorg/api/controllers/clients.py @@ -1,7 +1,7 @@ """Client simulation CRUD endpoints at /clients.""" from datetime import UTC, datetime -from typing import Any +from typing import Any, Final from litestar import Controller, Request, delete, get, patch, post from litestar.datastructures import State # noqa: TC002 @@ -29,6 +29,7 @@ from synthorg.settings.bridge_configs import ClientBridgeConfig logger = get_logger(__name__) +_DEFAULT_LIMIT: Final[int] = 50 class CreateClientRequest(BaseModel): @@ -213,7 +214,7 @@ async def list_clients( self, state: State, cursor: CursorParam = None, - limit: CursorLimit = 50, + limit: CursorLimit = _DEFAULT_LIMIT, ) -> PaginatedResponse[ClientProfile]: """List all configured clients (paginated).""" app_state: AppState = state.app_state diff --git a/src/synthorg/api/controllers/company_versions.py b/src/synthorg/api/controllers/company_versions.py index 185febf7c8..43cdeff230 100644 --- a/src/synthorg/api/controllers/company_versions.py +++ b/src/synthorg/api/controllers/company_versions.py @@ -1,7 +1,7 @@ """Company version history controller -- list, get.""" import asyncio -from typing import Annotated +from typing import Annotated, Final from litestar import Controller, Response, get from litestar.datastructures import State # noqa: TC002 @@ -25,6 +25,7 @@ from synthorg.versioning import VersionSnapshot logger = get_logger(__name__) +_DEFAULT_LIMIT: Final[int] = 20 SnapshotT = VersionSnapshot[Company] @@ -43,7 +44,7 @@ async def list_versions( self, state: State, cursor: CursorParam = None, - limit: CursorLimit = 20, + limit: CursorLimit = _DEFAULT_LIMIT, ) -> Response[PaginatedResponse[SnapshotT]]: """List version history for company structure.""" secret = state.app_state.cursor_secret diff --git a/src/synthorg/api/controllers/connections.py b/src/synthorg/api/controllers/connections.py index d592a8485a..ab77930823 100644 --- a/src/synthorg/api/controllers/connections.py +++ b/src/synthorg/api/controllers/connections.py @@ -5,7 +5,7 @@ """ import copy -from typing import Annotated +from typing import Annotated, Final from litestar import Controller, delete, get, patch, post from litestar.datastructures import State # noqa: TC002 @@ -60,10 +60,11 @@ _REVEAL_GENERIC_ERROR = "Connection or credential field not found" logger = get_logger(__name__) +_DEFAULT_LIMIT: Final[int] = 50 -_MAX_BASE_URL_LEN = 2048 -_MAX_CRED_VALUE_LEN = 8192 +_MAX_BASE_URL_LEN: Final[int] = 2048 +_MAX_CRED_VALUE_LEN: Final[int] = 8192 class CreateConnectionRequest(BaseModel): @@ -153,7 +154,7 @@ async def list_connections( self, state: State, cursor: CursorParam = None, - limit: CursorLimit = 50, + limit: CursorLimit = _DEFAULT_LIMIT, ) -> PaginatedResponse[Connection]: """List all connections in the catalog (paginated). diff --git a/src/synthorg/api/controllers/coordination_metrics.py b/src/synthorg/api/controllers/coordination_metrics.py index bb2d8460eb..97a0607744 100644 --- a/src/synthorg/api/controllers/coordination_metrics.py +++ b/src/synthorg/api/controllers/coordination_metrics.py @@ -6,7 +6,7 @@ import asyncio from datetime import datetime # noqa: TC003 -from typing import Annotated +from typing import Annotated, Final from litestar import Controller, get from litestar.datastructures import State # noqa: TC002 @@ -30,8 +30,9 @@ from synthorg.settings.enums import SettingNamespace logger = get_logger(__name__) +_DEFAULT_LIMIT: Final[int] = 50 -_MAX_METRICS_QUERY = 10_000 +_MAX_METRICS_QUERY: Final[int] = 10_000 """Fallback cap applied when no settings resolver is wired in.""" # Module-level log-once guard for the settings-resolution fallback: @@ -93,7 +94,7 @@ async def list_coordination_metrics( # noqa: PLR0913 since: datetime | None = None, until: datetime | None = None, cursor: CursorParam = None, - limit: CursorLimit = 50, + limit: CursorLimit = _DEFAULT_LIMIT, ) -> PaginatedResponse[CoordinationMetricsRecord]: """Query coordination metrics with optional filters. diff --git a/src/synthorg/api/controllers/custom_rules.py b/src/synthorg/api/controllers/custom_rules.py index 9b2103b4e6..ef0b3d9cad 100644 --- a/src/synthorg/api/controllers/custom_rules.py +++ b/src/synthorg/api/controllers/custom_rules.py @@ -6,7 +6,7 @@ """ from datetime import UTC, datetime -from typing import Any +from typing import Any, Final from litestar import Controller, delete, get, patch, post from litestar.datastructures import State # noqa: TC002 @@ -53,6 +53,7 @@ ) logger = get_logger(__name__) +_DEFAULT_LIMIT: Final[int] = 50 def _service(state: State) -> CustomRulesService: @@ -202,7 +203,7 @@ async def list_rules( self, state: State, cursor: CursorParam = None, - limit: CursorLimit = 50, + limit: CursorLimit = _DEFAULT_LIMIT, ) -> PaginatedResponse[dict[str, Any]]: """List all custom rules (paginated). @@ -432,7 +433,7 @@ async def list_metrics( self, state: State, cursor: CursorParam = None, - limit: CursorLimit = 50, + limit: CursorLimit = _DEFAULT_LIMIT, ) -> PaginatedResponse[dict[str, Any]]: """List available snapshot metrics for rule building (paginated). diff --git a/src/synthorg/api/controllers/departments.py b/src/synthorg/api/controllers/departments.py index c5ed59debf..b62aa0bcb0 100644 --- a/src/synthorg/api/controllers/departments.py +++ b/src/synthorg/api/controllers/departments.py @@ -53,6 +53,7 @@ ) logger = get_logger(__name__) +_DEFAULT_LIMIT: Final[int] = 50 # ── Department ceremony policy helpers ──────────────────────── @@ -389,7 +390,7 @@ async def list_departments( self, state: State, cursor: CursorParam = None, - limit: CursorLimit = 50, + limit: CursorLimit = _DEFAULT_LIMIT, ) -> PaginatedResponse[Department]: """List all departments. diff --git a/src/synthorg/api/controllers/escalations.py b/src/synthorg/api/controllers/escalations.py index 0703fd780c..4968e8bbb3 100644 --- a/src/synthorg/api/controllers/escalations.py +++ b/src/synthorg/api/controllers/escalations.py @@ -9,7 +9,7 @@ 9457 handlers registered by :mod:`synthorg.api.exception_handlers`. """ -from typing import Any +from typing import Any, Final from litestar import Controller, Request, get, post from litestar.datastructures import State # noqa: TC002 @@ -43,6 +43,7 @@ from synthorg.observability.metrics_hub import record_escalation_outcome logger = get_logger(__name__) +_DEFAULT_LIMIT: Final[int] = 50 # ── Request / response DTOs ───────────────────────────────────── @@ -130,7 +131,7 @@ async def list_escalations( self, state: State, cursor: CursorParam = None, - limit: CursorLimit = 50, + limit: CursorLimit = _DEFAULT_LIMIT, status: EscalationStatus = EscalationStatus.PENDING, ) -> PaginatedResponse[EscalationResponse]: """Page over escalations filtered by ``status`` (default PENDING).""" diff --git a/src/synthorg/api/controllers/evaluation_config_versions.py b/src/synthorg/api/controllers/evaluation_config_versions.py index f71ae703a5..1f5ef48c89 100644 --- a/src/synthorg/api/controllers/evaluation_config_versions.py +++ b/src/synthorg/api/controllers/evaluation_config_versions.py @@ -1,7 +1,7 @@ """Evaluation config version history controller -- list, get.""" import asyncio -from typing import Annotated +from typing import Annotated, Final from litestar import Controller, Response, get from litestar.datastructures import State # noqa: TC002 @@ -25,6 +25,7 @@ from synthorg.versioning import VersionSnapshot logger = get_logger(__name__) +_DEFAULT_LIMIT: Final[int] = 20 SnapshotT = VersionSnapshot[EvaluationConfig] @@ -43,7 +44,7 @@ async def list_versions( self, state: State, cursor: CursorParam = None, - limit: CursorLimit = 20, + limit: CursorLimit = _DEFAULT_LIMIT, ) -> Response[PaginatedResponse[SnapshotT]]: """List version history for evaluation configuration.""" secret = state.app_state.cursor_secret diff --git a/src/synthorg/api/controllers/events.py b/src/synthorg/api/controllers/events.py index 5b97fa0c4b..5f291f74bc 100644 --- a/src/synthorg/api/controllers/events.py +++ b/src/synthorg/api/controllers/events.py @@ -8,7 +8,7 @@ import json as _json from collections.abc import AsyncIterator # noqa: TC003 from datetime import UTC, datetime -from typing import Annotated, Any +from typing import Annotated, Any, Final from litestar import Controller, Request, get, post from litestar.datastructures import State # noqa: TC002 @@ -54,7 +54,7 @@ logger = get_logger(__name__) -_SSE_KEEPALIVE_FALLBACK_SECONDS = 30.0 +_SSE_KEEPALIVE_FALLBACK_SECONDS: Final[float] = 30.0 """Internal constant by design: fallback keepalive interval used only when the resolver is unavailable; the canonical operator-tunable value is ``api.sse_keepalive_seconds``. diff --git a/src/synthorg/api/controllers/integration_health.py b/src/synthorg/api/controllers/integration_health.py index b23c744edf..c499c911b0 100644 --- a/src/synthorg/api/controllers/integration_health.py +++ b/src/synthorg/api/controllers/integration_health.py @@ -6,6 +6,7 @@ import asyncio from datetime import UTC, datetime +from typing import Final from litestar import Controller, get from litestar.datastructures import State # noqa: TC002 @@ -23,6 +24,7 @@ from synthorg.observability.events.integrations import HEALTH_CHECK_FAILED logger = get_logger(__name__) +_DEFAULT_LIMIT: Final[int] = 50 async def _safe_check( @@ -74,7 +76,7 @@ async def aggregate_health( self, state: State, cursor: CursorParam = None, - limit: CursorLimit = 50, + limit: CursorLimit = _DEFAULT_LIMIT, ) -> PaginatedResponse[HealthReport]: """Return paginated health reports for connections. diff --git a/src/synthorg/api/controllers/meetings.py b/src/synthorg/api/controllers/meetings.py index 91a6a23237..200d4337b3 100644 --- a/src/synthorg/api/controllers/meetings.py +++ b/src/synthorg/api/controllers/meetings.py @@ -1,7 +1,7 @@ """Meeting controller -- list, get, and trigger meetings.""" import asyncio -from typing import Annotated, Any, Self +from typing import Annotated, Any, Final, Self from litestar import Controller, Request, delete, get, post from litestar.datastructures import State # noqa: TC002 @@ -28,6 +28,7 @@ from synthorg.settings.enums import SettingNamespace logger = get_logger(__name__) +_DEFAULT_LIMIT: Final[int] = 50 # Fallback used only when the settings backend is unavailable; the # authoritative cap is ``api.max_meeting_context_keys``, resolved @@ -241,7 +242,7 @@ async def list_meetings( self, state: State, cursor: CursorParam = None, - limit: CursorLimit = 50, + limit: CursorLimit = _DEFAULT_LIMIT, status: MeetingStatus | None = None, meeting_type: Annotated[str, Parameter(max_length=QUERY_MAX_LENGTH)] | None = None, diff --git a/src/synthorg/api/controllers/messages.py b/src/synthorg/api/controllers/messages.py index bd40df5ccb..befd60775a 100644 --- a/src/synthorg/api/controllers/messages.py +++ b/src/synthorg/api/controllers/messages.py @@ -1,6 +1,6 @@ """Message controller -- read + operator-driven DELETE via MessageService.""" -from typing import Any +from typing import Any, Final from litestar import Controller, Request, delete, get from litestar.datastructures import State # noqa: TC002 @@ -21,6 +21,7 @@ ) logger = get_logger(__name__) +_DEFAULT_LIMIT: Final[int] = 50 class MessageController(Controller): @@ -36,7 +37,7 @@ async def list_messages( state: State, channel: str | None = None, cursor: CursorParam = None, - limit: CursorLimit = 50, + limit: CursorLimit = _DEFAULT_LIMIT, ) -> PaginatedResponse[Message]: """List messages, optionally filtered by channel. @@ -130,7 +131,7 @@ async def list_channels( self, state: State, cursor: CursorParam = None, - limit: CursorLimit = 50, + limit: CursorLimit = _DEFAULT_LIMIT, ) -> PaginatedResponse[Channel]: """List available message bus channels (paginated). diff --git a/src/synthorg/api/controllers/meta.py b/src/synthorg/api/controllers/meta.py index 24f7817e98..e058ccbfe0 100644 --- a/src/synthorg/api/controllers/meta.py +++ b/src/synthorg/api/controllers/meta.py @@ -1,6 +1,6 @@ """Meta improvement controller -- self-improvement proposals and signals.""" -from typing import Any +from typing import Any, Final from uuid import UUID # noqa: TC003 from litestar import Controller, get, post @@ -39,6 +39,8 @@ class ChatRequest(BaseModel): logger = get_logger(__name__) +_DEFAULT_PAGE_SIZE: Final[int] = 50 + def _settings_service_from_state(state: State) -> Any: """Return the settings service from the app state, or ``None``. @@ -89,7 +91,7 @@ async def list_rules( self, state: State, cursor: CursorParam = None, - limit: CursorLimit = 50, # lint-allow: magic-numbers -- default page size + limit: CursorLimit = _DEFAULT_PAGE_SIZE, ) -> PaginatedResponse[dict[str, Any]]: """List all signal rules (built-in + custom) with status. @@ -142,7 +144,7 @@ async def list_mcp_tools( self, state: State, cursor: CursorParam = None, - limit: CursorLimit = 50, # lint-allow: magic-numbers -- default page size + limit: CursorLimit = _DEFAULT_PAGE_SIZE, ) -> PaginatedResponse[dict[str, str]]: """List available MCP signal tools (paginated). @@ -184,7 +186,7 @@ async def list_ab_tests( self, state: State, cursor: CursorParam = None, - limit: CursorLimit = 50, # lint-allow: magic-numbers -- default page size + limit: CursorLimit = _DEFAULT_PAGE_SIZE, ) -> PaginatedResponse[dict[str, Any]]: """List active A/B tests with status and current metrics. @@ -234,7 +236,7 @@ async def list_proposals( self, state: State, cursor: CursorParam = None, - limit: CursorLimit = 50, # lint-allow: magic-numbers -- default page size + limit: CursorLimit = _DEFAULT_PAGE_SIZE, ) -> PaginatedResponse[dict[str, Any]]: """List improvement proposals from the approval store. diff --git a/src/synthorg/api/controllers/meta_analytics.py b/src/synthorg/api/controllers/meta_analytics.py index 46c533f029..bd2b232cf7 100644 --- a/src/synthorg/api/controllers/meta_analytics.py +++ b/src/synthorg/api/controllers/meta_analytics.py @@ -4,6 +4,8 @@ pattern querying, and threshold recommendations. """ +from typing import Final + from litestar import Controller, get, post from litestar.datastructures import State # noqa: TC002 from litestar.params import Parameter @@ -29,6 +31,8 @@ from synthorg.observability import get_logger logger = get_logger(__name__) +_DEFAULT_MIN_DEPLOYMENTS_FLOOR: Final[int] = 3 +_DEFAULT_LIMIT: Final[int] = 50 # Module-level singleton instances. # Created lazily by the app startup hook; None when collector is disabled. @@ -41,7 +45,7 @@ def configure_analytics_controller( collector: InMemoryAnalyticsCollector | None, recommender: DefaultThresholdRecommender | None, *, - min_deployments_floor: int = 3, + min_deployments_floor: int = _DEFAULT_MIN_DEPLOYMENTS_FLOOR, ) -> None: """Configure the analytics controller with collector and recommender. @@ -112,7 +116,7 @@ async def get_patterns( state: State, min_deployments: int = Parameter(default=3, ge=1, le=100), cursor: CursorParam = None, - limit: CursorLimit = 50, + limit: CursorLimit = _DEFAULT_LIMIT, ) -> PaginatedResponse[AggregatedPattern]: """Query aggregated cross-deployment patterns (paginated). @@ -142,7 +146,7 @@ async def get_recommendations( self, state: State, cursor: CursorParam = None, - limit: CursorLimit = 50, + limit: CursorLimit = _DEFAULT_LIMIT, ) -> PaginatedResponse[ThresholdRecommendation]: """Get threshold recommendations from aggregated data (paginated).""" collector = _require_collector() diff --git a/src/synthorg/api/controllers/oauth.py b/src/synthorg/api/controllers/oauth.py index 334530d0a9..68d4a7814c 100644 --- a/src/synthorg/api/controllers/oauth.py +++ b/src/synthorg/api/controllers/oauth.py @@ -4,7 +4,7 @@ and checking token status. """ -from typing import Annotated, Any +from typing import Annotated, Any, Final from litestar import Controller, get, post from litestar.datastructures import State # noqa: TC002 @@ -37,10 +37,10 @@ logger = get_logger(__name__) # Length caps on attacker-controllable strings. -_MAX_CONNECTION_NAME_LEN = 128 -_MAX_SCOPE_LEN = 256 -_MAX_OAUTH_CODE_LEN = 2048 -_MAX_OAUTH_STATE_LEN = 512 +_MAX_CONNECTION_NAME_LEN: Final[int] = 128 +_MAX_SCOPE_LEN: Final[int] = 256 +_MAX_OAUTH_CODE_LEN: Final[int] = 2048 +_MAX_OAUTH_STATE_LEN: Final[int] = 512 class InitiateOAuthFlowRequest(BaseModel): diff --git a/src/synthorg/api/controllers/ontology.py b/src/synthorg/api/controllers/ontology.py index 9100c517d2..6a4bb68b03 100644 --- a/src/synthorg/api/controllers/ontology.py +++ b/src/synthorg/api/controllers/ontology.py @@ -5,6 +5,7 @@ """ from datetime import UTC, datetime +from typing import Final from litestar import Controller, delete, get, post, put from litestar.datastructures import State # noqa: TC002 @@ -59,6 +60,7 @@ ) logger = get_logger(__name__) +_DEFAULT_LIMIT: Final[int] = 50 def _entity_to_response(entity: EntityDefinition) -> EntityResponse: @@ -128,7 +130,7 @@ async def list_entities( self, state: State, cursor: CursorParam = None, - limit: CursorLimit = 50, + limit: CursorLimit = _DEFAULT_LIMIT, tier: str | None = None, ) -> PaginatedResponse[EntityResponse]: """List all entity definitions, filterable by tier.""" @@ -356,7 +358,7 @@ async def list_entity_versions( state: State, name: PathName, cursor: CursorParam = None, - limit: CursorLimit = 50, + limit: CursorLimit = _DEFAULT_LIMIT, ) -> PaginatedResponse[EntityVersionResponse]: """List all versions of an entity definition.""" app_state: AppState = state.app_state @@ -447,7 +449,7 @@ async def list_drift_reports( self, state: State, cursor: CursorParam = None, - limit: CursorLimit = 50, + limit: CursorLimit = _DEFAULT_LIMIT, ) -> PaginatedResponse[DriftReportResponse]: """Get latest drift reports for all entities.""" app_state: AppState = state.app_state diff --git a/src/synthorg/api/controllers/personalities.py b/src/synthorg/api/controllers/personalities.py index 8784ff5491..4ac34e48d3 100644 --- a/src/synthorg/api/controllers/personalities.py +++ b/src/synthorg/api/controllers/personalities.py @@ -1,6 +1,6 @@ """Personality preset controller -- discovery and CRUD endpoints.""" -from typing import Any +from typing import Any, Final from litestar import Controller, delete, get, post, put from litestar.datastructures import State # noqa: TC002 @@ -28,6 +28,7 @@ ) logger = get_logger(__name__) +_DEFAULT_LIMIT: Final[int] = 50 def _to_summary(entry: PresetEntry) -> PresetSummaryResponse: @@ -73,7 +74,7 @@ async def list_presets( self, state: State, cursor: CursorParam = None, - limit: CursorLimit = 50, + limit: CursorLimit = _DEFAULT_LIMIT, ) -> PaginatedResponse[PresetSummaryResponse]: """List all personality presets (builtin + custom).""" service = _get_service(state) diff --git a/src/synthorg/api/controllers/projects.py b/src/synthorg/api/controllers/projects.py index 0339386a6f..e34bb88591 100644 --- a/src/synthorg/api/controllers/projects.py +++ b/src/synthorg/api/controllers/projects.py @@ -1,7 +1,7 @@ """Project controller -- endpoints for project listing, creation and deletion.""" import uuid -from typing import Annotated, Any +from typing import Annotated, Any, Final from litestar import Controller, Request, Response, delete, get, post from litestar.datastructures import State # noqa: TC002 @@ -31,6 +31,7 @@ ) logger = get_logger(__name__) +_DEFAULT_LIMIT: Final[int] = 50 def _service(state: State) -> ProjectService: @@ -68,7 +69,7 @@ async def list_projects( self, state: State, cursor: CursorParam = None, - limit: CursorLimit = 50, + limit: CursorLimit = _DEFAULT_LIMIT, status: ProjectStatusFilter = None, lead: LeadFilter = None, ) -> PaginatedResponse[Project]: diff --git a/src/synthorg/api/controllers/reports.py b/src/synthorg/api/controllers/reports.py index 19b57c1c18..7f0ed4c025 100644 --- a/src/synthorg/api/controllers/reports.py +++ b/src/synthorg/api/controllers/reports.py @@ -1,6 +1,6 @@ """Reports controller -- automated report generation and retrieval.""" -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, Final from litestar import Controller, get, post from litestar.datastructures import State # noqa: TC002 @@ -25,6 +25,7 @@ from synthorg.budget.report_templates import ComprehensiveReport logger = get_logger(__name__) +_DEFAULT_LIMIT: Final[int] = 50 _SERVICE_UNAVAILABLE_MSG = "Automated reporting service not configured" @@ -141,7 +142,7 @@ async def list_periods( self, state: State, cursor: CursorParam = None, - limit: CursorLimit = 50, + limit: CursorLimit = _DEFAULT_LIMIT, ) -> PaginatedResponse[str]: """List available report periods (cursor-paginated for shape parity).""" entries = tuple(p.value for p in ReportPeriod) diff --git a/src/synthorg/api/controllers/requests.py b/src/synthorg/api/controllers/requests.py index cba9023b1f..7b4bb6d0dc 100644 --- a/src/synthorg/api/controllers/requests.py +++ b/src/synthorg/api/controllers/requests.py @@ -1,6 +1,6 @@ """Client request lifecycle endpoints at /requests.""" -from typing import Any +from typing import Any, Final from litestar import Controller, Request, get, post from litestar.datastructures import State # noqa: TC002 @@ -23,6 +23,7 @@ from synthorg.observability import get_logger logger = get_logger(__name__) +_DEFAULT_LIMIT: Final[int] = 50 class CreateRequestPayload(BaseModel): @@ -86,7 +87,7 @@ async def list_requests( state: State, status: RequestStatus | None = None, cursor: CursorParam = None, - limit: CursorLimit = 50, + limit: CursorLimit = _DEFAULT_LIMIT, ) -> PaginatedResponse[ClientRequest]: """List stored client requests, optionally filtered by status.""" app_state: AppState = state.app_state diff --git a/src/synthorg/api/controllers/role_versions.py b/src/synthorg/api/controllers/role_versions.py index 39ace17b2c..0492f1b98b 100644 --- a/src/synthorg/api/controllers/role_versions.py +++ b/src/synthorg/api/controllers/role_versions.py @@ -1,7 +1,7 @@ """Role version history controller -- list, get.""" import asyncio -from typing import Annotated +from typing import Annotated, Final from litestar import Controller, Response, get from litestar.datastructures import State # noqa: TC002 @@ -25,6 +25,7 @@ from synthorg.versioning import VersionSnapshot logger = get_logger(__name__) +_DEFAULT_LIMIT: Final[int] = 20 SnapshotT = VersionSnapshot[Role] @@ -41,7 +42,7 @@ async def list_versions( state: State, role_name: str, cursor: CursorParam = None, - limit: CursorLimit = 20, + limit: CursorLimit = _DEFAULT_LIMIT, ) -> Response[PaginatedResponse[SnapshotT]]: """List version history for a specific role definition.""" secret = state.app_state.cursor_secret diff --git a/src/synthorg/api/controllers/simulations.py b/src/synthorg/api/controllers/simulations.py index a00393da8d..351b2c39fa 100644 --- a/src/synthorg/api/controllers/simulations.py +++ b/src/synthorg/api/controllers/simulations.py @@ -3,7 +3,7 @@ import asyncio import contextlib from datetime import UTC, datetime -from typing import Any +from typing import Any, Final from litestar import Controller, Request, get, post from litestar.datastructures import State # noqa: TC002 @@ -36,6 +36,7 @@ from synthorg.settings.errors import SettingNotFoundError logger = get_logger(__name__) +_DEFAULT_LIMIT: Final[int] = 50 class StartSimulationPayload(BaseModel): @@ -288,7 +289,7 @@ async def list_simulations( self, state: State, cursor: CursorParam = None, - limit: CursorLimit = 50, + limit: CursorLimit = _DEFAULT_LIMIT, ) -> PaginatedResponse[SimulationStatusResponse]: """List all known simulation runs.""" app_state: AppState = state.app_state diff --git a/src/synthorg/api/controllers/tasks.py b/src/synthorg/api/controllers/tasks.py index cafd81d8ea..3d7c6c23b5 100644 --- a/src/synthorg/api/controllers/tasks.py +++ b/src/synthorg/api/controllers/tasks.py @@ -1,6 +1,6 @@ """Task controller -- full CRUD via TaskEngine.""" -from typing import Annotated +from typing import Annotated, Final from litestar import Controller, delete, get, patch, post from litestar.datastructures import State # noqa: TC002 @@ -55,6 +55,7 @@ ) logger = get_logger(__name__) +_DEFAULT_LIMIT: Final[int] = 50 def _extract_requester(state: State) -> str: @@ -169,7 +170,7 @@ async def list_tasks( # noqa: PLR0913 assigned_to: Annotated[str, Parameter(max_length=256)] | None = None, project: Annotated[str, Parameter(max_length=256)] | None = None, cursor: CursorParam = None, - limit: CursorLimit = 50, + limit: CursorLimit = _DEFAULT_LIMIT, ) -> PaginatedResponse[Task]: """List tasks with optional filters. diff --git a/src/synthorg/api/controllers/users.py b/src/synthorg/api/controllers/users.py index 2908293dc9..14fcf968d6 100644 --- a/src/synthorg/api/controllers/users.py +++ b/src/synthorg/api/controllers/users.py @@ -2,7 +2,7 @@ import uuid from datetime import UTC, datetime -from typing import Any, LiteralString +from typing import Any, Final, LiteralString from litestar import Controller, Request, delete, get, patch, post from litestar.datastructures import State # noqa: TC002 @@ -40,6 +40,7 @@ ) logger = get_logger(__name__) +_DEFAULT_LIMIT: Final[int] = 50 def _service(state: State) -> UserService: @@ -252,7 +253,7 @@ async def list_users( self, state: State, cursor: CursorParam = None, - limit: CursorLimit = 50, + limit: CursorLimit = _DEFAULT_LIMIT, ) -> PaginatedResponse[UserResponse]: """List human users with keyset-based cursor pagination. diff --git a/src/synthorg/api/controllers/workflow_executions.py b/src/synthorg/api/controllers/workflow_executions.py index 4045814e36..b9b6c35889 100644 --- a/src/synthorg/api/controllers/workflow_executions.py +++ b/src/synthorg/api/controllers/workflow_executions.py @@ -1,6 +1,6 @@ """Workflow execution controller -- activate, list, get, cancel.""" -from typing import Any +from typing import Any, Final from litestar import Controller, Request, Response, get, post from litestar.datastructures import State # noqa: TC002 @@ -38,6 +38,8 @@ logger = get_logger(__name__) +_DEFAULT_PAGE_SIZE: Final[int] = 50 + def _extract_username(request: Request[Any, Any, Any]) -> str: """Extract username from the request, falling back to ``"api"``. @@ -149,7 +151,7 @@ async def list_executions( state: State, workflow_id: PathId, cursor: CursorParam = None, - limit: CursorLimit = 50, # lint-allow: magic-numbers -- pagination default + limit: CursorLimit = _DEFAULT_PAGE_SIZE, ) -> Response[PaginatedResponse[WorkflowExecution] | ApiResponse[None]]: """List executions for a workflow definition with cursor pagination.""" service = await _build_service(state) diff --git a/src/synthorg/api/controllers/workflow_versions.py b/src/synthorg/api/controllers/workflow_versions.py index 73680e4e5a..507d3ce864 100644 --- a/src/synthorg/api/controllers/workflow_versions.py +++ b/src/synthorg/api/controllers/workflow_versions.py @@ -2,7 +2,7 @@ import asyncio from datetime import UTC, datetime -from typing import Annotated +from typing import Annotated, Final from litestar import Controller, Response, get, post from litestar.datastructures import State # noqa: TC002 @@ -48,6 +48,7 @@ from synthorg.versioning import VersionSnapshot logger = get_logger(__name__) +_DEFAULT_LIMIT: Final[int] = 20 SnapshotT = VersionSnapshot[WorkflowDefinition] @@ -194,7 +195,7 @@ async def list_versions( state: State, workflow_id: PathId, cursor: CursorParam = None, - limit: CursorLimit = 20, + limit: CursorLimit = _DEFAULT_LIMIT, ) -> Response[PaginatedResponse[SnapshotT]]: """List version history for a workflow definition.""" secret = state.app_state.cursor_secret diff --git a/src/synthorg/api/cursor.py b/src/synthorg/api/cursor.py index 6c8c584c9e..acfd19bbfc 100644 --- a/src/synthorg/api/cursor.py +++ b/src/synthorg/api/cursor.py @@ -17,7 +17,7 @@ import hmac import json import secrets -from typing import ClassVar, Self +from typing import ClassVar, Final, Self from synthorg.api.cursor_config import CursorConfig # noqa: TC001 from synthorg.core.domain_errors import ValidationError @@ -41,14 +41,14 @@ class InvalidCursorError(ValidationError): # Minimum key length (bytes) enforced on HMAC secrets. 16 bytes = 128 # bits of entropy; below this the signing strength falls off fast. -_MIN_KEY_BYTES = 16 +_MIN_KEY_BYTES: Final[int] = 16 # Hard cap on cursor token length (characters). Litestar's # ``CursorParam`` type already enforces this at HTTP-parameter parsing, # but internal callers (tests, future RPC layers) can bypass that path. # Keeping a defense-in-depth limit here bounds worst-case decode cost # for any path that hits ``decode_cursor`` directly. -_MAX_CURSOR_LEN = 512 +_MAX_CURSOR_LEN: Final[int] = 512 class CursorSecret: @@ -235,7 +235,7 @@ def decode_cursor(token: str, *, secret: CursorSecret) -> int: # repos can swap encodings without breaking the frontend. -_MAX_KEYSET_KEY_LEN = 256 +_MAX_KEYSET_KEY_LEN: Final[int] = 256 """Cap on the raw key length before encoding (defense in depth).""" diff --git a/src/synthorg/api/dto_discovery.py b/src/synthorg/api/dto_discovery.py index cb94da2434..2ed9b991fa 100644 --- a/src/synthorg/api/dto_discovery.py +++ b/src/synthorg/api/dto_discovery.py @@ -1,5 +1,7 @@ """Discovery allowlist DTOs for the provider management API.""" +from typing import Final + from pydantic import ( BaseModel, ConfigDict, @@ -12,7 +14,7 @@ _HOST_PORT_PATTERN = r"^[a-zA-Z0-9._\[\]%-]+:[0-9]{1,5}$" -_MAX_PORT = 65535 +_MAX_PORT: Final[int] = 65535 class DiscoveryPolicyResponse(BaseModel): diff --git a/src/synthorg/api/exception_handlers.py b/src/synthorg/api/exception_handlers.py index 90ae484bc3..5f0d870d96 100644 --- a/src/synthorg/api/exception_handlers.py +++ b/src/synthorg/api/exception_handlers.py @@ -348,8 +348,8 @@ def _category_for_status( _RESERVED_LOG_KEYS: Final = frozenset( {"method", "path", "status_code", "error_type", "error"}, ) -_MAX_LOG_STR_LEN: Final = 256 # lint-allow: magic-numbers -- log clamp -_MAX_LOG_TUPLE_LEN: Final = 16 # lint-allow: magic-numbers -- log clamp +_MAX_LOG_STR_LEN: Final = 256 +_MAX_LOG_TUPLE_LEN: Final = 16 def _safe_log_attrs(exc: Exception) -> dict[str, object]: diff --git a/src/synthorg/api/lifecycle.py b/src/synthorg/api/lifecycle.py index 510b7d59f1..9aa8152ec6 100644 --- a/src/synthorg/api/lifecycle.py +++ b/src/synthorg/api/lifecycle.py @@ -63,17 +63,17 @@ # SIGKILL mid-teardown; not exposed to the settings registry because # the shape of the contract -- not its operator-tunability -- is what # the orchestrator depends on. -_TASK_ENGINE_SHUTDOWN_SECONDS: float = 8.0 # lint-allow: magic-numbers -- bootstrap -_MEETING_SCHEDULER_SHUTDOWN_SECONDS: float = 2.0 # lint-allow: magic-numbers -- boot -_PERFORMANCE_TRACKER_SHUTDOWN_SECONDS: float = 2.0 # lint-allow: magic-numbers -- boot -_BACKUP_SHUTDOWN_SECONDS: float = 5.0 # lint-allow: magic-numbers -- boot -_SETTINGS_DISPATCHER_SHUTDOWN_SECONDS: float = 2.0 # lint-allow: magic-numbers -- boot -_BRIDGE_SHUTDOWN_SECONDS: float = 2.0 # lint-allow: magic-numbers -- boot -_DISTRIBUTED_QUEUE_SHUTDOWN_SECONDS: float = 3.0 # lint-allow: magic-numbers -- boot -_MESSAGE_BUS_SHUTDOWN_SECONDS: float = 3.0 # lint-allow: magic-numbers -- boot -_PERSISTENCE_SHUTDOWN_SECONDS: float = 5.0 # lint-allow: magic-numbers -- boot -_APPROVAL_TIMEOUT_SHUTDOWN_SECONDS: float = 1.0 # lint-allow: magic-numbers -- boot -_DRAIN_TIMEOUT_SECONDS: float = 25.0 # lint-allow: magic-numbers -- bootstrap +_TASK_ENGINE_SHUTDOWN_SECONDS: float = 8.0 +_MEETING_SCHEDULER_SHUTDOWN_SECONDS: float = 2.0 +_PERFORMANCE_TRACKER_SHUTDOWN_SECONDS: float = 2.0 +_BACKUP_SHUTDOWN_SECONDS: float = 5.0 +_SETTINGS_DISPATCHER_SHUTDOWN_SECONDS: float = 2.0 +_BRIDGE_SHUTDOWN_SECONDS: float = 2.0 +_DISTRIBUTED_QUEUE_SHUTDOWN_SECONDS: float = 3.0 +_MESSAGE_BUS_SHUTDOWN_SECONDS: float = 3.0 +_PERSISTENCE_SHUTDOWN_SECONDS: float = 5.0 +_APPROVAL_TIMEOUT_SHUTDOWN_SECONDS: float = 1.0 +_DRAIN_TIMEOUT_SECONDS: float = 25.0 """Default budget for :class:`RequestDrainMiddleware`. Maximum time the on_shutdown drain hook waits for in-flight HTTP diff --git a/src/synthorg/api/lifecycle_builder.py b/src/synthorg/api/lifecycle_builder.py index 82dc405b8d..f0b07bdaf5 100644 --- a/src/synthorg/api/lifecycle_builder.py +++ b/src/synthorg/api/lifecycle_builder.py @@ -69,9 +69,9 @@ # even when a task body shields ``CancelledError`` (third-party # callees, hung I/O); the orchestrator's SIGKILL deadline must not # slip past ``graceful_shutdown`` (75s in api/server.py). -_TICKET_CLEANUP_SHUTDOWN_SECONDS: float = 2.0 # lint-allow: magic-numbers -- bootstrap -_AUDIT_RETENTION_SHUTDOWN_SECONDS: float = 2.0 # lint-allow: magic-numbers -- bootstrap -_WEBHOOK_CLEANUP_SHUTDOWN_SECONDS: float = 2.0 # lint-allow: magic-numbers -- bootstrap +_TICKET_CLEANUP_SHUTDOWN_SECONDS: float = 2.0 +_AUDIT_RETENTION_SHUTDOWN_SECONDS: float = 2.0 +_WEBHOOK_CLEANUP_SHUTDOWN_SECONDS: float = 2.0 async def _cancel_with_timeout( diff --git a/src/synthorg/api/rate_limits/in_memory.py b/src/synthorg/api/rate_limits/in_memory.py index afa77c4bb3..1601b0d48b 100644 --- a/src/synthorg/api/rate_limits/in_memory.py +++ b/src/synthorg/api/rate_limits/in_memory.py @@ -26,8 +26,8 @@ logger = get_logger(__name__) -_GC_EVERY_N_ACQUIRES: Final[int] = 1024 # lint-allow: magic-numbers -- bootstrap -_MIN_GC_HORIZON_SECONDS: Final[int] = 60 # lint-allow: magic-numbers -- bootstrap +_GC_EVERY_N_ACQUIRES: Final[int] = 1024 +_MIN_GC_HORIZON_SECONDS: Final[int] = 60 @dataclass diff --git a/src/synthorg/api/rate_limits/in_memory_inflight.py b/src/synthorg/api/rate_limits/in_memory_inflight.py index 67d16dcee5..47f44bf748 100644 --- a/src/synthorg/api/rate_limits/in_memory_inflight.py +++ b/src/synthorg/api/rate_limits/in_memory_inflight.py @@ -30,8 +30,8 @@ logger = get_logger(__name__) -_GC_EVERY_N_ACQUIRES: Final[int] = 1024 # lint-allow: magic-numbers -- bootstrap -_MIN_RETRY_AFTER_SECONDS: Final[int] = 1 # lint-allow: magic-numbers -- bootstrap +_GC_EVERY_N_ACQUIRES: Final[int] = 1024 +_MIN_RETRY_AFTER_SECONDS: Final[int] = 1 class InMemoryInflightStore(InflightStore): diff --git a/src/synthorg/api/services/org_mutations.py b/src/synthorg/api/services/org_mutations.py index cd6b310e3c..3b29f1283f 100644 --- a/src/synthorg/api/services/org_mutations.py +++ b/src/synthorg/api/services/org_mutations.py @@ -9,7 +9,7 @@ import json import math -from typing import TYPE_CHECKING, Any +from typing import TYPE_CHECKING, Any, Final from synthorg.api.concurrency import check_if_match, compute_etag from synthorg.api.dto_org import ( # noqa: TC001 @@ -41,7 +41,7 @@ logger = get_logger(__name__) -_BUDGET_PERCENT_CAP = 100.0 +_BUDGET_PERCENT_CAP: Final[float] = 100.0 class OrgMutationService(OrgAgentMutationsMixin, OrgDepartmentMutationsMixin): diff --git a/src/synthorg/api/services/ssrf_violation_service.py b/src/synthorg/api/services/ssrf_violation_service.py index 0719758c80..7eeaf63619 100644 --- a/src/synthorg/api/services/ssrf_violation_service.py +++ b/src/synthorg/api/services/ssrf_violation_service.py @@ -20,7 +20,7 @@ implication. """ -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, Final from synthorg.core.types import NotBlankStr # noqa: TC001 from synthorg.observability import get_logger, safe_error_description @@ -45,6 +45,7 @@ from synthorg.persistence.ssrf_violation_protocol import SsrfViolationRepository logger = get_logger(__name__) +_DEFAULT_LIMIT: Final[int] = 100 class SsrfViolationService: @@ -138,7 +139,7 @@ async def list_violations( self, *, status: SsrfViolationStatus | None = None, - limit: int = 100, + limit: int = _DEFAULT_LIMIT, ) -> tuple[SsrfViolation, ...]: """List violations with an optional ``status`` filter. diff --git a/src/synthorg/backup/service_archive.py b/src/synthorg/backup/service_archive.py index 4605aa3ff2..4eb74394b7 100644 --- a/src/synthorg/backup/service_archive.py +++ b/src/synthorg/backup/service_archive.py @@ -10,7 +10,7 @@ import shutil import tarfile from pathlib import Path -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, Final from pydantic import ValidationError @@ -35,11 +35,11 @@ logger = get_logger(__name__) -_CHECKSUM_CHUNK_SIZE = 65536 +_CHECKSUM_CHUNK_SIZE: Final[int] = 65536 # Internal constant by design: defensive cap on serialised backup # manifest size; prevents manifest bloat from runaway tags or notes. # Not exposed to the settings registry. -_MANIFEST_MAX_SIZE = 65536 +_MANIFEST_MAX_SIZE: Final[int] = 65536 class BackupServiceArchiveMixin: diff --git a/src/synthorg/budget/automated_reports.py b/src/synthorg/budget/automated_reports.py index 979c927962..b561fbff8f 100644 --- a/src/synthorg/budget/automated_reports.py +++ b/src/synthorg/budget/automated_reports.py @@ -12,7 +12,7 @@ import math from collections import defaultdict from datetime import UTC, datetime, timedelta -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, Final from synthorg.budget.report_config import ReportPeriod from synthorg.budget.report_templates import ( @@ -43,6 +43,7 @@ from synthorg.hr.performance.tracker import PerformanceTracker logger = get_logger(__name__) +_DEFAULT_TOP_N: Final[int] = 10 class AutomatedReportService: @@ -84,7 +85,7 @@ async def generate_spending_report( *, start: datetime, end: datetime, - top_n: int = 10, + top_n: int = _DEFAULT_TOP_N, ) -> SpendingReport: """Generate a spending report. Delegates to ``ReportGenerator``.""" return await self._report_generator.generate_report( diff --git a/src/synthorg/budget/coordination_metrics.py b/src/synthorg/budget/coordination_metrics.py index 9bb6b96db5..4fe304bdf3 100644 --- a/src/synthorg/budget/coordination_metrics.py +++ b/src/synthorg/budget/coordination_metrics.py @@ -693,11 +693,14 @@ def compute_token_speedup_ratio( ) +_DEFAULT_QUADRATIC_THRESHOLD: Final[float] = 0.5 + + def compute_message_overhead( *, team_size: int, message_count: int, - quadratic_threshold: float = 0.5, + quadratic_threshold: float = _DEFAULT_QUADRATIC_THRESHOLD, ) -> MessageOverhead: """Compute message overhead and detect O(n^2) growth. diff --git a/src/synthorg/budget/coordination_store.py b/src/synthorg/budget/coordination_store.py index 340ddead06..5da9856097 100644 --- a/src/synthorg/budget/coordination_store.py +++ b/src/synthorg/budget/coordination_store.py @@ -6,6 +6,7 @@ """ from collections import deque +from typing import Final from pydantic import AwareDatetime, BaseModel, ConfigDict, Field @@ -19,6 +20,8 @@ ) logger = get_logger(__name__) +_DEFAULT_MAX_ENTRIES: Final[int] = 10000 +_DEFAULT_LIMIT: Final[int] = 10000 class CoordinationMetricsRecord(BaseModel): @@ -53,7 +56,7 @@ class CoordinationMetricsStore: ValueError: If *max_entries* < 1. """ - def __init__(self, *, max_entries: int = 10_000) -> None: + def __init__(self, *, max_entries: int = _DEFAULT_MAX_ENTRIES) -> None: if max_entries < 1: msg = f"max_entries must be >= 1, got {max_entries}" logger.warning( @@ -89,7 +92,7 @@ def query( agent_id: str | None = None, since: AwareDatetime | None = None, until: AwareDatetime | None = None, - limit: int = 10_000, + limit: int = _DEFAULT_LIMIT, ) -> tuple[tuple[CoordinationMetricsRecord, ...], int]: """Query records with optional AND-combined filters. diff --git a/src/synthorg/budget/enforcer.py b/src/synthorg/budget/enforcer.py index ba6e85ba22..7c63ca212c 100644 --- a/src/synthorg/budget/enforcer.py +++ b/src/synthorg/budget/enforcer.py @@ -8,7 +8,7 @@ import copy from types import MappingProxyType -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, Final from synthorg.budget._enforcer_helpers import ( _apply_downgrade, @@ -83,6 +83,7 @@ from synthorg.budget.risk_enforcer import BudgetEnforcerRiskMixin logger = get_logger(__name__) +_DEFAULT_TIMEOUT_SEC: Final[float] = 5.0 class BudgetEnforcer(BudgetEnforcerRiskMixin): @@ -143,7 +144,7 @@ def __init__( # noqa: PLR0913 has_scorer=risk_scorer is not None, ) - async def stop(self, *, timeout_sec: float = 5.0) -> None: + async def stop(self, *, timeout_sec: float = _DEFAULT_TIMEOUT_SEC) -> None: """Drain pending budget-notification tasks. Mirrors :meth:`ApprovalTimeoutScheduler.stop` so the diff --git a/src/synthorg/budget/optimizer.py b/src/synthorg/budget/optimizer.py index a631dc29ca..5de60dfca5 100644 --- a/src/synthorg/budget/optimizer.py +++ b/src/synthorg/budget/optimizer.py @@ -13,7 +13,7 @@ import asyncio from datetime import UTC, datetime -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, Final from synthorg.budget._aggregation import group_by_agent from synthorg.budget._optimizer_helpers import ( @@ -62,6 +62,7 @@ from synthorg.providers.routing.resolver import ModelResolver logger = get_logger(__name__) +_DEFAULT_WINDOW_COUNT: Final[int] = 5 # Same ordering as BudgetEnforcer._ALERT_LEVEL_ORDER _ALERT_LEVEL_ORDER: dict[BudgetAlertLevel, int] = { @@ -73,7 +74,7 @@ # Maximum number of time windows for anomaly detection to avoid # excessive memory/compute from pathological inputs. -_MAX_WINDOW_COUNT = 1000 +_MAX_WINDOW_COUNT: Final[int] = 1000 class CostOptimizer: @@ -115,7 +116,7 @@ async def detect_anomalies( *, start: datetime, end: datetime, - window_count: int = 5, + window_count: int = _DEFAULT_WINDOW_COUNT, ) -> AnomalyDetectionResult: """Detect spending anomalies in the given period. diff --git a/src/synthorg/budget/quota_tracker.py b/src/synthorg/budget/quota_tracker.py index 8ccaace26f..9c3ac7124a 100644 --- a/src/synthorg/budget/quota_tracker.py +++ b/src/synthorg/budget/quota_tracker.py @@ -11,7 +11,7 @@ import asyncio import copy from datetime import UTC, datetime, timedelta -from typing import TYPE_CHECKING, NamedTuple +from typing import TYPE_CHECKING, Final, NamedTuple if TYPE_CHECKING: from collections.abc import Mapping @@ -516,7 +516,7 @@ def _build_exhaustion_reason( return " ".join(parts) -_MONTHS_PER_YEAR = 12 +_MONTHS_PER_YEAR: Final[int] = 12 _WINDOW_DELTAS: dict[QuotaWindow, timedelta] = { QuotaWindow.PER_MINUTE: timedelta(minutes=1), diff --git a/src/synthorg/budget/rebalance.py b/src/synthorg/budget/rebalance.py index 00df6038cd..998b264bc2 100644 --- a/src/synthorg/budget/rebalance.py +++ b/src/synthorg/budget/rebalance.py @@ -6,7 +6,7 @@ """ from enum import StrEnum -from typing import Any, NamedTuple +from typing import Any, Final, NamedTuple from synthorg.constants import BUDGET_ROUNDING_PRECISION from synthorg.observability import get_logger @@ -42,12 +42,15 @@ class RebalanceResult(NamedTuple): rejected: bool +_DEFAULT_MAX_BUDGET: Final[float] = 100.0 + + def compute_rebalance( existing_depts: list[dict[str, Any]], new_depts: list[dict[str, Any]], mode: RebalanceMode, *, - max_budget: float = 100.0, + max_budget: float = _DEFAULT_MAX_BUDGET, rounding_precision: int = BUDGET_ROUNDING_PRECISION, ) -> RebalanceResult: """Compute rebalanced department budgets after adding new departments. diff --git a/src/synthorg/budget/reports.py b/src/synthorg/budget/reports.py index 9f11795f07..058d4f00dc 100644 --- a/src/synthorg/budget/reports.py +++ b/src/synthorg/budget/reports.py @@ -11,7 +11,7 @@ import math from collections import defaultdict from datetime import UTC, datetime -from typing import TYPE_CHECKING, Self +from typing import TYPE_CHECKING, Final, Self from pydantic import BaseModel, ConfigDict, Field, computed_field, model_validator @@ -39,6 +39,8 @@ logger = get_logger(__name__) +_DEFAULT_TOP_N: Final[int] = 10 + # ── Report Models ───────────────────────────────────────────────── @@ -295,7 +297,7 @@ async def generate_report( *, start: datetime, end: datetime, - top_n: int = 10, # lint-allow: magic-numbers -- caller-tunable rank cap + top_n: int = _DEFAULT_TOP_N, include_period_comparison: bool = True, ) -> SpendingReport: """Generate a spending report for the given period. diff --git a/src/synthorg/budget/risk_tracker.py b/src/synthorg/budget/risk_tracker.py index 3f540b9e18..0f3ab62cb3 100644 --- a/src/synthorg/budget/risk_tracker.py +++ b/src/synthorg/budget/risk_tracker.py @@ -11,7 +11,7 @@ import asyncio import math from datetime import UTC, datetime, timedelta -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, Final from synthorg.observability import get_logger from synthorg.observability.events.risk_budget import ( @@ -31,8 +31,8 @@ logger = get_logger(__name__) -_RISK_WINDOW_HOURS = 168 # 7 days -_AUTO_PRUNE_THRESHOLD = 100_000 +_RISK_WINDOW_HOURS: Final[int] = 168 # 7 days +_AUTO_PRUNE_THRESHOLD: Final[int] = 100_000 class RiskTracker: diff --git a/src/synthorg/budget/tracker.py b/src/synthorg/budget/tracker.py index 878afe96aa..13c98a385d 100644 --- a/src/synthorg/budget/tracker.py +++ b/src/synthorg/budget/tracker.py @@ -14,7 +14,7 @@ import time from collections import OrderedDict, defaultdict from datetime import datetime, timedelta -from typing import TYPE_CHECKING, NamedTuple +from typing import TYPE_CHECKING, Final, NamedTuple from synthorg.budget._tracker_helpers import ( _aggregate, @@ -65,8 +65,8 @@ logger = get_logger(__name__) -_COST_WINDOW_HOURS = 168 # 7 days -_AUTO_PRUNE_THRESHOLD = 100_000 +_COST_WINDOW_HOURS: Final[int] = 168 # 7 days +_AUTO_PRUNE_THRESHOLD: Final[int] = 100_000 #: Default capacity of the per-tracker LRU set used to dedupe #: ``CostRecord.claim_id``. Sized as 10% of ``_AUTO_PRUNE_THRESHOLD`` @@ -76,7 +76,7 @@ #: default redelivery horizon and any reasonable in-process retry #: while keeping the LRU footprint bounded so a misbehaving caller #: spamming unique ``claim_id`` values cannot grow it without limit. -_DEFAULT_CLAIM_LRU_CAPACITY = 10_000 +_DEFAULT_CLAIM_LRU_CAPACITY: Final[int] = 10_000 class ProviderUsageSummary(NamedTuple): diff --git a/src/synthorg/client/feedback/adversarial.py b/src/synthorg/client/feedback/adversarial.py index 1709f16985..f6d8ec3c8c 100644 --- a/src/synthorg/client/feedback/adversarial.py +++ b/src/synthorg/client/feedback/adversarial.py @@ -1,11 +1,15 @@ """Adversarial strict feedback strategy for stress testing.""" +from typing import Final + from synthorg.client.models import ClientFeedback, ReviewContext from synthorg.core.types import NotBlankStr # noqa: TC001 from synthorg.observability import get_logger from synthorg.observability.events.client import CLIENT_REVIEW_COMPLETED logger = get_logger(__name__) +_DEFAULT_MIN_LENGTH: Final[int] = 200 +_DEFAULT_MIN_WORDS: Final[int] = 30 class AdversarialFeedback: @@ -27,8 +31,8 @@ def __init__( self, *, client_id: NotBlankStr, - min_length: int = 200, - min_words: int = 30, + min_length: int = _DEFAULT_MIN_LENGTH, + min_words: int = _DEFAULT_MIN_WORDS, ) -> None: """Initialize the adversarial feedback strategy. diff --git a/src/synthorg/client/feedback/scored.py b/src/synthorg/client/feedback/scored.py index 51ff9ab7c5..445dfebbad 100644 --- a/src/synthorg/client/feedback/scored.py +++ b/src/synthorg/client/feedback/scored.py @@ -2,6 +2,7 @@ import hashlib import struct +from typing import Final from synthorg.client.models import ClientFeedback, ReviewContext from synthorg.core.types import NotBlankStr # noqa: TC001 @@ -9,6 +10,7 @@ from synthorg.observability.events.client import CLIENT_REVIEW_COMPLETED logger = get_logger(__name__) +_DEFAULT_PASSING_SCORE: Final[float] = 0.7 class ScoredFeedback: @@ -38,7 +40,7 @@ def __init__( self, *, client_id: NotBlankStr, - passing_score: float = 0.7, + passing_score: float = _DEFAULT_PASSING_SCORE, strictness_multiplier: float = 1.0, ) -> None: """Initialize the scored feedback strategy. diff --git a/src/synthorg/client/generators/llm.py b/src/synthorg/client/generators/llm.py index a2c12de2fb..95ebde0bed 100644 --- a/src/synthorg/client/generators/llm.py +++ b/src/synthorg/client/generators/llm.py @@ -1,7 +1,7 @@ """LLM-backed requirement generator.""" from collections.abc import Sequence -from typing import TYPE_CHECKING, Any +from typing import TYPE_CHECKING, Any, Final from pydantic import ValidationError @@ -26,6 +26,8 @@ from synthorg.budget.tracker import CostTracker logger = get_logger(__name__) +_DEFAULT_TEMPERATURE: Final[float] = 0.7 +_DEFAULT_MAX_TOKENS: Final[int] = 2048 _DEFAULT_PERSONA = ( @@ -53,8 +55,8 @@ def __init__( # noqa: PLR0913 provider: CompletionProvider, model: NotBlankStr, persona: str = _DEFAULT_PERSONA, - temperature: float = 0.7, - max_tokens: int = 2048, + temperature: float = _DEFAULT_TEMPERATURE, + max_tokens: int = _DEFAULT_MAX_TOKENS, cost_tracker: CostTracker | None = None, ) -> None: """Initialize the LLM generator. diff --git a/src/synthorg/client/generators/procedural.py b/src/synthorg/client/generators/procedural.py index e0c3dfd045..71771cd269 100644 --- a/src/synthorg/client/generators/procedural.py +++ b/src/synthorg/client/generators/procedural.py @@ -2,6 +2,7 @@ import hashlib import random +from typing import Final from synthorg.client.models import ( GenerationContext, @@ -12,6 +13,7 @@ from synthorg.observability.events.client import CLIENT_REQUIREMENT_GENERATED logger = get_logger(__name__) +_DEFAULT_SEED: Final[int] = 42 _TITLE_TEMPLATES: tuple[str, ...] = ( @@ -63,7 +65,7 @@ class ProceduralGenerator: and fuzzing runs reproducible. """ - def __init__(self, *, seed: int = 42) -> None: + def __init__(self, *, seed: int = _DEFAULT_SEED) -> None: """Initialize the procedural generator. Args: diff --git a/src/synthorg/communication/bus/_nats_history.py b/src/synthorg/communication/bus/_nats_history.py index 05dc2fa014..a5121ea69b 100644 --- a/src/synthorg/communication/bus/_nats_history.py +++ b/src/synthorg/communication/bus/_nats_history.py @@ -5,7 +5,7 @@ durable consumer state. """ -from typing import Any +from typing import Any, Final from synthorg.communication.bus._nats_channels import ( resolve_channel_or_raise, @@ -24,6 +24,9 @@ logger = get_logger(__name__) +_DEFAULT_BATCH_SIZE: Final[int] = 100 +_DEFAULT_FETCH_TIMEOUT_SECONDS: Final[float] = 0.5 + async def create_history_scan_consumer( js: Any, @@ -71,8 +74,8 @@ async def collect_history_batches( subject: str, stream_name: str, *, - batch_size: int = 100, - fetch_timeout_seconds: float = 0.5, + batch_size: int = _DEFAULT_BATCH_SIZE, + fetch_timeout_seconds: float = _DEFAULT_FETCH_TIMEOUT_SECONDS, ) -> list[Message]: """Drain the history consumer into a list, stopping on idle timeout. @@ -154,8 +157,8 @@ async def scan_stream_for_subject( # noqa: PLR0913 *, subject: str, max_to_return: int, - batch_size: int = 100, - fetch_timeout_seconds: float = 0.5, + batch_size: int = _DEFAULT_BATCH_SIZE, + fetch_timeout_seconds: float = _DEFAULT_FETCH_TIMEOUT_SECONDS, ) -> list[Message]: """Collect the most recent messages on a subject, oldest-first. @@ -200,8 +203,8 @@ async def get_channel_history( channel_name: str, *, limit: int | None = None, - batch_size: int = 100, - fetch_timeout_seconds: float = 0.5, + batch_size: int = _DEFAULT_BATCH_SIZE, + fetch_timeout_seconds: float = _DEFAULT_FETCH_TIMEOUT_SECONDS, ) -> tuple[Message, ...]: """Get message history for a channel. diff --git a/src/synthorg/communication/conflict_resolution/escalation/config.py b/src/synthorg/communication/conflict_resolution/escalation/config.py index 8a35dbb41f..d14033385e 100644 --- a/src/synthorg/communication/conflict_resolution/escalation/config.py +++ b/src/synthorg/communication/conflict_resolution/escalation/config.py @@ -1,7 +1,7 @@ """Escalation queue configuration.""" import re -from typing import Annotated, Literal +from typing import Annotated, Final, Literal from pydantic import BaseModel, ConfigDict, Field, StringConstraints @@ -12,7 +12,7 @@ # letter/digit/underscore, starting with a letter or underscore) is # also friendly to every driver we care about. _NOTIFY_CHANNEL_PATTERN = r"^[A-Za-z_][A-Za-z0-9_]*$" -_NOTIFY_CHANNEL_MAX_LEN = 63 # PostgreSQL's NAMEDATALEN-1 +_NOTIFY_CHANNEL_MAX_LEN: Final[int] = 63 # PostgreSQL's NAMEDATALEN-1 NotifyChannel = Annotated[ str, diff --git a/src/synthorg/communication/conflict_resolution/escalation/in_memory_store.py b/src/synthorg/communication/conflict_resolution/escalation/in_memory_store.py index 46a5cd9415..2eea09827f 100644 --- a/src/synthorg/communication/conflict_resolution/escalation/in_memory_store.py +++ b/src/synthorg/communication/conflict_resolution/escalation/in_memory_store.py @@ -10,6 +10,7 @@ from collections.abc import AsyncIterator # noqa: TC003 from contextlib import asynccontextmanager from datetime import UTC, datetime +from typing import Final from synthorg.communication.conflict_resolution.escalation.models import ( Escalation, @@ -31,7 +32,7 @@ logger = get_logger(__name__) -_DEFAULT_LIMIT = 50 +_DEFAULT_LIMIT: Final[int] = 50 _DEFAULT_OFFSET = 0 diff --git a/src/synthorg/communication/conflict_resolution/escalation/notify.py b/src/synthorg/communication/conflict_resolution/escalation/notify.py index bd49528a49..5164462241 100644 --- a/src/synthorg/communication/conflict_resolution/escalation/notify.py +++ b/src/synthorg/communication/conflict_resolution/escalation/notify.py @@ -47,9 +47,7 @@ _SAFE_IDENTIFIER_PATTERN: Final[re.Pattern[str]] = re.compile( r"^[A-Za-z_][A-Za-z0-9_]*$", ) -_MAX_IDENTIFIER_LEN: Final[int] = ( - 63 # lint-allow: magic-numbers -- Postgres NAMEDATALEN-1 protocol constant. -) +_MAX_IDENTIFIER_LEN: Final[int] = 63 @runtime_checkable diff --git a/src/synthorg/communication/conflict_resolution/escalation/protocol.py b/src/synthorg/communication/conflict_resolution/escalation/protocol.py index e46ab6e6c7..fb0bc7d32c 100644 --- a/src/synthorg/communication/conflict_resolution/escalation/protocol.py +++ b/src/synthorg/communication/conflict_resolution/escalation/protocol.py @@ -1,6 +1,6 @@ """Protocols for the escalation queue backend and decision processor.""" -from typing import TYPE_CHECKING, Literal, Protocol, runtime_checkable +from typing import TYPE_CHECKING, Final, Literal, Protocol, runtime_checkable from synthorg.communication.conflict_resolution.escalation.models import ( Escalation, @@ -18,7 +18,7 @@ from collections.abc import AsyncIterator from contextlib import AbstractAsyncContextManager -_DEFAULT_LIMIT = 50 +_DEFAULT_LIMIT: Final[int] = 50 _DEFAULT_OFFSET = 0 diff --git a/src/synthorg/communication/conflict_resolution/escalation/sweeper.py b/src/synthorg/communication/conflict_resolution/escalation/sweeper.py index 77ec34b631..4dc1fadad3 100644 --- a/src/synthorg/communication/conflict_resolution/escalation/sweeper.py +++ b/src/synthorg/communication/conflict_resolution/escalation/sweeper.py @@ -12,7 +12,7 @@ import asyncio from datetime import UTC, datetime -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, Final from synthorg.communication.conflict_resolution.escalation.protocol import ( EscalationQueueStore, # noqa: TC001 @@ -31,6 +31,7 @@ from synthorg.settings.resolver import ConfigResolver logger = get_logger(__name__) +_DEFAULT_INTERVAL_SECONDS: Final[float] = 30.0 class EscalationExpirationSweeper: @@ -40,7 +41,7 @@ def __init__( self, store: EscalationQueueStore, *, - interval_seconds: float = 30.0, + interval_seconds: float = _DEFAULT_INTERVAL_SECONDS, config_resolver: ConfigResolver | None = None, ) -> None: """Initialise the sweeper. diff --git a/src/synthorg/communication/conflict_resolution/models.py b/src/synthorg/communication/conflict_resolution/models.py index 0abdd80042..0b43016ddd 100644 --- a/src/synthorg/communication/conflict_resolution/models.py +++ b/src/synthorg/communication/conflict_resolution/models.py @@ -5,7 +5,7 @@ """ from enum import StrEnum -from typing import Self +from typing import Final, Self from pydantic import ( AwareDatetime, @@ -23,7 +23,7 @@ from synthorg.core.enums import SeniorityLevel # noqa: TC001 from synthorg.core.types import NotBlankStr # noqa: TC001 -_MIN_POSITIONS = 2 +_MIN_POSITIONS: Final[int] = 2 class ConflictResolutionOutcome(StrEnum): diff --git a/src/synthorg/communication/delegation/record_store.py b/src/synthorg/communication/delegation/record_store.py index 7fdafe7d32..5d3251fe3b 100644 --- a/src/synthorg/communication/delegation/record_store.py +++ b/src/synthorg/communication/delegation/record_store.py @@ -8,7 +8,7 @@ import asyncio import threading from collections import deque -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, Final from synthorg.observability import get_logger from synthorg.observability.events.delegation import ( @@ -26,7 +26,7 @@ logger = get_logger(__name__) -_DEFAULT_MAX_RECORDS = 10_000 +_DEFAULT_MAX_RECORDS: Final[int] = 10_000 class DelegationRecordStore: diff --git a/src/synthorg/communication/event_stream/stream.py b/src/synthorg/communication/event_stream/stream.py index 309bd08829..a1adfdbc73 100644 --- a/src/synthorg/communication/event_stream/stream.py +++ b/src/synthorg/communication/event_stream/stream.py @@ -20,7 +20,7 @@ from collections import OrderedDict from dataclasses import dataclass, field from datetime import UTC, datetime -from typing import ClassVar +from typing import ClassVar, Final from uuid import uuid4 from synthorg.communication.event_stream.types import ( @@ -43,12 +43,12 @@ logger = get_logger(__name__) -_DEFAULT_MAX_QUEUE_SIZE = 256 -_DEFAULT_DEDUP_TTL_SECONDS = 60.0 -_DEFAULT_DEDUP_MAX_ENTRIES_PER_SESSION = 1024 -_DEFAULT_SUBSCRIBER_IDLE_TTL_SECONDS = 86400.0 -_DEFAULT_JANITOR_INTERVAL_SECONDS = 300.0 -_DEFAULT_JANITOR_STOP_TIMEOUT_SECONDS = 10.0 +_DEFAULT_MAX_QUEUE_SIZE: Final[int] = 256 +_DEFAULT_DEDUP_TTL_SECONDS: Final[float] = 60.0 +_DEFAULT_DEDUP_MAX_ENTRIES_PER_SESSION: Final[int] = 1024 +_DEFAULT_SUBSCRIBER_IDLE_TTL_SECONDS: Final[float] = 86400.0 +_DEFAULT_JANITOR_INTERVAL_SECONDS: Final[float] = 300.0 +_DEFAULT_JANITOR_STOP_TIMEOUT_SECONDS: Final[float] = 10.0 class EventStreamHubUnrestartableError(ConflictError): diff --git a/src/synthorg/communication/loop_prevention/dedup.py b/src/synthorg/communication/loop_prevention/dedup.py index 5571738a32..c10c42694f 100644 --- a/src/synthorg/communication/loop_prevention/dedup.py +++ b/src/synthorg/communication/loop_prevention/dedup.py @@ -2,6 +2,7 @@ import time from collections.abc import Callable # noqa: TC003 +from typing import Final from synthorg.communication.loop_prevention.models import GuardCheckOutcome from synthorg.observability import get_logger @@ -10,6 +11,7 @@ ) logger = get_logger(__name__) +_DEFAULT_WINDOW_SECONDS: Final[int] = 60 _MECHANISM = "dedup" @@ -35,7 +37,7 @@ class DelegationDeduplicator: def __init__( self, - window_seconds: int = 60, + window_seconds: int = _DEFAULT_WINDOW_SECONDS, *, clock: Callable[[], float] = time.monotonic, ) -> None: diff --git a/src/synthorg/communication/loop_prevention/rate_limit.py b/src/synthorg/communication/loop_prevention/rate_limit.py index f9e7a8aef1..395019e86c 100644 --- a/src/synthorg/communication/loop_prevention/rate_limit.py +++ b/src/synthorg/communication/loop_prevention/rate_limit.py @@ -2,6 +2,7 @@ import time from collections.abc import Callable # noqa: TC003 +from typing import Final from synthorg.communication.config import RateLimitConfig # noqa: TC001 from synthorg.communication.loop_prevention._pair_key import pair_key @@ -14,7 +15,7 @@ logger = get_logger(__name__) _MECHANISM = "rate_limit" -_DEFAULT_WINDOW_SECONDS = 60.0 # lint-allow: magic-numbers -- bootstrap +_DEFAULT_WINDOW_SECONDS: Final[float] = 60.0 class DelegationRateLimiter: diff --git a/src/synthorg/config/rate_limits.py b/src/synthorg/config/rate_limits.py index 1bf2a6c595..7ddecf9ced 100644 --- a/src/synthorg/config/rate_limits.py +++ b/src/synthorg/config/rate_limits.py @@ -7,7 +7,7 @@ 144 layer violation). """ -from typing import Any, Literal +from typing import Any, Final, Literal from pydantic import BaseModel, ConfigDict, Field, model_validator @@ -17,7 +17,7 @@ logger = get_logger(__name__) -_OVERRIDE_TUPLE_LEN = 2 +_OVERRIDE_TUPLE_LEN: Final[int] = 2 def _warn_and_raise(msg: str, **ctx: object) -> None: diff --git a/src/synthorg/constants.py b/src/synthorg/constants.py index 174efce1f3..9da699ff2f 100644 --- a/src/synthorg/constants.py +++ b/src/synthorg/constants.py @@ -1,4 +1,6 @@ """Shared constants for the synthorg package.""" -BUDGET_ROUNDING_PRECISION = 10 +from typing import Final + +BUDGET_ROUNDING_PRECISION: Final[int] = 10 """Decimal places for budget sum rounding; avoids IEEE 754 float artifacts.""" diff --git a/src/synthorg/core/auth/config.py b/src/synthorg/core/auth/config.py index 78555c639a..2a565ab052 100644 --- a/src/synthorg/core/auth/config.py +++ b/src/synthorg/core/auth/config.py @@ -1,12 +1,12 @@ """Authentication configuration.""" -from typing import Literal, Self +from typing import Final, Literal, Self from pydantic import BaseModel, ConfigDict, Field, model_validator from synthorg.core.types import NotBlankStr # noqa: TC001 -MIN_SECRET_LENGTH = 32 +MIN_SECRET_LENGTH: Final[int] = 32 DEFAULT_COOKIE_NAME = "session" DEFAULT_CSRF_COOKIE_NAME = "csrf_token" DEFAULT_CSRF_HEADER_NAME = "x-csrf-token" diff --git a/src/synthorg/core/concurrency/cas_retry.py b/src/synthorg/core/concurrency/cas_retry.py index 9f3bfd2cca..6c18c53ed4 100644 --- a/src/synthorg/core/concurrency/cas_retry.py +++ b/src/synthorg/core/concurrency/cas_retry.py @@ -27,7 +27,7 @@ T = TypeVar("T") V = TypeVar("V") -_DEFAULT_MAX_ATTEMPTS: Final[int] = 2 # lint-allow: magic-numbers -- bootstrap +_DEFAULT_MAX_ATTEMPTS: Final[int] = 2 class CASRetryHandler: diff --git a/src/synthorg/core/personality.py b/src/synthorg/core/personality.py index 2a46417047..660189d097 100644 --- a/src/synthorg/core/personality.py +++ b/src/synthorg/core/personality.py @@ -6,7 +6,7 @@ import itertools from types import MappingProxyType -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, Final from synthorg.core.enums import CollaborationPreference, ConflictApproach from synthorg.observability import get_logger @@ -22,16 +22,16 @@ # ── Weight configuration ───────────────────────────────────────── -_WEIGHT_BIG_FIVE = 0.6 -_WEIGHT_COLLABORATION = 0.2 -_WEIGHT_CONFLICT = 0.2 +_WEIGHT_BIG_FIVE: Final[float] = 0.6 +_WEIGHT_COLLABORATION: Final[float] = 0.2 +_WEIGHT_CONFLICT: Final[float] = 0.2 # Big Five dimension weights (sum to 1.0 within the Big Five component). -_BF_OPENNESS = 0.2 -_BF_CONSCIENTIOUSNESS = 0.25 -_BF_EXTRAVERSION = 0.15 -_BF_AGREEABLENESS = 0.25 -_BF_STRESS = 0.15 +_BF_OPENNESS: Final[float] = 0.2 +_BF_CONSCIENTIOUSNESS: Final[float] = 0.25 +_BF_EXTRAVERSION: Final[float] = 0.15 +_BF_AGREEABLENESS: Final[float] = 0.25 +_BF_STRESS: Final[float] = 0.15 # Collaboration adjacency: INDEPENDENT <-> PAIR <-> TEAM _COLLAB_ORDER: MappingProxyType[CollaborationPreference, int] = MappingProxyType( diff --git a/src/synthorg/engine/agent_engine_post_exec.py b/src/synthorg/engine/agent_engine_post_exec.py index 21ed16d122..e27bbcf2c9 100644 --- a/src/synthorg/engine/agent_engine_post_exec.py +++ b/src/synthorg/engine/agent_engine_post_exec.py @@ -3,7 +3,7 @@ import asyncio import contextlib import time -from typing import TYPE_CHECKING, Any +from typing import TYPE_CHECKING, Any, Final from synthorg.engine.checkpoint.resume import ( cleanup_checkpoint_artifacts, @@ -45,7 +45,7 @@ logger = get_logger(__name__) -_TRANSITION_REASON_CRITERIA_CAP = 5 +_TRANSITION_REASON_CRITERIA_CAP: Final[int] = 5 class AgentEnginePostExecMixin: diff --git a/src/synthorg/engine/approval_gate.py b/src/synthorg/engine/approval_gate.py index 1d215946d1..aa3d40c310 100644 --- a/src/synthorg/engine/approval_gate.py +++ b/src/synthorg/engine/approval_gate.py @@ -13,7 +13,7 @@ import contextlib from datetime import UTC, datetime -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, Final from uuid import uuid4 from synthorg.approval.models import EscalationInfo # noqa: TC001 @@ -50,6 +50,7 @@ from synthorg.engine.context import AgentContext logger = get_logger(__name__) +_DEFAULT_INTERRUPT_TIMEOUT_SECONDS: Final[float] = 300.0 class ApprovalGate: @@ -70,7 +71,7 @@ def __init__( # noqa: PLR0913 notification_dispatcher: NotificationDispatcher | None = None, event_hub: EventStreamHub | None = None, interrupt_store: InterruptStore | None = None, - interrupt_timeout_seconds: float = 300.0, + interrupt_timeout_seconds: float = _DEFAULT_INTERRUPT_TIMEOUT_SECONDS, ) -> None: self._park_service = park_service self._parked_context_repo = parked_context_repo diff --git a/src/synthorg/engine/classification/detectors.py b/src/synthorg/engine/classification/detectors.py index f2169e0da3..e72a2cf1e3 100644 --- a/src/synthorg/engine/classification/detectors.py +++ b/src/synthorg/engine/classification/detectors.py @@ -10,7 +10,7 @@ import re from collections import defaultdict -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, Final from synthorg.budget.coordination_config import ErrorCategory from synthorg.engine.classification.models import ( @@ -33,10 +33,11 @@ # ── Constants ─────────────────────────────────────────────────── -_MIN_TEXTS_FOR_CONTRADICTION = 2 -_MIN_TEXTS_FOR_OMISSION = 4 -_MIN_MENTIONS_FOR_DRIFT = 2 -_HIGH_DRIFT_THRESHOLD = 50.0 +_MIN_TEXTS_FOR_CONTRADICTION: Final[int] = 2 +_MIN_TEXTS_FOR_OMISSION: Final[int] = 4 +_MIN_MENTIONS_FOR_DRIFT: Final[int] = 2 +_HIGH_DRIFT_THRESHOLD: Final[float] = 50.0 +_DEFAULT_NUMERICAL_DRIFT_THRESHOLD_PERCENT: Final[float] = 5.0 # Words that are commonly capitalised but are not domain entities. _COMMON_CAPITALISED_WORDS = frozenset( @@ -273,7 +274,7 @@ def detect_logical_contradictions( def detect_numerical_drift( conversation: tuple[ChatMessage, ...], *, - threshold_percent: float = 5.0, + threshold_percent: float = _DEFAULT_NUMERICAL_DRIFT_THRESHOLD_PERCENT, ) -> tuple[ErrorFinding, ...]: """Detect numerical value drift across assistant messages. diff --git a/src/synthorg/engine/classification/loaders.py b/src/synthorg/engine/classification/loaders.py index b5e2210f93..b6691f0021 100644 --- a/src/synthorg/engine/classification/loaders.py +++ b/src/synthorg/engine/classification/loaders.py @@ -5,7 +5,7 @@ to enrich the detection context with delegation chain data). """ -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, Final from synthorg.budget.coordination_config import DetectionScope from synthorg.communication.delegation.models import DelegationRequest @@ -27,8 +27,8 @@ # Internal constants by design: defensive caps on classification-tree # recursion depth and on the length of cross-agent evidence included # in findings; not exposed to the settings registry. -_MAX_TREE_DEPTH = 5 -_SANITIZE_MAX_LENGTH = 2000 +_MAX_TREE_DEPTH: Final[int] = 5 +_SANITIZE_MAX_LENGTH: Final[int] = 2000 class SameTaskLoader: diff --git a/src/synthorg/engine/classification/semantic_detectors.py b/src/synthorg/engine/classification/semantic_detectors.py index 49c366a007..ee4364c1d7 100644 --- a/src/synthorg/engine/classification/semantic_detectors.py +++ b/src/synthorg/engine/classification/semantic_detectors.py @@ -8,7 +8,7 @@ import json from types import MappingProxyType -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, Final from synthorg.budget.call_category import LLMCallCategory from synthorg.budget.coordination_config import ( @@ -45,14 +45,15 @@ from synthorg.providers.base import BaseCompletionProvider logger = get_logger(__name__) +_DEFAULT_MAX_TOKENS: Final[int] = 1024 -_SANITIZE_MAX_LENGTH = 2000 +_SANITIZE_MAX_LENGTH: Final[int] = 2000 # Cost reserved per LLM semantic detector invocation. Small enough # that the reservation gate admits several concurrent detectors # inside a reasonable per-run budget, large enough that a runaway # provider cannot silently overshoot. Actual cost is reconciled via # ``ClassificationBudgetTracker.settle`` once the call completes. -_ESTIMATED_LLM_COST = 0.001 +_ESTIMATED_LLM_COST: Final[float] = 0.001 _SEVERITY_MAP: MappingProxyType[str, ErrorSeverity] = MappingProxyType( { "low": ErrorSeverity.LOW, @@ -218,7 +219,7 @@ def __init__( # noqa: PLR0913 model_id: str, budget_tracker: ClassificationBudgetTracker | None = None, temperature: float = 0.0, - max_tokens: int = 1024, + max_tokens: int = _DEFAULT_MAX_TOKENS, cost_tracker: CostTracker | None = None, ) -> None: self._provider = provider diff --git a/src/synthorg/engine/classification/sinks.py b/src/synthorg/engine/classification/sinks.py index 0da4d3a88b..ea55332c7d 100644 --- a/src/synthorg/engine/classification/sinks.py +++ b/src/synthorg/engine/classification/sinks.py @@ -9,7 +9,7 @@ import time from datetime import UTC, datetime from types import MappingProxyType -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, Final from synthorg.budget.coordination_config import ErrorCategory from synthorg.engine.classification.models import ErrorSeverity @@ -33,6 +33,7 @@ from synthorg.notifications.dispatcher import NotificationDispatcher logger = get_logger(__name__) +_DEFAULT_WINDOW_SECONDS: Final[float] = 60.0 _SEVERITY_MAP: MappingProxyType[ErrorSeverity, NotificationSeverity] = MappingProxyType( copy.deepcopy( @@ -265,7 +266,7 @@ def __init__( *, min_severity: ErrorSeverity = ErrorSeverity.HIGH, max_events_per_window: int = 1, - window_seconds: float = 60.0, + window_seconds: float = _DEFAULT_WINDOW_SECONDS, clock: Callable[[], float] | None = None, ) -> None: self._dispatcher = dispatcher diff --git a/src/synthorg/engine/classification/taxonomy_store.py b/src/synthorg/engine/classification/taxonomy_store.py index 2cd48222b0..bbec639fe2 100644 --- a/src/synthorg/engine/classification/taxonomy_store.py +++ b/src/synthorg/engine/classification/taxonomy_store.py @@ -16,7 +16,7 @@ import asyncio import copy from collections import deque -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, Final from synthorg.core.types import NotBlankStr from synthorg.engine.classification.models import ErrorSeverity @@ -44,7 +44,7 @@ logger = get_logger(__name__) -_DEFAULT_MAX_RESULTS = 10_000 +_DEFAULT_MAX_RESULTS: Final[int] = 10_000 """Default ring-buffer capacity. At ~50 results per minute under heavy load this covers the last 3.5 diff --git a/src/synthorg/engine/compaction/epistemic.py b/src/synthorg/engine/compaction/epistemic.py index e1bd503547..da7927f20e 100644 --- a/src/synthorg/engine/compaction/epistemic.py +++ b/src/synthorg/engine/compaction/epistemic.py @@ -10,6 +10,7 @@ """ import re +from typing import Final from synthorg.core.enums import Complexity from synthorg.observability import get_logger @@ -55,7 +56,7 @@ # Marker count thresholds per complexity tier. _COMPLEX_THRESHOLD = 1 -_SIMPLE_THRESHOLD = 3 +_SIMPLE_THRESHOLD: Final[int] = 3 def count_epistemic_markers(text: str) -> int: @@ -97,10 +98,13 @@ def should_preserve_message( return count >= threshold +_DEFAULT_MARKER_MAX_CHARS: Final[int] = 200 + + def extract_marker_sentences( text: str, *, - max_chars: int = 200, + max_chars: int = _DEFAULT_MARKER_MAX_CHARS, ) -> str: """Extract sentences containing epistemic markers. diff --git a/src/synthorg/engine/decomposition/classifier.py b/src/synthorg/engine/decomposition/classifier.py index 1daaaa2cbc..8b07432065 100644 --- a/src/synthorg/engine/decomposition/classifier.py +++ b/src/synthorg/engine/decomposition/classifier.py @@ -5,7 +5,7 @@ """ import re -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, Final from synthorg.core.enums import TaskStructure from synthorg.observability import get_logger @@ -40,7 +40,7 @@ ) # Artifact count at or below which sequential is favoured -_ARTIFACT_COUNT_THRESHOLD = 4 +_ARTIFACT_COUNT_THRESHOLD: Final[int] = 4 class TaskStructureClassifier: diff --git a/src/synthorg/engine/evolution/guards/rate_limit.py b/src/synthorg/engine/evolution/guards/rate_limit.py index 89e2fe7752..faf8f79c9f 100644 --- a/src/synthorg/engine/evolution/guards/rate_limit.py +++ b/src/synthorg/engine/evolution/guards/rate_limit.py @@ -2,12 +2,14 @@ import asyncio from datetime import datetime, timedelta +from typing import Final from synthorg.engine.evolution.models import AdaptationDecision, AdaptationProposal from synthorg.observability import get_logger from synthorg.observability.events.evolution import EVOLUTION_RATE_LIMITED logger = get_logger(__name__) +_DEFAULT_MAX_PER_DAY: Final[int] = 3 class RateLimitGuard: @@ -18,7 +20,7 @@ class RateLimitGuard: cleaned up. """ - def __init__(self, max_per_day: int = 3) -> None: + def __init__(self, max_per_day: int = _DEFAULT_MAX_PER_DAY) -> None: """Initialize RateLimitGuard. Args: diff --git a/src/synthorg/engine/evolution/guards/rollback.py b/src/synthorg/engine/evolution/guards/rollback.py index d4e9dbc021..810de1be66 100644 --- a/src/synthorg/engine/evolution/guards/rollback.py +++ b/src/synthorg/engine/evolution/guards/rollback.py @@ -1,6 +1,6 @@ """RollbackGuard -- monitors post-adaptation performance for regression.""" -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, Final from synthorg.engine.evolution.models import AdaptationDecision, AdaptationProposal from synthorg.observability import get_logger @@ -13,6 +13,8 @@ from synthorg.core.types import NotBlankStr logger = get_logger(__name__) +_DEFAULT_WINDOW_TASKS: Final[int] = 20 +_DEFAULT_REGRESSION_THRESHOLD: Final[float] = 0.1 class RollbackGuard: @@ -25,8 +27,8 @@ class RollbackGuard: def __init__( self, - window_tasks: int = 20, - regression_threshold: float = 0.1, + window_tasks: int = _DEFAULT_WINDOW_TASKS, + regression_threshold: float = _DEFAULT_REGRESSION_THRESHOLD, ) -> None: """Initialize RollbackGuard. diff --git a/src/synthorg/engine/evolution/proposers/composite.py b/src/synthorg/engine/evolution/proposers/composite.py index d98da3da12..9b3ecd49d9 100644 --- a/src/synthorg/engine/evolution/proposers/composite.py +++ b/src/synthorg/engine/evolution/proposers/composite.py @@ -4,7 +4,7 @@ otherwise. Returns proposals from the selected path without merging. """ -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, Final from synthorg.engine.evolution.protocols import ( AdaptationProposer, # noqa: TC001 @@ -24,7 +24,7 @@ logger = get_logger(__name__) -_QUALITY_DECLINE_THRESHOLD = 5.0 +_QUALITY_DECLINE_THRESHOLD: Final[float] = 5.0 """Quality score threshold below which failure path is triggered.""" diff --git a/src/synthorg/engine/evolution/proposers/self_report.py b/src/synthorg/engine/evolution/proposers/self_report.py index a5737e7c6b..f787d707a8 100644 --- a/src/synthorg/engine/evolution/proposers/self_report.py +++ b/src/synthorg/engine/evolution/proposers/self_report.py @@ -7,7 +7,7 @@ Never proposes identity axis (too risky for self-report). """ -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, Final from synthorg.core.types import NotBlankStr from synthorg.engine.evolution.models import ( @@ -26,8 +26,10 @@ from synthorg.engine.evolution.protocols import EvolutionContext logger = get_logger(__name__) +_DEFAULT_TEMPERATURE: Final[float] = 0.3 +_DEFAULT_MAX_TOKENS: Final[int] = 1000 -_HIGH_QUALITY_THRESHOLD = 9.0 +_HIGH_QUALITY_THRESHOLD: Final[float] = 9.0 """Quality score threshold for strategy adaptation proposals.""" @@ -50,8 +52,8 @@ def __init__( provider: CompletionProvider, *, model: str, - temperature: float = 0.3, - max_tokens: int = 1000, + temperature: float = _DEFAULT_TEMPERATURE, + max_tokens: int = _DEFAULT_MAX_TOKENS, ) -> None: self._provider = provider self._model = model diff --git a/src/synthorg/engine/evolution/proposers/separate_analyzer.py b/src/synthorg/engine/evolution/proposers/separate_analyzer.py index 00c22d4834..a391b066a6 100644 --- a/src/synthorg/engine/evolution/proposers/separate_analyzer.py +++ b/src/synthorg/engine/evolution/proposers/separate_analyzer.py @@ -42,6 +42,8 @@ from synthorg.engine.evolution.protocols import EvolutionContext logger = get_logger(__name__) +_DEFAULT_TEMPERATURE: Final[float] = 0.3 +_DEFAULT_MAX_TOKENS: Final[int] = 2000 _UNTRUSTED_TAGS: Final[tuple[str, ...]] = (TAG_TASK_FACT, TAG_UNTRUSTED_ARTIFACT) @@ -253,8 +255,8 @@ def __init__( # noqa: PLR0913 provider: CompletionProvider, *, model: str, - temperature: float = 0.3, - max_tokens: int = 2000, + temperature: float = _DEFAULT_TEMPERATURE, + max_tokens: int = _DEFAULT_MAX_TOKENS, summary_cap: int = _DEFAULT_SUMMARY_CAP, cost_tracker: CostTracker | None = None, ) -> None: diff --git a/src/synthorg/engine/intake/strategies/agent_intake.py b/src/synthorg/engine/intake/strategies/agent_intake.py index 8ed417b6e9..41bd99481e 100644 --- a/src/synthorg/engine/intake/strategies/agent_intake.py +++ b/src/synthorg/engine/intake/strategies/agent_intake.py @@ -1,7 +1,7 @@ """Agent-driven intake strategy using a completion provider.""" import json -from typing import TYPE_CHECKING, Any +from typing import TYPE_CHECKING, Any, Final from pydantic import ValidationError @@ -34,6 +34,7 @@ from synthorg.providers.protocol import CompletionProvider logger = get_logger(__name__) +_DEFAULT_MAX_TOKENS: Final[int] = 512 _DEFAULT_PERSONA = ( @@ -64,7 +65,7 @@ def __init__( # noqa: PLR0913 requested_by: NotBlankStr = "intake-agent", persona: str = _DEFAULT_PERSONA, temperature: float = 0.0, - max_tokens: int = 512, + max_tokens: int = _DEFAULT_MAX_TOKENS, cost_tracker: CostTracker | None = None, ) -> None: """Initialize the agent intake strategy. diff --git a/src/synthorg/engine/loop_selector.py b/src/synthorg/engine/loop_selector.py index fdbfdc89ce..19a9fa73b5 100644 --- a/src/synthorg/engine/loop_selector.py +++ b/src/synthorg/engine/loop_selector.py @@ -13,7 +13,7 @@ ``hybrid_fallback`` can redirect hybrid to another loop type. """ -from typing import TYPE_CHECKING, Self +from typing import TYPE_CHECKING, Final, Self from pydantic import BaseModel, ConfigDict, Field, field_validator, model_validator @@ -49,6 +49,9 @@ ) """Loop types that ``build_execution_loop`` can instantiate.""" +_DEFAULT_BUDGET_TIGHT_THRESHOLD: Final[int] = 80 +"""Default budget utilization threshold for tight-budget downgrade.""" + class AutoLoopRule(BaseModel): """Maps a task complexity level to an execution loop type. @@ -115,7 +118,7 @@ class AutoLoopConfig(BaseModel): description="Complexity-to-loop mapping rules", ) budget_tight_threshold: int = Field( - default=80, + default=_DEFAULT_BUDGET_TIGHT_THRESHOLD, ge=0, le=100, description="Budget utilization % that triggers tight-budget mode", @@ -230,7 +233,7 @@ def select_loop_type( # noqa: PLR0913 complexity: Complexity, rules: tuple[AutoLoopRule, ...], budget_utilization_pct: float | None = None, - budget_tight_threshold: int = 80, + budget_tight_threshold: int = _DEFAULT_BUDGET_TIGHT_THRESHOLD, hybrid_fallback: str | None = None, default_loop_type: str = "react", ) -> str: diff --git a/src/synthorg/engine/middleware/behavior_tagger.py b/src/synthorg/engine/middleware/behavior_tagger.py index b3a5e0faaf..0b0462717b 100644 --- a/src/synthorg/engine/middleware/behavior_tagger.py +++ b/src/synthorg/engine/middleware/behavior_tagger.py @@ -9,6 +9,8 @@ to the company's ``AgentMiddlewareConfig.chain``. """ +from typing import Final + from synthorg.engine.loop_protocol import BehaviorTag from synthorg.engine.middleware.models import AgentMiddlewareContext # noqa: TC001 from synthorg.engine.middleware.protocol import BaseAgentMiddleware @@ -57,7 +59,7 @@ } # Output token threshold for inferring SUMMARIZATION vs CONVERSATION. -_SUMMARIZATION_TOKEN_THRESHOLD = 500 +_SUMMARIZATION_TOKEN_THRESHOLD: Final[int] = 500 class BehaviorTaggerMiddleware(BaseAgentMiddleware): diff --git a/src/synthorg/engine/middleware/coordination_constraints.py b/src/synthorg/engine/middleware/coordination_constraints.py index 726b1d841a..6b029aa66d 100644 --- a/src/synthorg/engine/middleware/coordination_constraints.py +++ b/src/synthorg/engine/middleware/coordination_constraints.py @@ -10,7 +10,7 @@ """ from datetime import UTC, datetime -from typing import TYPE_CHECKING, Protocol, runtime_checkable +from typing import TYPE_CHECKING, Final, Protocol, runtime_checkable from synthorg.core.enums import AutonomyLevel from synthorg.engine.middleware.coordination_protocol import ( @@ -40,6 +40,9 @@ from synthorg.budget.enforcer import BudgetEnforcer logger = get_logger(__name__) +_DEFAULT_ESCALATION_THRESHOLD: Final[int] = 3 +_DEFAULT_MAX_STALL_COUNT: Final[int] = 3 +_DEFAULT_MAX_RESET_COUNT: Final[int] = 2 # ── TaskLedgerMiddleware ────────────────────────────────────────── @@ -127,7 +130,7 @@ class ProgressLedgerMiddleware(BaseCoordinationMiddleware): def __init__( self, *, - escalation_threshold: int = 3, + escalation_threshold: int = _DEFAULT_ESCALATION_THRESHOLD, **_kwargs: object, ) -> None: super().__init__(name="progress_ledger") @@ -254,8 +257,8 @@ class MagenticReplanHook: def __init__( self, *, - max_stall_count: int = 3, - max_reset_count: int = 2, + max_stall_count: int = _DEFAULT_MAX_STALL_COUNT, + max_reset_count: int = _DEFAULT_MAX_RESET_COUNT, budget_enforcer: BudgetEnforcer | None = None, ) -> None: self._max_stall_count = max_stall_count diff --git a/src/synthorg/engine/middleware/semantic_drift.py b/src/synthorg/engine/middleware/semantic_drift.py index 96945c1c6c..345d789bde 100644 --- a/src/synthorg/engine/middleware/semantic_drift.py +++ b/src/synthorg/engine/middleware/semantic_drift.py @@ -9,7 +9,7 @@ """ import threading -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, Final from pydantic import BaseModel, ConfigDict, Field @@ -30,7 +30,7 @@ logger = get_logger(__name__) -_MAX_SKIPPED_LOGGED = 1024 +_MAX_SKIPPED_LOGGED: Final[int] = 1024 _SKIPPED_LOGGED: dict[str, None] = {} _SKIPPED_LOCK = threading.Lock() diff --git a/src/synthorg/engine/plan_helpers.py b/src/synthorg/engine/plan_helpers.py index 5496b52094..53e19c8193 100644 --- a/src/synthorg/engine/plan_helpers.py +++ b/src/synthorg/engine/plan_helpers.py @@ -4,7 +4,7 @@ for common plan-step operations. """ -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, Final from synthorg.observability import get_logger from synthorg.observability.events.execution import ( @@ -16,7 +16,7 @@ logger = get_logger(__name__) -_MAX_TASK_SUMMARY_LENGTH = 200 +_MAX_TASK_SUMMARY_LENGTH: Final[int] = 200 """Internal constant by design: maximum character length for task summary strings; defensive truncation prevents bloated summaries. Not exposed to the settings registry.""" diff --git a/src/synthorg/engine/quality/decomposers/llm.py b/src/synthorg/engine/quality/decomposers/llm.py index d80c6b2c55..c768ff95e9 100644 --- a/src/synthorg/engine/quality/decomposers/llm.py +++ b/src/synthorg/engine/quality/decomposers/llm.py @@ -49,6 +49,7 @@ class of parse errors, retries on malformed JSON, and prompt-injection from synthorg.providers.protocol import CompletionProvider # noqa: TC001 logger = get_logger(__name__) +_DEFAULT_MAX_PROBES_PER_CRITERION: Final[int] = 5 _DECOMPOSER_TOOL_NAME: Final[str] = "emit_atomic_probes" _DECOMPOSER_TOOL_DESCRIPTION: Final[str] = ( @@ -211,7 +212,7 @@ def __init__( *, provider: CompletionProvider, model_id: NotBlankStr, - max_probes_per_criterion: int = 5, + max_probes_per_criterion: int = _DEFAULT_MAX_PROBES_PER_CRITERION, cost_tracker: CostTracker | None = None, ) -> None: """Store dependencies and enforce a positive cap.""" diff --git a/src/synthorg/engine/routing/scorer.py b/src/synthorg/engine/routing/scorer.py index 671f6cf45c..ce0b10539a 100644 --- a/src/synthorg/engine/routing/scorer.py +++ b/src/synthorg/engine/routing/scorer.py @@ -34,8 +34,8 @@ # from settings because the validator runs at Pydantic construction # time -- before any resolver is available -- and the values describe # a documented design envelope, not an operator-tunable knob. -_DOC_WEIGHT_SUM_MAX: Final[float] = 1.1 # lint-allow: magic-numbers -- envelope -_WEIGHT_SUM_WARN_CEILING: Final[float] = 1.3 # lint-allow: magic-numbers -- ceiling +_DOC_WEIGHT_SUM_MAX: Final[float] = 1.1 +_WEIGHT_SUM_WARN_CEILING: Final[float] = 1.3 class RoutingScorerConfig(BaseModel): diff --git a/src/synthorg/engine/sanitization.py b/src/synthorg/engine/sanitization.py index 9ac5ab0204..51d9499cf1 100644 --- a/src/synthorg/engine/sanitization.py +++ b/src/synthorg/engine/sanitization.py @@ -7,6 +7,7 @@ """ import re +from typing import Final _PATH_PATTERN = re.compile( # Quoted Windows paths (handles spaces in paths like Program Files) @@ -59,7 +60,10 @@ ) -def sanitize_message(raw: str, *, max_length: int = 200) -> str: +_DEFAULT_MAX_LENGTH: Final[int] = 200 + + +def sanitize_message(raw: str, *, max_length: int = _DEFAULT_MAX_LENGTH) -> str: """Redact paths/URLs/injection markers, strip non-printable chars, cap length. Args: diff --git a/src/synthorg/engine/shutdown.py b/src/synthorg/engine/shutdown.py index 438a50c423..87b243384d 100644 --- a/src/synthorg/engine/shutdown.py +++ b/src/synthorg/engine/shutdown.py @@ -19,7 +19,7 @@ import time import types # noqa: TC003 -- used in runtime-visible annotation from collections.abc import Callable, Coroutine, Mapping, Sequence -from typing import Any, Protocol, runtime_checkable +from typing import Any, Final, Protocol, runtime_checkable from pydantic import BaseModel, ConfigDict, Field @@ -39,6 +39,8 @@ ) logger = get_logger(__name__) +_DEFAULT_GRACE_SECONDS: Final[float] = 30.0 +_DEFAULT_CLEANUP_SECONDS: Final[float] = 5.0 CleanupCallback = Callable[[], Coroutine[Any, Any, None]] """Async callback invoked during shutdown cleanup phase.""" @@ -141,8 +143,8 @@ class CooperativeTimeoutStrategy: def __init__( self, *, - grace_seconds: float = 30.0, - cleanup_seconds: float = 5.0, + grace_seconds: float = _DEFAULT_GRACE_SECONDS, + cleanup_seconds: float = _DEFAULT_CLEANUP_SECONDS, ) -> None: if grace_seconds <= 0: msg = f"grace_seconds must be positive, got {grace_seconds}" diff --git a/src/synthorg/engine/shutdown_strategies.py b/src/synthorg/engine/shutdown_strategies.py index 494b4e7e81..2a53dcfa21 100644 --- a/src/synthorg/engine/shutdown_strategies.py +++ b/src/synthorg/engine/shutdown_strategies.py @@ -8,7 +8,7 @@ import asyncio import time -from typing import TYPE_CHECKING, Any +from typing import TYPE_CHECKING, Any, Final if TYPE_CHECKING: from collections.abc import Callable, Mapping, Sequence @@ -38,6 +38,9 @@ logger = get_logger(__name__) +_DEFAULT_CLEANUP_SECONDS: Final[float] = 5.0 +_DEFAULT_GRACE_SECONDS: Final[float] = 30.0 + # ── Shared helpers ─────────────────────────────────────────────── @@ -85,7 +88,7 @@ class ImmediateCancelStrategy: billed-but-lost LLM responses. """ - def __init__(self, *, cleanup_seconds: float = 5.0) -> None: + def __init__(self, *, cleanup_seconds: float = _DEFAULT_CLEANUP_SECONDS) -> None: """Initialize the strategy. Args: @@ -187,7 +190,7 @@ def __init__( self, *, tool_timeout_seconds: float, - cleanup_seconds: float = 5.0, + cleanup_seconds: float = _DEFAULT_CLEANUP_SECONDS, ) -> None: """Initialize the strategy. @@ -348,8 +351,8 @@ class CheckpointAndStopStrategy: def __init__( self, *, - grace_seconds: float = 30.0, - cleanup_seconds: float = 5.0, + grace_seconds: float = _DEFAULT_GRACE_SECONDS, + cleanup_seconds: float = _DEFAULT_CLEANUP_SECONDS, checkpoint_saver: CheckpointSaver | None = None, ) -> None: """Initialize the strategy. diff --git a/src/synthorg/engine/stagnation/quality_erosion_detector.py b/src/synthorg/engine/stagnation/quality_erosion_detector.py index 678979249c..bd086e6fd0 100644 --- a/src/synthorg/engine/stagnation/quality_erosion_detector.py +++ b/src/synthorg/engine/stagnation/quality_erosion_detector.py @@ -9,6 +9,8 @@ complexity). """ +from typing import Final + from synthorg.engine.loop_protocol import TurnRecord # noqa: TC001 from synthorg.engine.trajectory.structural_erosion import ( compute_structural_erosion_score, @@ -28,6 +30,8 @@ ) logger = get_logger(__name__) +_DEFAULT_THRESHOLD: Final[float] = 0.5 +_DEFAULT_WINDOW_SIZE: Final[int] = 10 _MIN_WINDOW_SIZE: int = 2 _MAX_WINDOW_SIZE: int = 50 @@ -47,8 +51,8 @@ class QualityErosionDetector: def __init__( self, *, - threshold: float = 0.5, - window_size: int = 10, + threshold: float = _DEFAULT_THRESHOLD, + window_size: int = _DEFAULT_WINDOW_SIZE, ) -> None: if not 0.0 <= threshold <= 1.0: msg = f"threshold must be in [0.0, 1.0], got {threshold}" diff --git a/src/synthorg/engine/strategy/consensus.py b/src/synthorg/engine/strategy/consensus.py index d918a5856f..82cda7f49c 100644 --- a/src/synthorg/engine/strategy/consensus.py +++ b/src/synthorg/engine/strategy/consensus.py @@ -7,7 +7,7 @@ """ import difflib -from typing import Self +from typing import Final, Self from pydantic import BaseModel, ConfigDict, Field, model_validator @@ -24,6 +24,7 @@ ) logger = get_logger(__name__) +_DEFAULT_MIN_DISAGREEMENTS: Final[int] = 2 class ConsensusVelocityResult(BaseModel): @@ -82,7 +83,7 @@ class ConsensusVelocityDetector: recommendations. """ - def __init__(self, *, min_disagreements: int = 2) -> None: + def __init__(self, *, min_disagreements: int = _DEFAULT_MIN_DISAGREEMENTS) -> None: """Initialize detector. Args: diff --git a/src/synthorg/engine/trajectory/budget_guard.py b/src/synthorg/engine/trajectory/budget_guard.py index 3f28340366..a3235956f2 100644 --- a/src/synthorg/engine/trajectory/budget_guard.py +++ b/src/synthorg/engine/trajectory/budget_guard.py @@ -5,6 +5,8 @@ caller falls back to single-candidate execution. """ +from typing import Final + from synthorg.observability import get_logger from synthorg.observability.events.trajectory import ( TRAJECTORY_BUDGET_GUARD_BLOCKED, @@ -13,12 +15,15 @@ logger = get_logger(__name__) +_DEFAULT_MARGIN: Final[float] = 0.2 + + def check_trajectory_budget( *, remaining_budget: float, estimated_step_cost: float, k: int, - margin: float = 0.2, + margin: float = _DEFAULT_MARGIN, ) -> bool: """Check if K-candidate sampling fits within the budget. diff --git a/src/synthorg/engine/trajectory/structural_erosion.py b/src/synthorg/engine/trajectory/structural_erosion.py index 9a152c71a3..6ffbf76b9d 100644 --- a/src/synthorg/engine/trajectory/structural_erosion.py +++ b/src/synthorg/engine/trajectory/structural_erosion.py @@ -12,26 +12,31 @@ quality metrics to tool-integrated reasoning traces. """ +from typing import Final + from synthorg.engine.loop_protocol import TurnRecord # noqa: TC001 from synthorg.observability import get_logger logger = get_logger(__name__) # Composite weights (sum to 1.0). -_WEIGHT_DUPLICATED: float = 0.4 -_WEIGHT_CYCLOMATIC: float = 0.4 -_WEIGHT_DEAD_BRANCH: float = 0.2 +_WEIGHT_DUPLICATED: Final[float] = 0.4 +_WEIGHT_CYCLOMATIC: Final[float] = 0.4 +_WEIGHT_DEAD_BRANCH: Final[float] = 0.2 # Minimum turn thresholds for each sub-metric. -_MIN_TURNS_DUPLICATED: int = 2 -_MIN_TURNS_CYCLOMATIC: int = 4 -_MIN_TURNS_DEAD_BRANCH: int = 3 +_MIN_TURNS_DUPLICATED: Final[int] = 2 +_MIN_TURNS_CYCLOMATIC: Final[int] = 4 +_MIN_TURNS_DEAD_BRANCH: Final[int] = 3 + +# Default sliding-window size for the four detect_* helpers. +_DEFAULT_WINDOW_SIZE: Final[int] = 10 def detect_duplicated_blocks( turns: tuple[TurnRecord, ...], *, - window_size: int = 10, + window_size: int = _DEFAULT_WINDOW_SIZE, ) -> float: """Fraction of turns whose fingerprints duplicate an earlier turn. @@ -66,7 +71,7 @@ def detect_duplicated_blocks( def compute_cyclomatic_complexity_delta( turns: tuple[TurnRecord, ...], *, - window_size: int = 10, + window_size: int = _DEFAULT_WINDOW_SIZE, ) -> float: """Growth in distinct tool-call patterns between window halves. @@ -104,7 +109,7 @@ def compute_cyclomatic_complexity_delta( def detect_dead_branches( turns: tuple[TurnRecord, ...], *, - window_size: int = 10, + window_size: int = _DEFAULT_WINDOW_SIZE, ) -> float: """Fraction of tool calls whose results were never consumed. @@ -144,7 +149,7 @@ def detect_dead_branches( def compute_structural_erosion_score( turns: tuple[TurnRecord, ...], *, - window_size: int = 10, + window_size: int = _DEFAULT_WINDOW_SIZE, ) -> float: """Composite structural erosion score. diff --git a/src/synthorg/engine/workflow/condition_eval.py b/src/synthorg/engine/workflow/condition_eval.py index bd7d311847..fc3db5d49c 100644 --- a/src/synthorg/engine/workflow/condition_eval.py +++ b/src/synthorg/engine/workflow/condition_eval.py @@ -34,7 +34,7 @@ """ import re -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, Final from synthorg.observability import get_logger from synthorg.observability.events.condition_eval import ( @@ -47,7 +47,7 @@ logger = get_logger(__name__) # Maximum number of tokens accepted before rejecting as too complex. -_MAX_TOKEN_COUNT = 256 +_MAX_TOKEN_COUNT: Final[int] = 256 def _eval_comparison( diff --git a/src/synthorg/engine/workflow/sprint_velocity.py b/src/synthorg/engine/workflow/sprint_velocity.py index bda28bcdc7..7609f88c6f 100644 --- a/src/synthorg/engine/workflow/sprint_velocity.py +++ b/src/synthorg/engine/workflow/sprint_velocity.py @@ -4,7 +4,7 @@ velocity from completed sprints and computing rolling averages. """ -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, Final from pydantic import BaseModel, ConfigDict, Field, computed_field @@ -141,9 +141,12 @@ def record_velocity( return record +_DEFAULT_WINDOW: Final[int] = 3 + + def calculate_average_velocity( records: Sequence[VelocityRecord], - window: int = 3, + window: int = _DEFAULT_WINDOW, ) -> float: """Compute rolling average of story_points_completed. diff --git a/src/synthorg/engine/workflow/subworkflow_registry.py b/src/synthorg/engine/workflow/subworkflow_registry.py index ebc78e191f..e7822d7425 100644 --- a/src/synthorg/engine/workflow/subworkflow_registry.py +++ b/src/synthorg/engine/workflow/subworkflow_registry.py @@ -20,6 +20,7 @@ """ import json +from typing import Final from packaging.version import InvalidVersion, Version @@ -54,7 +55,7 @@ logger = get_logger(__name__) -_SUBWORKFLOW_KEYSET_ARITY = 3 # lint-allow: magic-numbers -- composite-key arity +_SUBWORKFLOW_KEYSET_ARITY: Final[int] = 3 def encode_subworkflow_keyset(summary: SubworkflowSummary) -> str: diff --git a/src/synthorg/engine/workflow/validate_edges.py b/src/synthorg/engine/workflow/validate_edges.py index 3ecc8fcfda..b58af36765 100644 --- a/src/synthorg/engine/workflow/validate_edges.py +++ b/src/synthorg/engine/workflow/validate_edges.py @@ -8,6 +8,7 @@ from synthorg.core.enums import WorkflowEdgeType, WorkflowNodeType from synthorg.engine.workflow.validation_types import ( + _MIN_SPLIT_BRANCHES, ValidationErrorCode, WorkflowValidationError, ) @@ -15,8 +16,6 @@ if TYPE_CHECKING: from synthorg.engine.workflow.definition import WorkflowDefinition -_MIN_SPLIT_BRANCHES = 2 - _CONDITIONAL_EDGE_TYPES = frozenset( { WorkflowEdgeType.CONDITIONAL_TRUE, diff --git a/src/synthorg/engine/workflow/validation_types.py b/src/synthorg/engine/workflow/validation_types.py index 2d0ec6a503..5c52385bd6 100644 --- a/src/synthorg/engine/workflow/validation_types.py +++ b/src/synthorg/engine/workflow/validation_types.py @@ -1,13 +1,14 @@ """Types and constants for workflow validation.""" from enum import StrEnum +from typing import Final from pydantic import BaseModel, ConfigDict, Field, computed_field from synthorg.core.enums import WorkflowEdgeType from synthorg.core.types import NotBlankStr # noqa: TC001 -_MIN_SPLIT_BRANCHES = 2 +_MIN_SPLIT_BRANCHES: Final[int] = 2 _CONDITIONAL_EDGE_TYPES = frozenset( { diff --git a/src/synthorg/engine/workflow/version_service.py b/src/synthorg/engine/workflow/version_service.py index 457dfe5b3a..a7d0e676ad 100644 --- a/src/synthorg/engine/workflow/version_service.py +++ b/src/synthorg/engine/workflow/version_service.py @@ -7,7 +7,7 @@ """ import asyncio -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, Final from synthorg.core.types import NotBlankStr # noqa: TC001 -- runtime annotation from synthorg.observability import get_logger @@ -21,6 +21,7 @@ from synthorg.versioning.models import VersionSnapshot logger = get_logger(__name__) +_DEFAULT_LIMIT: Final[int] = 50 class WorkflowVersionService: @@ -40,7 +41,7 @@ async def list_versions( definition_id: NotBlankStr, *, offset: int = 0, - limit: int = 50, + limit: int = _DEFAULT_LIMIT, ) -> tuple[tuple[VersionSnapshot[WorkflowDefinition], ...], int]: """Return a paginated list of version snapshots and the total count. diff --git a/src/synthorg/engine/workflow/webhook_bridge.py b/src/synthorg/engine/workflow/webhook_bridge.py index ef9f4a4223..2e4c62379a 100644 --- a/src/synthorg/engine/workflow/webhook_bridge.py +++ b/src/synthorg/engine/workflow/webhook_bridge.py @@ -36,9 +36,7 @@ _SUBSCRIBER_ID: Final[str] = "__webhook_bridge__" _POLL_TIMEOUT: Final[float] = 1.0 """Fallback poll timeout used when no resolver is wired in.""" -_MAX_CONSECUTIVE_ERRORS: Final[int] = ( - 30 # lint-allow: magic-numbers -- resolver-unwired fallback -) +_MAX_CONSECUTIVE_ERRORS: Final[int] = 30 """Fallback error budget used when no resolver is wired in.""" diff --git a/src/synthorg/engine/workspace/disk_quota.py b/src/synthorg/engine/workspace/disk_quota.py index c6afd47f84..123bd1accc 100644 --- a/src/synthorg/engine/workspace/disk_quota.py +++ b/src/synthorg/engine/workspace/disk_quota.py @@ -7,7 +7,7 @@ import asyncio from pathlib import Path # noqa: TC003 -from typing import Literal +from typing import Final, Literal from pydantic import BaseModel, ConfigDict, Field @@ -23,7 +23,7 @@ logger = get_logger(__name__) -_BYTES_PER_GB = 1_073_741_824 +_BYTES_PER_GB: Final[int] = 1_073_741_824 class DiskQuotaStatus(BaseModel): diff --git a/src/synthorg/engine/workspace/semantic_checks.py b/src/synthorg/engine/workspace/semantic_checks.py index bc414a3f01..879c55cc12 100644 --- a/src/synthorg/engine/workspace/semantic_checks.py +++ b/src/synthorg/engine/workspace/semantic_checks.py @@ -7,7 +7,7 @@ import ast from collections import Counter -from typing import TYPE_CHECKING, NamedTuple +from typing import TYPE_CHECKING, Final, NamedTuple if TYPE_CHECKING: from collections.abc import Mapping @@ -71,7 +71,7 @@ def _all_name_references(tree: ast.Module) -> set[str]: return refs -_VARIADIC_ARG_SENTINEL = 999 +_VARIADIC_ARG_SENTINEL: Final[int] = 999 def _function_min_args(node: ast.FunctionDef | ast.AsyncFunctionDef) -> int: diff --git a/src/synthorg/engine/workspace/semantic_llm.py b/src/synthorg/engine/workspace/semantic_llm.py index 619b4f881d..85a0a1e670 100644 --- a/src/synthorg/engine/workspace/semantic_llm.py +++ b/src/synthorg/engine/workspace/semantic_llm.py @@ -8,6 +8,7 @@ import asyncio from collections.abc import Mapping # noqa: TC003 +from typing import Final from synthorg.budget.call_category import LLMCallCategory @@ -43,6 +44,7 @@ from synthorg.providers.protocol import CompletionProvider # noqa: TC001 logger = get_logger(__name__) +_DEFAULT_MAX_RETRIES: Final[int] = 2 class LlmSemanticAnalyzer: @@ -178,7 +180,7 @@ async def _call_with_retry( messages: list[ChatMessage], tool_def: ToolDefinition, comp_config: CompletionConfig, - max_retries: int = 2, + max_retries: int = _DEFAULT_MAX_RETRIES, ) -> tuple[MergeConflict, ...]: """Call LLM with retry on parse failure. diff --git a/src/synthorg/hr/performance/ci_quality_strategy.py b/src/synthorg/hr/performance/ci_quality_strategy.py index 2d548c474c..847f8f2a4c 100644 --- a/src/synthorg/hr/performance/ci_quality_strategy.py +++ b/src/synthorg/hr/performance/ci_quality_strategy.py @@ -5,7 +5,7 @@ """ import math -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, Final from synthorg.core.types import NotBlankStr from synthorg.hr.performance.models import QualityScoreResult, TaskMetricRecord @@ -16,6 +16,7 @@ from synthorg.core.task import AcceptanceCriterion logger = get_logger(__name__) +_DEFAULT_COST_BUDGET: Final[float] = 100.0 # Scoring weights. _CRITERIA_WEIGHT: float = 0.70 @@ -46,7 +47,7 @@ class CISignalQualityStrategy: def __init__( self, *, - cost_budget: float = 100.0, + cost_budget: float = _DEFAULT_COST_BUDGET, ) -> None: self._cost_budget = max(cost_budget, 0.01) diff --git a/src/synthorg/hr/performance/composite_quality_strategy.py b/src/synthorg/hr/performance/composite_quality_strategy.py index 81c504c5b0..fb5c7cf08d 100644 --- a/src/synthorg/hr/performance/composite_quality_strategy.py +++ b/src/synthorg/hr/performance/composite_quality_strategy.py @@ -8,7 +8,7 @@ import asyncio import math -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, Final from synthorg.core.types import NotBlankStr from synthorg.hr.performance.models import QualityScoreResult @@ -30,6 +30,11 @@ logger = get_logger(__name__) +_DEFAULT_CI_WEIGHT: Final[float] = 0.4 +_DEFAULT_LLM_WEIGHT: Final[float] = 0.6 +_DEFAULT_CONFIDENCE_DISCOUNT: Final[float] = 0.9 +_DEFAULT_CI_ONLY_CONFIDENCE_DISCOUNT: Final[float] = 0.7 + class CompositeQualityStrategy: """Composite quality scoring combining multiple layers. @@ -73,10 +78,10 @@ def __init__( # noqa: PLR0913 ci_strategy: QualityScoringStrategy, llm_strategy: QualityScoringStrategy | None = None, override_store: QualityOverrideStore | None = None, - ci_weight: float = 0.4, - llm_weight: float = 0.6, - confidence_discount: float = 0.9, - ci_only_confidence_discount: float = 0.7, + ci_weight: float = _DEFAULT_CI_WEIGHT, + llm_weight: float = _DEFAULT_LLM_WEIGHT, + confidence_discount: float = _DEFAULT_CONFIDENCE_DISCOUNT, + ci_only_confidence_discount: float = _DEFAULT_CI_ONLY_CONFIDENCE_DISCOUNT, ) -> None: if not math.isfinite(ci_weight) or not math.isfinite(llm_weight): msg = ( diff --git a/src/synthorg/hr/performance/llm_calibration_sampler.py b/src/synthorg/hr/performance/llm_calibration_sampler.py index e158b5a5b5..a808553a64 100644 --- a/src/synthorg/hr/performance/llm_calibration_sampler.py +++ b/src/synthorg/hr/performance/llm_calibration_sampler.py @@ -8,7 +8,7 @@ import json import random from datetime import UTC, datetime, timedelta -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, Final from synthorg.budget.call_category import LLMCallCategory from synthorg.budget.currency import DEFAULT_CURRENCY, CurrencyCode @@ -37,6 +37,8 @@ from synthorg.providers.protocol import CompletionProvider logger = get_logger(__name__) +_DEFAULT_SAMPLING_RATE: Final[float] = 0.01 +_DEFAULT_RETENTION_DAYS: Final[int] = 90 #: Static head of the calibration prompt (no user-controlled data). #: @@ -79,8 +81,8 @@ def __init__( # noqa: PLR0913 *, provider: CompletionProvider, model: NotBlankStr, - sampling_rate: float = 0.01, - retention_days: int = 90, + sampling_rate: float = _DEFAULT_SAMPLING_RATE, + retention_days: int = _DEFAULT_RETENTION_DAYS, currency: CurrencyCode = DEFAULT_CURRENCY, cost_tracker: CostTracker | None = None, ) -> None: diff --git a/src/synthorg/hr/performance/multi_window_strategy.py b/src/synthorg/hr/performance/multi_window_strategy.py index 3f55970fcb..73bfb6f274 100644 --- a/src/synthorg/hr/performance/multi_window_strategy.py +++ b/src/synthorg/hr/performance/multi_window_strategy.py @@ -7,7 +7,7 @@ import math import re from datetime import timedelta -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, Final from synthorg.budget.currency import assert_currencies_match from synthorg.core.types import NotBlankStr @@ -19,6 +19,7 @@ from pydantic import AwareDatetime logger = get_logger(__name__) +_DEFAULT_MIN_DATA_POINTS: Final[int] = 5 # Pattern for parsing window size strings (e.g. '7d', '30d', '90d'). _WINDOW_PATTERN = re.compile(r"^(\d+)d$") @@ -59,7 +60,7 @@ def __init__( self, *, windows: tuple[str, ...] = ("7d", "30d", "90d"), - min_data_points: int = 5, + min_data_points: int = _DEFAULT_MIN_DATA_POINTS, ) -> None: self._windows = windows self._min_data_points = min_data_points diff --git a/src/synthorg/hr/performance/quality_override_store.py b/src/synthorg/hr/performance/quality_override_store.py index 64f0c6885a..f235f38d36 100644 --- a/src/synthorg/hr/performance/quality_override_store.py +++ b/src/synthorg/hr/performance/quality_override_store.py @@ -5,7 +5,7 @@ """ from datetime import UTC, datetime -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, Final from synthorg.observability import get_logger from synthorg.observability.events.performance import ( @@ -23,7 +23,7 @@ logger = get_logger(__name__) -_DEFAULT_MAX_OVERRIDES = 10_000 +_DEFAULT_MAX_OVERRIDES: Final[int] = 10_000 class QualityOverrideStore: diff --git a/src/synthorg/hr/performance/theil_sen_strategy.py b/src/synthorg/hr/performance/theil_sen_strategy.py index 3c68a8337c..3c9e300605 100644 --- a/src/synthorg/hr/performance/theil_sen_strategy.py +++ b/src/synthorg/hr/performance/theil_sen_strategy.py @@ -5,7 +5,7 @@ """ from itertools import combinations -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, Final from synthorg.hr.enums import TrendDirection from synthorg.hr.performance.models import TrendResult @@ -18,6 +18,9 @@ from synthorg.core.types import NotBlankStr logger = get_logger(__name__) +_DEFAULT_MIN_DATA_POINTS: Final[int] = 5 +_DEFAULT_IMPROVING_THRESHOLD: Final[float] = 0.05 +_DEFAULT_DECLINING_THRESHOLD: Final[float] = -0.05 # Seconds per day for timestamp normalization. _SECONDS_PER_DAY: float = 86400.0 @@ -58,9 +61,9 @@ class TheilSenTrendStrategy: def __init__( self, *, - min_data_points: int = 5, - improving_threshold: float = 0.05, - declining_threshold: float = -0.05, + min_data_points: int = _DEFAULT_MIN_DATA_POINTS, + improving_threshold: float = _DEFAULT_IMPROVING_THRESHOLD, + declining_threshold: float = _DEFAULT_DECLINING_THRESHOLD, ) -> None: self._min_data_points = min_data_points self._improving_threshold = improving_threshold diff --git a/src/synthorg/hr/scaling/guards/approval_gate.py b/src/synthorg/hr/scaling/guards/approval_gate.py index 30afb0b86d..0f41677247 100644 --- a/src/synthorg/hr/scaling/guards/approval_gate.py +++ b/src/synthorg/hr/scaling/guards/approval_gate.py @@ -7,7 +7,7 @@ """ from datetime import UTC, datetime, timedelta -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, Final from uuid import NAMESPACE_URL, uuid5 from synthorg.core.approval import ApprovalItem @@ -22,6 +22,7 @@ from synthorg.hr.scaling.models import ScalingDecision logger = get_logger(__name__) +_DEFAULT_EXPIRY_DAYS: Final[int] = 7 # Map scaling actions to approval risk levels. _RISK_MAP: dict[ScalingActionType, ApprovalRiskLevel] = { @@ -46,7 +47,7 @@ def __init__( self, *, approval_store: ApprovalStoreProtocol, - expiry_days: int = 7, + expiry_days: int = _DEFAULT_EXPIRY_DAYS, ) -> None: if expiry_days <= 0: msg = f"expiry_days must be > 0, got {expiry_days}" diff --git a/src/synthorg/hr/scaling/guards/conflict_resolver.py b/src/synthorg/hr/scaling/guards/conflict_resolver.py index f55b6c3edf..75653a8769 100644 --- a/src/synthorg/hr/scaling/guards/conflict_resolver.py +++ b/src/synthorg/hr/scaling/guards/conflict_resolver.py @@ -7,7 +7,7 @@ import copy from types import MappingProxyType -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, Final from synthorg.core.types import NotBlankStr from synthorg.hr.scaling.enums import ScalingActionType, ScalingStrategyName @@ -27,7 +27,7 @@ ScalingStrategyName.WORKLOAD.value: 3, } -_LOWEST_PRIORITY = 999 +_LOWEST_PRIORITY: Final[int] = 999 def _decision_key(decision: ScalingDecision) -> str: diff --git a/src/synthorg/hr/scaling/guards/cooldown.py b/src/synthorg/hr/scaling/guards/cooldown.py index 04364b56b5..09a914fe45 100644 --- a/src/synthorg/hr/scaling/guards/cooldown.py +++ b/src/synthorg/hr/scaling/guards/cooldown.py @@ -6,7 +6,7 @@ import asyncio from datetime import UTC, datetime, timedelta -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, Final from synthorg.core.types import NotBlankStr from synthorg.observability import get_logger @@ -16,6 +16,7 @@ from synthorg.hr.scaling.models import ScalingDecision logger = get_logger(__name__) +_DEFAULT_COOLDOWN_SECONDS: Final[int] = 3600 class CooldownGuard: @@ -31,7 +32,7 @@ class CooldownGuard: def __init__( self, *, - cooldown_seconds: int = 3600, + cooldown_seconds: int = _DEFAULT_COOLDOWN_SECONDS, ) -> None: if cooldown_seconds < 0: msg = f"cooldown_seconds must be >= 0, got {cooldown_seconds}" diff --git a/src/synthorg/hr/scaling/guards/rate_limit.py b/src/synthorg/hr/scaling/guards/rate_limit.py index 56b2d1bd60..127587e4db 100644 --- a/src/synthorg/hr/scaling/guards/rate_limit.py +++ b/src/synthorg/hr/scaling/guards/rate_limit.py @@ -5,7 +5,7 @@ import asyncio from datetime import UTC, datetime -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, Final from synthorg.core.types import NotBlankStr from synthorg.hr.scaling.enums import ScalingActionType @@ -16,6 +16,7 @@ from synthorg.hr.scaling.models import ScalingDecision logger = get_logger(__name__) +_DEFAULT_MAX_HIRES_PER_DAY: Final[int] = 3 class RateLimitGuard: @@ -32,7 +33,7 @@ class RateLimitGuard: def __init__( self, *, - max_hires_per_day: int = 3, + max_hires_per_day: int = _DEFAULT_MAX_HIRES_PER_DAY, max_prunes_per_day: int = 1, ) -> None: self._limits: dict[str, int] = { diff --git a/src/synthorg/hr/scaling/service.py b/src/synthorg/hr/scaling/service.py index 474b54cdd2..7331259bed 100644 --- a/src/synthorg/hr/scaling/service.py +++ b/src/synthorg/hr/scaling/service.py @@ -7,7 +7,7 @@ import asyncio from collections import deque from datetime import UTC, datetime -from typing import TYPE_CHECKING, Any +from typing import TYPE_CHECKING, Any, Final from synthorg.core.types import NotBlankStr from synthorg.hr.enums import FiringReason @@ -60,7 +60,7 @@ async def get(self, agent_id: str) -> Any | None: logger = get_logger(__name__) -_MAX_HISTORY = 100 +_MAX_HISTORY: Final[int] = 100 _STRATEGY_TO_FIRING_REASON: dict[str, FiringReason] = { ScalingStrategyName.BUDGET_CAP.value: FiringReason.BUDGET, diff --git a/src/synthorg/hr/scaling/signals/workload.py b/src/synthorg/hr/scaling/signals/workload.py index b71b493c04..7e79735555 100644 --- a/src/synthorg/hr/scaling/signals/workload.py +++ b/src/synthorg/hr/scaling/signals/workload.py @@ -1,7 +1,7 @@ """Workload signal source -- reads agent utilization from assignment.""" from datetime import UTC, datetime -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, Final from synthorg.core.types import NotBlankStr from synthorg.hr.scaling.models import ScalingSignal @@ -12,6 +12,7 @@ from synthorg.engine.assignment.models import AgentWorkload logger = get_logger(__name__) +_DEFAULT_MAX_CONCURRENT_TASKS: Final[int] = 3 _SOURCE_NAME = NotBlankStr("workload") @@ -30,7 +31,7 @@ class WorkloadSignalSource: def __init__( self, *, - max_concurrent_tasks: int = 3, + max_concurrent_tasks: int = _DEFAULT_MAX_CONCURRENT_TASKS, ) -> None: if max_concurrent_tasks <= 0: msg = "max_concurrent_tasks must be > 0" diff --git a/src/synthorg/hr/scaling/strategies/budget_cap.py b/src/synthorg/hr/scaling/strategies/budget_cap.py index 423e9bcc2c..83eec1bafe 100644 --- a/src/synthorg/hr/scaling/strategies/budget_cap.py +++ b/src/synthorg/hr/scaling/strategies/budget_cap.py @@ -6,6 +6,7 @@ """ from datetime import UTC, datetime +from typing import Final from synthorg.core.types import NotBlankStr from synthorg.hr.scaling.enums import ScalingActionType, ScalingStrategyName @@ -17,6 +18,8 @@ ) logger = get_logger(__name__) +_DEFAULT_SAFETY_MARGIN: Final[float] = 0.9 +_DEFAULT_HEADROOM_FRACTION: Final[float] = 0.6 _NAME = NotBlankStr("budget_cap") _ACTION_TYPES = frozenset( @@ -44,8 +47,8 @@ class BudgetCapStrategy: def __init__( self, *, - safety_margin: float = 0.90, - headroom_fraction: float = 0.60, + safety_margin: float = _DEFAULT_SAFETY_MARGIN, + headroom_fraction: float = _DEFAULT_HEADROOM_FRACTION, ) -> None: if not 0.0 < safety_margin <= 1.0: msg = f"safety_margin must be in (0, 1], got {safety_margin}" diff --git a/src/synthorg/hr/scaling/strategies/workload.py b/src/synthorg/hr/scaling/strategies/workload.py index d26665df89..c096ce7210 100644 --- a/src/synthorg/hr/scaling/strategies/workload.py +++ b/src/synthorg/hr/scaling/strategies/workload.py @@ -5,6 +5,7 @@ """ from datetime import UTC, datetime +from typing import Final from synthorg.core.types import NotBlankStr from synthorg.hr.scaling.enums import ScalingActionType, ScalingStrategyName @@ -13,6 +14,8 @@ from synthorg.observability.events.hr import HR_SCALING_STRATEGY_EVALUATED logger = get_logger(__name__) +_DEFAULT_HIRE_THRESHOLD: Final[float] = 0.85 +_DEFAULT_PRUNE_THRESHOLD: Final[float] = 0.3 _NAME = NotBlankStr("workload") _ACTION_TYPES = frozenset({ScalingActionType.HIRE, ScalingActionType.PRUNE}) @@ -38,8 +41,8 @@ class WorkloadAutoScaleStrategy: def __init__( self, *, - hire_threshold: float = 0.85, - prune_threshold: float = 0.30, + hire_threshold: float = _DEFAULT_HIRE_THRESHOLD, + prune_threshold: float = _DEFAULT_PRUNE_THRESHOLD, ) -> None: if not 0.0 <= prune_threshold < hire_threshold <= 1.0: msg = ( diff --git a/src/synthorg/hr/scaling/triggers/batched.py b/src/synthorg/hr/scaling/triggers/batched.py index ef03e29c1a..966e4f1bb2 100644 --- a/src/synthorg/hr/scaling/triggers/batched.py +++ b/src/synthorg/hr/scaling/triggers/batched.py @@ -6,6 +6,7 @@ import asyncio from datetime import UTC, datetime +from typing import Final from synthorg.core.types import NotBlankStr from synthorg.observability import get_logger @@ -15,6 +16,7 @@ ) logger = get_logger(__name__) +_DEFAULT_INTERVAL_SECONDS: Final[int] = 900 class BatchedScalingTrigger: @@ -30,7 +32,7 @@ class BatchedScalingTrigger: def __init__( self, *, - interval_seconds: int = 900, + interval_seconds: int = _DEFAULT_INTERVAL_SECONDS, ) -> None: self._interval = max(1, interval_seconds) self._last_run: datetime | None = None diff --git a/src/synthorg/hr/training/curation/llm_curated.py b/src/synthorg/hr/training/curation/llm_curated.py index 1a1890a530..bd0ee0d174 100644 --- a/src/synthorg/hr/training/curation/llm_curated.py +++ b/src/synthorg/hr/training/curation/llm_curated.py @@ -6,7 +6,7 @@ the provider call fails. """ -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, Final from synthorg.budget.call_category import LLMCallCategory @@ -43,6 +43,8 @@ from synthorg.core.enums import SeniorityLevel logger = get_logger(__name__) +_DEFAULT_TEMPERATURE: Final[float] = 0.3 +_DEFAULT_TOP_K: Final[int] = 50 class LLMCurated: @@ -66,8 +68,8 @@ def __init__( *, provider: CompletionProvider | None = None, model: str = "example-small-001", - temperature: float = 0.3, - top_k: int = 50, + temperature: float = _DEFAULT_TEMPERATURE, + top_k: int = _DEFAULT_TOP_K, cost_tracker: CostTracker | None = None, ) -> None: if top_k <= 0: diff --git a/src/synthorg/hr/training/curation/relevance.py b/src/synthorg/hr/training/curation/relevance.py index 54dc91a4a4..e5dbded8ca 100644 --- a/src/synthorg/hr/training/curation/relevance.py +++ b/src/synthorg/hr/training/curation/relevance.py @@ -6,7 +6,7 @@ """ import hashlib -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, Final from synthorg.hr.training.models import ContentType, TrainingItem # noqa: TC001 from synthorg.observability import get_logger @@ -20,15 +20,15 @@ logger = get_logger(__name__) -_DEFAULT_TOP_K = 50 +_DEFAULT_TOP_K: Final[int] = 50 # Scoring weights. -_CONTENT_RICHNESS_WEIGHT = 0.4 -_DIVERSITY_WEIGHT = 0.3 +_CONTENT_RICHNESS_WEIGHT: Final[float] = 0.4 +_DIVERSITY_WEIGHT: Final[float] = 0.3 # Deterministic SHA-256 tie-breaker weight (not actual recency). # Named for clarity: the score is a hash-derived stable ordering, # not a timestamp signal. -_TIEBREAKER_WEIGHT = 0.3 +_TIEBREAKER_WEIGHT: Final[float] = 0.3 class RelevanceScoreCuration: diff --git a/src/synthorg/hr/training/extractors/procedural.py b/src/synthorg/hr/training/extractors/procedural.py index 24cd32eba3..7e5e6d6625 100644 --- a/src/synthorg/hr/training/extractors/procedural.py +++ b/src/synthorg/hr/training/extractors/procedural.py @@ -5,7 +5,7 @@ """ import asyncio -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, Final from synthorg.core.enums import MemoryCategory from synthorg.hr.training.models import ContentType, TrainingItem @@ -24,7 +24,7 @@ logger = get_logger(__name__) -_MAX_ENTRIES_PER_AGENT = 100 +_MAX_ENTRIES_PER_AGENT: Final[int] = 100 class ProceduralMemoryExtractor: diff --git a/src/synthorg/hr/training/extractors/semantic.py b/src/synthorg/hr/training/extractors/semantic.py index 0e3a37344e..de4af76edf 100644 --- a/src/synthorg/hr/training/extractors/semantic.py +++ b/src/synthorg/hr/training/extractors/semantic.py @@ -5,7 +5,7 @@ """ import asyncio -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, Final from synthorg.core.enums import MemoryCategory from synthorg.hr.training.models import ContentType, TrainingItem @@ -24,7 +24,7 @@ logger = get_logger(__name__) -_MAX_ENTRIES_PER_AGENT = 100 +_MAX_ENTRIES_PER_AGENT: Final[int] = 100 class SemanticMemoryExtractor: diff --git a/src/synthorg/hr/training/extractors/tool_patterns.py b/src/synthorg/hr/training/extractors/tool_patterns.py index ea48ba2d4f..8f133aacc6 100644 --- a/src/synthorg/hr/training/extractors/tool_patterns.py +++ b/src/synthorg/hr/training/extractors/tool_patterns.py @@ -7,7 +7,7 @@ import asyncio from collections import defaultdict from datetime import UTC, datetime, timedelta -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, Final from synthorg.core.collections import dedupe_preserving_order from synthorg.hr.training.models import ContentType, TrainingItem @@ -26,7 +26,7 @@ logger = get_logger(__name__) -_DEFAULT_LOOKBACK_DAYS = 90 +_DEFAULT_LOOKBACK_DAYS: Final[int] = 90 class ToolPatternExtractor: diff --git a/src/synthorg/hr/training/guards/review_gate.py b/src/synthorg/hr/training/guards/review_gate.py index c44807c9c5..1f6202d976 100644 --- a/src/synthorg/hr/training/guards/review_gate.py +++ b/src/synthorg/hr/training/guards/review_gate.py @@ -6,7 +6,7 @@ """ from datetime import UTC, datetime, timedelta -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, Final from uuid import uuid4 from synthorg.core.approval import ApprovalItem @@ -28,7 +28,7 @@ logger = get_logger(__name__) -_APPROVAL_EXPIRY_HOURS = 24 +_APPROVAL_EXPIRY_HOURS: Final[int] = 24 class ReviewGateGuard: diff --git a/src/synthorg/hr/training/guards/sanitization.py b/src/synthorg/hr/training/guards/sanitization.py index d8b197f415..5d1fa2b9b4 100644 --- a/src/synthorg/hr/training/guards/sanitization.py +++ b/src/synthorg/hr/training/guards/sanitization.py @@ -4,7 +4,7 @@ from training items using the shared sanitize_message utility. """ -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, Final from synthorg.engine.sanitization import sanitize_message from synthorg.hr.training.models import ( @@ -22,7 +22,7 @@ logger = get_logger(__name__) -_DEFAULT_MAX_LENGTH = 2000 +_DEFAULT_MAX_LENGTH: Final[int] = 2000 class SanitizationGuard: diff --git a/src/synthorg/hr/training/source_selectors/department_diversity.py b/src/synthorg/hr/training/source_selectors/department_diversity.py index 2a6ee24b52..68f6b813cc 100644 --- a/src/synthorg/hr/training/source_selectors/department_diversity.py +++ b/src/synthorg/hr/training/source_selectors/department_diversity.py @@ -5,7 +5,7 @@ """ import asyncio -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, Final from synthorg.core.normalization import compare_ci from synthorg.core.types import NotBlankStr @@ -27,8 +27,8 @@ logger = get_logger(__name__) -_DEFAULT_TOP_PERFORMER_COUNT = 2 -_DEFAULT_COMPLEMENTARY_COUNT = 2 +_DEFAULT_TOP_PERFORMER_COUNT: Final[int] = 2 +_DEFAULT_COMPLEMENTARY_COUNT: Final[int] = 2 class DepartmentDiversitySampling: diff --git a/src/synthorg/hr/training/source_selectors/role_top_performers.py b/src/synthorg/hr/training/source_selectors/role_top_performers.py index c450cc3151..9ee08a1c1e 100644 --- a/src/synthorg/hr/training/source_selectors/role_top_performers.py +++ b/src/synthorg/hr/training/source_selectors/role_top_performers.py @@ -5,7 +5,7 @@ """ import asyncio -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, Final from synthorg.core.normalization import compare_ci from synthorg.observability import get_logger, safe_error_description @@ -27,7 +27,7 @@ logger = get_logger(__name__) -_DEFAULT_TOP_N = 3 +_DEFAULT_TOP_N: Final[int] = 3 class RoleTopPerformers: diff --git a/src/synthorg/infrastructure/services.py b/src/synthorg/infrastructure/services.py index ac2af52b99..446abfe9fa 100644 --- a/src/synthorg/infrastructure/services.py +++ b/src/synthorg/infrastructure/services.py @@ -29,7 +29,7 @@ import copy import json from datetime import UTC, datetime -from typing import TYPE_CHECKING, Any, cast +from typing import TYPE_CHECKING, Any, Final, cast from uuid import UUID, uuid4 from synthorg.communication.mcp_errors import CapabilityNotSupportedError @@ -69,6 +69,7 @@ from synthorg.settings.service import SettingsService logger = get_logger(__name__) +_DEFAULT_LIMIT: Final[int] = 100 def _capability_missing( @@ -914,7 +915,7 @@ async def list_entries( self, *, offset: int = 0, - limit: int = 100, + limit: int = _DEFAULT_LIMIT, ) -> tuple[tuple[object, ...], int]: """Return paginated audit entries plus the unfiltered total. @@ -954,7 +955,7 @@ async def list_events( self, *, offset: int = 0, - limit: int = 100, + limit: int = _DEFAULT_LIMIT, ) -> tuple[tuple[object, ...], int]: """Return paginated recent events plus the unfiltered total. diff --git a/src/synthorg/integrations/health/checks/generic_http.py b/src/synthorg/integrations/health/checks/generic_http.py index 4614695d4d..581c2fca5f 100644 --- a/src/synthorg/integrations/health/checks/generic_http.py +++ b/src/synthorg/integrations/health/checks/generic_http.py @@ -2,6 +2,7 @@ import time from datetime import UTC, datetime +from typing import Final import httpx @@ -18,10 +19,10 @@ logger = get_logger(__name__) -_TIMEOUT = 10.0 -_ERROR_THRESHOLD = 400 -_METHOD_NOT_ALLOWED = 405 -_NOT_IMPLEMENTED = 501 +_TIMEOUT: Final[float] = 10.0 +_ERROR_THRESHOLD: Final[int] = 400 +_METHOD_NOT_ALLOWED: Final[int] = 405 +_NOT_IMPLEMENTED: Final[int] = 501 class GenericHttpHealthCheck: diff --git a/src/synthorg/integrations/health/checks/github.py b/src/synthorg/integrations/health/checks/github.py index f4e75b7acd..b802798157 100644 --- a/src/synthorg/integrations/health/checks/github.py +++ b/src/synthorg/integrations/health/checks/github.py @@ -2,6 +2,7 @@ import time from datetime import UTC, datetime +from typing import Final from urllib.parse import urlparse import httpx @@ -27,8 +28,8 @@ without its own ``base_url`` falls through to the operator-configured endpoint rather than the public GitHub API.""" -_TIMEOUT = 10.0 -_HTTP_OK = 200 +_TIMEOUT: Final[float] = 10.0 +_HTTP_OK: Final[int] = 200 # Allow-list of hostnames the GitHub health check will send a bearer # token to. Prevents token exfiltration when a malicious operator diff --git a/src/synthorg/integrations/health/checks/slack.py b/src/synthorg/integrations/health/checks/slack.py index cdf4e1156e..08a08a3fac 100644 --- a/src/synthorg/integrations/health/checks/slack.py +++ b/src/synthorg/integrations/health/checks/slack.py @@ -2,6 +2,7 @@ import time from datetime import UTC, datetime +from typing import Final import httpx @@ -19,7 +20,7 @@ logger = get_logger(__name__) -_TIMEOUT = 10.0 +_TIMEOUT: Final[float] = 10.0 class SlackHealthCheck: diff --git a/src/synthorg/integrations/health/checks/smtp.py b/src/synthorg/integrations/health/checks/smtp.py index 55b1094f6e..2f8d693f1d 100644 --- a/src/synthorg/integrations/health/checks/smtp.py +++ b/src/synthorg/integrations/health/checks/smtp.py @@ -4,6 +4,7 @@ import smtplib import time from datetime import UTC, datetime +from typing import Final from synthorg.integrations.connections.models import ( Connection, @@ -18,7 +19,7 @@ logger = get_logger(__name__) -_TIMEOUT = 10 +_TIMEOUT: Final[int] = 10 class SmtpHealthCheck: diff --git a/src/synthorg/integrations/health/prober.py b/src/synthorg/integrations/health/prober.py index 745298efea..226737b383 100644 --- a/src/synthorg/integrations/health/prober.py +++ b/src/synthorg/integrations/health/prober.py @@ -34,6 +34,8 @@ ) logger = get_logger(__name__) +_DEFAULT_INTERVAL_SECONDS: Final[int] = 300 +_DEFAULT_UNHEALTHY_THRESHOLD: Final[int] = 3 _CHECK_REGISTRY: Final[MappingProxyType[ConnectionType, ConnectionHealthCheck]] = ( MappingProxyType( @@ -85,8 +87,8 @@ def __init__( self, catalog: ConnectionCatalog, *, - interval_seconds: int = 300, - unhealthy_threshold: int = 3, + interval_seconds: int = _DEFAULT_INTERVAL_SECONDS, + unhealthy_threshold: int = _DEFAULT_UNHEALTHY_THRESHOLD, degraded_threshold: int = 1, clock: Clock | None = None, ) -> None: diff --git a/src/synthorg/integrations/mcp_catalog/in_memory_installations.py b/src/synthorg/integrations/mcp_catalog/in_memory_installations.py index b297660ca4..0db12692fd 100644 --- a/src/synthorg/integrations/mcp_catalog/in_memory_installations.py +++ b/src/synthorg/integrations/mcp_catalog/in_memory_installations.py @@ -6,6 +6,8 @@ is the source of truth in production. """ +from typing import Final + from synthorg.core.types import NotBlankStr # noqa: TC001 from synthorg.integrations.mcp_catalog.installations import ( McpInstallation, # noqa: TC001 @@ -21,6 +23,7 @@ from synthorg.persistence._shared.pagination import validate_pagination_args logger = get_logger(__name__) +_DEFAULT_LIMIT: Final[int] = 100 class InMemoryMcpInstallationRepository: @@ -62,7 +65,7 @@ async def get( async def list_items( self, *, - limit: int = 100, + limit: int = _DEFAULT_LIMIT, offset: int = 0, ) -> tuple[McpInstallation, ...]: """List installations ordered by ``installed_at, catalog_entry_id`` ASC. diff --git a/src/synthorg/integrations/mcp_catalog/installations.py b/src/synthorg/integrations/mcp_catalog/installations.py index 6991ce0f7f..ae499056da 100644 --- a/src/synthorg/integrations/mcp_catalog/installations.py +++ b/src/synthorg/integrations/mcp_catalog/installations.py @@ -11,12 +11,14 @@ ``installed_at`` and overwrites the associated ``connection_name``. """ -from typing import Protocol, runtime_checkable +from typing import Final, Protocol, runtime_checkable from pydantic import AwareDatetime, BaseModel, ConfigDict from synthorg.core.types import NotBlankStr # noqa: TC001 +_DEFAULT_LIMIT: Final[int] = 100 + class McpInstallation(BaseModel): """A recorded MCP catalog installation. @@ -50,7 +52,7 @@ async def get(self, catalog_entry_id: NotBlankStr) -> McpInstallation | None: async def list_items( self, *, - limit: int = 100, + limit: int = _DEFAULT_LIMIT, offset: int = 0, ) -> tuple[McpInstallation, ...]: """List recorded installations. diff --git a/src/synthorg/integrations/oauth/flows/device_flow.py b/src/synthorg/integrations/oauth/flows/device_flow.py index ad5f3a50aa..2cbeb141ca 100644 --- a/src/synthorg/integrations/oauth/flows/device_flow.py +++ b/src/synthorg/integrations/oauth/flows/device_flow.py @@ -3,6 +3,7 @@ import json import math from datetime import timedelta +from typing import Final import httpx @@ -22,6 +23,8 @@ ) logger = get_logger(__name__) +_DEFAULT_EXPIRES_IN: Final[int] = 600 +_DEFAULT_MAX_WAIT_SECONDS: Final[int] = 600 class DeviceFlowResult: @@ -53,7 +56,7 @@ def __init__( # noqa: PLR0913 verification_uri: str, verification_uri_complete: str = "", interval: int, - expires_in: int = 600, + expires_in: int = _DEFAULT_EXPIRES_IN, ) -> None: self.device_code = device_code self.user_code = user_code @@ -253,7 +256,7 @@ def _positive_int(field_name: str, default: int) -> int: return raw interval_value = _positive_int("interval", self._default_poll_interval_seconds) - expires_in_value = _positive_int("expires_in", 600) + expires_in_value = _positive_int("expires_in", _DEFAULT_EXPIRES_IN) # user_code is an active credential -- do not log it at # INFO. Only the verification URI is safe to surface. @@ -279,7 +282,7 @@ async def poll_for_token( # noqa: C901, PLR0912, PLR0915 client_id: str, device_code: str, interval: int, - max_wait_seconds: int = 600, + max_wait_seconds: int = _DEFAULT_MAX_WAIT_SECONDS, ) -> OAuthToken: """Poll the token endpoint until the user authorizes. diff --git a/src/synthorg/integrations/oauth/pkce.py b/src/synthorg/integrations/oauth/pkce.py index 84e43a944d..3c77163a60 100644 --- a/src/synthorg/integrations/oauth/pkce.py +++ b/src/synthorg/integrations/oauth/pkce.py @@ -18,6 +18,7 @@ import re import secrets from threading import Lock +from typing import Final from cryptography.fernet import Fernet, InvalidToken @@ -27,9 +28,9 @@ logger = get_logger(__name__) _UNRESERVED_RE = re.compile(r"^[A-Za-z0-9\-._~]+$") -_VERIFIER_LENGTH = 128 -_MIN_VERIFIER_LENGTH = 43 -_MAX_VERIFIER_LENGTH = 128 +_VERIFIER_LENGTH: Final[int] = 128 +_MIN_VERIFIER_LENGTH: Final[int] = 43 +_MAX_VERIFIER_LENGTH: Final[int] = 128 _MASTER_KEY_ENV = "SYNTHORG_MASTER_KEY" _cipher_lock = Lock() diff --git a/src/synthorg/integrations/oauth/state_service.py b/src/synthorg/integrations/oauth/state_service.py index 4f28719946..99cadebcee 100644 --- a/src/synthorg/integrations/oauth/state_service.py +++ b/src/synthorg/integrations/oauth/state_service.py @@ -16,7 +16,7 @@ this service's scope by design. """ -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, Final from synthorg.core.types import NotBlankStr # noqa: TC001 -- runtime annotation from synthorg.integrations.connections.models import ( @@ -35,7 +35,7 @@ # Audit-safe correlation prefix length: long enough to deduplicate state # tokens across the audit chain without exposing the full secret. -_STATE_TOKEN_PREFIX_LENGTH = 8 # lint-allow: magic-numbers -- audit prefix width +_STATE_TOKEN_PREFIX_LENGTH: Final[int] = 8 class OAuthStateService: diff --git a/src/synthorg/integrations/oauth/token_manager.py b/src/synthorg/integrations/oauth/token_manager.py index 002a8296be..fb1bbcdc3e 100644 --- a/src/synthorg/integrations/oauth/token_manager.py +++ b/src/synthorg/integrations/oauth/token_manager.py @@ -7,7 +7,7 @@ import asyncio import contextlib from datetime import UTC, datetime, timedelta -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, Final from synthorg.integrations.connections.catalog import ConnectionCatalog # noqa: TC001 from synthorg.integrations.connections.models import ( @@ -32,6 +32,8 @@ from synthorg.settings.resolver import ConfigResolver logger = get_logger(__name__) +_DEFAULT_REFRESH_THRESHOLD_SECONDS: Final[int] = 300 +_DEFAULT_CHECK_INTERVAL_SECONDS: Final[int] = 60 class OAuthTokenManager: @@ -57,8 +59,8 @@ def __init__( self, catalog: ConnectionCatalog, *, - refresh_threshold_seconds: int = 300, - check_interval_seconds: int = 60, + refresh_threshold_seconds: int = _DEFAULT_REFRESH_THRESHOLD_SECONDS, + check_interval_seconds: int = _DEFAULT_CHECK_INTERVAL_SECONDS, config_resolver: ConfigResolver | None = None, ) -> None: self._catalog = catalog diff --git a/src/synthorg/integrations/rate_limiting/shared_state.py b/src/synthorg/integrations/rate_limiting/shared_state.py index 58c192c854..b950b0eac1 100644 --- a/src/synthorg/integrations/rate_limiting/shared_state.py +++ b/src/synthorg/integrations/rate_limiting/shared_state.py @@ -15,6 +15,7 @@ from collections.abc import Callable # noqa: TC003 from datetime import UTC, datetime from types import MappingProxyType +from typing import Final from uuid import uuid4 from synthorg.communication.bus_protocol import MessageBus # noqa: TC001 @@ -43,8 +44,10 @@ def _wall_clock_seconds() -> float: logger = get_logger(__name__) +_DEFAULT_MAX_RPM: Final[int] = 60 + _RATELIMIT_CHANNEL = Channel(name="#ratelimit", type=ChannelType.TOPIC) -_POLL_TIMEOUT = 0.5 +_POLL_TIMEOUT: Final[float] = 0.5 _SUBSCRIBER_PREFIX = "__ratelimit_" @@ -62,7 +65,7 @@ def __init__( bus: MessageBus, connection_name: str, *, - max_rpm: int = 60, + max_rpm: int = _DEFAULT_MAX_RPM, ) -> None: self._bus = bus self._connection_name = connection_name diff --git a/src/synthorg/integrations/tunnel/ngrok_adapter.py b/src/synthorg/integrations/tunnel/ngrok_adapter.py index 3c486b5664..54b0bb1be4 100644 --- a/src/synthorg/integrations/tunnel/ngrok_adapter.py +++ b/src/synthorg/integrations/tunnel/ngrok_adapter.py @@ -12,7 +12,7 @@ import asyncio import os -from typing import Any +from typing import Any, Final from pyngrok import conf, ngrok # type: ignore[import-untyped] @@ -26,6 +26,7 @@ ) logger = get_logger(__name__) +_DEFAULT_PORT: Final[int] = 8000 class NgrokAdapter: @@ -50,7 +51,7 @@ def __init__( self, *, auth_token_env: str = "NGROK_AUTHTOKEN", # noqa: S107 - port: int = 8000, + port: int = _DEFAULT_PORT, ) -> None: self._port = port # The ngrok auth token is a bootstrap secret read from the diff --git a/src/synthorg/integrations/webhooks/replay_protection.py b/src/synthorg/integrations/webhooks/replay_protection.py index 402d012d04..a976653965 100644 --- a/src/synthorg/integrations/webhooks/replay_protection.py +++ b/src/synthorg/integrations/webhooks/replay_protection.py @@ -15,6 +15,7 @@ import math import threading from collections import OrderedDict +from typing import Final from synthorg.core.clock import Clock, SystemClock from synthorg.observability import get_logger @@ -22,8 +23,8 @@ logger = get_logger(__name__) -_DEFAULT_WINDOW_SECONDS = 300 -_DEFAULT_MAX_ENTRIES = 10_000 +_DEFAULT_WINDOW_SECONDS: Final[int] = 300 +_DEFAULT_MAX_ENTRIES: Final[int] = 10_000 # Attacker-controlled nonces are hashed to a fixed 32-byte digest # before being stored in ``_seen`` so the cache's per-entry memory # is bounded regardless of how long the incoming header is. diff --git a/src/synthorg/integrations/webhooks/verifiers/slack_signing.py b/src/synthorg/integrations/webhooks/verifiers/slack_signing.py index dca72469c6..a595bb8d6f 100644 --- a/src/synthorg/integrations/webhooks/verifiers/slack_signing.py +++ b/src/synthorg/integrations/webhooks/verifiers/slack_signing.py @@ -2,6 +2,7 @@ import hashlib import hmac +from typing import Final from synthorg.core.clock import Clock, SystemClock from synthorg.observability import get_logger @@ -12,7 +13,7 @@ logger = get_logger(__name__) -_MAX_CLOCK_SKEW = 300 # 5 minutes +_MAX_CLOCK_SKEW: Final[int] = 300 # 5 minutes class SlackSigningVerifier: diff --git a/src/synthorg/memory/backends/inmemory/adapter.py b/src/synthorg/memory/backends/inmemory/adapter.py index 93c96b1df6..a4b8cb7d9f 100644 --- a/src/synthorg/memory/backends/inmemory/adapter.py +++ b/src/synthorg/memory/backends/inmemory/adapter.py @@ -10,6 +10,7 @@ import asyncio import uuid from datetime import UTC, datetime +from typing import Final from synthorg.core.enums import MemoryCategory from synthorg.core.types import NotBlankStr @@ -39,6 +40,7 @@ ) logger = get_logger(__name__) +_DEFAULT_MAX_MEMORIES_PER_AGENT: Final[int] = 10000 _ALL_CATEGORIES: frozenset[MemoryCategory] = frozenset(MemoryCategory) @@ -55,7 +57,7 @@ class InMemoryBackend: def __init__( self, *, - max_memories_per_agent: int = 10_000, + max_memories_per_agent: int = _DEFAULT_MAX_MEMORIES_PER_AGENT, ) -> None: if max_memories_per_agent < 1: msg = f"max_memories_per_agent must be >= 1, got {max_memories_per_agent}" diff --git a/src/synthorg/memory/backends/mem0/adapter.py b/src/synthorg/memory/backends/mem0/adapter.py index 4963295d67..618bc005d8 100644 --- a/src/synthorg/memory/backends/mem0/adapter.py +++ b/src/synthorg/memory/backends/mem0/adapter.py @@ -9,7 +9,7 @@ import asyncio import builtins -from typing import TYPE_CHECKING, Any +from typing import TYPE_CHECKING, Any, Final from synthorg.core.enums import MemoryCategory from synthorg.core.types import NotBlankStr @@ -89,6 +89,7 @@ def delete(self, memory_id: str) -> None: ... # noqa: D102 logger = get_logger(__name__) +_DEFAULT_MAX_MEMORIES_PER_AGENT: Final[int] = 10000 class Mem0MemoryBackend(Mem0AdapterCostMixin, Mem0AdapterSharedMixin): @@ -106,7 +107,7 @@ def __init__( self, *, mem0_config: Mem0BackendConfig, - max_memories_per_agent: int = 10_000, + max_memories_per_agent: int = _DEFAULT_MAX_MEMORIES_PER_AGENT, cost_tracker: CostTracker | None = None, ) -> None: if max_memories_per_agent < 1: diff --git a/src/synthorg/memory/consolidation/abstractive.py b/src/synthorg/memory/consolidation/abstractive.py index 6d77cb8c34..1c6123f958 100644 --- a/src/synthorg/memory/consolidation/abstractive.py +++ b/src/synthorg/memory/consolidation/abstractive.py @@ -6,6 +6,7 @@ """ import asyncio +from typing import Final from synthorg.budget.call_category import LLMCallCategory @@ -35,7 +36,10 @@ logger = get_logger(__name__) -_TRUNCATE_LENGTH = 200 +_DEFAULT_MAX_SUMMARY_TOKENS: Final[int] = 200 +_DEFAULT_TEMPERATURE: Final[float] = 0.3 + +_TRUNCATE_LENGTH: Final[int] = 200 _SYSTEM_PROMPT = ( "You are a memory consolidation assistant. Summarize the following " @@ -74,8 +78,8 @@ def __init__( *, provider: CompletionProvider, model: NotBlankStr, - max_summary_tokens: int = 200, - temperature: float = 0.3, + max_summary_tokens: int = _DEFAULT_MAX_SUMMARY_TOKENS, + temperature: float = _DEFAULT_TEMPERATURE, cost_tracker: CostTracker | None = None, ) -> None: if not model or not model.strip(): diff --git a/src/synthorg/memory/consolidation/config.py b/src/synthorg/memory/consolidation/config.py index f0cb015516..ec3e862f39 100644 --- a/src/synthorg/memory/consolidation/config.py +++ b/src/synthorg/memory/consolidation/config.py @@ -6,7 +6,7 @@ """ from pathlib import PurePosixPath, PureWindowsPath -from typing import Literal, Self +from typing import Final, Literal, Self from pydantic import BaseModel, ConfigDict, Field, model_validator @@ -348,7 +348,7 @@ class ConsolidationConfig(BaseModel): ) -_MIN_LLM_GROUP_THRESHOLD = 3 +_MIN_LLM_GROUP_THRESHOLD: Final[int] = 3 class LLMConsolidationConfig(BaseModel): diff --git a/src/synthorg/memory/consolidation/density.py b/src/synthorg/memory/consolidation/density.py index 6a8786d9e2..ef54c27745 100644 --- a/src/synthorg/memory/consolidation/density.py +++ b/src/synthorg/memory/consolidation/density.py @@ -34,13 +34,13 @@ class ContentDensity(StrEnum): # ── Heuristic signal weights ──────────────────────────────────── -_WEIGHT_CODE = 0.30 -_WEIGHT_STRUCTURED = 0.25 -_WEIGHT_IDENTIFIERS = 0.20 -_WEIGHT_NUMERIC = 0.10 -_WEIGHT_LINE_STRUCTURE = 0.15 +_WEIGHT_CODE: Final[float] = 0.30 +_WEIGHT_STRUCTURED: Final[float] = 0.25 +_WEIGHT_IDENTIFIERS: Final[float] = 0.20 +_WEIGHT_NUMERIC: Final[float] = 0.10 +_WEIGHT_LINE_STRUCTURE: Final[float] = 0.15 -_WEIGHT_SUM_TOLERANCE = 1e-9 +_WEIGHT_SUM_TOLERANCE: Final[float] = 1e-9 # Guard: weights must sum to 1.0 for threshold interpretability. assert ( # noqa: S101 diff --git a/src/synthorg/memory/consolidation/dual_mode_strategy.py b/src/synthorg/memory/consolidation/dual_mode_strategy.py index 59a35dc209..51102fccd2 100644 --- a/src/synthorg/memory/consolidation/dual_mode_strategy.py +++ b/src/synthorg/memory/consolidation/dual_mode_strategy.py @@ -8,6 +8,7 @@ import asyncio from itertools import groupby from operator import attrgetter +from typing import Final from synthorg.core.enums import MemoryCategory # noqa: TC001 from synthorg.core.types import NotBlankStr # noqa: TC001 @@ -37,8 +38,8 @@ logger = get_logger(__name__) -_DEFAULT_GROUP_THRESHOLD = 3 -_MIN_GROUP_THRESHOLD = 2 +_DEFAULT_GROUP_THRESHOLD: Final[int] = 3 +_MIN_GROUP_THRESHOLD: Final[int] = 2 class DualModeConsolidationStrategy: diff --git a/src/synthorg/memory/consolidation/extractive.py b/src/synthorg/memory/consolidation/extractive.py index 6f746db3c7..6a90c0fa12 100644 --- a/src/synthorg/memory/consolidation/extractive.py +++ b/src/synthorg/memory/consolidation/extractive.py @@ -7,6 +7,7 @@ """ import re +from typing import Final from synthorg.observability import get_logger from synthorg.observability.events.consolidation import ( @@ -14,6 +15,8 @@ ) logger = get_logger(__name__) +_DEFAULT_MAX_FACTS: Final[int] = 20 +_DEFAULT_ANCHOR_LENGTH: Final[int] = 150 # ── Extraction patterns ───────────────────────────────────────── @@ -111,8 +114,8 @@ class ExtractivePreserver: def __init__( self, *, - max_facts: int = 20, - anchor_length: int = 150, + max_facts: int = _DEFAULT_MAX_FACTS, + anchor_length: int = _DEFAULT_ANCHOR_LENGTH, ) -> None: if max_facts < 1: msg = f"max_facts must be >= 1, got {max_facts}" diff --git a/src/synthorg/memory/consolidation/service.py b/src/synthorg/memory/consolidation/service.py index eaaa7e3357..b876e0da6b 100644 --- a/src/synthorg/memory/consolidation/service.py +++ b/src/synthorg/memory/consolidation/service.py @@ -6,7 +6,7 @@ from collections.abc import Mapping # noqa: TC003 from datetime import UTC, datetime -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, Final from synthorg.core.enums import MemoryCategory # noqa: TC001 from synthorg.core.types import NotBlankStr # noqa: TC001 @@ -47,15 +47,15 @@ logger = get_logger(__name__) -_MAX_ENFORCE_BATCH = 1000 -_MAX_ENFORCE_BATCH_MIN = 100 -_MAX_ENFORCE_BATCH_MAX = 10_000 +_MAX_ENFORCE_BATCH: Final[int] = 1000 +_MAX_ENFORCE_BATCH_MIN: Final[int] = 100 +_MAX_ENFORCE_BATCH_MAX: Final[int] = 10_000 # ``MemoryQuery.limit`` has its own schema bound (``le=1000``); the # max-enforce batch settings allows up to 10k records, so each # per-batch fetch has to be clamped to the query-layer bound before # constructing the MemoryQuery below. The enforce loop just issues # more MemoryQuery fetches to cover any excess. -_MEMORY_QUERY_MAX_LIMIT = 1000 +_MEMORY_QUERY_MAX_LIMIT: Final[int] = 1000 class MemoryConsolidationService: diff --git a/src/synthorg/memory/consolidation/simple_strategy.py b/src/synthorg/memory/consolidation/simple_strategy.py index 27ab9db72d..61a3574ff3 100644 --- a/src/synthorg/memory/consolidation/simple_strategy.py +++ b/src/synthorg/memory/consolidation/simple_strategy.py @@ -7,6 +7,7 @@ from itertools import groupby from operator import attrgetter +from typing import Final from synthorg.core.enums import MemoryCategory # noqa: TC001 from synthorg.core.types import NotBlankStr # noqa: TC001 @@ -21,11 +22,11 @@ logger = get_logger(__name__) -_SUMMARY_TRUNCATE_LENGTH = 200 +_SUMMARY_TRUNCATE_LENGTH: Final[int] = 200 -_DEFAULT_GROUP_THRESHOLD = 3 +_DEFAULT_GROUP_THRESHOLD: Final[int] = 3 -_MIN_GROUP_THRESHOLD = 2 +_MIN_GROUP_THRESHOLD: Final[int] = 2 class SimpleConsolidationStrategy: diff --git a/src/synthorg/memory/consolidation/two_tier_strategy.py b/src/synthorg/memory/consolidation/two_tier_strategy.py index 2ff121341b..7b5a0c2cc3 100644 --- a/src/synthorg/memory/consolidation/two_tier_strategy.py +++ b/src/synthorg/memory/consolidation/two_tier_strategy.py @@ -8,7 +8,7 @@ import asyncio import builtins import json -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, Final from synthorg.core.enums import MemoryCategory from synthorg.memory.consolidation.models import ConsolidationResult @@ -36,7 +36,7 @@ _DETAILED_TAG = "detailed_experience" _COMPRESSED_TAG = "compressed_experience" -_MAX_CONTEXT_ENTRIES = 5 +_MAX_CONTEXT_ENTRIES: Final[int] = 5 class TwoTierCompressionStrategy: diff --git a/src/synthorg/memory/embedding/fine_tune.py b/src/synthorg/memory/embedding/fine_tune.py index fb6e162344..5ed8ddc1f3 100644 --- a/src/synthorg/memory/embedding/fine_tune.py +++ b/src/synthorg/memory/embedding/fine_tune.py @@ -19,7 +19,7 @@ from collections.abc import Callable from enum import StrEnum from pathlib import Path -from typing import TYPE_CHECKING, Any +from typing import TYPE_CHECKING, Any, Final from synthorg.memory.errors import FineTuneDependencyError from synthorg.observability import get_logger @@ -74,6 +74,15 @@ class FineTuneStage(StrEnum): "`pip install 'synthorg[fine-tune-cpu]'`." ) +_DEFAULT_CHUNK_SIZE_WORDS: Final[int] = 512 +_DEFAULT_VALIDATION_SPLIT: Final[float] = 0.1 +_DEFAULT_HARD_NEGATIVE_TOP_K: Final[int] = 4 +_DEFAULT_TRAIN_EPOCHS: Final[int] = 3 +_DEFAULT_TRAIN_LEARNING_RATE: Final[float] = 1e-5 +_DEFAULT_TRAIN_TEMPERATURE: Final[float] = 0.02 +_DEFAULT_TRAIN_BATCH_SIZE: Final[int] = 128 +_DEFAULT_METRICS_K: Final[int] = 10 + def _import_sentence_transformers() -> ModuleType: """Lazy-import sentence-transformers with friendly error.""" @@ -138,7 +147,7 @@ def _ensure_dir(path: str) -> Path: def _chunk_text( text: str, - chunk_size: int = 512, # lint-allow: magic-numbers -- bootstrap + chunk_size: int = _DEFAULT_CHUNK_SIZE_WORDS, ) -> list[str]: """Split text into word-boundary chunks. @@ -179,7 +188,7 @@ async def generate_training_data( # noqa: PLR0913 output_dir: str, *, llm_provider: object | None = None, - validation_split: float = 0.1, + validation_split: float = _DEFAULT_VALIDATION_SPLIT, progress_callback: ProgressCallback | None = None, cancellation: CancellationToken | None = None, ) -> tuple[Path, Path]: @@ -282,7 +291,7 @@ async def mine_hard_negatives( # noqa: PLR0913 base_model: str, output_dir: str, *, - top_k: int = 4, + top_k: int = _DEFAULT_HARD_NEGATIVE_TOP_K, progress_callback: ProgressCallback | None = None, cancellation: CancellationToken | None = None, ) -> Path: @@ -406,10 +415,10 @@ async def contrastive_fine_tune( # noqa: PLR0913 base_model: str, output_dir: str, *, - epochs: int = 3, - learning_rate: float = 1e-5, - temperature: float = 0.02, - batch_size: int = 128, + epochs: int = _DEFAULT_TRAIN_EPOCHS, + learning_rate: float = _DEFAULT_TRAIN_LEARNING_RATE, + temperature: float = _DEFAULT_TRAIN_TEMPERATURE, + batch_size: int = _DEFAULT_TRAIN_BATCH_SIZE, progress_callback: ProgressCallback | None = None, cancellation: CancellationToken | None = None, ) -> Path: @@ -642,7 +651,7 @@ async def evaluate_checkpoint( # noqa: PLR0913 def _compute_metrics( query_embs: object, passage_embs: object, - k: int = 10, + k: int = _DEFAULT_METRICS_K, ) -> tuple[float, float]: """Compute NDCG@k and Recall@k. diff --git a/src/synthorg/memory/fine_tune_plan.py b/src/synthorg/memory/fine_tune_plan.py index 47835bc154..fea472f2a1 100644 --- a/src/synthorg/memory/fine_tune_plan.py +++ b/src/synthorg/memory/fine_tune_plan.py @@ -20,7 +20,7 @@ """ from pathlib import PurePosixPath, PureWindowsPath -from typing import Literal, Self +from typing import Final, Self from pydantic import BaseModel, ConfigDict, Field, model_validator @@ -37,7 +37,7 @@ # Minimum length of a path with a Windows drive letter prefix ("C:"). # Declared before the class that uses it so the path-traversal check # reads in source order. -_MIN_DRIVE_LETTER_LEN: Literal[2] = 2 +_MIN_DRIVE_LETTER_LEN: Final[int] = 2 class MemoryBackendUnsupportedError(DomainError): diff --git a/src/synthorg/memory/procedural/capture/success_capture.py b/src/synthorg/memory/procedural/capture/success_capture.py index 1d72ac11bf..b3707cb8a1 100644 --- a/src/synthorg/memory/procedural/capture/success_capture.py +++ b/src/synthorg/memory/procedural/capture/success_capture.py @@ -4,6 +4,8 @@ a configurable quality threshold derived from the proposer's confidence. """ +from typing import Final + from synthorg.core.enums import MemoryCategory from synthorg.core.types import NotBlankStr # noqa: TC001 from synthorg.engine.loop_protocol import ( @@ -26,6 +28,7 @@ ) logger = get_logger(__name__) +_DEFAULT_MIN_QUALITY_SCORE: Final[float] = 8.0 class SuccessCaptureStrategy: @@ -47,7 +50,7 @@ def __init__( *, proposer: SuccessMemoryProposer, config: ProceduralMemoryConfig, - min_quality_score: float = 8.0, + min_quality_score: float = _DEFAULT_MIN_QUALITY_SCORE, ) -> None: self._proposer = proposer self._config = config diff --git a/src/synthorg/memory/procedural/propagation/department_scoped.py b/src/synthorg/memory/procedural/propagation/department_scoped.py index c5063c3637..63663da462 100644 --- a/src/synthorg/memory/procedural/propagation/department_scoped.py +++ b/src/synthorg/memory/procedural/propagation/department_scoped.py @@ -3,7 +3,7 @@ Shares memory with agents in the same department. """ -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, Final from synthorg.memory.models import MemoryStoreRequest from synthorg.observability import get_logger, safe_error_description @@ -18,6 +18,7 @@ from synthorg.memory.protocol import MemoryBackend logger = get_logger(__name__) +_DEFAULT_MAX_TARGETS: Final[int] = 10 class DepartmentScopedPropagation: @@ -28,7 +29,7 @@ class DepartmentScopedPropagation: (default 10). """ - def __init__(self, max_targets: int = 10) -> None: + def __init__(self, max_targets: int = _DEFAULT_MAX_TARGETS) -> None: """Initialize department-scoped propagation strategy. Args: diff --git a/src/synthorg/memory/procedural/propagation/role_scoped.py b/src/synthorg/memory/procedural/propagation/role_scoped.py index e30e35ed6b..b7a1cf1d57 100644 --- a/src/synthorg/memory/procedural/propagation/role_scoped.py +++ b/src/synthorg/memory/procedural/propagation/role_scoped.py @@ -3,7 +3,7 @@ Shares memory with agents of the same role. """ -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, Final from synthorg.memory.models import MemoryStoreRequest from synthorg.observability import get_logger, safe_error_description @@ -18,6 +18,7 @@ from synthorg.memory.protocol import MemoryBackend logger = get_logger(__name__) +_DEFAULT_MAX_TARGETS: Final[int] = 10 class RoleScopedPropagation: @@ -28,7 +29,7 @@ class RoleScopedPropagation: (default 10). """ - def __init__(self, max_targets: int = 10) -> None: + def __init__(self, max_targets: int = _DEFAULT_MAX_TARGETS) -> None: """Initialize role-scoped propagation strategy. Args: diff --git a/src/synthorg/memory/procedural/pruning/pareto_strategy.py b/src/synthorg/memory/procedural/pruning/pareto_strategy.py index 6d8e138750..d36dc2ad00 100644 --- a/src/synthorg/memory/procedural/pruning/pareto_strategy.py +++ b/src/synthorg/memory/procedural/pruning/pareto_strategy.py @@ -4,7 +4,7 @@ recency). Entries on the Pareto frontier are preserved. """ -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, Final from synthorg.observability import get_logger @@ -13,6 +13,7 @@ from synthorg.memory.models import MemoryEntry logger = get_logger(__name__) +_DEFAULT_MAX_ENTRIES: Final[int] = 100 class ParetoPruningStrategy: @@ -29,7 +30,7 @@ class ParetoPruningStrategy: max_entries: Maximum entries to keep (default 100). """ - def __init__(self, max_entries: int = 100) -> None: + def __init__(self, max_entries: int = _DEFAULT_MAX_ENTRIES) -> None: """Initialize Pareto pruning strategy. Args: diff --git a/src/synthorg/memory/procedural/pruning/ttl_strategy.py b/src/synthorg/memory/procedural/pruning/ttl_strategy.py index 0757f57177..de89a82328 100644 --- a/src/synthorg/memory/procedural/pruning/ttl_strategy.py +++ b/src/synthorg/memory/procedural/pruning/ttl_strategy.py @@ -4,7 +4,7 @@ """ from datetime import UTC, datetime -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, Final from synthorg.observability import get_logger @@ -13,6 +13,7 @@ from synthorg.memory.models import MemoryEntry logger = get_logger(__name__) +_DEFAULT_MAX_AGE_DAYS: Final[int] = 90 class TtlPruningStrategy: @@ -22,7 +23,7 @@ class TtlPruningStrategy: max_age_days: Maximum age in days for entries (default 90). """ - def __init__(self, max_age_days: int = 90) -> None: + def __init__(self, max_age_days: int = _DEFAULT_MAX_AGE_DAYS) -> None: """Initialize TTL pruning strategy. Args: diff --git a/src/synthorg/memory/procedural/trajectory_aggregator.py b/src/synthorg/memory/procedural/trajectory_aggregator.py index b61a1d57b8..7cd68ac7ab 100644 --- a/src/synthorg/memory/procedural/trajectory_aggregator.py +++ b/src/synthorg/memory/procedural/trajectory_aggregator.py @@ -6,7 +6,7 @@ import json from collections import defaultdict -from typing import Literal, Self +from typing import Final, Literal, Self from uuid import uuid4 from pydantic import AwareDatetime, BaseModel, ConfigDict, Field, model_validator @@ -19,6 +19,7 @@ ) logger = get_logger(__name__) +_DEFAULT_MIN_AGENTS_FOR_PATTERN: Final[int] = 3 class AggregatedTrajectory(BaseModel): @@ -121,7 +122,9 @@ class TrajectoryAggregator: __slots__ = ("_min_agents", "last_skipped_count") - def __init__(self, *, min_agents_for_pattern: int = 3) -> None: + def __init__( + self, *, min_agents_for_pattern: int = _DEFAULT_MIN_AGENTS_FOR_PATTERN + ) -> None: if min_agents_for_pattern <= 0: msg = ( f"min_agents_for_pattern must be positive, got {min_agents_for_pattern}" diff --git a/src/synthorg/memory/ranking.py b/src/synthorg/memory/ranking.py index 4bf14f569d..533b7cd55b 100644 --- a/src/synthorg/memory/ranking.py +++ b/src/synthorg/memory/ranking.py @@ -325,11 +325,15 @@ def _accumulate_rrf_scores( return scores, entries, duplicate_count +_DEFAULT_K: Final[int] = 60 +_DEFAULT_MAX_RESULTS: Final[int] = 20 + + def fuse_ranked_lists( ranked_lists: tuple[tuple[MemoryEntry, ...], ...], *, - k: int = 60, - max_results: int = 20, + k: int = _DEFAULT_K, + max_results: int = _DEFAULT_MAX_RESULTS, ) -> tuple[ScoredMemory, ...]: """Merge multiple pre-ranked lists via Reciprocal Rank Fusion. @@ -448,10 +452,13 @@ def bigram_jaccard(text_a: str, text_b: str) -> float: return intersection / union +_DEFAULT_DIVERSITY_LAMBDA: Final[float] = 0.7 + + def apply_diversity_penalty( scored: tuple[ScoredMemory, ...], *, - diversity_lambda: float = 0.7, + diversity_lambda: float = _DEFAULT_DIVERSITY_LAMBDA, similarity_fn: Callable[[str, str], float] | None = None, ) -> tuple[ScoredMemory, ...]: """Re-rank scored memories using Maximal Marginal Relevance. diff --git a/src/synthorg/memory/retrieval/hierarchical/supervisor.py b/src/synthorg/memory/retrieval/hierarchical/supervisor.py index db0ef60602..e6a3e229b8 100644 --- a/src/synthorg/memory/retrieval/hierarchical/supervisor.py +++ b/src/synthorg/memory/retrieval/hierarchical/supervisor.py @@ -6,6 +6,7 @@ import builtins import json +from typing import Final from synthorg.budget.call_category import LLMCallCategory @@ -77,8 +78,10 @@ + _UNTRUSTED_DIRECTIVE ) -_DEFAULT_QUALITY_THRESHOLD = 0.3 +_DEFAULT_QUALITY_THRESHOLD: Final[float] = 0.3 _DEFAULT_FALLBACK_WORKERS = ("semantic",) +_DEFAULT_MAX_WORKERS_PER_QUERY: Final[int] = 2 +_DEFAULT_MAX_RETRY_COUNT: Final[int] = 2 # Routing decisions and retry-evaluation must be deterministic so the # same query produces the same worker selection across runs; pin @@ -104,9 +107,9 @@ def __init__( # noqa: PLR0913 *, provider: CompletionProvider, model: NotBlankStr, - max_workers_per_query: int = 2, # lint-allow: magic-numbers -- bounded fan-out + max_workers_per_query: int = _DEFAULT_MAX_WORKERS_PER_QUERY, reflective_retry_enabled: bool = True, - max_retry_count: int = 2, # lint-allow: magic-numbers -- bounded retry budget + max_retry_count: int = _DEFAULT_MAX_RETRY_COUNT, quality_threshold: float = _DEFAULT_QUALITY_THRESHOLD, cost_tracker: CostTracker | None = None, ) -> None: diff --git a/src/synthorg/memory/retrieval/hierarchical/workers.py b/src/synthorg/memory/retrieval/hierarchical/workers.py index 6604fccd26..1e46cd88fb 100644 --- a/src/synthorg/memory/retrieval/hierarchical/workers.py +++ b/src/synthorg/memory/retrieval/hierarchical/workers.py @@ -6,7 +6,7 @@ import builtins from datetime import UTC, datetime, timedelta -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, Final from synthorg.core.clock import Clock, SystemClock from synthorg.core.enums import MemoryCategory @@ -39,7 +39,7 @@ logger = get_logger(__name__) -_DEFAULT_EPISODIC_WINDOW_HOURS = 72 +_DEFAULT_EPISODIC_WINDOW_HOURS: Final[int] = 72 def _scored_to_candidate( diff --git a/src/synthorg/memory/retrieval/reranking/cache.py b/src/synthorg/memory/retrieval/reranking/cache.py index f2ca14781f..cb520f0882 100644 --- a/src/synthorg/memory/retrieval/reranking/cache.py +++ b/src/synthorg/memory/retrieval/reranking/cache.py @@ -10,6 +10,7 @@ """ import asyncio +from typing import Final from synthorg.core.clock import Clock, SystemClock from synthorg.observability import get_logger @@ -23,8 +24,8 @@ logger = get_logger(__name__) -_DEFAULT_TTL_SECONDS = 3600 -_DEFAULT_MAX_SIZE = 1000 +_DEFAULT_TTL_SECONDS: Final[int] = 3600 +_DEFAULT_MAX_SIZE: Final[int] = 1000 class RerankerCache: diff --git a/src/synthorg/memory/retrieval/reranking/llm_reranker.py b/src/synthorg/memory/retrieval/reranking/llm_reranker.py index 1cf86c5652..0e7b380b30 100644 --- a/src/synthorg/memory/retrieval/reranking/llm_reranker.py +++ b/src/synthorg/memory/retrieval/reranking/llm_reranker.py @@ -7,6 +7,7 @@ import builtins import hashlib import json +from typing import Final from synthorg.budget.call_category import LLMCallCategory from synthorg.core.types import NotBlankStr @@ -57,7 +58,7 @@ "\n\n" + untrusted_content_directive((TAG_TASK_DATA,)) ) -_MAX_CANDIDATE_CONTENT_CHARS = 500 +_MAX_CANDIDATE_CONTENT_CHARS: Final[int] = 500 def _build_cache_key( diff --git a/src/synthorg/memory/retrieval_config.py b/src/synthorg/memory/retrieval_config.py index 270df6cc4e..460d7a1d19 100644 --- a/src/synthorg/memory/retrieval_config.py +++ b/src/synthorg/memory/retrieval_config.py @@ -5,7 +5,7 @@ query-specific re-ranking. """ -from typing import Literal, Self +from typing import Final, Literal, Self from pydantic import BaseModel, ConfigDict, Field, model_validator @@ -16,13 +16,13 @@ logger = get_logger(__name__) -_WEIGHT_SUM_TOLERANCE = 1e-6 -_DEFAULT_RRF_K = 60 -_DEFAULT_DIVERSITY_LAMBDA = 0.7 -_DEFAULT_CANDIDATE_POOL_MULTIPLIER = 3 -_DEFAULT_MAX_WORKERS_PER_QUERY = 2 -_DEFAULT_RERANK_CACHE_TTL_SECONDS = 3600 -_DEFAULT_MAX_RETRY_COUNT = 2 +_WEIGHT_SUM_TOLERANCE: Final[float] = 1e-6 +_DEFAULT_RRF_K: Final[int] = 60 +_DEFAULT_DIVERSITY_LAMBDA: Final[float] = 0.7 +_DEFAULT_CANDIDATE_POOL_MULTIPLIER: Final[int] = 3 +_DEFAULT_MAX_WORKERS_PER_QUERY: Final[int] = 2 +_DEFAULT_RERANK_CACHE_TTL_SECONDS: Final[int] = 3600 +_DEFAULT_MAX_RETRY_COUNT: Final[int] = 2 class MemoryRetrievalConfig(BaseModel): diff --git a/src/synthorg/memory/sparse.py b/src/synthorg/memory/sparse.py index 968691e78e..8b9848463f 100644 --- a/src/synthorg/memory/sparse.py +++ b/src/synthorg/memory/sparse.py @@ -9,7 +9,7 @@ import re import unicodedata from collections import Counter -from typing import Self +from typing import Final, Self import mmh3 from pydantic import BaseModel, ConfigDict, Field, model_validator @@ -77,7 +77,7 @@ ) _TOKEN_SPLIT_RE = re.compile(r"[\W_]+") -_MIN_INDICES_FOR_SORT_CHECK = 2 +_MIN_INDICES_FOR_SORT_CHECK: Final[int] = 2 class SparseVector(BaseModel): diff --git a/src/synthorg/memory/tools/_args.py b/src/synthorg/memory/tools/_args.py index c3b793986e..80a90d0fc2 100644 --- a/src/synthorg/memory/tools/_args.py +++ b/src/synthorg/memory/tools/_args.py @@ -37,6 +37,8 @@ re-exported from here so we don't double-define the discriminator. """ +from typing import Final + from pydantic import BaseModel, ConfigDict, Field from synthorg.core.enums import MemoryCategory # noqa: TC001 -- Pydantic field type @@ -49,7 +51,7 @@ ) -_MAX_MEMORY_ID_LEN = 256 +_MAX_MEMORY_ID_LEN: Final[int] = 256 # ── Tool-based retrieval (search_memory / recall_memory) ──────────── diff --git a/src/synthorg/meta/analytics/service.py b/src/synthorg/meta/analytics/service.py index 89ee67153f..5d7e971d25 100644 --- a/src/synthorg/meta/analytics/service.py +++ b/src/synthorg/meta/analytics/service.py @@ -15,7 +15,7 @@ import asyncio from datetime import UTC, datetime -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, Final from synthorg.core.types import NotBlankStr from synthorg.meta.analytics.models import ( @@ -40,8 +40,10 @@ from synthorg.meta.signals.service import SignalsService logger = get_logger(__name__) +_DEFAULT_HORIZON_DAYS: Final[int] = 30 +_DEFAULT_SAMPLE_COUNT: Final[int] = 8 -_MAX_HISTORY_SAMPLE_COUNT = 128 +_MAX_HISTORY_SAMPLE_COUNT: Final[int] = 128 class AnalyticsService: @@ -115,7 +117,7 @@ async def get_forecast( *, since: datetime, until: datetime, - horizon_days: int = 30, + horizon_days: int = _DEFAULT_HORIZON_DAYS, ) -> AnalyticsForecast: """Return a simple linear forecast derived from the budget window.""" if horizon_days < 1: @@ -183,7 +185,7 @@ async def get_metric_history( since: datetime, until: datetime, metric_names: Sequence[str], - sample_count: int = 8, + sample_count: int = _DEFAULT_SAMPLE_COUNT, ) -> MetricsHistory: """Return evenly-spaced point-in-time samples across the window. diff --git a/src/synthorg/meta/appliers/github_client.py b/src/synthorg/meta/appliers/github_client.py index 0d3973b5aa..7aedf59476 100644 --- a/src/synthorg/meta/appliers/github_client.py +++ b/src/synthorg/meta/appliers/github_client.py @@ -7,7 +7,7 @@ import base64 import re -from typing import Any, ClassVar, Self +from typing import Any, ClassVar, Final, Self import httpx @@ -59,7 +59,7 @@ class GitHubAuthError(GitHubAPIError): logger = get_logger(__name__) -_DEFAULT_TIMEOUT = 30 +_DEFAULT_TIMEOUT: Final[int] = 30 class HttpGitHubClient: diff --git a/src/synthorg/meta/appliers/prompt_applier.py b/src/synthorg/meta/appliers/prompt_applier.py index 1068a5d263..8c03cb90ab 100644 --- a/src/synthorg/meta/appliers/prompt_applier.py +++ b/src/synthorg/meta/appliers/prompt_applier.py @@ -6,7 +6,7 @@ quality, duplicates, and conflicting evolution modes. """ -from typing import Protocol, runtime_checkable +from typing import Final, Protocol, runtime_checkable from synthorg.meta.models import ( ApplyResult, @@ -26,8 +26,8 @@ logger = get_logger(__name__) -_PRINCIPLE_MIN_CHARS = 10 -_PRINCIPLE_MAX_CHARS = 4000 +_PRINCIPLE_MIN_CHARS: Final[int] = 10 +_PRINCIPLE_MAX_CHARS: Final[int] = 4000 _SCOPE_ALL = "all" diff --git a/src/synthorg/meta/chief_of_staff/inflection.py b/src/synthorg/meta/chief_of_staff/inflection.py index 527d7a6c2f..9a8b829971 100644 --- a/src/synthorg/meta/chief_of_staff/inflection.py +++ b/src/synthorg/meta/chief_of_staff/inflection.py @@ -5,7 +5,7 @@ """ from datetime import UTC, datetime -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, Final from synthorg.core.types import NotBlankStr from synthorg.meta.chief_of_staff.models import OrgInflection @@ -17,8 +17,10 @@ from collections.abc import Callable logger = get_logger(__name__) +_DEFAULT_WARNING_THRESHOLD: Final[float] = 0.15 +_DEFAULT_CRITICAL_THRESHOLD: Final[float] = 0.3 -_EPSILON = 1e-9 +_EPSILON: Final[float] = 1e-9 def _perf_quality(s: OrgSignalSnapshot) -> float: @@ -77,8 +79,8 @@ class OrgInflectionDetector: def __init__( self, *, - warning_threshold: float = 0.15, - critical_threshold: float = 0.30, + warning_threshold: float = _DEFAULT_WARNING_THRESHOLD, + critical_threshold: float = _DEFAULT_CRITICAL_THRESHOLD, ) -> None: self._warning = warning_threshold self._critical = critical_threshold diff --git a/src/synthorg/meta/chief_of_staff/learning.py b/src/synthorg/meta/chief_of_staff/learning.py index 63dfcf5afd..4e54cdac3d 100644 --- a/src/synthorg/meta/chief_of_staff/learning.py +++ b/src/synthorg/meta/chief_of_staff/learning.py @@ -7,7 +7,7 @@ - **Bayesian**: Beta-conjugate posterior blend. """ -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, Final from synthorg.core.types import NotBlankStr from synthorg.observability import get_logger @@ -22,6 +22,11 @@ logger = get_logger(__name__) +_DEFAULT_EMA_ALPHA: Final[float] = 0.5 +_DEFAULT_BAYESIAN_PRIOR_ALPHA: Final[float] = 2.0 +_DEFAULT_BAYESIAN_PRIOR_BETA: Final[float] = 2.0 +_DEFAULT_BAYESIAN_BLEND: Final[float] = 0.7 + class ExponentialMovingAverageAdjuster: """Confidence adjuster using exponential moving average. @@ -39,7 +44,7 @@ class ExponentialMovingAverageAdjuster: alpha: Blend factor between base confidence and history. """ - def __init__(self, *, alpha: float = 0.5) -> None: + def __init__(self, *, alpha: float = _DEFAULT_EMA_ALPHA) -> None: self._alpha = alpha @property @@ -112,9 +117,9 @@ class BayesianConfidenceAdjuster: def __init__( self, *, - prior_alpha: float = 2.0, - prior_beta: float = 2.0, - blend: float = 0.7, + prior_alpha: float = _DEFAULT_BAYESIAN_PRIOR_ALPHA, + prior_beta: float = _DEFAULT_BAYESIAN_PRIOR_BETA, + blend: float = _DEFAULT_BAYESIAN_BLEND, ) -> None: self._prior_alpha = prior_alpha self._prior_beta = prior_beta diff --git a/src/synthorg/meta/chief_of_staff/monitor.py b/src/synthorg/meta/chief_of_staff/monitor.py index c14f3c0384..35b234c255 100644 --- a/src/synthorg/meta/chief_of_staff/monitor.py +++ b/src/synthorg/meta/chief_of_staff/monitor.py @@ -8,7 +8,7 @@ import asyncio from datetime import UTC, datetime, timedelta -from typing import TYPE_CHECKING, ClassVar +from typing import TYPE_CHECKING, ClassVar, Final from synthorg.core.domain_errors import ConflictError from synthorg.observability import get_logger, safe_error_description @@ -29,6 +29,7 @@ from synthorg.meta.signals.snapshot import SnapshotBuilder logger = get_logger(__name__) +_DEFAULT_CHECK_INTERVAL_MINUTES: Final[int] = 15 class InflectionMonitorLifecycleError(ConflictError): @@ -65,7 +66,7 @@ def __init__( detector: OrgInflectionDetector, snapshot_builder: SnapshotBuilder, sinks: tuple[OrgInflectionSink, ...], - check_interval_minutes: int = 15, + check_interval_minutes: int = _DEFAULT_CHECK_INTERVAL_MINUTES, ) -> None: if check_interval_minutes < 1: msg = f"check_interval_minutes must be >= 1, got {check_interval_minutes}" diff --git a/src/synthorg/meta/chief_of_staff/outcome_store.py b/src/synthorg/meta/chief_of_staff/outcome_store.py index 1b1730039c..f0b9846fef 100644 --- a/src/synthorg/meta/chief_of_staff/outcome_store.py +++ b/src/synthorg/meta/chief_of_staff/outcome_store.py @@ -6,7 +6,7 @@ """ from datetime import UTC, datetime -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, Final from synthorg.core.enums import MemoryCategory from synthorg.core.types import NotBlankStr @@ -23,6 +23,8 @@ from synthorg.meta.models import ProposalAltitude logger = get_logger(__name__) +_DEFAULT_MIN_OUTCOMES: Final[int] = 3 +_DEFAULT_LIMIT: Final[int] = 10 _NAMESPACE = NotBlankStr("chief_of_staff") @@ -47,7 +49,7 @@ def __init__( *, backend: MemoryBackend, agent_id: NotBlankStr, - min_outcomes: int = 3, + min_outcomes: int = _DEFAULT_MIN_OUTCOMES, ) -> None: self._backend = backend self._agent_id = agent_id @@ -161,7 +163,7 @@ async def recent_outcomes( *, rule_name: NotBlankStr | None = None, altitude: ProposalAltitude | None = None, - limit: int = 10, + limit: int = _DEFAULT_LIMIT, ) -> tuple[ProposalOutcome, ...]: """Retrieve recent outcomes with optional filtering. diff --git a/src/synthorg/meta/chief_of_staff/protocol.py b/src/synthorg/meta/chief_of_staff/protocol.py index 9d630579a1..15a78a3455 100644 --- a/src/synthorg/meta/chief_of_staff/protocol.py +++ b/src/synthorg/meta/chief_of_staff/protocol.py @@ -5,7 +5,7 @@ alert emission. All protocols are runtime-checkable. """ -from typing import TYPE_CHECKING, Protocol, runtime_checkable +from typing import TYPE_CHECKING, Final, Protocol, runtime_checkable if TYPE_CHECKING: from synthorg.core.types import NotBlankStr @@ -18,6 +18,9 @@ from synthorg.meta.models import ImprovementProposal, ProposalAltitude +_DEFAULT_RECENT_OUTCOMES_LIMIT: Final[int] = 10 + + # MemoryOutcomeStore impl in chief_of_staff/outcome_store.py + factory wiring # + 3 consumers (service/learning/chat). @runtime_checkable @@ -67,7 +70,7 @@ async def recent_outcomes( *, rule_name: NotBlankStr | None = None, altitude: ProposalAltitude | None = None, - limit: int = 10, # lint-allow: magic-numbers -- protocol default arg. + limit: int = _DEFAULT_RECENT_OUTCOMES_LIMIT, ) -> tuple[ProposalOutcome, ...]: """Retrieve recent outcomes with optional filtering. diff --git a/src/synthorg/meta/evolution/outcome_store.py b/src/synthorg/meta/evolution/outcome_store.py index 651a61dcfa..896fd84407 100644 --- a/src/synthorg/meta/evolution/outcome_store.py +++ b/src/synthorg/meta/evolution/outcome_store.py @@ -14,7 +14,7 @@ import asyncio from collections import deque -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, Final from synthorg.core.types import NotBlankStr from synthorg.meta.evolution.outcome_models import EvolutionOutcomeRecord @@ -31,8 +31,9 @@ from datetime import datetime logger = get_logger(__name__) +_DEFAULT_MAX_RECENT: Final[int] = 10 -_DEFAULT_MAX_RESULTS = 5_000 +_DEFAULT_MAX_RESULTS: Final[int] = 5_000 """Default ring-buffer capacity. The self-improvement cycle runs on a ceremony cadence (hours/days), @@ -130,7 +131,7 @@ async def summarize( *, since: datetime, until: datetime, - max_recent: int = 10, + max_recent: int = _DEFAULT_MAX_RECENT, ) -> OrgEvolutionSummary: """Roll recorded outcomes into an :class:`OrgEvolutionSummary`.""" if max_recent < 1: diff --git a/src/synthorg/meta/evolution/outcome_store_protocol.py b/src/synthorg/meta/evolution/outcome_store_protocol.py index 794f26f942..fbea6151f9 100644 --- a/src/synthorg/meta/evolution/outcome_store_protocol.py +++ b/src/synthorg/meta/evolution/outcome_store_protocol.py @@ -17,7 +17,9 @@ behind the same protocol without changing any caller. """ -from typing import TYPE_CHECKING, Protocol, runtime_checkable +from typing import TYPE_CHECKING, Final, Protocol, runtime_checkable + +_DEFAULT_MAX_RECENT: Final[int] = 10 if TYPE_CHECKING: from datetime import datetime @@ -82,7 +84,7 @@ async def summarize( *, since: datetime, until: datetime, - max_recent: int = 10, + max_recent: int = _DEFAULT_MAX_RECENT, ) -> OrgEvolutionSummary: """Produce the org-wide evolution summary for the window. diff --git a/src/synthorg/meta/guards/approval_gate.py b/src/synthorg/meta/guards/approval_gate.py index 73ec284592..a71ba75b1d 100644 --- a/src/synthorg/meta/guards/approval_gate.py +++ b/src/synthorg/meta/guards/approval_gate.py @@ -9,7 +9,7 @@ """ from datetime import UTC, datetime, timedelta -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, Final from uuid import NAMESPACE_URL, uuid5 from synthorg.core.approval import ApprovalItem @@ -40,7 +40,7 @@ ProposalAltitude.CODE_MODIFICATION: ApprovalRiskLevel.CRITICAL, } -_DEFAULT_EXPIRY_DAYS = 7 +_DEFAULT_EXPIRY_DAYS: Final[int] = 7 class ApprovalGateGuard: diff --git a/src/synthorg/meta/guards/rate_limit.py b/src/synthorg/meta/guards/rate_limit.py index 04df0025c0..4db85bc184 100644 --- a/src/synthorg/meta/guards/rate_limit.py +++ b/src/synthorg/meta/guards/rate_limit.py @@ -7,6 +7,7 @@ import asyncio from collections import deque from datetime import UTC, datetime, timedelta +from typing import Final from synthorg.core.types import NotBlankStr from synthorg.meta.models import ( @@ -21,6 +22,8 @@ ) logger = get_logger(__name__) +_DEFAULT_MAX_PROPOSALS: Final[int] = 10 +_DEFAULT_WINDOW_HOURS: Final[int] = 24 class RateLimitGuard: @@ -36,8 +39,8 @@ class RateLimitGuard: def __init__( self, *, - max_proposals: int = 10, - window_hours: int = 24, + max_proposals: int = _DEFAULT_MAX_PROPOSALS, + window_hours: int = _DEFAULT_WINDOW_HOURS, ) -> None: self._max_proposals = max_proposals self._window = timedelta(hours=window_hours) diff --git a/src/synthorg/meta/mcp/handlers/analytics.py b/src/synthorg/meta/mcp/handlers/analytics.py index 3d85017601..6af4101f70 100644 --- a/src/synthorg/meta/mcp/handlers/analytics.py +++ b/src/synthorg/meta/mcp/handlers/analytics.py @@ -16,7 +16,7 @@ """ from types import MappingProxyType -from typing import TYPE_CHECKING, Any +from typing import TYPE_CHECKING, Any, Final from uuid import UUID from pydantic import TypeAdapter, ValidationError @@ -58,7 +58,7 @@ _ARG_METRIC_NAMES = "metric_names" _ARG_HORIZON_DAYS = "horizon_days" _ARG_SAMPLE_COUNT = "sample_count" -_MAX_SAMPLE_COUNT = 100 +_MAX_SAMPLE_COUNT: Final[int] = 100 _TY_POSITIVE_INT_CAP = f"positive int <= {_MAX_SAMPLE_COUNT}" diff --git a/src/synthorg/meta/mcp/handlers/common_args.py b/src/synthorg/meta/mcp/handlers/common_args.py index 47122d5a9c..acd80727de 100644 --- a/src/synthorg/meta/mcp/handlers/common_args.py +++ b/src/synthorg/meta/mcp/handlers/common_args.py @@ -26,7 +26,7 @@ import copy from datetime import UTC, datetime -from typing import Any +from typing import Any, Final from synthorg.core.types import NotBlankStr from synthorg.meta.mcp.errors import invalid_argument @@ -316,10 +316,13 @@ def parse_time_window( return since, until +_DEFAULT_DEFAULT_LIMIT: Final[int] = 50 + + def coerce_pagination( arguments: dict[str, Any], *, - default_limit: int = 50, + default_limit: int = _DEFAULT_DEFAULT_LIMIT, ) -> tuple[int, int]: """Parse ``offset``/``limit`` as ints with strict bounds + bool rejection. diff --git a/src/synthorg/meta/reports/service.py b/src/synthorg/meta/reports/service.py index 63ae271ac5..63bf6f6088 100644 --- a/src/synthorg/meta/reports/service.py +++ b/src/synthorg/meta/reports/service.py @@ -14,7 +14,7 @@ import asyncio from datetime import UTC, datetime, timedelta -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, Final from synthorg.core.types import NotBlankStr from synthorg.meta.reports.models import Report, ReportStatus @@ -31,6 +31,8 @@ from synthorg.meta.analytics.service import AnalyticsService logger = get_logger(__name__) +_DEFAULT_WINDOW_DAYS: Final[int] = 7 +_DEFAULT_LIMIT: Final[int] = 50 _SUPPORTED_TEMPLATES: frozenset[str] = frozenset( { @@ -61,7 +63,7 @@ def __init__( self, *, analytics: AnalyticsService, - window_days: int = 7, + window_days: int = _DEFAULT_WINDOW_DAYS, ) -> None: if window_days < 1: msg = f"window_days must be >= 1, got {window_days}" @@ -77,7 +79,7 @@ async def list_reports( self, *, offset: int = 0, - limit: int = 50, + limit: int = _DEFAULT_LIMIT, ) -> tuple[tuple[Report, ...], int]: """Return a page of reports ordered newest-first. diff --git a/src/synthorg/meta/rollout/ab_comparator.py b/src/synthorg/meta/rollout/ab_comparator.py index fce62ce10f..9461ed9f2e 100644 --- a/src/synthorg/meta/rollout/ab_comparator.py +++ b/src/synthorg/meta/rollout/ab_comparator.py @@ -7,7 +7,7 @@ """ import math -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, Final from synthorg.meta.rollout.ab_models import ( ABTestComparison, @@ -31,7 +31,10 @@ logger = get_logger(__name__) -_MIN_SAMPLES_FOR_VARIANCE = 2 +_MIN_SAMPLES_FOR_VARIANCE: Final[int] = 2 +_DEFAULT_MIN_OBSERVATIONS: Final[int] = 10 +_DEFAULT_IMPROVEMENT_THRESHOLD: Final[float] = 0.15 +_DEFAULT_SIGNIFICANCE_LEVEL: Final[float] = 0.05 class ABTestComparator: @@ -59,9 +62,9 @@ class ABTestComparator: def __init__( self, *, - min_observations: int = 10, - improvement_threshold: float = 0.15, - significance_level: float = 0.05, + min_observations: int = _DEFAULT_MIN_OBSERVATIONS, + improvement_threshold: float = _DEFAULT_IMPROVEMENT_THRESHOLD, + significance_level: float = _DEFAULT_SIGNIFICANCE_LEVEL, ) -> None: if not 0.0 < significance_level < 1.0: logger.warning( diff --git a/src/synthorg/meta/rollout/ab_models.py b/src/synthorg/meta/rollout/ab_models.py index 2174b9eab9..7eae1b3a6e 100644 --- a/src/synthorg/meta/rollout/ab_models.py +++ b/src/synthorg/meta/rollout/ab_models.py @@ -7,7 +7,7 @@ import math from datetime import UTC, datetime from enum import StrEnum -from typing import Self +from typing import Final, Self from uuid import UUID # noqa: TC003 -- Pydantic needs at runtime from pydantic import ( @@ -71,7 +71,7 @@ def _validate_disjoint_groups(self) -> Self: return self -_MAX_QUALITY = 10.0 +_MAX_QUALITY: Final[float] = 10.0 class GroupMetrics(BaseModel): diff --git a/src/synthorg/meta/rollout/ab_test.py b/src/synthorg/meta/rollout/ab_test.py index d85cca1fce..ea83a61336 100644 --- a/src/synthorg/meta/rollout/ab_test.py +++ b/src/synthorg/meta/rollout/ab_test.py @@ -10,7 +10,7 @@ import asyncio import hashlib from datetime import datetime, timedelta -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, Final from synthorg.core.clock import Clock, SystemClock from synthorg.core.types import NotBlankStr @@ -52,6 +52,13 @@ logger = get_logger(__name__) +_DEFAULT_CONTROL_FRACTION: Final[float] = 0.5 +_DEFAULT_MIN_AGENTS_PER_GROUP: Final[int] = 5 +_DEFAULT_MIN_OBSERVATIONS_PER_GROUP: Final[int] = 10 +_DEFAULT_IMPROVEMENT_THRESHOLD: Final[float] = 0.15 +_DEFAULT_SIGNIFICANCE_LEVEL: Final[float] = 0.05 +_DEFAULT_CHECK_INTERVAL_HOURS: Final[float] = 4.0 + class _NullGroupAggregator: """Aggregator returning no samples. Used as a safe default.""" @@ -93,16 +100,16 @@ class ABTestRollout: def __init__( # noqa: PLR0913 self, *, - control_fraction: float = 0.5, - min_agents_per_group: int = 5, - min_observations_per_group: int = 10, - improvement_threshold: float = 0.15, - significance_level: float = 0.05, + control_fraction: float = _DEFAULT_CONTROL_FRACTION, + min_agents_per_group: int = _DEFAULT_MIN_AGENTS_PER_GROUP, + min_observations_per_group: int = _DEFAULT_MIN_OBSERVATIONS_PER_GROUP, + improvement_threshold: float = _DEFAULT_IMPROVEMENT_THRESHOLD, + significance_level: float = _DEFAULT_SIGNIFICANCE_LEVEL, comparator: ABTestComparator | None = None, clock: Clock | None = None, roster: OrgRoster | None = None, group_aggregator: GroupSignalAggregator | None = None, - check_interval_hours: float = 4.0, + check_interval_hours: float = _DEFAULT_CHECK_INTERVAL_HOURS, thresholds: RegressionThresholds | None = None, ) -> None: if control_fraction <= 0.0 or control_fraction >= 1.0: diff --git a/src/synthorg/meta/rollout/before_after.py b/src/synthorg/meta/rollout/before_after.py index 473c913dd9..62e72c222f 100644 --- a/src/synthorg/meta/rollout/before_after.py +++ b/src/synthorg/meta/rollout/before_after.py @@ -8,7 +8,7 @@ """ from collections.abc import Awaitable, Callable -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, Final from synthorg.core.clock import Clock, SystemClock from synthorg.core.types import NotBlankStr @@ -30,6 +30,7 @@ from synthorg.meta.protocol import ProposalApplier, RegressionDetector logger = get_logger(__name__) +_DEFAULT_CHECK_INTERVAL_HOURS: Final[float] = 4.0 SnapshotBuilder = Callable[[], Awaitable[OrgSignalSnapshot]] """Coroutine producing the current org-wide signal snapshot.""" @@ -66,7 +67,7 @@ def __init__( *, clock: Clock | None = None, snapshot_builder: SnapshotBuilder | None = None, - check_interval_hours: float = 4.0, + check_interval_hours: float = _DEFAULT_CHECK_INTERVAL_HOURS, thresholds: RegressionThresholds | None = None, ) -> None: if check_interval_hours <= 0.0: diff --git a/src/synthorg/meta/rollout/canary.py b/src/synthorg/meta/rollout/canary.py index 72e845b527..fa1cd3eb27 100644 --- a/src/synthorg/meta/rollout/canary.py +++ b/src/synthorg/meta/rollout/canary.py @@ -7,7 +7,7 @@ """ import hashlib -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, Final from synthorg.core.clock import Clock, SystemClock from synthorg.core.types import NotBlankStr @@ -34,6 +34,8 @@ from synthorg.meta.protocol import ProposalApplier, RegressionDetector logger = get_logger(__name__) +_DEFAULT_CANARY_FRACTION: Final[float] = 0.2 +_DEFAULT_CHECK_INTERVAL_HOURS: Final[float] = 4.0 class CanarySubsetRollout: @@ -51,11 +53,11 @@ class CanarySubsetRollout: def __init__( # noqa: PLR0913 self, *, - canary_fraction: float = 0.2, + canary_fraction: float = _DEFAULT_CANARY_FRACTION, clock: Clock | None = None, roster: OrgRoster | None = None, snapshot_builder: SnapshotBuilder | None = None, - check_interval_hours: float = 4.0, + check_interval_hours: float = _DEFAULT_CHECK_INTERVAL_HOURS, thresholds: RegressionThresholds | None = None, ) -> None: if canary_fraction <= 0.0 or canary_fraction > 1.0: diff --git a/src/synthorg/meta/rollout/regression/statistical.py b/src/synthorg/meta/rollout/regression/statistical.py index c42a454870..24f7cdd49f 100644 --- a/src/synthorg/meta/rollout/regression/statistical.py +++ b/src/synthorg/meta/rollout/regression/statistical.py @@ -14,7 +14,7 @@ import math from datetime import datetime # noqa: TC003 -- Pydantic needs at runtime from enum import Enum -from typing import Protocol, runtime_checkable +from typing import Final, Protocol, runtime_checkable from pydantic import BaseModel, ConfigDict @@ -38,6 +38,8 @@ ) logger = get_logger(__name__) +_DEFAULT_MIN_DATA_POINTS: Final[int] = 10 +_DEFAULT_SIGNIFICANCE_LEVEL: Final[float] = 0.05 class WindowSamples(BaseModel): @@ -115,8 +117,8 @@ class StatisticalDetector: def __init__( self, *, - min_data_points: int = 10, - significance_level: float = 0.05, + min_data_points: int = _DEFAULT_MIN_DATA_POINTS, + significance_level: float = _DEFAULT_SIGNIFICANCE_LEVEL, sample_source: StatisticalSampleSource | None = None, ) -> None: if min_data_points < 2: # noqa: PLR2004 -- Welch requires n>=2 diff --git a/src/synthorg/meta/rollout/regression/welch.py b/src/synthorg/meta/rollout/regression/welch.py index 51bb77008d..8a198f4389 100644 --- a/src/synthorg/meta/rollout/regression/welch.py +++ b/src/synthorg/meta/rollout/regression/welch.py @@ -13,7 +13,7 @@ """ import math -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, Final from pydantic import BaseModel, ConfigDict, Field @@ -24,7 +24,7 @@ logger = get_logger(__name__) -_MIN_SAMPLES_PER_ARM = 2 +_MIN_SAMPLES_PER_ARM: Final[int] = 2 class InsufficientDataError( @@ -126,9 +126,9 @@ def welch_t_test( return WelchResult(t=t, df=df, p_two_sided=p_two_sided) -_BETACF_MAX_ITER = 200 -_BETACF_EPS = 3.0e-15 -_BETACF_FPMIN = 1.0e-300 +_BETACF_MAX_ITER: Final[int] = 200 +_BETACF_EPS: Final[float] = 3.0e-15 +_BETACF_FPMIN: Final[float] = 1.0e-300 def _regularized_incomplete_beta(a: float, b: float, x: float) -> float: diff --git a/src/synthorg/meta/rules/builtin.py b/src/synthorg/meta/rules/builtin.py index fe6cadb61f..5f1ece0d4c 100644 --- a/src/synthorg/meta/rules/builtin.py +++ b/src/synthorg/meta/rules/builtin.py @@ -8,7 +8,7 @@ with sensible defaults. """ -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, Final from synthorg.core.types import NotBlankStr from synthorg.meta.models import ( @@ -24,6 +24,17 @@ logger = get_logger(__name__) +_DEFAULT_QUALITY_DECLINING_THRESHOLD: Final[float] = 5.0 +_DEFAULT_SUCCESS_RATE_DROP_THRESHOLD: Final[float] = 0.7 +_DEFAULT_BUDGET_OVERRUN_DAYS_THRESHOLD: Final[int] = 14 +_DEFAULT_COORDINATION_COST_RATIO_THRESHOLD: Final[float] = 0.4 +_DEFAULT_COORDINATION_OVERHEAD_THRESHOLD_PCT: Final[float] = 35.0 +_DEFAULT_STRAGGLER_BOTTLENECK_THRESHOLD: Final[float] = 2.0 +_DEFAULT_REDUNDANCY_THRESHOLD: Final[float] = 0.3 +_DEFAULT_SCALING_FAILURE_THRESHOLD: Final[float] = 0.5 +_DEFAULT_SCALING_MIN_DECISIONS: Final[int] = 3 +_DEFAULT_ERROR_SPIKE_THRESHOLD: Final[int] = 10 + # ── Performance rules ────────────────────────────────────────────── @@ -37,7 +48,9 @@ class QualityDecliningRule: threshold: Minimum acceptable quality (0-10, default 5.0). """ - def __init__(self, *, threshold: float = 5.0) -> None: + def __init__( + self, *, threshold: float = _DEFAULT_QUALITY_DECLINING_THRESHOLD + ) -> None: self._threshold = threshold @property @@ -84,7 +97,9 @@ class SuccessRateDropRule: threshold: Minimum acceptable success rate (0-1, default 0.7). """ - def __init__(self, *, threshold: float = 0.7) -> None: + def __init__( + self, *, threshold: float = _DEFAULT_SUCCESS_RATE_DROP_THRESHOLD + ) -> None: self._threshold = threshold @property @@ -130,7 +145,9 @@ class BudgetOverrunRule: (default 14). """ - def __init__(self, *, days_threshold: int = 14) -> None: + def __init__( + self, *, days_threshold: int = _DEFAULT_BUDGET_OVERRUN_DAYS_THRESHOLD + ) -> None: self._days_threshold = days_threshold @property @@ -175,7 +192,9 @@ class CoordinationCostRatioRule: threshold: Max acceptable coordination ratio (0-1, default 0.4). """ - def __init__(self, *, threshold: float = 0.4) -> None: + def __init__( + self, *, threshold: float = _DEFAULT_COORDINATION_COST_RATIO_THRESHOLD + ) -> None: self._threshold = threshold @property @@ -222,7 +241,9 @@ class CoordinationOverheadRule: threshold: Max acceptable overhead % (default 35.0). """ - def __init__(self, *, threshold: float = 35.0) -> None: + def __init__( + self, *, threshold: float = _DEFAULT_COORDINATION_OVERHEAD_THRESHOLD_PCT + ) -> None: self._threshold = threshold @property @@ -266,7 +287,9 @@ class StragglerBottleneckRule: threshold: Max acceptable straggler ratio (default 2.0). """ - def __init__(self, *, threshold: float = 2.0) -> None: + def __init__( + self, *, threshold: float = _DEFAULT_STRAGGLER_BOTTLENECK_THRESHOLD + ) -> None: self._threshold = threshold @property @@ -313,7 +336,7 @@ class RedundancyRule: threshold: Max acceptable redundancy rate (0-1, default 0.3). """ - def __init__(self, *, threshold: float = 0.3) -> None: + def __init__(self, *, threshold: float = _DEFAULT_REDUNDANCY_THRESHOLD) -> None: self._threshold = threshold @property @@ -364,8 +387,8 @@ class ScalingFailureRule: def __init__( self, *, - threshold: float = 0.5, - min_decisions: int = 3, + threshold: float = _DEFAULT_SCALING_FAILURE_THRESHOLD, + min_decisions: int = _DEFAULT_SCALING_MIN_DECISIONS, ) -> None: self._threshold = threshold self._min_decisions = min_decisions @@ -417,7 +440,7 @@ class ErrorSpikeRule: threshold: Max acceptable total findings (default 10). """ - def __init__(self, *, threshold: int = 10) -> None: + def __init__(self, *, threshold: int = _DEFAULT_ERROR_SPIKE_THRESHOLD) -> None: self._threshold = threshold @property diff --git a/src/synthorg/meta/strategies/code_modification.py b/src/synthorg/meta/strategies/code_modification.py index ee2b7c2424..318290700e 100644 --- a/src/synthorg/meta/strategies/code_modification.py +++ b/src/synthorg/meta/strategies/code_modification.py @@ -7,7 +7,7 @@ import json from pathlib import Path -from typing import TYPE_CHECKING, Any +from typing import TYPE_CHECKING, Any, Final from uuid import uuid4 from synthorg.budget.call_category import LLMCallCategory @@ -477,7 +477,7 @@ def _parse_items( return changes -_MAX_MANIFEST_CHARS = 4000 +_MAX_MANIFEST_CHARS: Final[int] = 4000 def _build_file_manifest( diff --git a/src/synthorg/meta/telemetry/aggregator.py b/src/synthorg/meta/telemetry/aggregator.py index b183a03f2b..578eb9ae5a 100644 --- a/src/synthorg/meta/telemetry/aggregator.py +++ b/src/synthorg/meta/telemetry/aggregator.py @@ -6,6 +6,7 @@ """ from collections import Counter, defaultdict +from typing import Final from synthorg.core.types import NotBlankStr from synthorg.meta.telemetry.models import AggregatedPattern, AnonymizedOutcomeEvent @@ -14,10 +15,13 @@ logger = get_logger(__name__) +_DEFAULT_MIN_DEPLOYMENTS: Final[int] = 3 + + def aggregate_patterns( events: tuple[AnonymizedOutcomeEvent, ...], *, - min_deployments: int = 3, + min_deployments: int = _DEFAULT_MIN_DEPLOYMENTS, ) -> tuple[AggregatedPattern, ...]: """Identify cross-deployment patterns from anonymized events. diff --git a/src/synthorg/meta/telemetry/collector.py b/src/synthorg/meta/telemetry/collector.py index e3894d0d05..252fd97c2c 100644 --- a/src/synthorg/meta/telemetry/collector.py +++ b/src/synthorg/meta/telemetry/collector.py @@ -5,6 +5,7 @@ """ import asyncio +from typing import Final from synthorg.meta.telemetry.aggregator import aggregate_patterns from synthorg.meta.telemetry.models import ( # noqa: TC001 @@ -20,7 +21,8 @@ logger = get_logger(__name__) -_DEFAULT_MAX_EVENTS = 100_000 +_DEFAULT_MAX_EVENTS: Final[int] = 100_000 +_DEFAULT_MIN_DEPLOYMENTS: Final[int] = 3 class InMemoryAnalyticsCollector: @@ -82,7 +84,7 @@ async def ingest( async def query_patterns( self, *, - min_deployments: int = 3, + min_deployments: int = _DEFAULT_MIN_DEPLOYMENTS, ) -> tuple[AggregatedPattern, ...]: """Query cross-deployment patterns from collected data. diff --git a/src/synthorg/meta/telemetry/emitter.py b/src/synthorg/meta/telemetry/emitter.py index 7051b56faf..afbee91bc5 100644 --- a/src/synthorg/meta/telemetry/emitter.py +++ b/src/synthorg/meta/telemetry/emitter.py @@ -9,7 +9,7 @@ """ import asyncio -from typing import TYPE_CHECKING, Self +from typing import TYPE_CHECKING, Final, Self import httpx @@ -41,14 +41,14 @@ logger = get_logger(__name__) -_MAX_RETRIES = 3 -_BACKOFF_BASE_SECONDS = 1.0 -_BACKOFF_CAP_SECONDS = 30.0 -_SUCCESS_MIN = 200 -_SUCCESS_MAX = 300 -_CLIENT_ERROR_MIN = 400 -_SERVER_ERROR_MIN = 500 -_LOG_BODY_MAX_LEN = 500 +_MAX_RETRIES: Final[int] = 3 +_BACKOFF_BASE_SECONDS: Final[float] = 1.0 +_BACKOFF_CAP_SECONDS: Final[float] = 30.0 +_SUCCESS_MIN: Final[int] = 200 +_SUCCESS_MAX: Final[int] = 300 +_CLIENT_ERROR_MIN: Final[int] = 400 +_SERVER_ERROR_MIN: Final[int] = 500 +_LOG_BODY_MAX_LEN: Final[int] = 500 class _TransientPostError( diff --git a/src/synthorg/meta/telemetry/protocol.py b/src/synthorg/meta/telemetry/protocol.py index 802f621728..e7a3f40da0 100644 --- a/src/synthorg/meta/telemetry/protocol.py +++ b/src/synthorg/meta/telemetry/protocol.py @@ -5,7 +5,7 @@ recommendations. All protocols are runtime-checkable. """ -from typing import TYPE_CHECKING, Protocol, runtime_checkable +from typing import TYPE_CHECKING, Final, Protocol, runtime_checkable if TYPE_CHECKING: from synthorg.meta.chief_of_staff.models import ProposalOutcome @@ -17,6 +17,9 @@ ) +_DEFAULT_MIN_DEPLOYMENTS: Final[int] = 3 + + # Impl in telemetry/emitter.py + telemetry/factory.py. @runtime_checkable class AnalyticsEmitter(Protocol): @@ -90,7 +93,7 @@ async def ingest( async def query_patterns( self, *, - min_deployments: int = 3, # lint-allow: magic-numbers -- protocol default arg. + min_deployments: int = _DEFAULT_MIN_DEPLOYMENTS, ) -> tuple[AggregatedPattern, ...]: """Query cross-deployment patterns. diff --git a/src/synthorg/meta/telemetry/recommender.py b/src/synthorg/meta/telemetry/recommender.py index ab443aa5f0..0170dfa627 100644 --- a/src/synthorg/meta/telemetry/recommender.py +++ b/src/synthorg/meta/telemetry/recommender.py @@ -22,6 +22,8 @@ from synthorg.meta.telemetry.protocol import AnalyticsCollector logger = get_logger(__name__) +_DEFAULT_MIN_DEPLOYMENTS: Final[int] = 3 +_DEFAULT_MIN_OBSERVATIONS: Final[int] = 10 # Pattern-level thresholds that gate relax / tighten recommendations. # Empirically tuned from v0.6.x rollout data: 0.7 approval / success @@ -110,8 +112,8 @@ class DefaultThresholdRecommender: def __init__( self, *, - min_deployments: int = 3, - min_observations: int = 10, + min_deployments: int = _DEFAULT_MIN_DEPLOYMENTS, + min_observations: int = _DEFAULT_MIN_OBSERVATIONS, ) -> None: self._min_deployments = min_deployments self._min_observations = min_observations diff --git a/src/synthorg/meta/validation/ci_validator.py b/src/synthorg/meta/validation/ci_validator.py index dbdc45b287..ff3ee7dfcb 100644 --- a/src/synthorg/meta/validation/ci_validator.py +++ b/src/synthorg/meta/validation/ci_validator.py @@ -8,6 +8,7 @@ import asyncio import contextlib from pathlib import Path +from typing import Final from synthorg.core.clock import Clock, SystemClock from synthorg.meta.models import CIValidationResult @@ -20,7 +21,9 @@ logger = get_logger(__name__) -_MAX_ERROR_OUTPUT_LENGTH = 2000 +_DEFAULT_TIMEOUT_SECONDS: Final[int] = 300 + +_MAX_ERROR_OUTPUT_LENGTH: Final[int] = 2000 class LocalCIValidator: @@ -36,7 +39,7 @@ class LocalCIValidator: def __init__( self, *, - timeout_seconds: int = 300, + timeout_seconds: int = _DEFAULT_TIMEOUT_SECONDS, clock: Clock | None = None, ) -> None: self._timeout = timeout_seconds diff --git a/src/synthorg/notifications/adapters/email.py b/src/synthorg/notifications/adapters/email.py index 9c0b321e4f..170ea7b2fe 100644 --- a/src/synthorg/notifications/adapters/email.py +++ b/src/synthorg/notifications/adapters/email.py @@ -6,7 +6,7 @@ import smtplib import ssl from email.message import EmailMessage -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, Final from synthorg.observability import get_logger, safe_error_description from synthorg.observability.events.notification import ( @@ -19,6 +19,8 @@ from synthorg.notifications.models import Notification logger = get_logger(__name__) +_DEFAULT_PORT: Final[int] = 587 +_DEFAULT_SMTP_TIMEOUT_SECONDS: Final[float] = 10.0 _CONTROL_CHAR_RE = re.compile(r"[\x00-\x1f\x7f]") @@ -63,13 +65,13 @@ def __init__( # noqa: PLR0913 self, *, host: str, - port: int = 587, + port: int = _DEFAULT_PORT, username: str | None = None, password: str | None = None, from_addr: str, to_addrs: tuple[str, ...], use_tls: bool = True, - smtp_timeout_seconds: float = 10.0, + smtp_timeout_seconds: float = _DEFAULT_SMTP_TIMEOUT_SECONDS, ) -> None: if not math.isfinite(smtp_timeout_seconds) or smtp_timeout_seconds <= 0: msg = ( diff --git a/src/synthorg/notifications/adapters/ntfy.py b/src/synthorg/notifications/adapters/ntfy.py index 561545e5b8..68c53afbc5 100644 --- a/src/synthorg/notifications/adapters/ntfy.py +++ b/src/synthorg/notifications/adapters/ntfy.py @@ -4,7 +4,7 @@ import ipaddress import math import re -from typing import TYPE_CHECKING, Self +from typing import TYPE_CHECKING, Final, Self from urllib.parse import urlparse import httpx @@ -24,6 +24,7 @@ ) logger = get_logger(__name__) +_DEFAULT_WEBHOOK_TIMEOUT_SECONDS: Final[float] = 10.0 _SEVERITY_TO_PRIORITY: dict[NotificationSeverity, str] = { NotificationSeverity.INFO: "default", @@ -99,7 +100,7 @@ def __init__( server_url: str, topic: str, token: str | None = None, - webhook_timeout_seconds: float = 10.0, + webhook_timeout_seconds: float = _DEFAULT_WEBHOOK_TIMEOUT_SECONDS, ) -> None: _validate_outbound_url(server_url, "server_url") if not math.isfinite(webhook_timeout_seconds) or webhook_timeout_seconds <= 0: diff --git a/src/synthorg/notifications/adapters/slack.py b/src/synthorg/notifications/adapters/slack.py index b78b93a104..d2951d8ab0 100644 --- a/src/synthorg/notifications/adapters/slack.py +++ b/src/synthorg/notifications/adapters/slack.py @@ -2,7 +2,7 @@ import asyncio import math -from typing import TYPE_CHECKING, Self +from typing import TYPE_CHECKING, Final, Self import httpx @@ -19,6 +19,7 @@ from synthorg.notifications.models import Notification logger = get_logger(__name__) +_DEFAULT_WEBHOOK_TIMEOUT_SECONDS: Final[float] = 10.0 def _escape_mrkdwn(text: str) -> str: @@ -89,7 +90,7 @@ def __init__( self, *, webhook_url: str, - webhook_timeout_seconds: float = 10.0, + webhook_timeout_seconds: float = _DEFAULT_WEBHOOK_TIMEOUT_SECONDS, ) -> None: _validate_outbound_url(webhook_url, "webhook_url") if not math.isfinite(webhook_timeout_seconds) or webhook_timeout_seconds <= 0: diff --git a/src/synthorg/observability/audit_chain/tsa_client.py b/src/synthorg/observability/audit_chain/tsa_client.py index 42eb37d606..0d0d304d68 100644 --- a/src/synthorg/observability/audit_chain/tsa_client.py +++ b/src/synthorg/observability/audit_chain/tsa_client.py @@ -26,7 +26,7 @@ import hmac from dataclasses import dataclass from datetime import UTC, datetime -from typing import TYPE_CHECKING, Any +from typing import TYPE_CHECKING, Any, Final import httpx import rfc3161_client @@ -53,6 +53,8 @@ logger = get_logger(__name__) +_DEFAULT_TIMEOUT_SECONDS: Final[float] = 5.0 + _HASH_ALGORITHMS: dict[str, Any] = { "sha256": _rfc_base.HashAlgorithm.SHA256, "sha512": _rfc_base.HashAlgorithm.SHA512, @@ -159,7 +161,7 @@ def __init__( self, tsa_url: str, *, - timeout_sec: float = 5.0, + timeout_sec: float = _DEFAULT_TIMEOUT_SECONDS, hash_algorithm: str = "sha256", trusted_roots: Iterable[bytes] = (), http_client: httpx.AsyncClient | None = None, diff --git a/src/synthorg/observability/background_tasks.py b/src/synthorg/observability/background_tasks.py index d8b46229bc..d9b01fe680 100644 --- a/src/synthorg/observability/background_tasks.py +++ b/src/synthorg/observability/background_tasks.py @@ -18,7 +18,7 @@ import copy import time from types import MappingProxyType -from typing import TYPE_CHECKING, Any +from typing import TYPE_CHECKING, Any, Final from synthorg.observability import get_logger from synthorg.observability.events.async_task import ( @@ -31,6 +31,8 @@ logger = get_logger(__name__) +_DEFAULT_DRAIN_TIMEOUT_SECONDS: Final[float] = 5.0 + class BackgroundTaskRegistry: """Tracks fire-and-forget asyncio tasks so failures surface in logs. @@ -153,7 +155,9 @@ def _on_done(task: asyncio.Task[Any]) -> None: return _on_done - async def drain(self, *, timeout_sec: float = 5.0) -> None: + async def drain( + self, *, timeout_sec: float = _DEFAULT_DRAIN_TIMEOUT_SECONDS + ) -> None: """Wait for all tracked tasks to complete. On timeout, logs a warning at diff --git a/src/synthorg/observability/http_handler.py b/src/synthorg/observability/http_handler.py index 7725a80e18..f41f6fb861 100644 --- a/src/synthorg/observability/http_handler.py +++ b/src/synthorg/observability/http_handler.py @@ -11,7 +11,7 @@ import threading import urllib.error import urllib.request -from typing import TYPE_CHECKING, Any +from typing import TYPE_CHECKING, Any, Final import structlog from structlog.stdlib import ProcessorFormatter @@ -22,6 +22,12 @@ from synthorg.observability.config import SinkConfig +_DEFAULT_BATCH_SIZE: Final[int] = 100 +_DEFAULT_FLUSH_INTERVAL_SECONDS: Final[float] = 5.0 +_DEFAULT_TIMEOUT_SECONDS: Final[float] = 10.0 +_DEFAULT_MAX_RETRIES: Final[int] = 3 + + class HttpBatchHandler(logging.Handler): """Handler that batches log records and POSTs them as JSON arrays. @@ -43,10 +49,10 @@ def __init__( # noqa: PLR0913 url: str, *, headers: tuple[tuple[str, str], ...] = (), - batch_size: int = 100, - flush_interval: float = 5.0, - timeout: float = 10.0, - max_retries: int = 3, + batch_size: int = _DEFAULT_BATCH_SIZE, + flush_interval: float = _DEFAULT_FLUSH_INTERVAL_SECONDS, + timeout: float = _DEFAULT_TIMEOUT_SECONDS, + max_retries: int = _DEFAULT_MAX_RETRIES, ) -> None: super().__init__() self._url = url diff --git a/src/synthorg/observability/otlp_handler.py b/src/synthorg/observability/otlp_handler.py index 85787d74f6..955fa3bcd3 100644 --- a/src/synthorg/observability/otlp_handler.py +++ b/src/synthorg/observability/otlp_handler.py @@ -13,7 +13,7 @@ import threading import urllib.error import urllib.request -from typing import TYPE_CHECKING, Any +from typing import TYPE_CHECKING, Any, Final import structlog from structlog.stdlib import ProcessorFormatter @@ -59,6 +59,11 @@ } +_DEFAULT_BATCH_SIZE: Final[int] = 100 +_DEFAULT_FLUSH_INTERVAL_SECONDS: Final[float] = 5.0 +_DEFAULT_TIMEOUT_SECONDS: Final[float] = 10.0 + + class OtlpHandler(logging.Handler): """Handler that batches log records and exports them as OTLP log records. @@ -86,9 +91,9 @@ def __init__( # noqa: PLR0913 *, protocol: OtlpProtocol = OtlpProtocol.HTTP_JSON, headers: tuple[tuple[str, str], ...] = (), - batch_size: int = 100, - flush_interval: float = 5.0, - timeout: float = 10.0, + batch_size: int = _DEFAULT_BATCH_SIZE, + flush_interval: float = _DEFAULT_FLUSH_INTERVAL_SECONDS, + timeout: float = _DEFAULT_TIMEOUT_SECONDS, _start_flusher: bool = True, ) -> None: super().__init__() diff --git a/src/synthorg/observability/otlp_trace_handler.py b/src/synthorg/observability/otlp_trace_handler.py index 1039e0bc90..89f99f7744 100644 --- a/src/synthorg/observability/otlp_trace_handler.py +++ b/src/synthorg/observability/otlp_trace_handler.py @@ -12,7 +12,7 @@ """ import asyncio -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, Final from opentelemetry import trace as _ot_trace from opentelemetry.exporter.otlp.proto.http.trace_exporter import ( @@ -38,6 +38,8 @@ logger = get_logger(__name__) +_DEFAULT_FORCE_FLUSH_TIMEOUT_SECONDS: Final[float] = 5.0 + _TRACES_ENDPOINT_SUFFIX = "/v1/traces" # Process singleton: OpenTelemetry's global TracerProvider is set @@ -144,7 +146,9 @@ def get_tracer(self, name: str) -> Tracer: """Return a tracer bound to this handler's provider.""" return self._provider.get_tracer(name) - async def force_flush(self, timeout_sec: float = 5.0) -> None: + async def force_flush( + self, timeout_sec: float = _DEFAULT_FORCE_FLUSH_TIMEOUT_SECONDS + ) -> None: """Block until pending spans are exported or the deadline fires.""" timeout_ms = int(timeout_sec * 1000) flushed = await asyncio.to_thread( diff --git a/src/synthorg/observability/tracing/protocol.py b/src/synthorg/observability/tracing/protocol.py index 864f262e9f..ac4216280b 100644 --- a/src/synthorg/observability/tracing/protocol.py +++ b/src/synthorg/observability/tracing/protocol.py @@ -12,7 +12,7 @@ on ``AppState``. """ -from typing import TYPE_CHECKING, Protocol +from typing import TYPE_CHECKING, Final, Protocol from opentelemetry import trace from opentelemetry.trace import NoOpTracer @@ -21,6 +21,9 @@ from opentelemetry.trace import Tracer +_DEFAULT_FORCE_FLUSH_TIMEOUT_SECONDS: Final[float] = 5.0 + + # NoopTraceHandler impl in this file + OtlpTraceHandler impl in # otlp_trace_handler.py + tracing/factory.py + AppState wiring. class TraceHandler(Protocol): @@ -38,7 +41,7 @@ def get_tracer(self, name: str) -> Tracer: async def force_flush( self, - timeout_sec: float = 5.0, # lint-allow: magic-numbers -- default. + timeout_sec: float = _DEFAULT_FORCE_FLUSH_TIMEOUT_SECONDS, ) -> None: """Block until pending spans are exported or the deadline fires.""" ... @@ -64,7 +67,7 @@ def get_tracer(self, name: str) -> Tracer: # noqa: ARG002 async def force_flush( self, - timeout_sec: float = 5.0, # lint-allow: magic-numbers -- mirror protocol arg + timeout_sec: float = _DEFAULT_FORCE_FLUSH_TIMEOUT_SECONDS, ) -> None: """No-op: there is no exporter to flush.""" del timeout_sec diff --git a/src/synthorg/ontology/drift/active.py b/src/synthorg/ontology/drift/active.py index fa206f09e9..2518627ba4 100644 --- a/src/synthorg/ontology/drift/active.py +++ b/src/synthorg/ontology/drift/active.py @@ -4,7 +4,7 @@ (triggered by ``EntityAlignmentGuard`` in validate/enforce mode). """ -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, Final from synthorg.observability import get_logger from synthorg.ontology.drift.passive import PassiveMonitorStrategy @@ -14,6 +14,7 @@ from synthorg.ontology.protocol import OntologyBackend logger = get_logger(__name__) +_DEFAULT_THRESHOLD: Final[float] = 0.3 class ActiveValidatorStrategy(PassiveMonitorStrategy): @@ -34,7 +35,7 @@ def __init__( *, ontology: OntologyBackend, memory: MemoryBackend, - threshold: float = 0.3, + threshold: float = _DEFAULT_THRESHOLD, ) -> None: super().__init__( ontology=ontology, diff --git a/src/synthorg/ontology/drift/passive.py b/src/synthorg/ontology/drift/passive.py index 0d4401b9c4..69f4bf74d5 100644 --- a/src/synthorg/ontology/drift/passive.py +++ b/src/synthorg/ontology/drift/passive.py @@ -5,7 +5,7 @@ canonical entity definition. """ -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, Final from synthorg.observability import get_logger from synthorg.observability.events.ontology import ( @@ -39,8 +39,9 @@ def _keyword_overlap(text_a: str, text_b: str) -> float: return len(words_a & words_b) / len(words_b) -_NOTIFY_THRESHOLD = 0.5 -_RETRAIN_THRESHOLD = 0.7 +_NOTIFY_THRESHOLD: Final[float] = 0.5 +_RETRAIN_THRESHOLD: Final[float] = 0.7 +_DEFAULT_DRIFT_DETECT_THRESHOLD: Final[float] = 0.3 def _recommend(score: float, threshold: float) -> DriftAction: @@ -82,7 +83,7 @@ def __init__( *, ontology: OntologyBackend, memory: MemoryBackend, - threshold: float = 0.3, + threshold: float = _DEFAULT_DRIFT_DETECT_THRESHOLD, ) -> None: self._ontology = ontology self._memory = memory diff --git a/src/synthorg/ontology/injection/hybrid.py b/src/synthorg/ontology/injection/hybrid.py index 9340a11ae2..5fd437dfcc 100644 --- a/src/synthorg/ontology/injection/hybrid.py +++ b/src/synthorg/ontology/injection/hybrid.py @@ -5,7 +5,7 @@ is the default and recommended strategy. """ -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, Final from synthorg.observability import get_logger from synthorg.observability.events.ontology import ONTOLOGY_INJECTION_PREPARED @@ -23,6 +23,7 @@ from synthorg.providers.models import ChatMessage, ToolDefinition logger = get_logger(__name__) +_DEFAULT_CORE_TOKEN_BUDGET: Final[int] = 2000 class HybridInjectionStrategy: @@ -44,7 +45,7 @@ def __init__( self, *, backend: OntologyBackend, - core_token_budget: int = 2000, + core_token_budget: int = _DEFAULT_CORE_TOKEN_BUDGET, tool_name: str = LOOKUP_ENTITY_TOOL_NAME, token_estimator: TokenEstimator | None = None, ) -> None: diff --git a/src/synthorg/ontology/injection/prompt.py b/src/synthorg/ontology/injection/prompt.py index f3fdbaba3d..f8115cb639 100644 --- a/src/synthorg/ontology/injection/prompt.py +++ b/src/synthorg/ontology/injection/prompt.py @@ -5,7 +5,7 @@ Respects the configured ``core_token_budget``. """ -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, Final from synthorg.memory.injection import DefaultTokenEstimator, TokenEstimator from synthorg.observability import get_logger @@ -20,6 +20,7 @@ from synthorg.providers.models import ToolDefinition logger = get_logger(__name__) +_DEFAULT_CORE_TOKEN_BUDGET: Final[int] = 2000 def format_entity(entity: EntityDefinition) -> str: @@ -70,7 +71,7 @@ def __init__( self, *, backend: OntologyBackend, - core_token_budget: int = 2000, + core_token_budget: int = _DEFAULT_CORE_TOKEN_BUDGET, token_estimator: TokenEstimator | None = None, ) -> None: self._backend = backend diff --git a/src/synthorg/ontology/service.py b/src/synthorg/ontology/service.py index 2e877d2f95..9e35f24a60 100644 --- a/src/synthorg/ontology/service.py +++ b/src/synthorg/ontology/service.py @@ -1,7 +1,7 @@ """Ontology service -- orchestrates backend, versioning, and bootstrap.""" from datetime import UTC, datetime -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, Final from synthorg.observability import get_logger from synthorg.observability.events.ontology import ( @@ -26,6 +26,7 @@ from synthorg.versioning.service import VersioningService logger = get_logger(__name__) +_DEFAULT_LIMIT: Final[int] = 50 class OntologyService: @@ -239,7 +240,7 @@ async def list_versions( self, entity_name: str, *, - limit: int = 50, + limit: int = _DEFAULT_LIMIT, offset: int = 0, ) -> tuple[VersionSnapshot[EntityDefinition], ...]: """List version snapshots for an entity. diff --git a/src/synthorg/persistence/approval_protocol.py b/src/synthorg/persistence/approval_protocol.py index 56535a4353..d5cc9190de 100644 --- a/src/synthorg/persistence/approval_protocol.py +++ b/src/synthorg/persistence/approval_protocol.py @@ -19,6 +19,7 @@ ApprovalStatus, # noqa: TC001 ) from synthorg.core.types import NotBlankStr # noqa: TC001 +from synthorg.persistence._shared import DEFAULT_LIST_LIMIT if TYPE_CHECKING: from collections.abc import Sequence @@ -105,7 +106,7 @@ async def list_items( status: ApprovalStatus | None = None, risk_level: ApprovalRiskLevel | None = None, action_type: NotBlankStr | None = None, - limit: int = 100, + limit: int = DEFAULT_LIST_LIMIT, offset: int = 0, ) -> tuple[ApprovalItem, ...]: """List approval items with optional filters. diff --git a/src/synthorg/persistence/artifact_protocol.py b/src/synthorg/persistence/artifact_protocol.py index 772991bceb..ad304237a7 100644 --- a/src/synthorg/persistence/artifact_protocol.py +++ b/src/synthorg/persistence/artifact_protocol.py @@ -5,6 +5,7 @@ from synthorg.core.artifact import Artifact # noqa: TC001 from synthorg.core.enums import ArtifactType # noqa: TC001 from synthorg.core.types import NotBlankStr # noqa: TC001 +from synthorg.persistence._shared import DEFAULT_LIST_LIMIT @runtime_checkable @@ -50,7 +51,7 @@ async def list_artifacts( task_id: NotBlankStr | None = None, created_by: NotBlankStr | None = None, artifact_type: ArtifactType | None = None, - limit: int = 100, + limit: int = DEFAULT_LIST_LIMIT, offset: int = 0, ) -> tuple[Artifact, ...]: """List artifacts with optional filters (paginated). diff --git a/src/synthorg/persistence/atlas.py b/src/synthorg/persistence/atlas.py index 619e33f330..9fa6150533 100644 --- a/src/synthorg/persistence/atlas.py +++ b/src/synthorg/persistence/atlas.py @@ -16,7 +16,7 @@ import urllib.parse from dataclasses import dataclass from pathlib import Path, PurePosixPath, PureWindowsPath -from typing import Literal +from typing import Final, Literal from synthorg.core.persistence_errors import MigrationError from synthorg.observability import get_logger, safe_error_description @@ -37,8 +37,8 @@ # Protects callers from hanging indefinitely if Atlas gets stuck on a # lock, network hang, or malformed migration. Applied via # ``Popen.wait(timeout=...)`` in ``_run_atlas``. -_ATLAS_SUBPROCESS_TIMEOUT_SECONDS = 120.0 -_ATLAS_KILL_GRACE_SECONDS = 5.0 +_ATLAS_SUBPROCESS_TIMEOUT_SECONDS: Final[float] = 120.0 +_ATLAS_KILL_GRACE_SECONDS: Final[float] = 5.0 """Hardcoded fallback grace period for the Atlas subprocess after kill. The value matches the default of the diff --git a/src/synthorg/persistence/audit_protocol.py b/src/synthorg/persistence/audit_protocol.py index 621aa58c28..30c9cc5ffb 100644 --- a/src/synthorg/persistence/audit_protocol.py +++ b/src/synthorg/persistence/audit_protocol.py @@ -6,6 +6,7 @@ from synthorg.core.enums import ApprovalRiskLevel # noqa: TC001 from synthorg.core.types import NotBlankStr # noqa: TC001 +from synthorg.persistence._shared import DEFAULT_LIST_LIMIT from synthorg.security.models import AuditEntry, AuditVerdictStr # noqa: TC001 @@ -44,7 +45,7 @@ async def query( # noqa: PLR0913 risk_level: ApprovalRiskLevel | None = None, since: AwareDatetime | None = None, until: AwareDatetime | None = None, - limit: int = 100, + limit: int = DEFAULT_LIST_LIMIT, ) -> tuple[AuditEntry, ...]: """Query audit entries with optional filters. diff --git a/src/synthorg/persistence/auth_protocol.py b/src/synthorg/persistence/auth_protocol.py index 9702d29576..a63b7a4e94 100644 --- a/src/synthorg/persistence/auth_protocol.py +++ b/src/synthorg/persistence/auth_protocol.py @@ -8,6 +8,8 @@ from typing import TYPE_CHECKING, Protocol, runtime_checkable +from synthorg.persistence._shared import DEFAULT_LIST_LIMIT + if TYPE_CHECKING: from collections.abc import Callable from datetime import datetime @@ -50,7 +52,7 @@ async def list_by_user( self, user_id: str, *, - limit: int = 100, + limit: int = DEFAULT_LIST_LIMIT, offset: int = 0, ) -> tuple[Session, ...]: """List active sessions for a user, optionally paginated.""" @@ -59,7 +61,7 @@ async def list_by_user( async def list_all( self, *, - limit: int = 100, + limit: int = DEFAULT_LIST_LIMIT, offset: int = 0, ) -> tuple[Session, ...]: """List all active sessions, optionally paginated.""" diff --git a/src/synthorg/persistence/connection_protocol.py b/src/synthorg/persistence/connection_protocol.py index 605f477f11..307f09fde9 100644 --- a/src/synthorg/persistence/connection_protocol.py +++ b/src/synthorg/persistence/connection_protocol.py @@ -14,6 +14,7 @@ OAuthState, # noqa: TC001 WebhookReceipt, # noqa: TC001 ) +from synthorg.persistence._shared import DEFAULT_LIST_LIMIT @runtime_checkable @@ -31,15 +32,16 @@ async def get(self, name: NotBlankStr) -> Connection | None: async def list_all( self, *, - limit: int = 100, + limit: int = DEFAULT_LIST_LIMIT, offset: int = 0, ) -> tuple[Connection, ...]: """List all connections, optionally bounded by *limit* / *offset*. - Sorted by ``name`` ascending; pass ``limit=None`` (default) to - retain the legacy fetch-all semantics, or set both to consume - a paginated window. ``limit <= 0`` returns ``()``; negative - ``offset`` is treated as ``0``. + Sorted by ``name`` ascending; ``limit`` defaults to + ``DEFAULT_LIST_LIMIT`` so callers receive at most that many + connections unless they pass a larger positive integer. + ``limit <= 0`` returns ``()``; negative ``offset`` is treated + as ``0``. """ ... @@ -47,7 +49,7 @@ async def list_by_type( self, connection_type: ConnectionType, *, - limit: int = 100, + limit: int = DEFAULT_LIST_LIMIT, offset: int = 0, ) -> tuple[Connection, ...]: """List connections of a specific type with optional limit/offset. @@ -165,7 +167,7 @@ async def get_by_connection( self, connection_name: NotBlankStr, *, - limit: int = 100, + limit: int = DEFAULT_LIST_LIMIT, offset: int = 0, ) -> tuple[WebhookReceipt, ...]: """List receipts for a connection, newest first. diff --git a/src/synthorg/persistence/cost_record_protocol.py b/src/synthorg/persistence/cost_record_protocol.py index 48c7a63d7d..771b941c4b 100644 --- a/src/synthorg/persistence/cost_record_protocol.py +++ b/src/synthorg/persistence/cost_record_protocol.py @@ -4,6 +4,7 @@ from synthorg.budget.cost_record import CostRecord # noqa: TC001 from synthorg.core.types import NotBlankStr # noqa: TC001 +from synthorg.persistence._shared import DEFAULT_LIST_LIMIT @runtime_checkable @@ -26,7 +27,7 @@ async def query( *, agent_id: NotBlankStr | None = None, task_id: NotBlankStr | None = None, - limit: int = 100, + limit: int = DEFAULT_LIST_LIMIT, offset: int = 0, ) -> tuple[CostRecord, ...]: """Query cost records with optional filters and pagination. @@ -34,10 +35,10 @@ async def query( Args: agent_id: Filter by agent identifier. task_id: Filter by task identifier. - limit: Maximum rows to return; ``None`` (default) preserves - fetch-all semantics. - offset: Rows to skip before applying *limit*; ignored when - *limit* is ``None``. + limit: Maximum rows to return. Defaults to + ``DEFAULT_LIST_LIMIT``; pass a larger positive integer + for a wider window. + offset: Rows to skip before applying *limit*. Returns: Matching cost records as a tuple. diff --git a/src/synthorg/persistence/decision_protocol.py b/src/synthorg/persistence/decision_protocol.py index 7f38e69f5a..df277a03fe 100644 --- a/src/synthorg/persistence/decision_protocol.py +++ b/src/synthorg/persistence/decision_protocol.py @@ -6,6 +6,7 @@ from synthorg.core.enums import DecisionOutcome # noqa: TC001 from synthorg.core.types import NotBlankStr # noqa: TC001 +from synthorg.persistence._shared import DEFAULT_LIST_LIMIT if TYPE_CHECKING: from synthorg.engine.decisions import DecisionRecord @@ -98,7 +99,7 @@ async def list_by_task( self, task_id: NotBlankStr, *, - limit: int = 100, + limit: int = DEFAULT_LIST_LIMIT, offset: int = 0, ) -> tuple[DecisionRecord, ...]: """List decision records for a task (paginated, oldest first). @@ -122,7 +123,7 @@ async def list_by_agent( agent_id: NotBlankStr, *, role: DecisionRole, - limit: int = 100, + limit: int = DEFAULT_LIST_LIMIT, offset: int = 0, ) -> tuple[DecisionRecord, ...]: """List decision records by agent role (paginated, newest first). diff --git a/src/synthorg/persistence/fine_tune_protocol.py b/src/synthorg/persistence/fine_tune_protocol.py index 2d7b949093..4e51b9ca0c 100644 --- a/src/synthorg/persistence/fine_tune_protocol.py +++ b/src/synthorg/persistence/fine_tune_protocol.py @@ -6,13 +6,15 @@ backend can be swapped without touching service code. """ -from typing import Protocol, runtime_checkable +from typing import Final, Protocol, runtime_checkable from synthorg.memory.embedding.fine_tune_models import ( CheckpointRecord, # noqa: TC001 FineTuneRun, # noqa: TC001 ) +_DEFAULT_LIST_LIMIT_50: Final[int] = 50 + @runtime_checkable class FineTuneRunRepository(Protocol): @@ -33,7 +35,7 @@ async def get_active_run(self) -> FineTuneRun | None: async def list_runs( self, *, - limit: int = 50, + limit: int = _DEFAULT_LIST_LIMIT_50, offset: int = 0, ) -> tuple[tuple[FineTuneRun, ...], int]: """List runs ordered by start time descending. @@ -77,7 +79,7 @@ async def get_checkpoint( async def list_checkpoints( self, *, - limit: int = 50, + limit: int = _DEFAULT_LIST_LIMIT_50, offset: int = 0, ) -> tuple[tuple[CheckpointRecord, ...], int]: """List checkpoints ordered by creation time descending. diff --git a/src/synthorg/persistence/integration_stubs.py b/src/synthorg/persistence/integration_stubs.py index e88e013504..56d62a4032 100644 --- a/src/synthorg/persistence/integration_stubs.py +++ b/src/synthorg/persistence/integration_stubs.py @@ -29,6 +29,7 @@ OAuthState, # noqa: TC001 WebhookReceipt, # noqa: TC001 ) +from synthorg.persistence._shared import DEFAULT_LIST_LIMIT class InMemoryConnectionRepository: @@ -49,7 +50,7 @@ async def get(self, name: str) -> Connection | None: async def list_all( self, *, - limit: int = 100, + limit: int = DEFAULT_LIST_LIMIT, offset: int = 0, ) -> tuple[Connection, ...]: """List all (deep-copied).""" @@ -63,7 +64,7 @@ async def list_by_type( self, connection_type: ConnectionType, *, - limit: int = 100, + limit: int = DEFAULT_LIST_LIMIT, offset: int = 0, ) -> tuple[Connection, ...]: """List by type (deep-copied).""" @@ -142,7 +143,7 @@ async def get_by_connection( self, connection_name: str, *, - limit: int = 100, + limit: int = DEFAULT_LIST_LIMIT, offset: int = 0, ) -> tuple[WebhookReceipt, ...]: """List by connection (deep-copied), newest-first.""" diff --git a/src/synthorg/persistence/jsonb_capability.py b/src/synthorg/persistence/jsonb_capability.py index 1eaf5031f2..24618c008c 100644 --- a/src/synthorg/persistence/jsonb_capability.py +++ b/src/synthorg/persistence/jsonb_capability.py @@ -11,6 +11,8 @@ from typing import TYPE_CHECKING, Any, Protocol, runtime_checkable +from synthorg.persistence._shared import DEFAULT_LIST_LIMIT + if TYPE_CHECKING: from datetime import datetime @@ -34,7 +36,7 @@ async def query_jsonb_contains( # noqa: PLR0913 *, since: datetime | None = None, until: datetime | None = None, - limit: int = 100, + limit: int = DEFAULT_LIST_LIMIT, offset: int = 0, ) -> tuple[tuple[Any, ...], int]: """Query rows where a JSONB column contains the given value. @@ -62,7 +64,7 @@ async def query_jsonb_key_exists( # noqa: PLR0913 *, since: datetime | None = None, until: datetime | None = None, - limit: int = 100, + limit: int = DEFAULT_LIST_LIMIT, offset: int = 0, ) -> tuple[tuple[Any, ...], int]: """Query rows where a JSONB column has the given top-level key. diff --git a/src/synthorg/persistence/mcp_protocol.py b/src/synthorg/persistence/mcp_protocol.py index f6c64bbcc4..bde6d0a1ea 100644 --- a/src/synthorg/persistence/mcp_protocol.py +++ b/src/synthorg/persistence/mcp_protocol.py @@ -7,6 +7,8 @@ from typing import TYPE_CHECKING, Protocol, runtime_checkable +from synthorg.persistence._shared import DEFAULT_LIST_LIMIT + if TYPE_CHECKING: from synthorg.core.types import NotBlankStr from synthorg.integrations.mcp_catalog.installations import McpInstallation @@ -30,7 +32,7 @@ async def get( async def list_items( self, *, - limit: int = 100, + limit: int = DEFAULT_LIST_LIMIT, offset: int = 0, ) -> tuple[McpInstallation, ...]: """List recorded installations, optionally paginated. diff --git a/src/synthorg/persistence/memory_protocol.py b/src/synthorg/persistence/memory_protocol.py index fe66897b11..b6045b95d0 100644 --- a/src/synthorg/persistence/memory_protocol.py +++ b/src/synthorg/persistence/memory_protocol.py @@ -5,7 +5,9 @@ ``synthorg.memory.org.models``. """ -from typing import TYPE_CHECKING, Protocol, runtime_checkable +from typing import TYPE_CHECKING, Final, Protocol, runtime_checkable + +from synthorg.persistence._shared import DEFAULT_LIST_LIMIT if TYPE_CHECKING: from pydantic import AwareDatetime @@ -20,6 +22,9 @@ ) +_DEFAULT_LIST_LIMIT_5: Final[int] = 5 + + @runtime_checkable class OrgFactRepository(Protocol): """Protocol for organizational fact persistence with MVCC.""" @@ -37,7 +42,7 @@ async def query( *, categories: frozenset[OrgFactCategory] | None = None, text: str | None = None, - limit: int = 5, + limit: int = _DEFAULT_LIST_LIMIT_5, offset: int = 0, ) -> tuple[OrgFact, ...]: """Query active facts by category and/or text substring.""" @@ -47,7 +52,7 @@ async def list_by_category( self, category: OrgFactCategory, *, - limit: int = 100, + limit: int = DEFAULT_LIST_LIMIT, offset: int = 0, ) -> tuple[OrgFact, ...]: """List all active facts in a category, optionally paginated.""" diff --git a/src/synthorg/persistence/message_protocol.py b/src/synthorg/persistence/message_protocol.py index 74bacece75..d64e2f0abe 100644 --- a/src/synthorg/persistence/message_protocol.py +++ b/src/synthorg/persistence/message_protocol.py @@ -4,6 +4,7 @@ from synthorg.communication.message import Message # noqa: TC001 from synthorg.core.types import NotBlankStr # noqa: TC001 +from synthorg.persistence._shared import DEFAULT_LIST_LIMIT @runtime_checkable @@ -26,7 +27,7 @@ async def get_history( self, channel: NotBlankStr, *, - limit: int = 100, + limit: int = DEFAULT_LIST_LIMIT, ) -> tuple[Message, ...]: """Retrieve message history for a channel. diff --git a/src/synthorg/persistence/ontology_protocol.py b/src/synthorg/persistence/ontology_protocol.py index bd810e2faa..8971540e7d 100644 --- a/src/synthorg/persistence/ontology_protocol.py +++ b/src/synthorg/persistence/ontology_protocol.py @@ -8,7 +8,9 @@ ``is_connected`` / ``get_db``) belong to :class:`PersistenceBackend`. """ -from typing import TYPE_CHECKING, Protocol, runtime_checkable +from typing import TYPE_CHECKING, Final, Protocol, runtime_checkable + +from synthorg.persistence._shared import DEFAULT_LIST_LIMIT if TYPE_CHECKING: from synthorg.core.types import NotBlankStr @@ -18,6 +20,8 @@ EntityTier, ) +_DEFAULT_DRIFT_REPORTS_LIMIT: Final[int] = 10 + @runtime_checkable class OntologyEntityRepository(Protocol): @@ -64,7 +68,7 @@ async def list_entities( self, *, tier: EntityTier | None = None, - limit: int = 100, + limit: int = DEFAULT_LIST_LIMIT, offset: int = 0, ) -> tuple[EntityDefinition, ...]: """List all entity definitions, optionally filtered by tier.""" @@ -74,7 +78,7 @@ async def search( self, query: str, *, - limit: int = 100, + limit: int = DEFAULT_LIST_LIMIT, offset: int = 0, ) -> tuple[EntityDefinition, ...]: """Substring search against entity name and definition text.""" @@ -104,7 +108,7 @@ async def get_latest( self, entity_name: NotBlankStr, *, - limit: int = 10, + limit: int = _DEFAULT_DRIFT_REPORTS_LIMIT, ) -> tuple[DriftReport, ...]: """Return most recent drift reports for an entity.""" ... @@ -112,7 +116,7 @@ async def get_latest( async def get_all_latest( self, *, - limit: int = 100, + limit: int = DEFAULT_LIST_LIMIT, ) -> tuple[DriftReport, ...]: """Return the most recent drift report for each entity.""" ... diff --git a/src/synthorg/persistence/postgres/approval_repo.py b/src/synthorg/persistence/postgres/approval_repo.py index af34238df2..d5c428fb8e 100644 --- a/src/synthorg/persistence/postgres/approval_repo.py +++ b/src/synthorg/persistence/postgres/approval_repo.py @@ -29,7 +29,7 @@ API_APPROVAL_REPO_FETCHED, API_APPROVAL_REPO_LISTED, ) -from synthorg.persistence._shared import coerce_row_timestamp +from synthorg.persistence._shared import DEFAULT_LIST_LIMIT, coerce_row_timestamp if TYPE_CHECKING: from collections.abc import Sequence @@ -392,7 +392,7 @@ async def list_items( status: ApprovalStatus | None = None, risk_level: ApprovalRiskLevel | None = None, action_type: NotBlankStr | None = None, - limit: int = 100, # lint-allow: magic-numbers -- default page size + limit: int = DEFAULT_LIST_LIMIT, offset: int = 0, ) -> tuple[ApprovalItem, ...]: """List approval items with optional filters (paginated).""" diff --git a/src/synthorg/persistence/postgres/artifact_repo.py b/src/synthorg/persistence/postgres/artifact_repo.py index 67739a99a3..090e7a7535 100644 --- a/src/synthorg/persistence/postgres/artifact_repo.py +++ b/src/synthorg/persistence/postgres/artifact_repo.py @@ -26,6 +26,7 @@ PERSISTENCE_ARTIFACT_LISTED, PERSISTENCE_ARTIFACT_SAVE_FAILED, ) +from synthorg.persistence._shared import DEFAULT_LIST_LIMIT if TYPE_CHECKING: from psycopg_pool import AsyncConnectionPool @@ -199,7 +200,7 @@ async def list_artifacts( task_id: NotBlankStr | None = None, created_by: NotBlankStr | None = None, artifact_type: ArtifactType | None = None, - limit: int = 100, + limit: int = DEFAULT_LIST_LIMIT, offset: int = 0, ) -> tuple[Artifact, ...]: """List artifacts with optional filters (paginated).""" diff --git a/src/synthorg/persistence/postgres/audit_repository.py b/src/synthorg/persistence/postgres/audit_repository.py index df124aa8c7..d2473fab60 100644 --- a/src/synthorg/persistence/postgres/audit_repository.py +++ b/src/synthorg/persistence/postgres/audit_repository.py @@ -13,6 +13,7 @@ PERSISTENCE_AUDIT_ENTRY_QUERIED, PERSISTENCE_AUDIT_ENTRY_QUERY_FAILED, ) +from synthorg.persistence._shared import DEFAULT_LIST_LIMIT from synthorg.persistence._shared.audit import ( AUDIT_COLUMNS, audit_entry_to_payload, @@ -99,7 +100,7 @@ async def query( # noqa: PLR0913 risk_level: ApprovalRiskLevel | None = None, since: AwareDatetime | None = None, until: AwareDatetime | None = None, - limit: int = 100, + limit: int = DEFAULT_LIST_LIMIT, ) -> tuple[AuditEntry, ...]: """Query audit entries with optional filters (newest first). @@ -356,7 +357,7 @@ async def query_jsonb_contains( # noqa: PLR0913 *, since: AwareDatetime | None = None, until: AwareDatetime | None = None, - limit: int = 100, + limit: int = DEFAULT_LIST_LIMIT, offset: int = 0, ) -> tuple[tuple[AuditEntry, ...], int]: """Query audit entries where *column* contains *value*. @@ -381,7 +382,7 @@ async def query_jsonb_key_exists( # noqa: PLR0913 *, since: AwareDatetime | None = None, until: AwareDatetime | None = None, - limit: int = 100, + limit: int = DEFAULT_LIST_LIMIT, offset: int = 0, ) -> tuple[tuple[AuditEntry, ...], int]: """Query audit entries where *column* has a top-level *key*. diff --git a/src/synthorg/persistence/postgres/connection_repo.py b/src/synthorg/persistence/postgres/connection_repo.py index 59dfb55417..4abe051f0b 100644 --- a/src/synthorg/persistence/postgres/connection_repo.py +++ b/src/synthorg/persistence/postgres/connection_repo.py @@ -32,7 +32,11 @@ PERSISTENCE_CONNECTION_LIST_FAILED, PERSISTENCE_CONNECTION_SAVE_FAILED, ) -from synthorg.persistence._shared import coerce_row_timestamp, normalize_utc +from synthorg.persistence._shared import ( + DEFAULT_LIST_LIMIT, + coerce_row_timestamp, + normalize_utc, +) if TYPE_CHECKING: from psycopg_pool import AsyncConnectionPool @@ -209,7 +213,7 @@ async def get(self, name: NotBlankStr) -> Connection | None: async def list_all( self, *, - limit: int = 100, + limit: int = DEFAULT_LIST_LIMIT, offset: int = 0, ) -> tuple[Connection, ...]: """List all connections, sorted by name for determinism.""" @@ -253,7 +257,7 @@ async def list_by_type( self, connection_type: ConnectionType, *, - limit: int = 100, + limit: int = DEFAULT_LIST_LIMIT, offset: int = 0, ) -> tuple[Connection, ...]: """List connections of *connection_type*, sorted by name.""" diff --git a/src/synthorg/persistence/postgres/decision_repo.py b/src/synthorg/persistence/postgres/decision_repo.py index 62bbedd9e9..dfe7e5eddd 100644 --- a/src/synthorg/persistence/postgres/decision_repo.py +++ b/src/synthorg/persistence/postgres/decision_repo.py @@ -40,7 +40,7 @@ PERSISTENCE_DECISION_RECORD_QUERY_FAILED, PERSISTENCE_DECISION_RECORD_SAVE_FAILED, ) -from synthorg.persistence._shared import validate_pagination_args +from synthorg.persistence._shared import DEFAULT_LIST_LIMIT, validate_pagination_args from synthorg.persistence.decision_protocol import DecisionRole # noqa: TC001 if TYPE_CHECKING: @@ -435,7 +435,7 @@ async def list_by_task( self, task_id: NotBlankStr, *, - limit: int = 100, + limit: int = DEFAULT_LIST_LIMIT, offset: int = 0, ) -> tuple[DecisionRecord, ...]: """List decision records for a task, oldest first. @@ -511,7 +511,7 @@ async def list_by_agent( agent_id: NotBlankStr, *, role: DecisionRole, - limit: int = 100, + limit: int = DEFAULT_LIST_LIMIT, offset: int = 0, ) -> tuple[DecisionRecord, ...]: """List decision records where the agent acted in the given role. diff --git a/src/synthorg/persistence/postgres/escalation_repo.py b/src/synthorg/persistence/postgres/escalation_repo.py index 04308ca61a..5e029fb90f 100644 --- a/src/synthorg/persistence/postgres/escalation_repo.py +++ b/src/synthorg/persistence/postgres/escalation_repo.py @@ -26,6 +26,8 @@ EscalationStatus, ) from synthorg.communication.conflict_resolution.escalation.protocol import ( + _DEFAULT_LIMIT, + _DEFAULT_OFFSET, EscalationQueueStore, ) from synthorg.communication.conflict_resolution.models import Conflict @@ -39,9 +41,6 @@ logger = get_logger(__name__) -_DEFAULT_LIMIT = 50 -_DEFAULT_OFFSET = 0 - # Postgres unquoted identifier regex (defence-in-depth for LISTEN / # UNLISTEN arg interpolation in ``subscribe_notifications``). _SAFE_IDENTIFIER_PATTERN: Final[re.Pattern[str]] = re.compile( diff --git a/src/synthorg/persistence/postgres/fine_tune_repo.py b/src/synthorg/persistence/postgres/fine_tune_repo.py index 50b057b82c..b9cdb9e5a5 100644 --- a/src/synthorg/persistence/postgres/fine_tune_repo.py +++ b/src/synthorg/persistence/postgres/fine_tune_repo.py @@ -30,6 +30,7 @@ MEMORY_FINE_TUNE_PERSIST_FAILED, ) from synthorg.persistence._shared import normalize_utc +from synthorg.persistence.fine_tune_protocol import _DEFAULT_LIST_LIMIT_50 if TYPE_CHECKING: from psycopg_pool import AsyncConnectionPool @@ -241,7 +242,7 @@ async def get_active_run(self) -> FineTuneRun | None: async def list_runs( self, *, - limit: int = 50, + limit: int = _DEFAULT_LIST_LIMIT_50, offset: int = 0, ) -> tuple[tuple[FineTuneRun, ...], int]: """List runs ordered by start time descending. @@ -453,7 +454,7 @@ async def get_checkpoint( async def list_checkpoints( self, *, - limit: int = 50, + limit: int = _DEFAULT_LIST_LIMIT_50, offset: int = 0, ) -> tuple[tuple[CheckpointRecord, ...], int]: """List checkpoints ordered by creation time descending. diff --git a/src/synthorg/persistence/postgres/mcp_installation_repo.py b/src/synthorg/persistence/postgres/mcp_installation_repo.py index ac26e15139..9f80ad4052 100644 --- a/src/synthorg/persistence/postgres/mcp_installation_repo.py +++ b/src/synthorg/persistence/postgres/mcp_installation_repo.py @@ -25,7 +25,11 @@ from synthorg.observability.events.persistence import ( PERSISTENCE_MCP_INSTALLATION_LIST_FAILED, ) -from synthorg.persistence._shared import coerce_row_timestamp, normalize_utc +from synthorg.persistence._shared import ( + DEFAULT_LIST_LIMIT, + coerce_row_timestamp, + normalize_utc, +) from synthorg.persistence._shared.pagination import validate_pagination_args if TYPE_CHECKING: @@ -131,7 +135,7 @@ async def get( async def list_items( self, *, - limit: int = 100, + limit: int = DEFAULT_LIST_LIMIT, offset: int = 0, ) -> tuple[McpInstallation, ...]: """List recorded installations in a deterministic order. diff --git a/src/synthorg/persistence/postgres/ontology_drift_repo.py b/src/synthorg/persistence/postgres/ontology_drift_repo.py index 9005c61c1d..297fab73ad 100644 --- a/src/synthorg/persistence/postgres/ontology_drift_repo.py +++ b/src/synthorg/persistence/postgres/ontology_drift_repo.py @@ -1,7 +1,7 @@ """Postgres-backed drift report repository.""" import json -from typing import TYPE_CHECKING, Any +from typing import TYPE_CHECKING, Any, Final from synthorg.observability import get_logger from synthorg.observability.events.ontology import ( @@ -9,6 +9,7 @@ ONTOLOGY_DRIFT_STORE_WRITE_FAILED, ) from synthorg.ontology.models import AgentDrift, DriftAction, DriftReport +from synthorg.persistence._shared import DEFAULT_LIST_LIMIT if TYPE_CHECKING: from psycopg_pool import AsyncConnectionPool @@ -25,6 +26,8 @@ def _import_dict_row() -> Any: logger = get_logger(__name__) +_DEFAULT_LIST_LIMIT_10: Final[int] = 10 + def _row_to_report(row: dict[str, Any]) -> DriftReport: """Deserialize a dict row into a DriftReport.""" @@ -102,7 +105,7 @@ async def get_latest( self, entity_name: NotBlankStr, *, - limit: int = 10, + limit: int = _DEFAULT_LIST_LIMIT_10, ) -> tuple[DriftReport, ...]: """Return most recent drift reports for an entity. @@ -129,7 +132,7 @@ async def get_latest( async def get_all_latest( self, *, - limit: int = 100, + limit: int = DEFAULT_LIST_LIMIT, ) -> tuple[DriftReport, ...]: """Return the latest drift report for each entity. diff --git a/src/synthorg/persistence/postgres/ontology_entity_repo.py b/src/synthorg/persistence/postgres/ontology_entity_repo.py index 613ff9c86a..828767cf18 100644 --- a/src/synthorg/persistence/postgres/ontology_entity_repo.py +++ b/src/synthorg/persistence/postgres/ontology_entity_repo.py @@ -25,6 +25,7 @@ EntitySource, EntityTier, ) +from synthorg.persistence._shared import DEFAULT_LIST_LIMIT if TYPE_CHECKING: from psycopg_pool import AsyncConnectionPool @@ -223,7 +224,7 @@ async def list_entities( self, *, tier: EntityTier | None = None, - limit: int = 100, + limit: int = DEFAULT_LIST_LIMIT, offset: int = 0, ) -> tuple[EntityDefinition, ...]: """List entities, optionally filtered by tier and paginated.""" @@ -260,7 +261,7 @@ async def search( self, query: str, *, - limit: int = 100, + limit: int = DEFAULT_LIST_LIMIT, offset: int = 0, ) -> tuple[EntityDefinition, ...]: """Search entities by name or definition text.""" diff --git a/src/synthorg/persistence/postgres/org_fact_repo.py b/src/synthorg/persistence/postgres/org_fact_repo.py index 7c919606e1..66a7e09c0a 100644 --- a/src/synthorg/persistence/postgres/org_fact_repo.py +++ b/src/synthorg/persistence/postgres/org_fact_repo.py @@ -33,7 +33,8 @@ ORG_MEMORY_ROW_PARSE_FAILED, ORG_MEMORY_WRITE_FAILED, ) -from synthorg.persistence._shared import normalize_utc +from synthorg.persistence._shared import DEFAULT_LIST_LIMIT, normalize_utc +from synthorg.persistence.memory_protocol import _DEFAULT_LIST_LIMIT_5 if TYPE_CHECKING: import psycopg @@ -414,7 +415,7 @@ async def query( *, categories: frozenset[OrgFactCategory] | None = None, text: str | None = None, - limit: int = 5, + limit: int = _DEFAULT_LIST_LIMIT_5, offset: int = 0, ) -> tuple[OrgFact, ...]: """Query active facts by category and/or text content.""" @@ -470,7 +471,7 @@ async def list_by_category( self, category: OrgFactCategory, *, - limit: int = 100, + limit: int = DEFAULT_LIST_LIMIT, offset: int = 0, ) -> tuple[OrgFact, ...]: """List all active facts in a category, optionally paginated.""" diff --git a/src/synthorg/persistence/postgres/provider_audit_repo.py b/src/synthorg/persistence/postgres/provider_audit_repo.py index efbba994c6..aa388a6cc0 100644 --- a/src/synthorg/persistence/postgres/provider_audit_repo.py +++ b/src/synthorg/persistence/postgres/provider_audit_repo.py @@ -24,6 +24,7 @@ PERSISTENCE_AUDIT_ENTRY_QUERY_FAILED, ) from synthorg.persistence._shared import normalize_utc +from synthorg.persistence.provider_audit_protocol import _DEFAULT_LIST_LIMIT_50 if TYPE_CHECKING: from psycopg_pool import AsyncConnectionPool @@ -105,7 +106,7 @@ async def list( *, provider_name: NotBlankStr, after_id: int | None = None, - limit: int = 50, + limit: int = _DEFAULT_LIST_LIMIT_50, ) -> tuple[tuple[ProviderAuditEvent, ...], bool]: """List events for one provider, newest first, with ``has_more``.""" if limit < 1: diff --git a/src/synthorg/persistence/postgres/repositories.py b/src/synthorg/persistence/postgres/repositories.py index 0002b1ed2c..291a3fe1fb 100644 --- a/src/synthorg/persistence/postgres/repositories.py +++ b/src/synthorg/persistence/postgres/repositories.py @@ -42,6 +42,7 @@ PERSISTENCE_TASK_LISTED, PERSISTENCE_TASK_SAVE_FAILED, ) +from synthorg.persistence._shared import DEFAULT_LIST_LIMIT if TYPE_CHECKING: from psycopg_pool import AsyncConnectionPool @@ -221,7 +222,7 @@ async def list_tasks( status: TaskStatus | None = None, assigned_to: str | None = None, project: str | None = None, - limit: int = 100, + limit: int = DEFAULT_LIST_LIMIT, offset: int = 0, ) -> tuple[Task, ...]: """List tasks with optional filters and pagination. @@ -389,7 +390,7 @@ async def query( *, agent_id: str | None = None, task_id: str | None = None, - limit: int = 100, + limit: int = DEFAULT_LIST_LIMIT, offset: int = 0, ) -> tuple[CostRecord, ...]: """Query cost records with optional filters and pagination.""" @@ -617,7 +618,7 @@ async def get_history( self, channel: str, *, - limit: int = 100, + limit: int = DEFAULT_LIST_LIMIT, ) -> tuple[Message, ...]: """Retrieve message history for a channel, newest first.""" if limit is not None and ( diff --git a/src/synthorg/persistence/postgres/session_repo.py b/src/synthorg/persistence/postgres/session_repo.py index 9ccdfc4ec3..1cfa289e7a 100644 --- a/src/synthorg/persistence/postgres/session_repo.py +++ b/src/synthorg/persistence/postgres/session_repo.py @@ -16,6 +16,7 @@ from synthorg.observability.events.api import ( API_SESSION_CLEANUP, ) +from synthorg.persistence._shared import DEFAULT_LIST_LIMIT if TYPE_CHECKING: from psycopg_pool import AsyncConnectionPool @@ -124,7 +125,7 @@ async def list_by_user( self, user_id: str, *, - limit: int = 100, + limit: int = DEFAULT_LIST_LIMIT, offset: int = 0, ) -> tuple[Session, ...]: """List active (non-expired, non-revoked) sessions for a user.""" @@ -152,7 +153,7 @@ async def list_by_user( async def list_all( self, *, - limit: int = 100, + limit: int = DEFAULT_LIST_LIMIT, offset: int = 0, ) -> tuple[Session, ...]: """List all active (non-expired, non-revoked) sessions.""" diff --git a/src/synthorg/persistence/postgres/settings_repo.py b/src/synthorg/persistence/postgres/settings_repo.py index 96b3183a22..9d2ed927ac 100644 --- a/src/synthorg/persistence/postgres/settings_repo.py +++ b/src/synthorg/persistence/postgres/settings_repo.py @@ -27,6 +27,7 @@ SETTINGS_VALUE_SET, ) from synthorg.persistence._shared import format_iso_utc, parse_iso_utc +from synthorg.persistence.settings_protocol import _DEFAULT_LIST_LIMIT_200 logger = get_logger(__name__) @@ -124,7 +125,7 @@ async def get_namespace( async def get_all( self, *, - limit: int = 200, + limit: int = _DEFAULT_LIST_LIMIT_200, offset: int = 0, ) -> tuple[tuple[str, str, str, str], ...]: """Return all (namespace, key, value, updated_at) (paginated).""" diff --git a/src/synthorg/persistence/postgres/ssrf_violation_repo.py b/src/synthorg/persistence/postgres/ssrf_violation_repo.py index a29d0ce1ca..acec9ea88b 100644 --- a/src/synthorg/persistence/postgres/ssrf_violation_repo.py +++ b/src/synthorg/persistence/postgres/ssrf_violation_repo.py @@ -17,7 +17,7 @@ PERSISTENCE_SSRF_VIOLATION_QUERY_FAILED, PERSISTENCE_SSRF_VIOLATION_SAVE_FAILED, ) -from synthorg.persistence._shared import normalize_utc +from synthorg.persistence._shared import DEFAULT_LIST_LIMIT, normalize_utc from synthorg.security.ssrf_violation import SsrfViolation, SsrfViolationStatus if TYPE_CHECKING: @@ -140,7 +140,7 @@ async def list_violations( self, *, status: SsrfViolationStatus | None = None, - limit: int = 100, + limit: int = DEFAULT_LIST_LIMIT, ) -> tuple[SsrfViolation, ...]: """List violations, optionally filtered by status.""" if limit <= 0: diff --git a/src/synthorg/persistence/postgres/version_repo.py b/src/synthorg/persistence/postgres/version_repo.py index 006fb318ae..4f65ce8af1 100644 --- a/src/synthorg/persistence/postgres/version_repo.py +++ b/src/synthorg/persistence/postgres/version_repo.py @@ -43,6 +43,7 @@ VERSION_LISTED, VERSION_SAVE_FAILED, ) +from synthorg.persistence.version_protocol import _DEFAULT_LIST_LIMIT_50 from synthorg.versioning.models import VersionSnapshot if TYPE_CHECKING: @@ -336,7 +337,7 @@ async def list_versions( self, entity_id: NotBlankStr, *, - limit: int = 50, + limit: int = _DEFAULT_LIST_LIMIT_50, offset: int = 0, ) -> tuple[VersionSnapshot[T], ...]: """List version snapshots ordered by version descending.""" diff --git a/src/synthorg/persistence/postgres/webhook_receipt_repo.py b/src/synthorg/persistence/postgres/webhook_receipt_repo.py index 498dd274e8..6f1b07fbfa 100644 --- a/src/synthorg/persistence/postgres/webhook_receipt_repo.py +++ b/src/synthorg/persistence/postgres/webhook_receipt_repo.py @@ -22,7 +22,11 @@ PERSISTENCE_WEBHOOK_RECEIPT_LIST_FAILED, PERSISTENCE_WEBHOOK_RECEIPT_LOG_FAILED, ) -from synthorg.persistence._shared import coerce_row_timestamp, normalize_utc +from synthorg.persistence._shared import ( + DEFAULT_LIST_LIMIT, + coerce_row_timestamp, + normalize_utc, +) if TYPE_CHECKING: from psycopg_pool import AsyncConnectionPool @@ -144,7 +148,7 @@ async def get_by_connection( self, connection_name: NotBlankStr, *, - limit: int = 100, + limit: int = DEFAULT_LIST_LIMIT, offset: int = 0, ) -> tuple[WebhookReceipt, ...]: """List receipts for *connection_name*, newest-first up to *limit*.""" diff --git a/src/synthorg/persistence/provider_audit_protocol.py b/src/synthorg/persistence/provider_audit_protocol.py index a11b57d34a..4835d30df2 100644 --- a/src/synthorg/persistence/provider_audit_protocol.py +++ b/src/synthorg/persistence/provider_audit_protocol.py @@ -21,11 +21,13 @@ write path. """ -from typing import Protocol, runtime_checkable +from typing import Final, Protocol, runtime_checkable from synthorg.api.dto_provider_capabilities import ProviderAuditEvent # noqa: TC001 from synthorg.core.types import NotBlankStr # noqa: TC001 +_DEFAULT_LIST_LIMIT_50: Final[int] = 50 + @runtime_checkable class ProviderAuditRepo(Protocol): @@ -59,7 +61,7 @@ async def list( *, provider_name: NotBlankStr, after_id: int | None = None, - limit: int = 50, + limit: int = _DEFAULT_LIST_LIMIT_50, ) -> tuple[tuple[ProviderAuditEvent, ...], bool]: """List events for one provider, newest first, with keyset paging. diff --git a/src/synthorg/persistence/settings_protocol.py b/src/synthorg/persistence/settings_protocol.py index 522acf6d39..02890d919c 100644 --- a/src/synthorg/persistence/settings_protocol.py +++ b/src/synthorg/persistence/settings_protocol.py @@ -1,10 +1,12 @@ """Settings repository protocol.""" from collections.abc import Mapping, Sequence # noqa: TC003 -from typing import Protocol, runtime_checkable +from typing import Final, Protocol, runtime_checkable from synthorg.core.types import NotBlankStr # noqa: TC001 +_DEFAULT_LIST_LIMIT_200: Final[int] = 200 + @runtime_checkable class SettingsRepository(Protocol): @@ -53,7 +55,7 @@ async def get_namespace( async def get_all( self, *, - limit: int = 200, + limit: int = _DEFAULT_LIST_LIMIT_200, offset: int = 0, ) -> tuple[tuple[NotBlankStr, NotBlankStr, str, str], ...]: """Retrieve all settings across all namespaces (paginated). diff --git a/src/synthorg/persistence/sqlite/approval_repo.py b/src/synthorg/persistence/sqlite/approval_repo.py index 3d7de5cea5..5a5b8cc264 100644 --- a/src/synthorg/persistence/sqlite/approval_repo.py +++ b/src/synthorg/persistence/sqlite/approval_repo.py @@ -23,7 +23,11 @@ API_APPROVAL_REPO_FETCHED, API_APPROVAL_REPO_LISTED, ) -from synthorg.persistence._shared import coerce_row_timestamp, format_iso_utc +from synthorg.persistence._shared import ( + DEFAULT_LIST_LIMIT, + coerce_row_timestamp, + format_iso_utc, +) logger = get_logger(__name__) @@ -450,7 +454,7 @@ async def list_items( status: ApprovalStatus | None = None, risk_level: ApprovalRiskLevel | None = None, action_type: NotBlankStr | None = None, - limit: int = 100, # lint-allow: magic-numbers -- default page size + limit: int = DEFAULT_LIST_LIMIT, offset: int = 0, ) -> tuple[ApprovalItem, ...]: """List approval items with optional filters (paginated, newest-first). diff --git a/src/synthorg/persistence/sqlite/artifact_repo.py b/src/synthorg/persistence/sqlite/artifact_repo.py index 0d1b231fa9..1f08cce339 100644 --- a/src/synthorg/persistence/sqlite/artifact_repo.py +++ b/src/synthorg/persistence/sqlite/artifact_repo.py @@ -21,6 +21,7 @@ PERSISTENCE_ARTIFACT_LISTED, PERSISTENCE_ARTIFACT_SAVE_FAILED, ) +from synthorg.persistence._shared import DEFAULT_LIST_LIMIT logger = get_logger(__name__) @@ -266,7 +267,7 @@ async def list_artifacts( task_id: NotBlankStr | None = None, created_by: NotBlankStr | None = None, artifact_type: ArtifactType | None = None, - limit: int = 100, + limit: int = DEFAULT_LIST_LIMIT, offset: int = 0, ) -> tuple[Artifact, ...]: """List artifacts with optional filters (paginated).""" diff --git a/src/synthorg/persistence/sqlite/audit_repository.py b/src/synthorg/persistence/sqlite/audit_repository.py index 102cc70a11..2a3a129c80 100644 --- a/src/synthorg/persistence/sqlite/audit_repository.py +++ b/src/synthorg/persistence/sqlite/audit_repository.py @@ -28,6 +28,7 @@ from synthorg.core.types import NotBlankStr from synthorg.security.models import AuditEntry, AuditVerdictStr +from synthorg.persistence._shared import DEFAULT_LIST_LIMIT from synthorg.persistence.sqlite._shared import is_unique_constraint_error logger = get_logger(__name__) @@ -124,7 +125,7 @@ async def query( # noqa: PLR0913 risk_level: ApprovalRiskLevel | None = None, since: AwareDatetime | None = None, until: AwareDatetime | None = None, - limit: int = 100, + limit: int = DEFAULT_LIST_LIMIT, ) -> tuple[AuditEntry, ...]: """Query audit entries with optional filters (newest first). diff --git a/src/synthorg/persistence/sqlite/connection_repo.py b/src/synthorg/persistence/sqlite/connection_repo.py index b3cb372db5..4e0575e021 100644 --- a/src/synthorg/persistence/sqlite/connection_repo.py +++ b/src/synthorg/persistence/sqlite/connection_repo.py @@ -36,7 +36,11 @@ PERSISTENCE_CONNECTION_LIST_FAILED, PERSISTENCE_CONNECTION_SAVE_FAILED, ) -from synthorg.persistence._shared import coerce_row_timestamp, format_iso_utc +from synthorg.persistence._shared import ( + DEFAULT_LIST_LIMIT, + coerce_row_timestamp, + format_iso_utc, +) logger = get_logger(__name__) @@ -228,7 +232,7 @@ async def get(self, name: NotBlankStr) -> Connection | None: async def list_all( self, *, - limit: int = 100, + limit: int = DEFAULT_LIST_LIMIT, offset: int = 0, ) -> tuple[Connection, ...]: """List all connections, sorted by name for determinism.""" @@ -265,7 +269,7 @@ async def list_by_type( self, connection_type: ConnectionType, *, - limit: int = 100, + limit: int = DEFAULT_LIST_LIMIT, offset: int = 0, ) -> tuple[Connection, ...]: """List connections of *connection_type*, sorted by name.""" diff --git a/src/synthorg/persistence/sqlite/decision_repo.py b/src/synthorg/persistence/sqlite/decision_repo.py index 44fbaaec2d..3a74ed1f1d 100644 --- a/src/synthorg/persistence/sqlite/decision_repo.py +++ b/src/synthorg/persistence/sqlite/decision_repo.py @@ -28,7 +28,7 @@ PERSISTENCE_DECISION_RECORD_QUERY_FAILED, PERSISTENCE_DECISION_RECORD_SAVE_FAILED, ) -from synthorg.persistence._shared import validate_pagination_args +from synthorg.persistence._shared import DEFAULT_LIST_LIMIT, validate_pagination_args from synthorg.persistence.decision_protocol import DecisionRole # noqa: TC001 from synthorg.persistence.sqlite._shared import is_unique_constraint_error @@ -479,7 +479,7 @@ async def list_by_task( self, task_id: NotBlankStr, *, - limit: int = 100, + limit: int = DEFAULT_LIST_LIMIT, offset: int = 0, ) -> tuple[DecisionRecord, ...]: """List decision records for a task, oldest first. @@ -553,7 +553,7 @@ async def list_by_agent( agent_id: NotBlankStr, *, role: DecisionRole, - limit: int = 100, + limit: int = DEFAULT_LIST_LIMIT, offset: int = 0, ) -> tuple[DecisionRecord, ...]: """List decision records where the agent acted in the given role. diff --git a/src/synthorg/persistence/sqlite/escalation_repo.py b/src/synthorg/persistence/sqlite/escalation_repo.py index 292eb8dbbf..307f9a8945 100644 --- a/src/synthorg/persistence/sqlite/escalation_repo.py +++ b/src/synthorg/persistence/sqlite/escalation_repo.py @@ -23,6 +23,8 @@ EscalationStatus, ) from synthorg.communication.conflict_resolution.escalation.protocol import ( + _DEFAULT_LIMIT, + _DEFAULT_OFFSET, EscalationQueueStore, ) from synthorg.communication.conflict_resolution.models import Conflict @@ -33,9 +35,6 @@ logger = get_logger(__name__) -_DEFAULT_LIMIT = 50 -_DEFAULT_OFFSET = 0 - _decision_adapter: TypeAdapter[EscalationDecision] = TypeAdapter(EscalationDecision) _UPSERT_SQL = """ diff --git a/src/synthorg/persistence/sqlite/fine_tune_repo.py b/src/synthorg/persistence/sqlite/fine_tune_repo.py index 965c5e6175..217869c8d1 100644 --- a/src/synthorg/persistence/sqlite/fine_tune_repo.py +++ b/src/synthorg/persistence/sqlite/fine_tune_repo.py @@ -22,6 +22,7 @@ MEMORY_FINE_TUNE_PERSIST_FAILED, ) from synthorg.persistence._shared import coerce_row_timestamp, format_iso_utc +from synthorg.persistence.fine_tune_protocol import _DEFAULT_LIST_LIMIT_50 logger = get_logger(__name__) @@ -209,7 +210,7 @@ async def get_active_run(self) -> FineTuneRun | None: async def list_runs( self, *, - limit: int = 50, + limit: int = _DEFAULT_LIST_LIMIT_50, offset: int = 0, ) -> tuple[tuple[FineTuneRun, ...], int]: """List runs ordered by start time descending. @@ -416,7 +417,7 @@ async def get_checkpoint( async def list_checkpoints( self, *, - limit: int = 50, + limit: int = _DEFAULT_LIST_LIMIT_50, offset: int = 0, ) -> tuple[tuple[CheckpointRecord, ...], int]: """List checkpoints ordered by creation time descending. diff --git a/src/synthorg/persistence/sqlite/mcp_installation_repo.py b/src/synthorg/persistence/sqlite/mcp_installation_repo.py index f36c181016..f87b2d39c4 100644 --- a/src/synthorg/persistence/sqlite/mcp_installation_repo.py +++ b/src/synthorg/persistence/sqlite/mcp_installation_repo.py @@ -20,7 +20,11 @@ PERSISTENCE_MCP_INSTALLATION_LIST_FAILED, PERSISTENCE_MCP_INSTALLATION_SAVE_FAILED, ) -from synthorg.persistence._shared import coerce_row_timestamp, format_iso_utc +from synthorg.persistence._shared import ( + DEFAULT_LIST_LIMIT, + coerce_row_timestamp, + format_iso_utc, +) from synthorg.persistence._shared.pagination import validate_pagination_args logger = get_logger(__name__) @@ -106,7 +110,7 @@ async def get( async def list_items( self, *, - limit: int = 100, + limit: int = DEFAULT_LIST_LIMIT, offset: int = 0, ) -> tuple[McpInstallation, ...]: """Return up to ``limit`` recorded installations, oldest-first. diff --git a/src/synthorg/persistence/sqlite/ontology_drift_repo.py b/src/synthorg/persistence/sqlite/ontology_drift_repo.py index 19dc1c39af..c4c0e4f97b 100644 --- a/src/synthorg/persistence/sqlite/ontology_drift_repo.py +++ b/src/synthorg/persistence/sqlite/ontology_drift_repo.py @@ -4,7 +4,7 @@ import contextlib import json import sqlite3 -from typing import TYPE_CHECKING, Any +from typing import TYPE_CHECKING, Any, Final import aiosqlite @@ -14,12 +14,15 @@ ONTOLOGY_DRIFT_STORE_WRITE_FAILED, ) from synthorg.ontology.models import AgentDrift, DriftAction, DriftReport +from synthorg.persistence._shared import DEFAULT_LIST_LIMIT if TYPE_CHECKING: from synthorg.core.types import NotBlankStr logger = get_logger(__name__) +_DEFAULT_LIST_LIMIT_10: Final[int] = 10 + def _row_to_report(row: Any) -> DriftReport: """Deserialize a row into a DriftReport.""" @@ -109,7 +112,7 @@ async def get_latest( self, entity_name: NotBlankStr, *, - limit: int = 10, + limit: int = _DEFAULT_LIST_LIMIT_10, ) -> tuple[DriftReport, ...]: """Return most recent drift reports for an entity.""" cursor = await self._db.execute( @@ -126,7 +129,7 @@ async def get_latest( async def get_all_latest( self, *, - limit: int = 100, + limit: int = DEFAULT_LIST_LIMIT, ) -> tuple[DriftReport, ...]: """Return the latest drift report for each entity.""" cursor = await self._db.execute( diff --git a/src/synthorg/persistence/sqlite/ontology_entity_repo.py b/src/synthorg/persistence/sqlite/ontology_entity_repo.py index 163e0c45cb..5d432fb97f 100644 --- a/src/synthorg/persistence/sqlite/ontology_entity_repo.py +++ b/src/synthorg/persistence/sqlite/ontology_entity_repo.py @@ -28,6 +28,7 @@ EntitySource, EntityTier, ) +from synthorg.persistence._shared import DEFAULT_LIST_LIMIT logger = get_logger(__name__) @@ -213,7 +214,7 @@ async def list_entities( self, *, tier: EntityTier | None = None, - limit: int = 100, + limit: int = DEFAULT_LIST_LIMIT, offset: int = 0, ) -> tuple[EntityDefinition, ...]: """List entities, optionally filtered by tier and paginated. @@ -250,7 +251,7 @@ async def search( self, query: str, *, - limit: int = 100, + limit: int = DEFAULT_LIST_LIMIT, offset: int = 0, ) -> tuple[EntityDefinition, ...]: """Search entities by name or definition text.""" diff --git a/src/synthorg/persistence/sqlite/org_fact_repo.py b/src/synthorg/persistence/sqlite/org_fact_repo.py index f83a9f088d..3648b82c85 100644 --- a/src/synthorg/persistence/sqlite/org_fact_repo.py +++ b/src/synthorg/persistence/sqlite/org_fact_repo.py @@ -37,7 +37,12 @@ ORG_MEMORY_ROW_PARSE_FAILED, ORG_MEMORY_WRITE_FAILED, ) -from synthorg.persistence._shared import coerce_row_timestamp, format_iso_utc +from synthorg.persistence._shared import ( + DEFAULT_LIST_LIMIT, + coerce_row_timestamp, + format_iso_utc, +) +from synthorg.persistence.memory_protocol import _DEFAULT_LIST_LIMIT_5 logger = get_logger(__name__) @@ -436,7 +441,7 @@ async def query( *, categories: frozenset[OrgFactCategory] | None = None, text: str | None = None, - limit: int = 5, + limit: int = _DEFAULT_LIST_LIMIT_5, offset: int = 0, ) -> tuple[OrgFact, ...]: """Query active facts by category and/or text content.""" @@ -488,7 +493,7 @@ async def list_by_category( self, category: OrgFactCategory, *, - limit: int = 100, + limit: int = DEFAULT_LIST_LIMIT, offset: int = 0, ) -> tuple[OrgFact, ...]: """List all active facts in a category, optionally paginated.""" diff --git a/src/synthorg/persistence/sqlite/provider_audit_repo.py b/src/synthorg/persistence/sqlite/provider_audit_repo.py index 91873f61ae..0970095c5e 100644 --- a/src/synthorg/persistence/sqlite/provider_audit_repo.py +++ b/src/synthorg/persistence/sqlite/provider_audit_repo.py @@ -28,6 +28,7 @@ format_iso_utc, parse_iso_utc, ) +from synthorg.persistence.provider_audit_protocol import _DEFAULT_LIST_LIMIT_50 if TYPE_CHECKING: from synthorg.core.types import NotBlankStr @@ -113,7 +114,7 @@ async def list( *, provider_name: NotBlankStr, after_id: int | None = None, - limit: int = 50, + limit: int = _DEFAULT_LIST_LIMIT_50, ) -> tuple[tuple[ProviderAuditEvent, ...], bool]: """List events for one provider, newest first, with ``has_more`` overflow.""" if limit < 1: diff --git a/src/synthorg/persistence/sqlite/repositories.py b/src/synthorg/persistence/sqlite/repositories.py index b4bd751349..4fdfe786e3 100644 --- a/src/synthorg/persistence/sqlite/repositories.py +++ b/src/synthorg/persistence/sqlite/repositories.py @@ -41,6 +41,7 @@ PERSISTENCE_TASK_LISTED, PERSISTENCE_TASK_SAVE_FAILED, ) +from synthorg.persistence._shared import DEFAULT_LIST_LIMIT from synthorg.persistence.sqlite._shared import is_unique_constraint_error logger = get_logger(__name__) @@ -212,7 +213,7 @@ async def list_tasks( status: TaskStatus | None = None, assigned_to: str | None = None, project: str | None = None, - limit: int = 100, + limit: int = DEFAULT_LIST_LIMIT, offset: int = 0, ) -> tuple[Task, ...]: """List tasks with optional filters and pagination. @@ -370,7 +371,7 @@ async def query( *, agent_id: str | None = None, task_id: str | None = None, - limit: int = 100, + limit: int = DEFAULT_LIST_LIMIT, offset: int = 0, ) -> tuple[CostRecord, ...]: """Query cost records with optional filters and pagination.""" @@ -624,7 +625,7 @@ async def get_history( self, channel: str, *, - limit: int = 100, + limit: int = DEFAULT_LIST_LIMIT, ) -> tuple[Message, ...]: """Retrieve message history for a channel, newest first.""" if limit is not None and limit < 1: diff --git a/src/synthorg/persistence/sqlite/session_repo.py b/src/synthorg/persistence/sqlite/session_repo.py index 87ad4a099b..5b69ceee17 100644 --- a/src/synthorg/persistence/sqlite/session_repo.py +++ b/src/synthorg/persistence/sqlite/session_repo.py @@ -23,7 +23,11 @@ API_SESSION_CREATE_FAILED, API_SESSION_REVOKE_FAILED, ) -from synthorg.persistence._shared import coerce_row_timestamp, format_iso_utc +from synthorg.persistence._shared import ( + DEFAULT_LIST_LIMIT, + coerce_row_timestamp, + format_iso_utc, +) logger = get_logger(__name__) @@ -142,7 +146,7 @@ async def list_by_user( self, user_id: str, *, - limit: int = 100, + limit: int = DEFAULT_LIST_LIMIT, offset: int = 0, ) -> tuple[Session, ...]: """List active (non-expired, non-revoked) sessions for a user.""" @@ -164,7 +168,7 @@ async def list_by_user( async def list_all( self, *, - limit: int = 100, + limit: int = DEFAULT_LIST_LIMIT, offset: int = 0, ) -> tuple[Session, ...]: """List all active (non-expired, non-revoked) sessions.""" diff --git a/src/synthorg/persistence/sqlite/settings_repo.py b/src/synthorg/persistence/sqlite/settings_repo.py index b802e44757..19aba7d5b9 100644 --- a/src/synthorg/persistence/sqlite/settings_repo.py +++ b/src/synthorg/persistence/sqlite/settings_repo.py @@ -15,6 +15,7 @@ SETTINGS_SET_FAILED, SETTINGS_VALUE_SET, ) +from synthorg.persistence.settings_protocol import _DEFAULT_LIST_LIMIT_200 logger = get_logger(__name__) @@ -95,7 +96,7 @@ async def get_namespace( async def get_all( self, *, - limit: int = 200, + limit: int = _DEFAULT_LIST_LIMIT_200, offset: int = 0, ) -> tuple[tuple[str, str, str, str], ...]: """Return all (namespace, key, value, updated_at) (paginated).""" diff --git a/src/synthorg/persistence/sqlite/ssrf_violation_repo.py b/src/synthorg/persistence/sqlite/ssrf_violation_repo.py index e434cb57dc..7fd3b29358 100644 --- a/src/synthorg/persistence/sqlite/ssrf_violation_repo.py +++ b/src/synthorg/persistence/sqlite/ssrf_violation_repo.py @@ -13,7 +13,11 @@ PERSISTENCE_SSRF_VIOLATION_QUERY_FAILED, PERSISTENCE_SSRF_VIOLATION_SAVE_FAILED, ) -from synthorg.persistence._shared import coerce_row_timestamp, format_iso_utc +from synthorg.persistence._shared import ( + DEFAULT_LIST_LIMIT, + coerce_row_timestamp, + format_iso_utc, +) from synthorg.persistence.sqlite._shared import is_unique_constraint_error from synthorg.security.ssrf_violation import SsrfViolation, SsrfViolationStatus @@ -153,7 +157,7 @@ async def list_violations( self, *, status: SsrfViolationStatus | None = None, - limit: int = 100, + limit: int = DEFAULT_LIST_LIMIT, ) -> tuple[SsrfViolation, ...]: """List violations, optionally filtered by status.""" if limit <= 0: diff --git a/src/synthorg/persistence/sqlite/version_repo.py b/src/synthorg/persistence/sqlite/version_repo.py index 2b2b7a5f7e..1ebc3a2acb 100644 --- a/src/synthorg/persistence/sqlite/version_repo.py +++ b/src/synthorg/persistence/sqlite/version_repo.py @@ -38,6 +38,7 @@ VERSION_LISTED, VERSION_SAVE_FAILED, ) +from synthorg.persistence.version_protocol import _DEFAULT_LIST_LIMIT_50 from synthorg.versioning.models import VersionSnapshot if TYPE_CHECKING: @@ -328,7 +329,7 @@ async def list_versions( self, entity_id: NotBlankStr, *, - limit: int = 50, + limit: int = _DEFAULT_LIST_LIMIT_50, offset: int = 0, ) -> tuple[VersionSnapshot[T], ...]: """List version snapshots ordered by version descending.""" diff --git a/src/synthorg/persistence/sqlite/webhook_receipt_repo.py b/src/synthorg/persistence/sqlite/webhook_receipt_repo.py index ce1e1c4143..235105a82c 100644 --- a/src/synthorg/persistence/sqlite/webhook_receipt_repo.py +++ b/src/synthorg/persistence/sqlite/webhook_receipt_repo.py @@ -23,7 +23,11 @@ PERSISTENCE_WEBHOOK_RECEIPT_LIST_FAILED, PERSISTENCE_WEBHOOK_RECEIPT_LOG_FAILED, ) -from synthorg.persistence._shared import coerce_row_timestamp, format_iso_utc +from synthorg.persistence._shared import ( + DEFAULT_LIST_LIMIT, + coerce_row_timestamp, + format_iso_utc, +) logger = get_logger(__name__) @@ -123,7 +127,7 @@ async def get_by_connection( self, connection_name: NotBlankStr, *, - limit: int = 100, + limit: int = DEFAULT_LIST_LIMIT, offset: int = 0, ) -> tuple[WebhookReceipt, ...]: """List receipts for *connection_name*, newest-first up to *limit*.""" diff --git a/src/synthorg/persistence/ssrf_violation_protocol.py b/src/synthorg/persistence/ssrf_violation_protocol.py index 6659a82c41..e301c50502 100644 --- a/src/synthorg/persistence/ssrf_violation_protocol.py +++ b/src/synthorg/persistence/ssrf_violation_protocol.py @@ -2,6 +2,8 @@ from typing import TYPE_CHECKING, Protocol, runtime_checkable +from synthorg.persistence._shared import DEFAULT_LIST_LIMIT + if TYPE_CHECKING: from pydantic import AwareDatetime @@ -46,7 +48,7 @@ async def list_violations( self, *, status: SsrfViolationStatus | None = None, - limit: int = 100, + limit: int = DEFAULT_LIST_LIMIT, ) -> tuple[SsrfViolation, ...]: """List violations, optionally filtered by status. diff --git a/src/synthorg/persistence/task_protocol.py b/src/synthorg/persistence/task_protocol.py index f10873174d..402b610854 100644 --- a/src/synthorg/persistence/task_protocol.py +++ b/src/synthorg/persistence/task_protocol.py @@ -5,6 +5,7 @@ from synthorg.core.enums import TaskStatus # noqa: TC001 from synthorg.core.task import Task # noqa: TC001 from synthorg.core.types import NotBlankStr # noqa: TC001 +from synthorg.persistence._shared import DEFAULT_LIST_LIMIT @runtime_checkable @@ -42,7 +43,7 @@ async def list_tasks( status: TaskStatus | None = None, assigned_to: NotBlankStr | None = None, project: NotBlankStr | None = None, - limit: int = 100, + limit: int = DEFAULT_LIST_LIMIT, offset: int = 0, ) -> tuple[Task, ...]: """List tasks with optional filters and pagination. @@ -51,9 +52,9 @@ async def list_tasks( status: Filter by task status. assigned_to: Filter by assignee agent ID. project: Filter by project ID. - limit: Maximum rows to return. ``None`` means "no - repository-level cap" (the caller remains free to - impose a safety cap above). + limit: Maximum rows to return. Defaults to + ``DEFAULT_LIST_LIMIT``; callers may pass a larger + positive integer when they need a wider window. offset: Rows to skip before the window (``0`` = no offset). Paired with ``limit`` for cursor/offset pagination. diff --git a/src/synthorg/persistence/version_protocol.py b/src/synthorg/persistence/version_protocol.py index 70dfb8a7e0..bb6d1157b2 100644 --- a/src/synthorg/persistence/version_protocol.py +++ b/src/synthorg/persistence/version_protocol.py @@ -1,6 +1,6 @@ """Generic repository protocol for versioned entity persistence.""" -from typing import TYPE_CHECKING, Protocol, runtime_checkable +from typing import TYPE_CHECKING, Final, Protocol, runtime_checkable from pydantic import BaseModel @@ -10,6 +10,9 @@ from synthorg.versioning.models import VersionSnapshot +_DEFAULT_LIST_LIMIT_50: Final[int] = 50 + + @runtime_checkable class VersionRepository[T: BaseModel](Protocol): """CRUD interface for versioned entity snapshots. @@ -104,7 +107,7 @@ async def list_versions( self, entity_id: NotBlankStr, *, - limit: int = 50, + limit: int = _DEFAULT_LIST_LIMIT_50, offset: int = 0, ) -> tuple[VersionSnapshot[T], ...]: """List version snapshots for an entity. diff --git a/src/synthorg/providers/drivers/litellm_driver.py b/src/synthorg/providers/drivers/litellm_driver.py index 3b5d9ab9fd..54ed6197e8 100644 --- a/src/synthorg/providers/drivers/litellm_driver.py +++ b/src/synthorg/providers/drivers/litellm_driver.py @@ -9,7 +9,7 @@ Mapping, # noqa: TC003 # runtime annotation on driver method ) from types import MappingProxyType -from typing import TYPE_CHECKING, Any +from typing import TYPE_CHECKING, Any, Final import litellm as _litellm from litellm.exceptions import ( @@ -94,7 +94,7 @@ logger = get_logger(__name__) -_CREDENTIAL_CACHE_TTL = 300.0 +_CREDENTIAL_CACHE_TTL: Final[float] = 300.0 """Cached credentials from the connection catalog are refreshed at most every ``_CREDENTIAL_CACHE_TTL`` seconds. Prevents pinning stale OAuth/rotating tokens for the lifetime of the driver.""" diff --git a/src/synthorg/providers/health.py b/src/synthorg/providers/health.py index 4832588d2d..c785e79810 100644 --- a/src/synthorg/providers/health.py +++ b/src/synthorg/providers/health.py @@ -10,7 +10,7 @@ from datetime import UTC, datetime, timedelta from enum import StrEnum from types import MappingProxyType -from typing import TYPE_CHECKING, Self +from typing import TYPE_CHECKING, Final, Self if TYPE_CHECKING: from collections.abc import Mapping @@ -34,10 +34,10 @@ logger = get_logger(__name__) -_HEALTH_WINDOW_HOURS = 24 -_DEGRADED_THRESHOLD = 10.0 # error_rate >= 10% -> DEGRADED -_DOWN_THRESHOLD = 50.0 # error_rate >= 50% -> DOWN -_AUTO_PRUNE_THRESHOLD = 100_000 +_HEALTH_WINDOW_HOURS: Final[int] = 24 +_DEGRADED_THRESHOLD: Final[float] = 10.0 # error_rate >= 10% -> DEGRADED +_DOWN_THRESHOLD: Final[float] = 50.0 # error_rate >= 50% -> DOWN +_AUTO_PRUNE_THRESHOLD: Final[int] = 100_000 class ProviderHealthStatus(StrEnum): diff --git a/src/synthorg/providers/health_prober.py b/src/synthorg/providers/health_prober.py index ec1acccc67..4858063bbe 100644 --- a/src/synthorg/providers/health_prober.py +++ b/src/synthorg/providers/health_prober.py @@ -46,18 +46,10 @@ logger = get_logger(__name__) -_DEFAULT_INTERVAL_SECONDS: Final[int] = ( - 1800 # lint-allow: magic-numbers -- 30 min ctor-kwarg default -) -_PROBE_TIMEOUT_SECONDS: Final[float] = ( - 10.0 # lint-allow: magic-numbers -- HTTP client timeout baseline -) -_HTTP_SERVER_ERROR_THRESHOLD: Final[int] = ( - 500 # lint-allow: magic-numbers -- HTTP 5xx protocol constant -) -_MAX_ERROR_MESSAGE_LENGTH: Final[int] = ( - 200 # lint-allow: magic-numbers -- log truncation limit -) +_DEFAULT_INTERVAL_SECONDS: Final[int] = 1800 +_PROBE_TIMEOUT_SECONDS: Final[float] = 10.0 +_HTTP_SERVER_ERROR_THRESHOLD: Final[int] = 500 +_MAX_ERROR_MESSAGE_LENGTH: Final[int] = 200 def _build_ping_url( diff --git a/src/synthorg/providers/management/_capability_helpers.py b/src/synthorg/providers/management/_capability_helpers.py index 8e0381f628..072835497f 100644 --- a/src/synthorg/providers/management/_capability_helpers.py +++ b/src/synthorg/providers/management/_capability_helpers.py @@ -7,6 +7,7 @@ """ from datetime import UTC, datetime +from typing import Final from synthorg.observability import get_logger, safe_error_description from synthorg.observability.events.provider import PROVIDER_VALIDATION_FAILED @@ -26,7 +27,7 @@ # from the controller derived from ``AuthenticatedUser``. SYSTEM_ACTOR = ProviderAuditActor(id="system", label="provider-management") -_SECRET_SHORT_THRESHOLD = 8 +_SECRET_SHORT_THRESHOLD: Final[int] = 8 def mask_secret(secret: str) -> str: diff --git a/src/synthorg/providers/management/audit_service.py b/src/synthorg/providers/management/audit_service.py index 14948b931c..1caf76f1d6 100644 --- a/src/synthorg/providers/management/audit_service.py +++ b/src/synthorg/providers/management/audit_service.py @@ -12,7 +12,7 @@ """ from datetime import UTC, datetime -from typing import TYPE_CHECKING, Any +from typing import TYPE_CHECKING, Any, Final from synthorg.observability import get_logger from synthorg.providers.management.capability_dtos import ( @@ -26,6 +26,7 @@ from synthorg.persistence.provider_audit_protocol import ProviderAuditRepo logger = get_logger(__name__) +_DEFAULT_LIMIT: Final[int] = 50 class ProviderAuditService: @@ -71,7 +72,7 @@ async def list_for_provider( *, provider_name: NotBlankStr, after_id: int | None = None, - limit: int = 50, + limit: int = _DEFAULT_LIMIT, ) -> tuple[tuple[ProviderAuditEvent, ...], bool]: """Read one provider's audit log, newest first, with ``has_more``.""" return await self._repo.list( diff --git a/src/synthorg/providers/management/local_models.py b/src/synthorg/providers/management/local_models.py index 1dbf3ea77e..bee81dcdb3 100644 --- a/src/synthorg/providers/management/local_models.py +++ b/src/synthorg/providers/management/local_models.py @@ -32,7 +32,7 @@ _HTTP_OK: int = 200 _HTTP_NOT_FOUND: int = 404 _HTTP_CLIENT_ERROR: int = 400 -_OLLAMA_ERROR_MAX_LEN: Final[int] = 200 # lint-allow: magic-numbers -- internal cap +_OLLAMA_ERROR_MAX_LEN: Final[int] = 200 # POSIX-style absolute paths (``/var/lib/ollama/models/x.bin``). The # segment class includes a literal space so paths like diff --git a/src/synthorg/security/audit.py b/src/synthorg/security/audit.py index cd325260fd..556093b8c7 100644 --- a/src/synthorg/security/audit.py +++ b/src/synthorg/security/audit.py @@ -1,6 +1,7 @@ """Append-only in-memory audit log for security evaluations.""" from collections import deque +from typing import Final from pydantic import AwareDatetime # noqa: TC002 @@ -15,6 +16,8 @@ from synthorg.security.models import AuditEntry # noqa: TC001 logger = get_logger(__name__) +_DEFAULT_MAX_ENTRIES: Final[int] = 100000 +_DEFAULT_LIMIT: Final[int] = 100 class AuditLog: @@ -27,7 +30,7 @@ class AuditLog: Future: backed by ``PersistenceBackend`` (see Memory design page). """ - def __init__(self, *, max_entries: int = 100_000) -> None: + def __init__(self, *, max_entries: int = _DEFAULT_MAX_ENTRIES) -> None: """Initialize the audit log. Args: @@ -96,7 +99,7 @@ def query( # noqa: PLR0913, C901 action_type: str | None = None, since: AwareDatetime | None = None, until: AwareDatetime | None = None, - limit: int = 100, + limit: int = _DEFAULT_LIMIT, ) -> tuple[AuditEntry, ...]: """Query audit entries with optional filters. diff --git a/src/synthorg/security/llm_evaluator.py b/src/synthorg/security/llm_evaluator.py index 12dd8ddbeb..b47533f35d 100644 --- a/src/synthorg/security/llm_evaluator.py +++ b/src/synthorg/security/llm_evaluator.py @@ -31,7 +31,7 @@ from collections.abc import Mapping # noqa: TC003 from datetime import UTC, datetime from types import MappingProxyType -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, Final from synthorg.budget.call_category import LLMCallCategory from synthorg.budget.tracker import CostTracker # noqa: TC001 @@ -78,16 +78,16 @@ logger = get_logger(__name__) # Maximum length for serialized arguments in the prompt. -_MAX_ARGS_DISPLAY = 1500 +_MAX_ARGS_DISPLAY: Final[int] = 1500 # Per-value truncation limit (chars) when using PER_VALUE or # KEYS_AND_VALUES strategy. -_MAX_VALUE_LENGTH = 200 +_MAX_VALUE_LENGTH: Final[int] = 200 # Maximum length for LLM-returned reason string (defense against # prompt injection exfiltration -- the reason flows into audit log # and approval queue). -_MAX_REASON_LENGTH = 300 +_MAX_REASON_LENGTH: Final[int] = 300 # Regex to strip control characters from LLM-returned reason. _CONTROL_CHAR_RE = re.compile(r"[\x00-\x1f\x7f]") diff --git a/src/synthorg/security/timeout/factory.py b/src/synthorg/security/timeout/factory.py index 038c546e88..38e0b29b77 100644 --- a/src/synthorg/security/timeout/factory.py +++ b/src/synthorg/security/timeout/factory.py @@ -1,5 +1,7 @@ """Factory for creating timeout policy instances from configuration.""" +from typing import Final + from synthorg.observability import get_logger from synthorg.observability.events.timeout import TIMEOUT_FACTORY_UNKNOWN_CONFIG from synthorg.security.timeout.config import ( @@ -20,7 +22,7 @@ logger = get_logger(__name__) -_SECONDS_PER_MINUTE = 60.0 +_SECONDS_PER_MINUTE: Final[float] = 60.0 def create_timeout_policy( diff --git a/src/synthorg/security/timeout/policies.py b/src/synthorg/security/timeout/policies.py index 5f3c9e7776..3344a6c993 100644 --- a/src/synthorg/security/timeout/policies.py +++ b/src/synthorg/security/timeout/policies.py @@ -1,6 +1,6 @@ """Timeout policy implementations -- wait, deny, tiered, escalation chain.""" -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, Final from synthorg.core.enums import ApprovalRiskLevel, TimeoutActionType from synthorg.observability import get_logger @@ -27,7 +27,7 @@ logger = get_logger(__name__) -_SECONDS_PER_MINUTE = 60.0 +_SECONDS_PER_MINUTE: Final[float] = 60.0 class WaitForeverPolicy: diff --git a/src/synthorg/settings/subscribers/per_op_rate_limit_subscriber.py b/src/synthorg/settings/subscribers/per_op_rate_limit_subscriber.py index e31b4577b2..93142b1474 100644 --- a/src/synthorg/settings/subscribers/per_op_rate_limit_subscriber.py +++ b/src/synthorg/settings/subscribers/per_op_rate_limit_subscriber.py @@ -15,7 +15,7 @@ import json import re -from typing import TYPE_CHECKING, Any +from typing import TYPE_CHECKING, Any, Final from synthorg.config.rate_limits import ( PerOpConcurrencyConfig, @@ -47,7 +47,7 @@ (_NAMESPACE, _CONCURRENCY_OVERRIDES), } ) -_RATE_LIMIT_OVERRIDE_TUPLE_LEN = 2 +_RATE_LIMIT_OVERRIDE_TUPLE_LEN: Final[int] = 2 # JSON numbers arrive either as Python ``int`` (preferred) or as # string digits for operators who wrote ``"5"`` instead of ``5`` in # the settings payload. We accept both but reject floats, booleans, diff --git a/src/synthorg/telemetry/event_counter.py b/src/synthorg/telemetry/event_counter.py index f5796b4abc..19722fc7cb 100644 --- a/src/synthorg/telemetry/event_counter.py +++ b/src/synthorg/telemetry/event_counter.py @@ -15,7 +15,7 @@ import threading from collections import deque -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, Final from synthorg.meta.signal_models import OrgTelemetrySummary from synthorg.observability import get_logger, safe_error_description @@ -30,8 +30,9 @@ from synthorg.telemetry.protocol import TelemetryEvent logger = get_logger(__name__) +_DEFAULT_MAX_TOP: Final[int] = 10 -_DEFAULT_MAX_EVENTS = 10_000 +_DEFAULT_MAX_EVENTS: Final[int] = 10_000 """Default ring-buffer capacity for telemetry events. Telemetry events fire on deployment lifecycle boundaries + heartbeat; @@ -113,7 +114,7 @@ async def summarize( *, since: datetime, until: datetime, - max_top: int = 10, + max_top: int = _DEFAULT_MAX_TOP, ) -> OrgTelemetrySummary: """Roll recorded events into an :class:`OrgTelemetrySummary`.""" _validate_window(since, until) diff --git a/src/synthorg/telemetry/event_counter_protocol.py b/src/synthorg/telemetry/event_counter_protocol.py index 105ccd4fb6..a4253cbf49 100644 --- a/src/synthorg/telemetry/event_counter_protocol.py +++ b/src/synthorg/telemetry/event_counter_protocol.py @@ -17,7 +17,9 @@ plumbing, so tests can feed events directly into the counter. """ -from typing import TYPE_CHECKING, Protocol, runtime_checkable +from typing import TYPE_CHECKING, Final, Protocol, runtime_checkable + +_DEFAULT_MAX_TOP: Final[int] = 10 if TYPE_CHECKING: from datetime import datetime @@ -58,7 +60,7 @@ async def summarize( *, since: datetime, until: datetime, - max_top: int = 10, + max_top: int = _DEFAULT_MAX_TOP, ) -> OrgTelemetrySummary: """Produce the org-wide telemetry summary for the window. diff --git a/src/synthorg/tools/design/image_generator.py b/src/synthorg/tools/design/image_generator.py index c5d7e49959..168164d132 100644 --- a/src/synthorg/tools/design/image_generator.py +++ b/src/synthorg/tools/design/image_generator.py @@ -26,6 +26,8 @@ from synthorg.tools.design.config import DesignToolsConfig # noqa: TC001 logger = get_logger(__name__) +_DEFAULT_WIDTH: Final[int] = 1024 +_DEFAULT_HEIGHT: Final[int] = 1024 class ImageResult(BaseModel): @@ -60,8 +62,8 @@ async def generate( self, *, prompt: str, - width: int = 1024, - height: int = 1024, + width: int = _DEFAULT_WIDTH, + height: int = _DEFAULT_HEIGHT, style: str = "realistic", quality: str = "standard", ) -> ImageResult: diff --git a/src/synthorg/tools/factory.py b/src/synthorg/tools/factory.py index 23776db73c..cc3f3feeae 100644 --- a/src/synthorg/tools/factory.py +++ b/src/synthorg/tools/factory.py @@ -8,7 +8,7 @@ """ import math -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, Final from synthorg.core.enums import ToolCategory from synthorg.observability import get_logger @@ -100,11 +100,14 @@ def _build_git_tools( ) +_DEFAULT_MAX_RESPONSE_BYTES: Final[int] = 1048576 + + def _build_web_tools( *, network_policy: NetworkPolicy | None = None, search_provider: WebSearchProvider | None = None, - max_response_bytes: int = 1_048_576, + max_response_bytes: int = _DEFAULT_MAX_RESPONSE_BYTES, request_timeout: float, ) -> tuple[BaseTool, ...]: """Instantiate the built-in web tools. diff --git a/src/synthorg/tools/invocation_tracker.py b/src/synthorg/tools/invocation_tracker.py index c880566514..4426bcd5dc 100644 --- a/src/synthorg/tools/invocation_tracker.py +++ b/src/synthorg/tools/invocation_tracker.py @@ -7,7 +7,7 @@ import asyncio from collections import deque -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, Final from synthorg.observability import get_logger from synthorg.observability.events.tool import ( @@ -25,7 +25,7 @@ logger = get_logger(__name__) -_DEFAULT_MAX_RECORDS = 10_000 +_DEFAULT_MAX_RECORDS: Final[int] = 10_000 class ToolInvocationTracker: diff --git a/src/synthorg/tools/mcp/cache.py b/src/synthorg/tools/mcp/cache.py index dd7fddcb99..16558f894e 100644 --- a/src/synthorg/tools/mcp/cache.py +++ b/src/synthorg/tools/mcp/cache.py @@ -7,7 +7,7 @@ import copy import threading from collections import OrderedDict -from typing import Any +from typing import Any, Final from synthorg.core.clock import Clock, SystemClock from synthorg.observability import get_logger @@ -22,6 +22,8 @@ _CACHE_NAME = "mcp_result" logger = get_logger(__name__) +_DEFAULT_MAX_SIZE: Final[int] = 256 +_DEFAULT_TTL_SECONDS: Final[float] = 60.0 class MCPResultCache: @@ -45,8 +47,8 @@ class MCPResultCache: def __init__( self, *, - max_size: int = 256, - ttl_seconds: float = 60.0, + max_size: int = _DEFAULT_MAX_SIZE, + ttl_seconds: float = _DEFAULT_TTL_SECONDS, clock: Clock | None = None, ) -> None: self._max_size = max_size diff --git a/src/synthorg/tools/sandbox/docker_config.py b/src/synthorg/tools/sandbox/docker_config.py index 9a47ac9352..dde0f83bb1 100644 --- a/src/synthorg/tools/sandbox/docker_config.py +++ b/src/synthorg/tools/sandbox/docker_config.py @@ -1,7 +1,7 @@ """Docker sandbox configuration model.""" import re -from typing import Any, Literal, Self +from typing import Any, Final, Literal, Self from pydantic import BaseModel, ConfigDict, Field, model_validator @@ -22,8 +22,8 @@ _VALID_NETWORK_MODES = frozenset({"none", "bridge", "host"}) _MIN_PORT = 1 -_MAX_PORT = 65535 -_HOST_PORT_PARTS = 2 +_MAX_PORT: Final[int] = 65535 +_HOST_PORT_PARTS: Final[int] = 2 # Docker tmpfs size syntax: positive integer, optional k/m/g suffix # (case-insensitive). Rejects leading zeros, negatives, and unknown diff --git a/src/synthorg/tools/web/base_web_tool.py b/src/synthorg/tools/web/base_web_tool.py index 9f52b3ef61..a2d55d7a92 100644 --- a/src/synthorg/tools/web/base_web_tool.py +++ b/src/synthorg/tools/web/base_web_tool.py @@ -6,7 +6,7 @@ """ from abc import ABC -from typing import Any +from typing import Any, Final from synthorg.core.enums import ToolCategory from synthorg.observability import get_logger @@ -20,6 +20,7 @@ ) logger = get_logger(__name__) +_DEFAULT_REQUEST_TIMEOUT: Final[float] = 30.0 class BaseWebTool(BaseTool, ABC): @@ -38,7 +39,7 @@ def __init__( # noqa: PLR0913 parameters_schema: dict[str, Any] | None = None, action_type: str | None = None, network_policy: NetworkPolicy | None = None, - request_timeout: float = 30.0, + request_timeout: float = _DEFAULT_REQUEST_TIMEOUT, ) -> None: """Initialize a web tool with network policy. diff --git a/src/synthorg/tools/web/http_request.py b/src/synthorg/tools/web/http_request.py index 83484db8f7..4c6c759867 100644 --- a/src/synthorg/tools/web/http_request.py +++ b/src/synthorg/tools/web/http_request.py @@ -29,6 +29,8 @@ from synthorg.tools.web.base_web_tool import BaseWebTool logger = get_logger(__name__) +_DEFAULT_MAX_RESPONSE_BYTES: Final[int] = 1048576 +_DEFAULT_REQUEST_TIMEOUT: Final[float] = 30.0 _ALLOWED_METHODS: Final[frozenset[str]] = frozenset( { @@ -62,8 +64,8 @@ def __init__( self, *, network_policy: NetworkPolicy | None = None, - max_response_bytes: int = 1_048_576, - request_timeout: float = 30.0, + max_response_bytes: int = _DEFAULT_MAX_RESPONSE_BYTES, + request_timeout: float = _DEFAULT_REQUEST_TIMEOUT, ) -> None: """Initialize the HTTP request tool. diff --git a/src/synthorg/tools/web/web_search.py b/src/synthorg/tools/web/web_search.py index e6f00783c6..17cf75a77e 100644 --- a/src/synthorg/tools/web/web_search.py +++ b/src/synthorg/tools/web/web_search.py @@ -6,7 +6,7 @@ implementation). """ -from typing import Any, ClassVar, Protocol, runtime_checkable +from typing import Any, ClassVar, Final, Protocol, runtime_checkable from pydantic import BaseModel, ConfigDict @@ -24,6 +24,8 @@ logger = get_logger(__name__) +_DEFAULT_MAX_RESULTS: Final[int] = 10 + class SearchResult(BaseModel): """A single web search result. @@ -55,7 +57,7 @@ class WebSearchProvider(Protocol): async def search( self, query: str, - max_results: int = 10, # lint-allow: magic-numbers -- protocol default arg. + max_results: int = _DEFAULT_MAX_RESULTS, ) -> list[SearchResult]: """Execute a web search query. diff --git a/src/synthorg/workers/__main__.py b/src/synthorg/workers/__main__.py index f033065b23..d7ba809c6d 100644 --- a/src/synthorg/workers/__main__.py +++ b/src/synthorg/workers/__main__.py @@ -16,6 +16,7 @@ import asyncio import os import sys +from typing import Final from synthorg.communication.config import NatsConfig from synthorg.observability import get_logger @@ -45,7 +46,7 @@ async def _placeholder_executor(claim: TaskClaim) -> TaskClaimStatus: return TaskClaimStatus.SUCCESS -_DEFAULT_WORKER_COUNT = 4 +_DEFAULT_WORKER_COUNT: Final[int] = 4 """Fallback worker count when neither --workers nor env var is set.""" diff --git a/src/synthorg/workers/dispatcher.py b/src/synthorg/workers/dispatcher.py index 38d060e99a..95167fa890 100644 --- a/src/synthorg/workers/dispatcher.py +++ b/src/synthorg/workers/dispatcher.py @@ -49,7 +49,7 @@ Values are matched case-insensitively against ``TaskStatus.value``. """ -_PUBLISH_MAX_ATTEMPTS: Final[int] = 3 # lint-allow: magic-numbers -- bootstrap +_PUBLISH_MAX_ATTEMPTS: Final[int] = 3 """Max publish attempts per claim before giving up. A transient NATS hiccup (reconnect, brief server unavailability) @@ -64,10 +64,10 @@ observer events (e.g., on engine restart). """ -_PUBLISH_BACKOFF_BASE_SECONDS: Final[float] = 0.1 # lint-allow: magic-numbers -- boot +_PUBLISH_BACKOFF_BASE_SECONDS: Final[float] = 0.1 """Base delay for exponential backoff between publish retries.""" -_PUBLISH_BACKOFF_CAP_SECONDS: Final[float] = 1.0 # lint-allow: magic-numbers -- boot +_PUBLISH_BACKOFF_CAP_SECONDS: Final[float] = 1.0 """Upper bound on a single inter-attempt delay. With ``base=0.1`` and ``max_attempts=3`` the unbounded delays are diff --git a/tests/evals/prompt/test_memory_consolidation_prompt.py b/tests/evals/prompt/test_memory_consolidation_prompt.py index 8782cbb477..1aaf33b007 100644 --- a/tests/evals/prompt/test_memory_consolidation_prompt.py +++ b/tests/evals/prompt/test_memory_consolidation_prompt.py @@ -28,12 +28,14 @@ def test_config_pins_explicit_temperature(self) -> None: from synthorg.memory.consolidation import abstractive source = inspect.getsource(abstractive) - # Match either a default-value binding ``temperature=0.3`` or a - # typed default ``temperature: float = 0.3`` so the regex - # survives reflow between keyword-only calls and signature - # annotations. + # Match either a direct default-value binding (``temperature=0.3``, + # ``temperature: float = 0.3``) OR a module-level Final constant + # of the form ``_DEFAULT_TEMPERATURE: Final[float] = 0.3`` that + # the function default references. Either pattern pins the value + # to a single explicit literal so drift is detectable. assert re.search( - r"temperature\s*(?::\s*[A-Za-z_][\w\[\], ]*)?\s*=\s*[-+]?[\d.]+", + r"(temperature\s*(?::\s*[A-Za-z_][\w\[\], ]*)?\s*=\s*[-+]?[\d.]+" + r"|_DEFAULT_TEMPERATURE\s*:\s*Final\[float\]\s*=\s*[-+]?[\d.]+)", source, ), ( "abstractive consolidation must bind temperature to an "