icdev 0.0.3__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- args/agent_config.yaml +113 -0
- args/audit_regimes/cisa_sbd.json +381 -0
- args/audit_regimes/cmmc_l2.json +906 -0
- args/audit_regimes/dod_cssp.json +393 -0
- args/audit_regimes/dodi_5000_87.json +297 -0
- args/audit_regimes/fedramp_moderate.json +650 -0
- args/audit_regimes/ieee_1012.json +373 -0
- args/audit_regimes/nist_800_171.json +624 -0
- args/audit_regimes/nist_800_53.json +907 -0
- args/cloudforge_blueprints/aws_commercial.yaml +29 -0
- args/cloudforge_blueprints/aws_govcloud_il4.yaml +34 -0
- args/cloudforge_blueprints/aws_govcloud_il5.yaml +38 -0
- args/cloudforge_blueprints/azure_commercial.yaml +28 -0
- args/cloudforge_blueprints/azure_gov_il4.yaml +32 -0
- args/cloudforge_blueprints/azure_gov_il5.yaml +36 -0
- args/cloudforge_blueprints/gcp_commercial.yaml +28 -0
- args/cloudforge_blueprints/oci_commercial.yaml +28 -0
- args/cloudforge_config.yaml +231 -0
- args/cloudforge_runbook_templates/backup_verify.yaml +98 -0
- args/cloudforge_runbook_templates/dr_failover.yaml +107 -0
- args/cloudforge_runbook_templates/health_check.yaml +97 -0
- args/cloudforge_runbook_templates/incident_response.yaml +101 -0
- args/cloudforge_runbook_templates/migration_cutover.yaml +105 -0
- args/cloudforge_runbook_templates/patch_rollout.yaml +92 -0
- args/cloudforge_runbook_templates/zone_provision.yaml +93 -0
- args/code_pattern_config.yaml +151 -0
- args/code_quality_config.yaml +47 -0
- args/compliance_config.yaml +17 -0
- args/control_inheritance.yaml +177 -0
- args/csp_mcp_config.yaml +41 -0
- args/cui_markings.yaml +35 -0
- args/databridge_config.yaml +232 -0
- args/db_config.yaml +116 -0
- args/decision_tables/agent_trust_decision.yaml +143 -0
- args/decision_tables/ato_boundary_impact.yaml +132 -0
- args/decision_tables/deployment_approval.yaml +152 -0
- args/degradation_matrix.yaml +163 -0
- args/devsecops_config.yaml +286 -0
- args/endpoint_security_config.yaml +207 -0
- args/exit_criteria.yaml +102 -0
- args/feature_flags.yaml +235 -0
- args/file_access_tiers.yaml +88 -0
- args/forge_studio/blueprint_config.yaml +27 -0
- args/forge_studio/component_catalog.json +411 -0
- args/forge_studio/workflow_templates.yaml +103 -0
- args/govcon_config.yaml +41 -0
- args/harness_config.yaml +67 -0
- args/innovation_config.yaml +321 -0
- args/knowledge_graph_config.yaml +113 -0
- args/llm_config.yaml +222 -0
- args/marketplace_config.yaml +260 -0
- args/monitoring_config.yaml +127 -0
- args/mosa_config.yaml +190 -0
- args/observability_tracing_config.yaml +170 -0
- args/owasp_agentic_config.yaml +171 -0
- args/pipeline_gates.yaml +197 -0
- args/project_defaults.yaml +235 -0
- args/prompt_chains.yaml +163 -0
- args/rag_config.yaml +167 -0
- args/research_config.yaml +89 -0
- args/resilience_config.yaml +197 -0
- args/ricoas_config.yaml +191 -0
- args/security_gates.yaml +763 -0
- args/storage_config.yaml +63 -0
- args/writeguard_config.yaml +131 -0
- args/zta_config.yaml +247 -0
- context/__init__.py +6 -0
- context/agent/__init__.py +6 -0
- context/agent/response_schemas/__init__.py +6 -0
- context/agent/response_schemas/debate_position.json +46 -0
- context/agent/response_schemas/fitness_scorecard.json +74 -0
- context/agent/response_schemas/review_decision.json +39 -0
- context/agent/response_schemas/task_decomposition.json +82 -0
- context/agent/response_schemas/veto_decision.json +40 -0
- context/agentic/__init__.py +6 -0
- context/agentic/architecture_patterns.md +269 -0
- context/agentic/capability_registry.yaml +223 -0
- context/agentic/csp_integration.md +30 -0
- context/agentic/csp_mcp_registry.yaml +280 -0
- context/agentic/fitness_rubric.md +56 -0
- context/agentic/governance_baseline.md +205 -0
- context/ci/__init__.py +6 -0
- context/ci/worktree_templates.json +44 -0
- context/cloud/__init__.py +6 -0
- context/cloud/csp_service_registry.json +739 -0
- context/compliance/__init__.py +6 -0
- context/compliance/ai_rmf_crosswalk.yaml +226 -0
- context/compliance/atlas_mitigations.json +293 -0
- context/compliance/atlas_techniques.json +833 -0
- context/compliance/cisa_sbd_requirements.json +477 -0
- context/compliance/cjis_security_policy.json +522 -0
- context/compliance/cmmc_practices.json +2494 -0
- context/compliance/cmmc_report_template.md +142 -0
- context/compliance/cnssi_1253_overlay.json +109 -0
- context/compliance/control_crosswalk.json +1914 -0
- context/compliance/control_families/__init__.py +6 -0
- context/compliance/csp_certifications.json +251 -0
- context/compliance/cssp_report_template.md +193 -0
- context/compliance/cui_templates/__init__.py +6 -0
- context/compliance/cui_templates/banner_block.txt +4 -0
- context/compliance/cui_templates/code_header.txt +8 -0
- context/compliance/cui_templates/document_template.md +35 -0
- context/compliance/data_type_framework_map.json +321 -0
- context/compliance/data_type_registry.json +147 -0
- context/compliance/dod_cssp_8530.json +463 -0
- context/compliance/eu_ai_act_annex_iii.json +108 -0
- context/compliance/export_templates/__init__.py +6 -0
- context/compliance/export_templates/emass_controls.csv.j2 +4 -0
- context/compliance/export_templates/evidence_package.md.j2 +39 -0
- context/compliance/export_templates/executive_summary.md.j2 +55 -0
- context/compliance/export_templates/poam_tracking.csv.j2 +4 -0
- context/compliance/fedramp_20x_ksi_schemas.json +133 -0
- context/compliance/fedramp_high_baseline.json +4370 -0
- context/compliance/fedramp_moderate_baseline.json +2183 -0
- context/compliance/fedramp_report_template.md +181 -0
- context/compliance/fips_200_areas.json +362 -0
- context/compliance/gao_ai_accountability.json +262 -0
- context/compliance/hipaa_security_rule.json +720 -0
- context/compliance/hitrust_csf_v11.json +930 -0
- context/compliance/impact_level_profiles.json +251 -0
- context/compliance/incident_response_template.md +1110 -0
- context/compliance/iso27001_2022_controls.json +750 -0
- context/compliance/iso27001_nist_bridge.json +382 -0
- context/compliance/iso42001_controls.json +254 -0
- context/compliance/ivv_checklist_template.md +80 -0
- context/compliance/ivv_report_template.md +116 -0
- context/compliance/ivv_requirements.json +372 -0
- context/compliance/mosa_crosswalk.json +327 -0
- context/compliance/mosa_framework.json +250 -0
- context/compliance/narrative_templates/AC.md.j2 +101 -0
- context/compliance/narrative_templates/AU.md.j2 +106 -0
- context/compliance/narrative_templates/IA.md.j2 +104 -0
- context/compliance/narrative_templates/SC.md.j2 +102 -0
- context/compliance/narrative_templates/SI.md.j2 +111 -0
- context/compliance/narrative_templates/__init__.py +6 -0
- context/compliance/narrative_templates/default.md.j2 +50 -0
- context/compliance/narrative_templates/executive_summary.j2 +27 -0
- context/compliance/narrative_templates/poam_milestone.j2 +19 -0
- context/compliance/narrative_templates/ssp_section.j2 +11 -0
- context/compliance/nist_800_171_controls.json +1552 -0
- context/compliance/nist_800_207_crosswalk.json +399 -0
- context/compliance/nist_800_207_zta.json +258 -0
- context/compliance/nist_800_53.json +324 -0
- context/compliance/nist_ai_600_1_genai.json +326 -0
- context/compliance/nist_ai_rmf.json +206 -0
- context/compliance/nist_sp_800_60_types.json +1667 -0
- context/compliance/omb_m25_21_high_impact_ai.json +248 -0
- context/compliance/omb_m26_04_unbiased_ai.json +262 -0
- context/compliance/owasp_agentic_asi.json +133 -0
- context/compliance/owasp_agentic_threats.json +285 -0
- context/compliance/owasp_llm_top10.json +274 -0
- context/compliance/pci_dss_v4.json +510 -0
- context/compliance/poam_template.md +117 -0
- context/compliance/safeai_controls.json +512 -0
- context/compliance/sbd_report_template.md +77 -0
- context/compliance/siem_config_templates/__init__.py +6 -0
- context/compliance/siem_config_templates/filebeat.yml +213 -0
- context/compliance/siem_config_templates/log_sources.json +208 -0
- context/compliance/soc2_trust_criteria.json +661 -0
- context/compliance/ssp_template.md +432 -0
- context/compliance/stig_templates/__init__.py +6 -0
- context/compliance/stig_templates/webapp_stig.json +139 -0
- context/compliance/xai_requirements.json +108 -0
- context/dashboard/__init__.py +6 -0
- context/dashboard/nlq_examples.json +50 -0
- context/dashboard/schema_descriptions.json +23 -0
- context/icdev_methodology.md +100 -0
- context/integration/__init__.py +6 -0
- context/integration/approval_workflows.json +32 -0
- context/integration/gitlab_field_mappings.json +33 -0
- context/integration/jira_field_mappings.json +32 -0
- context/integration/reqif_export_schema.json +23 -0
- context/integration/servicenow_field_mappings.json +22 -0
- context/languages/__init__.py +6 -0
- context/languages/framework_patterns.json +205 -0
- context/languages/language_registry.json +279 -0
- context/llm/__init__.py +6 -0
- context/llm/example_provider.py +89 -0
- context/marketplace/assets/writeguard-core.yaml +100 -0
- context/marketplace/assets/writeguard-govcon.yaml +45 -0
- context/marketplace/assets/writeguard-style-guides.yaml +44 -0
- context/mbse/__init__.py +6 -0
- context/mbse/des_report_template.md +162 -0
- context/mbse/des_requirements.json +411 -0
- context/mbse/digital_thread_patterns.json +403 -0
- context/mbse/reqif_schema.json +280 -0
- context/mbse/sysml_element_types.json +432 -0
- context/oscal/NIST_SP-800-53_rev5_catalog.json +254987 -0
- context/oscal/README.md +43 -0
- context/patterns/__init__.py +6 -0
- context/profiles/__init__.py +6 -0
- context/profiles/dod_baseline_v1.yaml +145 -0
- context/profiles/fedramp_baseline_v1.yaml +143 -0
- context/profiles/financial_baseline_v1.yaml +142 -0
- context/profiles/healthcare_baseline_v1.yaml +135 -0
- context/profiles/law_enforcement_v1.yaml +129 -0
- context/profiles/startup_v1.yaml +134 -0
- context/rag/source_mappings.json +42 -0
- context/requirements/__init__.py +6 -0
- context/requirements/ambiguity_patterns.json +97 -0
- context/requirements/boundary_impact_rules.json +123 -0
- context/requirements/default_constitutions.json +67 -0
- context/requirements/document_extraction_rules.json +58 -0
- context/requirements/gap_patterns.json +108 -0
- context/requirements/readiness_rubric.json +78 -0
- context/requirements/red_alternative_patterns.json +210 -0
- context/requirements/safe_templates.json +72 -0
- context/requirements/spec_quality_checklist.json +122 -0
- context/research/regulatory_registry.json +114 -0
- context/research/verticals/cybersecurity.json +127 -0
- context/research/verticals/defense.json +104 -0
- context/research/verticals/fintech.json +125 -0
- context/research/verticals/healthcare.json +118 -0
- context/research/verticals/logistics.json +117 -0
- context/research/verticals/trading.json +145 -0
- context/simulation/__init__.py +6 -0
- context/simulation/architecture_patterns.json +36 -0
- context/simulation/coa_templates.json +38 -0
- context/simulation/cost_models.json +23 -0
- context/simulation/risk_categories.json +46 -0
- context/supply_chain/__init__.py +6 -0
- context/supply_chain/isa_templates.json +129 -0
- context/supply_chain/nist_800_161_controls.json +247 -0
- context/supply_chain/scrm_risk_matrix.json +147 -0
- context/templates/__init__.py +6 -0
- context/templates/ansible/__init__.py +6 -0
- context/templates/ansible/playbooks/__init__.py +6 -0
- context/templates/ansible/roles/__init__.py +6 -0
- context/templates/gitlab_ci/__init__.py +6 -0
- context/templates/grafana/__init__.py +6 -0
- context/templates/kubernetes/__init__.py +6 -0
- context/templates/project/__init__.py +6 -0
- context/templates/project/api/__init__.py +6 -0
- context/templates/project/cli/__init__.py +6 -0
- context/templates/project/data_pipeline/__init__.py +6 -0
- context/templates/project/iac/__init__.py +6 -0
- context/templates/project/javascript_frontend/__init__.py +6 -0
- context/templates/project/javascript_frontend/src/__init__.py +6 -0
- context/templates/project/javascript_frontend/tests/__init__.py +6 -0
- context/templates/project/microservice/__init__.py +6 -0
- context/templates/project/python_backend/__init__.py +6 -0
- context/templates/project/python_backend/src/__init__.py +6 -0
- context/templates/project/python_backend/tests/__init__.py +6 -0
- context/templates/project/python_backend/tests/features/__init__.py +6 -0
- context/templates/project/python_backend/tests/steps/__init__.py +6 -0
- context/templates/terraform/__init__.py +6 -0
- context/templates/terraform/govcloud_base/__init__.py +6 -0
- context/templates/terraform/modules/__init__.py +6 -0
- context/tone/__init__.py +6 -0
- context/writing/grammar_rules/common_errors.json +306 -0
- context/writing/grammar_rules/govcon_vocabulary.json +113 -0
- context/writing/style_guides/academic.yaml +43 -0
- context/writing/style_guides/business.yaml +42 -0
- context/writing/style_guides/government.yaml +59 -0
- context/writing/style_guides/proposal.yaml +58 -0
- context/writing/style_guides/technical.yaml +43 -0
- docs/adr/README.md +66 -0
- docs/adr/connector-forge-decisions.md +318 -0
- docs/adr/core-decisions.md +289 -0
- docs/adr/db-decisions.md +94 -0
- docs/adr/harness-decisions.md +122 -0
- docs/adr/innovation-decisions.md +262 -0
- docs/adr/marketplace-decisions.md +109 -0
- docs/adr/sbd-decisions.md +109 -0
- docs/adr/scale-engine-decisions.md +108 -0
- docs/adr/writeguard-decisions.md +136 -0
- docs/architecture/bounded-contexts.md +1032 -0
- docs/features/phase-65-writeguard.md +139 -0
- docs/features/phase-66-marketplace-commerce.md +79 -0
- docs/features/phase-67-knowledge-ingestion-rag-autodraft.md +97 -0
- docs/features/phase-68-enhanced-autodraft-pipeline.md +109 -0
- docs/features/phase-69-proposalai-marketplace-module.md +131 -0
- docs/features/phase-70-databridge.md +214 -0
- docs/features/phase-71-databridge-messaging.md +102 -0
- docs/implementation-plan-architecture-evolution.md +614 -0
- docs/marketplace/CONTRIBUTING.md +124 -0
- docs/marketplace/module_manifest_schema.yaml +83 -0
- docs/research/ai-architecture-patterns-2024-2026.md +1236 -0
- docs/research/app-builder-platform-analysis.md +582 -0
- docs/research/architecture-patterns-c4-ddd-agentic.md +871 -0
- docs/research/flowable-boat-competitive-analysis.md +426 -0
- docs/research/modern-dev-practices-2024-2026.md +1615 -0
- docs/research/secure-by-design-cloudyrion-adaptation.md +270 -0
- goals/agent_management.md +144 -0
- goals/ai_accountability.md +90 -0
- goals/ai_narratives.md +79 -0
- goals/ai_transparency.md +76 -0
- goals/ato_simulator.md +78 -0
- goals/audit_engine.md +177 -0
- goals/bite_sized_plans.md +225 -0
- goals/boundary_supply_chain.md +206 -0
- goals/brainstorming_gate.md +186 -0
- goals/build_app.md +604 -0
- goals/cato_live_evidence.md +77 -0
- goals/cloudforge.md +106 -0
- goals/code_intelligence.md +197 -0
- goals/compliance_workflow.md +858 -0
- goals/connector_forge.md +133 -0
- goals/databridge.md +128 -0
- goals/deploy_workflow.md +390 -0
- goals/developer_scorecard.md +78 -0
- goals/devsecops_workflow.md +408 -0
- goals/firmware_sbom.md +79 -0
- goals/forge_hub.md +78 -0
- goals/golden_path.md +77 -0
- goals/harness_engineering.md +91 -0
- goals/integration_testing.md +189 -0
- goals/knowledge_graph.md +128 -0
- goals/maintenance_audit.md +196 -0
- goals/manifest.md +50 -0
- goals/monitoring.md +126 -0
- goals/mosa_workflow.md +463 -0
- goals/multi_agent_orchestration.md +68 -0
- goals/observability_traceability_xai.md +154 -0
- goals/owasp_agentic_security.md +395 -0
- goals/pr_intelligence.md +78 -0
- goals/requirements_intake.md +213 -0
- goals/secure_by_design.md +135 -0
- goals/security_scan.md +381 -0
- goals/self_healing.md +120 -0
- goals/simulation_engine.md +111 -0
- goals/subagent_review.md +205 -0
- goals/systematic_debugging.md +257 -0
- goals/tdd_workflow.md +403 -0
- goals/template_exchange.md +77 -0
- goals/thread_heatmap.md +77 -0
- goals/threat_modeler.md +77 -0
- goals/verification_iron_law.md +192 -0
- goals/vsm_dashboard.md +76 -0
- goals/writeguard.md +89 -0
- goals/zero_trust_architecture.md +403 -0
- hardprompts/__init__.py +6 -0
- hardprompts/agent/__init__.py +6 -0
- hardprompts/agent/agentic_architect.md +100 -0
- hardprompts/agent/debate_prompt.md +32 -0
- hardprompts/agent/fitness_evaluation.md +48 -0
- hardprompts/agent/governance_review.md +214 -0
- hardprompts/agent/reviewer_prompt.md +34 -0
- hardprompts/agent/skill_design.md +172 -0
- hardprompts/agent/task_decomposition.md +275 -0
- hardprompts/agent/veto_check_prompt.md +33 -0
- hardprompts/architect/__init__.py +6 -0
- hardprompts/architect/api_design.md +283 -0
- hardprompts/architect/data_model.md +277 -0
- hardprompts/architect/system_design.md +180 -0
- hardprompts/builder/__init__.py +6 -0
- hardprompts/builder/code_generation.md +59 -0
- hardprompts/builder/refactor.md +58 -0
- hardprompts/builder/scaffold_project.md +69 -0
- hardprompts/builder/test_generation.md +87 -0
- hardprompts/ci/__init__.py +6 -0
- hardprompts/ci/worktree_setup.md +35 -0
- hardprompts/compliance/__init__.py +6 -0
- hardprompts/compliance/cmmc_assessment.md +63 -0
- hardprompts/compliance/cssp_assessment.md +75 -0
- hardprompts/compliance/cui_marking.md +86 -0
- hardprompts/compliance/fedramp_assessment.md +55 -0
- hardprompts/compliance/ivv_assessment.md +96 -0
- hardprompts/compliance/poam_generation.md +57 -0
- hardprompts/compliance/sbd_assessment.md +101 -0
- hardprompts/compliance/security_categorization.md +74 -0
- hardprompts/compliance/ssp_generation.md +56 -0
- hardprompts/compliance/stig_evaluation.md +63 -0
- hardprompts/dashboard/__init__.py +6 -0
- hardprompts/dashboard/nlq_system_prompt.md +26 -0
- hardprompts/infra/__init__.py +6 -0
- hardprompts/infra/k8s_manifests.md +118 -0
- hardprompts/infra/pipeline_generation.md +160 -0
- hardprompts/infra/terraform_generation.md +92 -0
- hardprompts/integration/__init__.py +6 -0
- hardprompts/integration/approval_review.md +17 -0
- hardprompts/integration/jira_mapping.md +25 -0
- hardprompts/integration/servicenow_mapping.md +14 -0
- hardprompts/knowledge/__init__.py +6 -0
- hardprompts/knowledge/pattern_detection.md +73 -0
- hardprompts/knowledge/recommendation_engine.md +90 -0
- hardprompts/knowledge/root_cause_analysis.md +91 -0
- hardprompts/maintenance/__init__.py +6 -0
- hardprompts/maintenance/maintenance_assessment.md +82 -0
- hardprompts/mbse/__init__.py +6 -0
- hardprompts/mbse/digital_thread.md +67 -0
- hardprompts/mbse/model_import.md +62 -0
- hardprompts/mbse/model_to_code.md +65 -0
- hardprompts/modernization/__init__.py +6 -0
- hardprompts/modernization/legacy_analysis.md +93 -0
- hardprompts/modernization/migration_planning.md +150 -0
- hardprompts/modernization/seven_r_assessment.md +107 -0
- hardprompts/proposal_draft.md +53 -0
- hardprompts/rag_citation.md +12 -0
- hardprompts/rag_rerank.md +31 -0
- hardprompts/requirements/__init__.py +6 -0
- hardprompts/requirements/bdd_generation.md +35 -0
- hardprompts/requirements/clarification_prioritization.md +29 -0
- hardprompts/requirements/decomposition.md +60 -0
- hardprompts/requirements/document_extraction.md +45 -0
- hardprompts/requirements/gap_detection.md +70 -0
- hardprompts/requirements/intake_conversation.md +101 -0
- hardprompts/requirements/readiness_assessment.md +39 -0
- hardprompts/requirements/spec_quality.md +33 -0
- hardprompts/requirements/traceability_analysis.md +23 -0
- hardprompts/security/__init__.py +6 -0
- hardprompts/security/endpoint_security.md +78 -0
- hardprompts/security/threat_model.md +70 -0
- hardprompts/security/vulnerability_assessment.md +81 -0
- hardprompts/simulation/__init__.py +6 -0
- hardprompts/simulation/architecture_impact.md +27 -0
- hardprompts/simulation/coa_alternative.md +27 -0
- hardprompts/simulation/coa_generation.md +25 -0
- hardprompts/simulation/compliance_impact.md +28 -0
- hardprompts/simulation/cost_estimation.md +33 -0
- hardprompts/simulation/risk_assessment.md +28 -0
- hardprompts/translation/code_translation.md +68 -0
- hardprompts/translation/dependency_suggestion.md +44 -0
- hardprompts/translation/test_translation.md +64 -0
- hardprompts/translation/translation_repair.md +59 -0
- icdev-0.0.3.dist-info/METADATA +909 -0
- icdev-0.0.3.dist-info/RECORD +1214 -0
- icdev-0.0.3.dist-info/WHEEL +5 -0
- icdev-0.0.3.dist-info/entry_points.txt +9 -0
- icdev-0.0.3.dist-info/licenses/LICENSE +201 -0
- icdev-0.0.3.dist-info/licenses/NOTICE +11 -0
- icdev-0.0.3.dist-info/top_level.txt +7 -0
- memory/MEMORY.md +52 -0
- memory/logs/2026-02-14.md +17 -0
- memory/logs/2026-03-03.md +2 -0
- memory/logs/__init__.py +1 -0
- tools/a2a/icdev_callback_client.py +210 -0
- tools/agent/cards/architect_card.json +29 -0
- tools/agent/cards/builder_card.json +34 -0
- tools/agent/cards/compliance_card.json +29 -0
- tools/agent/cards/connector_forge_card.json +49 -0
- tools/agent/cards/devsecops_zta_card.json +24 -0
- tools/agent/cards/knowledge_card.json +29 -0
- tools/agent/cards/monitor_card.json +29 -0
- tools/agent/cards/orchestrator_card.json +29 -0
- tools/agent/cards/requirements_analyst_card.json +24 -0
- tools/agent/cards/security_card.json +29 -0
- tools/agent/cards/simulation_card.json +24 -0
- tools/agent/cards/supply_chain_card.json +24 -0
- tools/analysis/__init__.py +1 -0
- tools/analysis/code_analyzer.py +770 -0
- tools/analysis/runtime_feedback.py +379 -0
- tools/analytics/__init__.py +2 -0
- tools/analytics/scorecard.py +538 -0
- tools/analytics/vsm_engine.py +612 -0
- tools/architecture/__init__.py +2 -0
- tools/architecture/adr_extractor.py +393 -0
- tools/audit/__init__.py +1 -0
- tools/audit/audit_logger.py +199 -0
- tools/audit/audit_query.py +153 -0
- tools/audit/decision_recorder.py +73 -0
- tools/audit_engine/__init__.py +12 -0
- tools/audit_engine/ai_advisor.py +906 -0
- tools/audit_engine/cli.py +286 -0
- tools/audit_engine/comparator.py +305 -0
- tools/audit_engine/eject_scaffolder.py +399 -0
- tools/audit_engine/engine.py +614 -0
- tools/audit_engine/git_fetcher.py +341 -0
- tools/audit_engine/regime_loader.py +200 -0
- tools/audit_engine/regime_updater.py +325 -0
- tools/audit_engine/report_card.py +289 -0
- tools/audit_engine/scanner.py +684 -0
- tools/audit_engine/self_heal.py +1042 -0
- tools/ci/__init__.py +2 -0
- tools/ci/connectors/__init__.py +2 -0
- tools/ci/connectors/base_connector.py +80 -0
- tools/ci/connectors/connector_registry.py +188 -0
- tools/ci/connectors/mattermost_connector.py +159 -0
- tools/ci/connectors/slack_connector.py +197 -0
- tools/ci/core/__init__.py +2 -0
- tools/ci/core/air_gap_detector.py +115 -0
- tools/ci/core/comment_handler.py +192 -0
- tools/ci/core/conversation_manager.py +480 -0
- tools/ci/core/event_envelope.py +500 -0
- tools/ci/core/event_router.py +444 -0
- tools/ci/core/failure_parser.py +397 -0
- tools/ci/core/recovery_engine.py +527 -0
- tools/ci/gate_enforcer.py +361 -0
- tools/ci/modules/__init__.py +2 -0
- tools/ci/modules/agent.py +271 -0
- tools/ci/modules/git_ops.py +175 -0
- tools/ci/modules/state.py +117 -0
- tools/ci/modules/vcs.py +303 -0
- tools/ci/modules/workflow_ops.py +295 -0
- tools/ci/modules/worktree.py +337 -0
- tools/ci/pipeline_config_generator.py +558 -0
- tools/ci/pr_intelligence.py +485 -0
- tools/ci/triggers/__init__.py +2 -0
- tools/ci/triggers/gitlab_task_monitor.py +327 -0
- tools/ci/triggers/poll_trigger.py +237 -0
- tools/ci/triggers/webhook_server.py +356 -0
- tools/ci/workflows/__init__.py +2 -0
- tools/ci/workflows/icdev_build.py +140 -0
- tools/ci/workflows/icdev_comply.py +284 -0
- tools/ci/workflows/icdev_document.py +152 -0
- tools/ci/workflows/icdev_e2e.py +188 -0
- tools/ci/workflows/icdev_patch.py +186 -0
- tools/ci/workflows/icdev_plan.py +202 -0
- tools/ci/workflows/icdev_plan_build.py +41 -0
- tools/ci/workflows/icdev_plan_build_test.py +46 -0
- tools/ci/workflows/icdev_plan_build_test_review.py +47 -0
- tools/ci/workflows/icdev_review.py +126 -0
- tools/ci/workflows/icdev_sdlc.py +261 -0
- tools/ci/workflows/icdev_test.py +240 -0
- tools/cli/__init__.py +1 -0
- tools/cli/output_formatter.py +756 -0
- tools/cloudforge/__init__.py +12 -0
- tools/cloudforge/airgap/__init__.py +2 -0
- tools/cloudforge/airgap/il_classifier.py +70 -0
- tools/cloudforge/airgap/offline_validator.py +42 -0
- tools/cloudforge/airgap/shift_emulator.py +155 -0
- tools/cloudforge/airgap/sneakernet.py +91 -0
- tools/cloudforge/cd_hub/__init__.py +2 -0
- tools/cloudforge/cd_hub/canary_deployer.py +88 -0
- tools/cloudforge/cd_hub/gitops_renderer.py +123 -0
- tools/cloudforge/cd_hub/hub_controller.py +143 -0
- tools/cloudforge/cd_hub/pipeline_bridge.py +30 -0
- tools/cloudforge/cd_hub/rollback_engine.py +29 -0
- tools/cloudforge/cd_hub/spoke_agent.py +51 -0
- tools/cloudforge/compliance/__init__.py +2 -0
- tools/cloudforge/compliance/ato_accelerator.py +272 -0
- tools/cloudforge/compliance/control_inheritor.py +127 -0
- tools/cloudforge/compliance/evidence_generator.py +129 -0
- tools/cloudforge/compliance/poam_bridge.py +41 -0
- tools/cloudforge/compliance/ssp_bridge.py +52 -0
- tools/cloudforge/compliance/stig_bridge.py +41 -0
- tools/cloudforge/container_forge/__init__.py +2 -0
- tools/cloudforge/container_forge/bigbang_renderer.py +85 -0
- tools/cloudforge/container_forge/hardener.py +169 -0
- tools/cloudforge/container_forge/image_scanner_bridge.py +33 -0
- tools/cloudforge/container_forge/runtime_policy.py +87 -0
- tools/cloudforge/container_forge/sbom_bridge.py +42 -0
- tools/cloudforge/finops/__init__.py +2 -0
- tools/cloudforge/finops/anomaly_detector.py +78 -0
- tools/cloudforge/finops/budget_tracker.py +96 -0
- tools/cloudforge/finops/chargeback.py +69 -0
- tools/cloudforge/finops/cost_collector.py +141 -0
- tools/cloudforge/finops/optimizer.py +55 -0
- tools/cloudforge/hybrid/__init__.py +2 -0
- tools/cloudforge/hybrid/connection_manager.py +141 -0
- tools/cloudforge/hybrid/dns_federator.py +56 -0
- tools/cloudforge/hybrid/health_monitor.py +108 -0
- tools/cloudforge/hybrid/identity_federator.py +53 -0
- tools/cloudforge/hybrid/network_bridge.py +68 -0
- tools/cloudforge/hybrid/topology_manager.py +147 -0
- tools/cloudforge/hybrid/workload_abstractor.py +92 -0
- tools/cloudforge/iac/__init__.py +2 -0
- tools/cloudforge/iac/drift_detector.py +154 -0
- tools/cloudforge/iac/module_library.py +265 -0
- tools/cloudforge/iac/opentofu_adapter.py +89 -0
- tools/cloudforge/iac/pulumi_renderer.py +292 -0
- tools/cloudforge/iac/state_backend.py +146 -0
- tools/cloudforge/iac/terraform_renderer.py +626 -0
- tools/cloudforge/landing_zone/__init__.py +2 -0
- tools/cloudforge/landing_zone/blueprint_loader.py +98 -0
- tools/cloudforge/landing_zone/blueprint_validator.py +113 -0
- tools/cloudforge/landing_zone/zone_provisioner.py +306 -0
- tools/cloudforge/landing_zone/zone_state.py +143 -0
- tools/cloudforge/mbse_thread/__init__.py +2 -0
- tools/cloudforge/mbse_thread/ato_thread_weaver.py +111 -0
- tools/cloudforge/mbse_thread/control_tracer.py +68 -0
- tools/cloudforge/mbse_thread/system_boundary.py +83 -0
- tools/cloudforge/metastore/__init__.py +2 -0
- tools/cloudforge/metastore/dependency_graph.py +202 -0
- tools/cloudforge/metastore/discovery.py +192 -0
- tools/cloudforge/metastore/registry.py +185 -0
- tools/cloudforge/metastore/rto_tracker.py +92 -0
- tools/cloudforge/metastore/runbook_linker.py +82 -0
- tools/cloudforge/migration/__init__.py +2 -0
- tools/cloudforge/migration/assessor.py +187 -0
- tools/cloudforge/migration/cutover_orchestrator.py +117 -0
- tools/cloudforge/migration/databridge_bridge.py +92 -0
- tools/cloudforge/migration/planner.py +98 -0
- tools/cloudforge/migration/risk_scorer.py +97 -0
- tools/cloudforge/migration/validation_runner.py +45 -0
- tools/cloudforge/migration/workload_inventory.py +107 -0
- tools/cloudforge/provider.py +319 -0
- tools/cloudforge/providers/__init__.py +2 -0
- tools/cloudforge/providers/aws_commercial.py +92 -0
- tools/cloudforge/providers/aws_govcloud.py +229 -0
- tools/cloudforge/providers/aws_secret.py +83 -0
- tools/cloudforge/providers/azure_commercial.py +80 -0
- tools/cloudforge/providers/azure_gov.py +91 -0
- tools/cloudforge/providers/azure_secret.py +71 -0
- tools/cloudforge/providers/gcp.py +102 -0
- tools/cloudforge/providers/oci.py +102 -0
- tools/cloudforge/registry.py +140 -0
- tools/cloudforge/runbooks/__init__.py +2 -0
- tools/cloudforge/runbooks/ai_generator.py +119 -0
- tools/cloudforge/runbooks/dag_validator.py +219 -0
- tools/cloudforge/runbooks/engine.py +470 -0
- tools/cloudforge/runbooks/models.py +99 -0
- tools/cloudforge/runbooks/snippet_library.py +158 -0
- tools/cloudforge/runbooks/template_loader.py +122 -0
- tools/cloudforge/runbooks/visualization.py +108 -0
- tools/cloudforge/siem/__init__.py +2 -0
- tools/cloudforge/siem/alert_rules.py +86 -0
- tools/cloudforge/siem/correlation_engine.py +61 -0
- tools/cloudforge/siem/log_aggregator.py +113 -0
- tools/cloudforge/siem/siem_dashboard_data.py +28 -0
- tools/cloudforge/supply_chain/__init__.py +2 -0
- tools/cloudforge/supply_chain/bridge.py +33 -0
- tools/cloudforge/supply_chain/iac_dependency_scanner.py +36 -0
- tools/cloudforge/supply_chain/provider_trust_scorer.py +54 -0
- tools/compat/__init__.py +21 -0
- tools/compat/cli_harmonizer.py +251 -0
- tools/compat/datetime_utils.py +18 -0
- tools/compat/db_utils.py +190 -0
- tools/compat/platform_utils.py +123 -0
- tools/compliance/__init__.py +1 -0
- tools/compliance/accountability_manager.py +391 -0
- tools/compliance/ai_accountability_audit.py +287 -0
- tools/compliance/ai_impact_assessor.py +267 -0
- tools/compliance/ai_incident_response.py +295 -0
- tools/compliance/ai_inventory_manager.py +233 -0
- tools/compliance/ai_reassessment_scheduler.py +250 -0
- tools/compliance/ai_transparency_audit.py +247 -0
- tools/compliance/atlas_assessor.py +276 -0
- tools/compliance/atlas_report_generator.py +1199 -0
- tools/compliance/base_assessor.py +591 -0
- tools/compliance/cato_live_engine.py +607 -0
- tools/compliance/cato_monitor.py +1371 -0
- tools/compliance/cato_scheduler.py +698 -0
- tools/compliance/cjis_assessor.py +76 -0
- tools/compliance/classification_manager.py +1340 -0
- tools/compliance/cmmc_assessor.py +1478 -0
- tools/compliance/cmmc_report_generator.py +1087 -0
- tools/compliance/compliance_detector.py +452 -0
- tools/compliance/compliance_exporter.py +418 -0
- tools/compliance/compliance_status.py +810 -0
- tools/compliance/control_mapper.py +488 -0
- tools/compliance/crosswalk_engine.py +1208 -0
- tools/compliance/cssp_assessor.py +1032 -0
- tools/compliance/cssp_evidence_collector.py +716 -0
- tools/compliance/cssp_report_generator.py +1103 -0
- tools/compliance/cui_marker.py +387 -0
- tools/compliance/diagram_validator.py +599 -0
- tools/compliance/emass/__init__.py +2 -0
- tools/compliance/emass/emass_client.py +822 -0
- tools/compliance/emass/emass_export.py +758 -0
- tools/compliance/emass/emass_sync.py +807 -0
- tools/compliance/eu_ai_act_classifier.py +193 -0
- tools/compliance/evidence_collector.py +459 -0
- tools/compliance/fairness_assessor.py +310 -0
- tools/compliance/fedramp_20x_ksi_emitter.py +692 -0
- tools/compliance/fedramp_assessor.py +1795 -0
- tools/compliance/fedramp_authorization_packager.py +137 -0
- tools/compliance/fedramp_ksi_generator.py +349 -0
- tools/compliance/fedramp_report_generator.py +1115 -0
- tools/compliance/fips199_categorizer.py +869 -0
- tools/compliance/fips200_validator.py +304 -0
- tools/compliance/firmware_sbom.py +646 -0
- tools/compliance/gao_ai_assessor.py +228 -0
- tools/compliance/gao_evidence_builder.py +302 -0
- tools/compliance/hipaa_assessor.py +78 -0
- tools/compliance/hitrust_assessor.py +49 -0
- tools/compliance/incident_response_plan.py +705 -0
- tools/compliance/inheritance_engine.py +693 -0
- tools/compliance/iso27001_assessor.py +92 -0
- tools/compliance/iso42001_assessor.py +114 -0
- tools/compliance/ivv_assessor.py +2314 -0
- tools/compliance/ivv_report_generator.py +1649 -0
- tools/compliance/model_card_generator.py +291 -0
- tools/compliance/mosa_assessor.py +117 -0
- tools/compliance/multi_regime_assessor.py +441 -0
- tools/compliance/narrative_generator.py +1012 -0
- tools/compliance/narrative_quality_gate.py +701 -0
- tools/compliance/narrative_workflow.py +814 -0
- tools/compliance/nist_800_207_assessor.py +191 -0
- tools/compliance/nist_ai_600_1_assessor.py +185 -0
- tools/compliance/nist_ai_rmf_assessor.py +110 -0
- tools/compliance/nist_lookup.py +244 -0
- tools/compliance/omb_m25_21_assessor.py +225 -0
- tools/compliance/omb_m26_04_assessor.py +185 -0
- tools/compliance/oscal_catalog_adapter.py +395 -0
- tools/compliance/oscal_generator.py +2157 -0
- tools/compliance/oscal_tools.py +1182 -0
- tools/compliance/oscal_validator.py +692 -0
- tools/compliance/owasp_agentic_assessor.py +227 -0
- tools/compliance/owasp_asi_assessor.py +197 -0
- tools/compliance/owasp_llm_assessor.py +245 -0
- tools/compliance/pci_dss_assessor.py +80 -0
- tools/compliance/pi_compliance_tracker.py +1447 -0
- tools/compliance/poam_generator.py +388 -0
- tools/compliance/resolve_marking.py +272 -0
- tools/compliance/sbd_assessor.py +2070 -0
- tools/compliance/sbd_report_generator.py +1223 -0
- tools/compliance/sbom_generator.py +993 -0
- tools/compliance/siem_config_generator.py +661 -0
- tools/compliance/slsa_attestation_generator.py +479 -0
- tools/compliance/soc2_assessor.py +77 -0
- tools/compliance/ssp_generator.py +556 -0
- tools/compliance/stig_checker.py +712 -0
- tools/compliance/swft_evidence_bundler.py +326 -0
- tools/compliance/system_card_generator.py +303 -0
- tools/compliance/template_exchange.py +513 -0
- tools/compliance/traceability_matrix.py +1268 -0
- tools/compliance/universal_classification_manager.py +1159 -0
- tools/compliance/xacta/__init__.py +2 -0
- tools/compliance/xacta/xacta_client.py +438 -0
- tools/compliance/xacta/xacta_export.py +546 -0
- tools/compliance/xacta/xacta_sync.py +322 -0
- tools/compliance/xai_assessor.py +231 -0
- tools/core/__init__.py +2 -0
- tools/core/circuit_breaker.py +353 -0
- tools/core/compliance_sidecar.py +344 -0
- tools/core/container.py +110 -0
- tools/core/errors.py +256 -0
- tools/core/feature_flags.py +311 -0
- tools/core/task_dlq.py +350 -0
- tools/dashboard/__init__.py +2 -0
- tools/dashboard/app.py +6288 -0
- tools/dashboard/templates/agent_evolution.html +287 -0
- tools/dashboard/templates/agents/list.html +71 -0
- tools/dashboard/templates/agents.html +132 -0
- tools/dashboard/templates/architecture.html +289 -0
- tools/dashboard/templates/ato_simulator.html +170 -0
- tools/dashboard/templates/audit_engine.html +844 -0
- tools/dashboard/templates/base.html +236 -0
- tools/dashboard/templates/cato_live.html +116 -0
- tools/dashboard/templates/cloudforge.html +195 -0
- tools/dashboard/templates/cloudforge_finops.html +111 -0
- tools/dashboard/templates/cloudforge_hybrid.html +122 -0
- tools/dashboard/templates/cloudforge_metastore.html +234 -0
- tools/dashboard/templates/cloudforge_migration.html +87 -0
- tools/dashboard/templates/cloudforge_runbooks.html +201 -0
- tools/dashboard/templates/cloudforge_siem.html +94 -0
- tools/dashboard/templates/compliance_accel.html +292 -0
- tools/dashboard/templates/crashes.html +122 -0
- tools/dashboard/templates/databridge.html +305 -0
- tools/dashboard/templates/databridge_analytics.html +195 -0
- tools/dashboard/templates/databridge_mapping.html +345 -0
- tools/dashboard/templates/databridge_messaging.html +321 -0
- tools/dashboard/templates/decisions.html +258 -0
- tools/dashboard/templates/devices.html +151 -0
- tools/dashboard/templates/devsecops_maturity.html +278 -0
- tools/dashboard/templates/edge_ai.html +128 -0
- tools/dashboard/templates/firmware.html +120 -0
- tools/dashboard/templates/firmware_sbom.html +193 -0
- tools/dashboard/templates/forge_hub.html +196 -0
- tools/dashboard/templates/forge_studio.html +379 -0
- tools/dashboard/templates/forge_studio_analytics.html +360 -0
- tools/dashboard/templates/forge_studio_builder.html +1637 -0
- tools/dashboard/templates/forge_studio_compliance.html +310 -0
- tools/dashboard/templates/forge_studio_deploy.html +573 -0
- tools/dashboard/templates/forge_studio_enterprise.html +888 -0
- tools/dashboard/templates/forge_studio_marketplace.html +502 -0
- tools/dashboard/templates/forge_studio_workflow.html +696 -0
- tools/dashboard/templates/golden_path.html +175 -0
- tools/dashboard/templates/govcon.html +280 -0
- tools/dashboard/templates/harness.html +148 -0
- tools/dashboard/templates/index.html +207 -0
- tools/dashboard/templates/intelligence.html +336 -0
- tools/dashboard/templates/knowledge/index.html +190 -0
- tools/dashboard/templates/knowledge_graph.html +739 -0
- tools/dashboard/templates/login.html +51 -0
- tools/dashboard/templates/marketplace.html +336 -0
- tools/dashboard/templates/marketplace_admin.html +247 -0
- tools/dashboard/templates/missions.html +403 -0
- tools/dashboard/templates/narratives.html +154 -0
- tools/dashboard/templates/pr_intelligence.html +151 -0
- tools/dashboard/templates/proposals/detail.html +300 -0
- tools/dashboard/templates/proposals/list.html +52 -0
- tools/dashboard/templates/proposals/sam_detail.html +132 -0
- tools/dashboard/templates/proposals/section_detail.html +375 -0
- tools/dashboard/templates/research.html +222 -0
- tools/dashboard/templates/resilience.html +300 -0
- tools/dashboard/templates/scorecard.html +162 -0
- tools/dashboard/templates/simulator.html +131 -0
- tools/dashboard/templates/template_exchange.html +147 -0
- tools/dashboard/templates/thread_heatmap.html +151 -0
- tools/dashboard/templates/threat_model.html +195 -0
- tools/dashboard/templates/vsm.html +141 -0
- tools/dashboard/templates/writeguard.html +277 -0
- tools/databridge/__init__.py +5 -0
- tools/databridge/agent/__init__.py +2 -0
- tools/databridge/agent/daemon.py +227 -0
- tools/databridge/agent/tunnel.py +101 -0
- tools/databridge/agent/ws_relay.py +91 -0
- tools/databridge/analytics.py +167 -0
- tools/databridge/arrow_pipeline.py +327 -0
- tools/databridge/connection_manager.py +424 -0
- tools/databridge/connector.py +331 -0
- tools/databridge/connectors/__init__.py +2 -0
- tools/databridge/connectors/argocd_connector.py +160 -0
- tools/databridge/connectors/avro_connector.py +203 -0
- tools/databridge/connectors/azure_blob.py +63 -0
- tools/databridge/connectors/cdc_connector.py +205 -0
- tools/databridge/connectors/csv_connector.py +172 -0
- tools/databridge/connectors/datadog_connector.py +153 -0
- tools/databridge/connectors/discord_messaging.py +215 -0
- tools/databridge/connectors/dynamics365.py +151 -0
- tools/databridge/connectors/elasticsearch_connector.py +145 -0
- tools/databridge/connectors/email_base.py +114 -0
- tools/databridge/connectors/excel_connector.py +175 -0
- tools/databridge/connectors/fsspec_base.py +300 -0
- tools/databridge/connectors/gcs.py +53 -0
- tools/databridge/connectors/github_connector.py +138 -0
- tools/databridge/connectors/gitlab_connector.py +132 -0
- tools/databridge/connectors/gmail_connector.py +182 -0
- tools/databridge/connectors/hdfs.py +57 -0
- tools/databridge/connectors/health_base.py +401 -0
- tools/databridge/connectors/hubspot.py +124 -0
- tools/databridge/connectors/imap_connector.py +171 -0
- tools/databridge/connectors/jenkins_connector.py +138 -0
- tools/databridge/connectors/jira_connector.py +86 -0
- tools/databridge/connectors/json_connector.py +184 -0
- tools/databridge/connectors/kafka_connector.py +246 -0
- tools/databridge/connectors/kinesis_connector.py +238 -0
- tools/databridge/connectors/local_fs.py +30 -0
- tools/databridge/connectors/matrix.py +197 -0
- tools/databridge/connectors/mattermost_messaging.py +184 -0
- tools/databridge/connectors/messaging_base.py +172 -0
- tools/databridge/connectors/mssql.py +63 -0
- tools/databridge/connectors/mysql.py +57 -0
- tools/databridge/connectors/netsuite.py +170 -0
- tools/databridge/connectors/o365_mail.py +196 -0
- tools/databridge/connectors/oracle.py +65 -0
- tools/databridge/connectors/pagerduty_connector.py +162 -0
- tools/databridge/connectors/parquet_connector.py +131 -0
- tools/databridge/connectors/postgresql.py +58 -0
- tools/databridge/connectors/s3.py +65 -0
- tools/databridge/connectors/saas_base.py +198 -0
- tools/databridge/connectors/salesforce.py +126 -0
- tools/databridge/connectors/sap.py +89 -0
- tools/databridge/connectors/servicenow.py +60 -0
- tools/databridge/connectors/signal_messaging.py +150 -0
- tools/databridge/connectors/slack_messaging.py +203 -0
- tools/databridge/connectors/smtp_connector.py +126 -0
- tools/databridge/connectors/soap_base.py +258 -0
- tools/databridge/connectors/splunk_connector.py +171 -0
- tools/databridge/connectors/sql_base.py +310 -0
- tools/databridge/connectors/sqlite_connector.py +76 -0
- tools/databridge/connectors/teams.py +148 -0
- tools/databridge/connectors/telegram.py +192 -0
- tools/databridge/connectors/whatsapp.py +137 -0
- tools/databridge/data_profiler.py +99 -0
- tools/databridge/forge/__init__.py +6 -0
- tools/databridge/forge/base_selector.py +150 -0
- tools/databridge/forge/code_generator.py +206 -0
- tools/databridge/forge/community_hub.py +539 -0
- tools/databridge/forge/forge_agent.py +306 -0
- tools/databridge/forge/import_handler.py +133 -0
- tools/databridge/forge/integration_tester.py +127 -0
- tools/databridge/forge/marketplace_publisher.py +164 -0
- tools/databridge/forge/promoter.py +159 -0
- tools/databridge/forge/sandbox_manager.py +257 -0
- tools/databridge/forge/spec_parser.py +358 -0
- tools/databridge/forge/static_validator.py +363 -0
- tools/databridge/forge/templates/__init__.py +591 -0
- tools/databridge/format_converter.py +188 -0
- tools/databridge/mapping_engine.py +348 -0
- tools/databridge/messaging/__init__.py +5 -0
- tools/databridge/messaging/agent_bridge.py +254 -0
- tools/databridge/messaging/message_envelope.py +111 -0
- tools/databridge/messaging/message_logger.py +204 -0
- tools/databridge/messaging/messaging_daemon.py +326 -0
- tools/databridge/messaging/oauth2_manager.py +411 -0
- tools/databridge/pii_detector.py +221 -0
- tools/databridge/registry.py +352 -0
- tools/databridge/relay_server.py +105 -0
- tools/databridge/scale/__init__.py +16 -0
- tools/databridge/scale/backpressure.py +134 -0
- tools/databridge/scale/chunked_pipeline.py +169 -0
- tools/databridge/scale/connection_pool.py +293 -0
- tools/databridge/scale/engine.py +492 -0
- tools/databridge/scale/worker_pool.py +140 -0
- tools/databridge/scale/write_batcher.py +250 -0
- tools/databridge/schema_engine.py +324 -0
- tools/databridge/stream_manager.py +225 -0
- tools/databridge/sync_engine.py +411 -0
- tools/databridge/transforms.py +302 -0
- tools/db/__init__.py +1 -0
- tools/db/backup.py +312 -0
- tools/db/backup_manager.py +832 -0
- tools/db/init_icdev_db.py +7753 -0
- tools/db/init_sparkpilot_db.py +431 -0
- tools/db/migrate.py +177 -0
- tools/db/migrate_innovation_audit.py +165 -0
- tools/db/migration_runner.py +548 -0
- tools/db/migrations/001_baseline/meta.json +9 -0
- tools/db/migrations/001_baseline/up.py +67 -0
- tools/db/migrations/002_memory_enhancements/down.sql +8 -0
- tools/db/migrations/002_memory_enhancements/meta.json +9 -0
- tools/db/migrations/002_memory_enhancements/up.py +119 -0
- tools/db/migrations/003_dev_profiles/meta.json +8 -0
- tools/db/migrations/003_dev_profiles/up.py +93 -0
- tools/db/migrations/004_innovation_engine/down.py +19 -0
- tools/db/migrations/004_innovation_engine/up.py +227 -0
- tools/db/migrations/005_phase_37_ai_security/down.py +19 -0
- tools/db/migrations/005_phase_37_ai_security/up.py +257 -0
- tools/db/migrations/006_phase_36_evolution/down.py +21 -0
- tools/db/migrations/006_phase_36_evolution/up.py +323 -0
- tools/db/migrations/007_phase_38_cloud/down.py +14 -0
- tools/db/migrations/007_phase_38_cloud/up.py +110 -0
- tools/db/migrations/008_phase36_37_integration/up.py +55 -0
- tools/db/migrations/__init__.py +2 -0
- tools/db/pg_migrate.py +642 -0
- tools/db/storage.py +1080 -0
- tools/decisions/__init__.py +2 -0
- tools/decisions/dmn_engine.py +695 -0
- tools/devsecops/__init__.py +2 -0
- tools/devsecops/attestation_manager.py +449 -0
- tools/devsecops/network_segmentation_generator.py +604 -0
- tools/devsecops/pdp_config_generator.py +1246 -0
- tools/devsecops/pipeline_security_generator.py +475 -0
- tools/devsecops/policy_generator.py +644 -0
- tools/devsecops/profile_manager.py +374 -0
- tools/devsecops/service_mesh_generator.py +1063 -0
- tools/devsecops/zta_maturity_scorer.py +355 -0
- tools/devsecops/zta_terraform_generator.py +1301 -0
- tools/edge_ai/__init__.py +2 -0
- tools/edge_ai/model_manager.py +200 -0
- tools/embedded/__init__.py +2 -0
- tools/embedded/cmake_generator.py +318 -0
- tools/embedded/crash_analyzer.py +191 -0
- tools/embedded/nl_to_firmware.py +277 -0
- tools/events/__init__.py +1 -0
- tools/events/event_bus.py +199 -0
- tools/finetune/pair_generator.py +832 -0
- tools/fleet/__init__.py +2 -0
- tools/fleet/device_registry.py +148 -0
- tools/fleet/ota_manager.py +153 -0
- tools/forge_studio/__init__.py +13 -0
- tools/forge_studio/analytics/__init__.py +0 -0
- tools/forge_studio/analytics/process_miner.py +383 -0
- tools/forge_studio/audit.py +183 -0
- tools/forge_studio/blueprint/__init__.py +2 -0
- tools/forge_studio/blueprint/build_tracker.py +317 -0
- tools/forge_studio/blueprint/export_engine.py +441 -0
- tools/forge_studio/blueprint/parent_client.py +335 -0
- tools/forge_studio/catalog/__init__.py +2 -0
- tools/forge_studio/catalog/component_registry.py +176 -0
- tools/forge_studio/catalog/schema_validator.py +193 -0
- tools/forge_studio/compliance/__init__.py +1 -0
- tools/forge_studio/compliance/compliance_wiring.py +554 -0
- tools/forge_studio/deploy/__init__.py +1 -0
- tools/forge_studio/deploy/airgap_packager.py +466 -0
- tools/forge_studio/deploy/deploy_engine.py +1792 -0
- tools/forge_studio/deploy/env_manager.py +431 -0
- tools/forge_studio/eject/__init__.py +2 -0
- tools/forge_studio/eject/docker_compose_generator.py +237 -0
- tools/forge_studio/eject/eject_engine.py +230 -0
- tools/forge_studio/eject/expo_scaffolder.py +303 -0
- tools/forge_studio/eject/nextjs_scaffolder.py +338 -0
- tools/forge_studio/enterprise/__init__.py +0 -0
- tools/forge_studio/enterprise/custom_frameworks.py +826 -0
- tools/forge_studio/enterprise/hardening_engine.py +1530 -0
- tools/forge_studio/enterprise/sso_manager.py +718 -0
- tools/forge_studio/enterprise/whitelabel_engine.py +887 -0
- tools/forge_studio/formula/__init__.py +0 -0
- tools/forge_studio/formula/expression_engine.py +562 -0
- tools/forge_studio/formula/formula_registry.py +265 -0
- tools/forge_studio/generator/__init__.py +2 -0
- tools/forge_studio/generator/app_generator.py +584 -0
- tools/forge_studio/generator/complexity_detector.py +368 -0
- tools/forge_studio/generator/prompt_templates.py +104 -0
- tools/forge_studio/generator/spec_builder.py +192 -0
- tools/forge_studio/intake_bridge.py +898 -0
- tools/forge_studio/marketplace/__init__.py +0 -0
- tools/forge_studio/marketplace/component_hub.py +428 -0
- tools/forge_studio/models.py +369 -0
- tools/forge_studio/renderer/__init__.py +2 -0
- tools/forge_studio/renderer/json_render_engine.py +623 -0
- tools/forge_studio/renderer/layout_engine.py +214 -0
- tools/forge_studio/renderer/rn_component_map.py +182 -0
- tools/forge_studio/supabase/__init__.py +2 -0
- tools/forge_studio/supabase/auth_generator.py +283 -0
- tools/forge_studio/supabase/migration_generator.py +93 -0
- tools/forge_studio/supabase/schema_generator.py +281 -0
- tools/forge_studio/tenant_manager.py +387 -0
- tools/forge_studio/workflow/__init__.py +2 -0
- tools/forge_studio/workflow/bpmn_adapter.py +489 -0
- tools/govcon/draft_orchestrator.py +1151 -0
- tools/govcon/engine_enrichment.py +373 -0
- tools/govcon/knowledge_base.py +487 -0
- tools/govcon/knowledge_ingestion.py +510 -0
- tools/govcon/sam_scanner.py +754 -0
- tools/harness/__init__.py +6 -0
- tools/harness/exit_criteria_evaluator.py +231 -0
- tools/harness/maturity_assessor.py +347 -0
- tools/harness/scaffold_harness.py +416 -0
- tools/harness/trace_analyzer.py +281 -0
- tools/infra/__init__.py +1 -0
- tools/infra/ansible_generator.py +867 -0
- tools/infra/dockerfile_generator.py +359 -0
- tools/infra/infra_status.py +384 -0
- tools/infra/ironbank_metadata_generator.py +403 -0
- tools/infra/k8s_generator.py +1000 -0
- tools/infra/pipeline_generator.py +830 -0
- tools/infra/rollback.py +389 -0
- tools/infra/terraform_generator.py +1140 -0
- tools/infra/terraform_generator_azure.py +1252 -0
- tools/infra/terraform_generator_gcp.py +951 -0
- tools/infra/terraform_generator_ibm.py +359 -0
- tools/infra/terraform_generator_oci.py +918 -0
- tools/infra/terraform_generator_onprem.py +318 -0
- tools/knowledge/__init__.py +1 -0
- tools/knowledge/knowledge_ingest.py +281 -0
- tools/knowledge/pattern_detector.py +681 -0
- tools/knowledge/recommendation_engine.py +449 -0
- tools/knowledge/self_heal_analyzer.py +492 -0
- tools/knowledge_graph/__init__.py +2 -0
- tools/knowledge_graph/graph_rag.py +498 -0
- tools/knowledge_graph/ingester.py +406 -0
- tools/knowledge_graph/insight_generator.py +369 -0
- tools/knowledge_graph/text_network.py +832 -0
- tools/llm/__init__.py +72 -0
- tools/llm/anthropic_provider.py +170 -0
- tools/llm/azure_openai_provider.py +338 -0
- tools/llm/bedrock_provider.py +315 -0
- tools/llm/embedding_provider.py +438 -0
- tools/llm/gemini_provider.py +381 -0
- tools/llm/ibm_watsonx_provider.py +231 -0
- tools/llm/oci_genai_provider.py +462 -0
- tools/llm/ollama_provider.py +350 -0
- tools/llm/openai_provider.py +225 -0
- tools/llm/prompt_registry.py +447 -0
- tools/llm/provider.py +355 -0
- tools/llm/provider_sdk.py +175 -0
- tools/llm/router.py +1124 -0
- tools/llm/semantic_cache.py +394 -0
- tools/llm/vertex_ai_provider.py +374 -0
- tools/maintenance/__init__.py +2 -0
- tools/maintenance/dependency_scanner.py +1016 -0
- tools/maintenance/maintenance_auditor.py +804 -0
- tools/maintenance/remediation_engine.py +957 -0
- tools/maintenance/vulnerability_checker.py +978 -0
- tools/manifest.md +1066 -0
- tools/marketplace/asset_installer.py +639 -0
- tools/marketplace/feedback_validator.py +359 -0
- tools/marketplace/license_client.py +458 -0
- tools/marketplace/module_crypto.py +544 -0
- tools/marketplace/module_runtime.py +236 -0
- tools/marketplace/token_store.py +264 -0
- tools/mbse/__init__.py +3 -0
- tools/mbse/des_assessor.py +1173 -0
- tools/mbse/des_report_generator.py +787 -0
- tools/mbse/diagram_extractor.py +792 -0
- tools/mbse/digital_thread.py +1650 -0
- tools/mbse/model_code_generator.py +1115 -0
- tools/mbse/model_control_mapper.py +410 -0
- tools/mbse/pi_model_tracker.py +1079 -0
- tools/mbse/reqif_parser.py +1468 -0
- tools/mbse/sync_engine.py +1789 -0
- tools/mbse/thread_heatmap.py +445 -0
- tools/mbse/xmi_parser.py +1558 -0
- tools/mcp/builder_server.py +64 -0
- tools/mcp/compliance_server.py +64 -0
- tools/mcp/connector_forge_server.py +155 -0
- tools/mcp/core_server.py +64 -0
- tools/mcp/devsecops_server.py +11 -0
- tools/mcp/devsecops_zta_server.py +64 -0
- tools/mcp/knowledge_server.py +64 -0
- tools/mcp/monitor_server.py +64 -0
- tools/mcp/ops_server.py +300 -0
- tools/mcp/requirements_analyst_server.py +64 -0
- tools/mcp/requirements_server.py +11 -0
- tools/mcp/security_server.py +64 -0
- tools/mcp/simulation_server.py +64 -0
- tools/mcp/supply_chain_server.py +64 -0
- tools/mcp/tool_registry.py +299 -0
- tools/memory/__init__.py +2 -0
- tools/memory/auto_capture.py +346 -0
- tools/memory/embed_memory.py +157 -0
- tools/memory/history_compressor.py +334 -0
- tools/memory/hybrid_search.py +235 -0
- tools/memory/maintenance_cron.py +288 -0
- tools/memory/memory_consolidation.py +439 -0
- tools/memory/memory_db.py +132 -0
- tools/memory/memory_read.py +101 -0
- tools/memory/memory_write.py +221 -0
- tools/memory/semantic_search.py +138 -0
- tools/memory/time_decay.py +434 -0
- tools/missions/__init__.py +2 -0
- tools/missions/mission_engine.py +459 -0
- tools/monitor/__init__.py +1 -0
- tools/monitor/alert_correlator.py +486 -0
- tools/monitor/auto_resolver.py +603 -0
- tools/monitor/health_checker.py +507 -0
- tools/monitor/heartbeat_daemon.py +779 -0
- tools/monitor/log_analyzer.py +507 -0
- tools/monitor/metric_collector.py +484 -0
- tools/mosa/__init__.py +10 -0
- tools/mosa/icd_generator.py +358 -0
- tools/mosa/modular_design_analyzer.py +682 -0
- tools/mosa/mosa_code_enforcer.py +348 -0
- tools/mosa/tsp_generator.py +265 -0
- tools/observability/__init__.py +100 -0
- tools/observability/genai_attributes.py +88 -0
- tools/observability/instrumentation.py +140 -0
- tools/observability/mlflow_exporter.py +193 -0
- tools/observability/otel_tracer.py +168 -0
- tools/observability/provenance/__init__.py +3 -0
- tools/observability/provenance/prov_recorder.py +322 -0
- tools/observability/shap/__init__.py +3 -0
- tools/observability/shap/agent_shap.py +274 -0
- tools/observability/sqlite_tracer.py +360 -0
- tools/observability/trace_context.py +205 -0
- tools/observability/tracer.py +230 -0
- tools/orchestration/__init__.py +1 -0
- tools/orchestration/peer_channels.py +254 -0
- tools/orchestration/saga_coordinator.py +390 -0
- tools/project/__init__.py +1 -0
- tools/project/manifest_loader.py +418 -0
- tools/project/project_create.py +350 -0
- tools/project/project_list.py +171 -0
- tools/project/project_scaffold.py +1715 -0
- tools/project/project_status.py +478 -0
- tools/project/session_context_builder.py +752 -0
- tools/project/validate_manifest.py +54 -0
- tools/rag/corrective_rag.py +582 -0
- tools/rag/source_registry.py +482 -0
- tools/requirements/__init__.py +1 -0
- tools/requirements/ai_governance_scorer.py +207 -0
- tools/requirements/boundary_analyzer.py +1281 -0
- tools/requirements/clarification_engine.py +605 -0
- tools/requirements/complexity_scorer.py +369 -0
- tools/requirements/consistency_analyzer.py +789 -0
- tools/requirements/constitution_manager.py +592 -0
- tools/requirements/decomposition_engine.py +764 -0
- tools/requirements/document_extractor.py +1002 -0
- tools/requirements/elicitation_techniques.py +508 -0
- tools/requirements/gap_detector.py +260 -0
- tools/requirements/intake_engine.py +2175 -0
- tools/requirements/prd_generator.py +839 -0
- tools/requirements/prd_validator.py +584 -0
- tools/requirements/readiness_scorer.py +302 -0
- tools/requirements/spec_organizer.py +1015 -0
- tools/requirements/spec_quality_checker.py +1083 -0
- tools/requirements/traceability_builder.py +566 -0
- tools/research/__init__.py +3 -0
- tools/research/academic_scanner.py +130 -0
- tools/research/build_buy_analyzer.py +229 -0
- tools/research/challenge_scorer.py +280 -0
- tools/research/community_scanner.py +174 -0
- tools/research/cross_engine_bridge.py +124 -0
- tools/research/dossier_generator.py +305 -0
- tools/research/landscape_scanner.py +315 -0
- tools/research/regulatory_scanner.py +248 -0
- tools/research/research_manager.py +469 -0
- tools/research/source_scanner.py +150 -0
- tools/research/vertical_loader.py +118 -0
- tools/saas/__init__.py +0 -0
- tools/saas/licensing/__init__.py +0 -0
- tools/saas/licensing/license_validator.py +345 -0
- tools/scaffold/__init__.py +2 -0
- tools/scaffold/golden_path.py +504 -0
- tools/security/__init__.py +1 -0
- tools/security/agent_output_validator.py +330 -0
- tools/security/agent_trust_scorer.py +652 -0
- tools/security/ai_bom_generator.py +718 -0
- tools/security/ai_telemetry_logger.py +469 -0
- tools/security/atlas_red_team.py +541 -0
- tools/security/code_pattern_scanner.py +382 -0
- tools/security/confabulation_detector.py +265 -0
- tools/security/container_scanner.py +489 -0
- tools/security/dependency_auditor.py +942 -0
- tools/security/endpoint_security_scanner.py +626 -0
- tools/security/mcp_tool_authorizer.py +242 -0
- tools/security/output_verifier.py +427 -0
- tools/security/prompt_injection_detector.py +737 -0
- tools/security/sast_runner.py +946 -0
- tools/security/secret_detector.py +376 -0
- tools/security/threat_modeler.py +678 -0
- tools/security/tool_chain_validator.py +357 -0
- tools/security/vuln_scanner.py +536 -0
- tools/simulation/__init__.py +2 -0
- tools/simulation/ato_simulator.py +517 -0
- tools/simulation/coa_generator.py +1539 -0
- tools/simulation/monte_carlo.py +745 -0
- tools/simulation/scenario_manager.py +1060 -0
- tools/simulation/simulation_engine.py +1091 -0
- tools/simulator/__init__.py +2 -0
- tools/simulator/sim_runner.py +272 -0
- tools/supply_chain/__init__.py +2 -0
- tools/supply_chain/cve_triager.py +690 -0
- tools/supply_chain/dependency_graph.py +630 -0
- tools/supply_chain/isa_manager.py +526 -0
- tools/supply_chain/scrm_assessor.py +531 -0
- tools/supply_chain/slsa_verifier.py +473 -0
- tools/testing/__init__.py +2 -0
- tools/testing/acceptance_validator.py +411 -0
- tools/testing/api_surface_extractor.py +749 -0
- tools/testing/claude_dir_validator.py +831 -0
- tools/testing/data_types.py +199 -0
- tools/testing/e2e_runner.py +715 -0
- tools/testing/fuzz_cli.py +306 -0
- tools/testing/health_check.py +483 -0
- tools/testing/platform_check.py +143 -0
- tools/testing/production_audit.py +1836 -0
- tools/testing/production_remediate.py +803 -0
- tools/testing/screenshot_validator.py +538 -0
- tools/testing/smoke_test.py +283 -0
- tools/testing/test_agent_models.py +117 -0
- tools/testing/test_orchestrator.py +957 -0
- tools/testing/utils.py +229 -0
- tools/writeguard/__init__.py +1 -0
- tools/writeguard/main.py +1 -0
- tools/writing/__init__.py +7 -0
- tools/writing/ai_content_detector.py +316 -0
- tools/writing/analysis_engine.py +454 -0
- tools/writing/batch_analyzer.py +276 -0
- tools/writing/coherence_analyzer.py +221 -0
- tools/writing/govcon_bridge.py +509 -0
- tools/writing/grammar_checker.py +270 -0
- tools/writing/plagiarism_detector.py +106 -0
- tools/writing/readability_scorer.py +201 -0
- tools/writing/rewriter.py +96 -0
- tools/writing/signal_registrar.py +167 -0
- tools/writing/snippet_manager.py +276 -0
- tools/writing/style_enforcer.py +220 -0
- tools/writing/style_guide_manager.py +438 -0
- tools/writing/tone_profiler.py +168 -0
tools/db/storage.py
ADDED
|
@@ -0,0 +1,1080 @@
|
|
|
1
|
+
# CUI // SP-CTI
|
|
2
|
+
"""Storage Abstraction Layer — database-agnostic connection factory (D-DB-20).
|
|
3
|
+
|
|
4
|
+
Provides a unified interface over SQLite and PostgreSQL so that all 255+ tool
|
|
5
|
+
files can use the same API regardless of which backend is configured.
|
|
6
|
+
|
|
7
|
+
Design principles:
|
|
8
|
+
- Drop-in compatible: returned connections expose the same API as sqlite3
|
|
9
|
+
(.execute, .fetchall, .fetchone, .commit, .close, .row_factory-like behavior)
|
|
10
|
+
- Placeholder translation: SQL using ``?`` (sqlite3 style) is auto-translated
|
|
11
|
+
to ``%s`` (psycopg2 style) when PostgreSQL is the backend
|
|
12
|
+
- Context manager: ``with get_connection() as conn:`` for auto-cleanup
|
|
13
|
+
- Config-driven: env vars + args/storage_config.yaml, defaults to SQLite
|
|
14
|
+
- Zero new deps for SQLite path; psycopg2 only required for PostgreSQL
|
|
15
|
+
|
|
16
|
+
Usage (drop-in replacement for sqlite3.connect):
|
|
17
|
+
from tools.db.storage import get_connection
|
|
18
|
+
|
|
19
|
+
with get_connection() as conn:
|
|
20
|
+
rows = conn.execute("SELECT * FROM projects WHERE id = ?", (pid,)).fetchall()
|
|
21
|
+
for row in rows:
|
|
22
|
+
print(row["name"]) # dict-like access
|
|
23
|
+
|
|
24
|
+
# Or without context manager (legacy compatibility):
|
|
25
|
+
conn = get_connection()
|
|
26
|
+
try:
|
|
27
|
+
conn.execute("INSERT INTO t (a) VALUES (?)", (1,))
|
|
28
|
+
conn.commit()
|
|
29
|
+
finally:
|
|
30
|
+
conn.close()
|
|
31
|
+
|
|
32
|
+
Configuration (args/storage_config.yaml or env vars):
|
|
33
|
+
ICDEV_STORAGE_BACKEND=postgresql # or sqlite (default)
|
|
34
|
+
ICDEV_PG_HOST=localhost
|
|
35
|
+
ICDEV_PG_PORT=5432
|
|
36
|
+
ICDEV_PG_DATABASE=icdev
|
|
37
|
+
ICDEV_PG_USERNAME=icdev
|
|
38
|
+
ICDEV_PG_PASSWORD=secret # or use ICDEV_PG_SECRET_REF=env:PG_PASS
|
|
39
|
+
"""
|
|
40
|
+
|
|
41
|
+
import logging
|
|
42
|
+
import os
|
|
43
|
+
import re
|
|
44
|
+
import sqlite3
|
|
45
|
+
import threading
|
|
46
|
+
from contextlib import contextmanager
|
|
47
|
+
from enum import Enum
|
|
48
|
+
from pathlib import Path
|
|
49
|
+
from typing import Any, Dict, Iterator, List, Optional, Sequence, Tuple, Union
|
|
50
|
+
|
|
51
|
+
logger = logging.getLogger("icdev.db.storage")
|
|
52
|
+
|
|
53
|
+
_PROJECT_ROOT = Path(__file__).resolve().parent.parent.parent
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
# ---------------------------------------------------------------------------
|
|
57
|
+
# Backend enum
|
|
58
|
+
# ---------------------------------------------------------------------------
|
|
59
|
+
|
|
60
|
+
class StorageBackend(Enum):
|
|
61
|
+
"""Supported database backends."""
|
|
62
|
+
SQLITE = "sqlite"
|
|
63
|
+
POSTGRESQL = "postgresql"
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
# ---------------------------------------------------------------------------
|
|
67
|
+
# Configuration
|
|
68
|
+
# ---------------------------------------------------------------------------
|
|
69
|
+
|
|
70
|
+
class StorageConfig:
|
|
71
|
+
"""Resolved storage configuration from env vars and/or YAML.
|
|
72
|
+
|
|
73
|
+
Resolution order (highest priority first):
|
|
74
|
+
1. Explicit keyword arguments
|
|
75
|
+
2. Environment variables (ICDEV_STORAGE_BACKEND, ICDEV_PG_*, ICDEV_DB_PATH)
|
|
76
|
+
3. args/storage_config.yaml
|
|
77
|
+
4. Defaults (SQLite at data/icdev.db)
|
|
78
|
+
"""
|
|
79
|
+
|
|
80
|
+
def __init__(
|
|
81
|
+
self,
|
|
82
|
+
backend: Optional[str] = None,
|
|
83
|
+
db_path: Optional[Union[str, Path]] = None,
|
|
84
|
+
pg_host: Optional[str] = None,
|
|
85
|
+
pg_port: Optional[int] = None,
|
|
86
|
+
pg_database: Optional[str] = None,
|
|
87
|
+
pg_username: Optional[str] = None,
|
|
88
|
+
pg_password: Optional[str] = None,
|
|
89
|
+
pg_connection_string: Optional[str] = None,
|
|
90
|
+
pg_pool_size: int = 20,
|
|
91
|
+
):
|
|
92
|
+
# Load YAML config as base (lowest priority)
|
|
93
|
+
yaml_cfg = self._load_yaml_config()
|
|
94
|
+
|
|
95
|
+
# Backend resolution
|
|
96
|
+
raw_backend = (
|
|
97
|
+
backend
|
|
98
|
+
or os.environ.get("ICDEV_STORAGE_BACKEND")
|
|
99
|
+
or yaml_cfg.get("backend", "sqlite")
|
|
100
|
+
)
|
|
101
|
+
self.backend = StorageBackend(raw_backend.lower().strip())
|
|
102
|
+
|
|
103
|
+
# SQLite config
|
|
104
|
+
self.db_path = Path(
|
|
105
|
+
db_path
|
|
106
|
+
or os.environ.get("ICDEV_DB_PATH")
|
|
107
|
+
or yaml_cfg.get("sqlite", {}).get("path")
|
|
108
|
+
or str(_PROJECT_ROOT / "data" / "icdev.db")
|
|
109
|
+
)
|
|
110
|
+
|
|
111
|
+
# PostgreSQL config
|
|
112
|
+
pg_yaml = yaml_cfg.get("postgresql", {})
|
|
113
|
+
self.pg_host = (
|
|
114
|
+
pg_host
|
|
115
|
+
or os.environ.get("ICDEV_PG_HOST")
|
|
116
|
+
or pg_yaml.get("host", "localhost")
|
|
117
|
+
)
|
|
118
|
+
self.pg_port = int(
|
|
119
|
+
pg_port
|
|
120
|
+
or os.environ.get("ICDEV_PG_PORT")
|
|
121
|
+
or pg_yaml.get("port", 5432)
|
|
122
|
+
)
|
|
123
|
+
self.pg_database = (
|
|
124
|
+
pg_database
|
|
125
|
+
or os.environ.get("ICDEV_PG_DATABASE")
|
|
126
|
+
or pg_yaml.get("database", "icdev")
|
|
127
|
+
)
|
|
128
|
+
self.pg_username = (
|
|
129
|
+
pg_username
|
|
130
|
+
or os.environ.get("ICDEV_PG_USERNAME")
|
|
131
|
+
or pg_yaml.get("username", "icdev")
|
|
132
|
+
)
|
|
133
|
+
self.pg_password = self._resolve_password(
|
|
134
|
+
pg_password, pg_yaml
|
|
135
|
+
)
|
|
136
|
+
self.pg_connection_string = (
|
|
137
|
+
pg_connection_string
|
|
138
|
+
or os.environ.get("ICDEV_PG_CONNECTION_STRING")
|
|
139
|
+
or pg_yaml.get("connection_string")
|
|
140
|
+
)
|
|
141
|
+
self.pg_pool_size = int(
|
|
142
|
+
os.environ.get("ICDEV_PG_POOL_SIZE")
|
|
143
|
+
or pg_yaml.get("pool_size", pg_pool_size)
|
|
144
|
+
)
|
|
145
|
+
|
|
146
|
+
@staticmethod
|
|
147
|
+
def _resolve_password(explicit: Optional[str], yaml_cfg: Dict) -> str:
|
|
148
|
+
"""Resolve PostgreSQL password from explicit, env var, or YAML."""
|
|
149
|
+
if explicit:
|
|
150
|
+
return explicit
|
|
151
|
+
|
|
152
|
+
env_pw = os.environ.get("ICDEV_PG_PASSWORD")
|
|
153
|
+
if env_pw:
|
|
154
|
+
return env_pw
|
|
155
|
+
|
|
156
|
+
# Support secret reference pattern (D-DB-13)
|
|
157
|
+
secret_ref = (
|
|
158
|
+
os.environ.get("ICDEV_PG_SECRET_REF")
|
|
159
|
+
or yaml_cfg.get("secret_ref", "")
|
|
160
|
+
)
|
|
161
|
+
if secret_ref.startswith("env:"):
|
|
162
|
+
var_name = secret_ref[4:]
|
|
163
|
+
env_val = os.environ.get(var_name)
|
|
164
|
+
if env_val:
|
|
165
|
+
return env_val
|
|
166
|
+
# Fall through to YAML password if env var not set
|
|
167
|
+
|
|
168
|
+
return yaml_cfg.get("password", "")
|
|
169
|
+
|
|
170
|
+
@staticmethod
|
|
171
|
+
def _load_yaml_config() -> Dict[str, Any]:
|
|
172
|
+
"""Load args/storage_config.yaml if it exists."""
|
|
173
|
+
config_path = _PROJECT_ROOT / "args" / "storage_config.yaml"
|
|
174
|
+
if not config_path.exists():
|
|
175
|
+
return {}
|
|
176
|
+
try:
|
|
177
|
+
import yaml
|
|
178
|
+
with open(config_path, "r") as f:
|
|
179
|
+
return yaml.safe_load(f) or {}
|
|
180
|
+
except ImportError:
|
|
181
|
+
# Fall back to basic parsing if PyYAML not available
|
|
182
|
+
return {}
|
|
183
|
+
except Exception as exc:
|
|
184
|
+
logger.warning("Failed to load storage_config.yaml: %s", exc)
|
|
185
|
+
return {}
|
|
186
|
+
|
|
187
|
+
def pg_dsn(self) -> str:
|
|
188
|
+
"""Build PostgreSQL DSN string."""
|
|
189
|
+
if self.pg_connection_string:
|
|
190
|
+
return self.pg_connection_string
|
|
191
|
+
pw_part = f":{self.pg_password}" if self.pg_password else ""
|
|
192
|
+
return (
|
|
193
|
+
f"host={self.pg_host} port={self.pg_port} "
|
|
194
|
+
f"dbname={self.pg_database} user={self.pg_username} "
|
|
195
|
+
f"password={self.pg_password}"
|
|
196
|
+
)
|
|
197
|
+
|
|
198
|
+
|
|
199
|
+
# ---------------------------------------------------------------------------
|
|
200
|
+
# Placeholder translation
|
|
201
|
+
# ---------------------------------------------------------------------------
|
|
202
|
+
|
|
203
|
+
# Regex to find ? placeholders that are NOT inside single-quoted strings.
|
|
204
|
+
# This handles the common case; edge cases (escaped quotes, $$ strings)
|
|
205
|
+
# are rare in ICDEV's tool SQL.
|
|
206
|
+
_QMARK_RE = re.compile(
|
|
207
|
+
r"""
|
|
208
|
+
'(?:[^'\\]|\\.)*' # single-quoted string — skip
|
|
209
|
+
|
|
|
210
|
+
(\?) # ? placeholder — capture group 1
|
|
211
|
+
""",
|
|
212
|
+
re.VERBOSE | re.DOTALL,
|
|
213
|
+
)
|
|
214
|
+
|
|
215
|
+
|
|
216
|
+
def _translate_placeholders(sql: str) -> str:
|
|
217
|
+
"""Convert sqlite3-style ``?`` placeholders to psycopg2-style ``%s``.
|
|
218
|
+
|
|
219
|
+
Leaves ``?`` inside string literals untouched.
|
|
220
|
+
Also escapes literal ``%`` inside single-quoted strings so psycopg2
|
|
221
|
+
doesn't misinterpret them as format specifiers (e.g. LIKE '%foo%').
|
|
222
|
+
"""
|
|
223
|
+
def _replace(m: re.Match) -> str:
|
|
224
|
+
if m.group(1):
|
|
225
|
+
return "%s"
|
|
226
|
+
# It's a quoted string — escape any % inside it for psycopg2
|
|
227
|
+
text = m.group(0)
|
|
228
|
+
if "%" in text:
|
|
229
|
+
# Double the % to escape for psycopg2
|
|
230
|
+
return text.replace("%", "%%")
|
|
231
|
+
return text
|
|
232
|
+
|
|
233
|
+
return _QMARK_RE.sub(_replace, sql)
|
|
234
|
+
|
|
235
|
+
|
|
236
|
+
# Regex for INSERT OR REPLACE/IGNORE translation
|
|
237
|
+
_INSERT_OR_REPLACE_RE = re.compile(
|
|
238
|
+
r"\bINSERT\s+OR\s+REPLACE\b", re.IGNORECASE
|
|
239
|
+
)
|
|
240
|
+
_INSERT_OR_IGNORE_RE = re.compile(
|
|
241
|
+
r"\bINSERT\s+OR\s+IGNORE\b", re.IGNORECASE
|
|
242
|
+
)
|
|
243
|
+
|
|
244
|
+
# Cache for unique constraint lookups
|
|
245
|
+
_UNIQUE_CONSTRAINT_CACHE: Dict[str, List[str]] = {}
|
|
246
|
+
|
|
247
|
+
|
|
248
|
+
def _get_unique_constraint_cols(table_name: str, insert_cols: List[str]) -> Optional[List[str]]:
|
|
249
|
+
"""Find unique constraint columns for a table that overlap with insert columns.
|
|
250
|
+
|
|
251
|
+
Uses a module-level cache to avoid repeated PG metadata queries.
|
|
252
|
+
Returns the best matching unique constraint columns, or None.
|
|
253
|
+
"""
|
|
254
|
+
cache_key = table_name.lower()
|
|
255
|
+
if cache_key in _UNIQUE_CONSTRAINT_CACHE:
|
|
256
|
+
cached = _UNIQUE_CONSTRAINT_CACHE[cache_key]
|
|
257
|
+
return cached if cached else None
|
|
258
|
+
|
|
259
|
+
try:
|
|
260
|
+
import psycopg2
|
|
261
|
+
cfg = StorageConfig()
|
|
262
|
+
if cfg.backend != StorageBackend.POSTGRESQL:
|
|
263
|
+
_UNIQUE_CONSTRAINT_CACHE[cache_key] = []
|
|
264
|
+
return None
|
|
265
|
+
conn = psycopg2.connect(cfg.pg_dsn())
|
|
266
|
+
cur = conn.cursor()
|
|
267
|
+
# Find UNIQUE constraints (not PK) on this table
|
|
268
|
+
cur.execute("""
|
|
269
|
+
SELECT kcu.column_name
|
|
270
|
+
FROM information_schema.table_constraints tc
|
|
271
|
+
JOIN information_schema.key_column_usage kcu
|
|
272
|
+
ON tc.constraint_name = kcu.constraint_name
|
|
273
|
+
AND tc.table_schema = kcu.table_schema
|
|
274
|
+
WHERE tc.table_name = %s
|
|
275
|
+
AND tc.constraint_type = 'UNIQUE'
|
|
276
|
+
ORDER BY kcu.ordinal_position
|
|
277
|
+
""", (table_name,))
|
|
278
|
+
unique_cols = [row[0] for row in cur.fetchall()]
|
|
279
|
+
cur.close()
|
|
280
|
+
conn.close()
|
|
281
|
+
|
|
282
|
+
insert_set = {c.lower() for c in insert_cols}
|
|
283
|
+
matching = [c for c in unique_cols if c.lower() in insert_set]
|
|
284
|
+
_UNIQUE_CONSTRAINT_CACHE[cache_key] = matching
|
|
285
|
+
return matching if matching else None
|
|
286
|
+
except Exception:
|
|
287
|
+
_UNIQUE_CONSTRAINT_CACHE[cache_key] = []
|
|
288
|
+
return None
|
|
289
|
+
|
|
290
|
+
|
|
291
|
+
def _translate_sqlite_sql(sql: str) -> str:
|
|
292
|
+
"""Translate SQLite-specific SQL syntax to PostgreSQL.
|
|
293
|
+
|
|
294
|
+
Handles:
|
|
295
|
+
- ? -> %s placeholders
|
|
296
|
+
- INSERT OR REPLACE -> INSERT ... ON CONFLICT DO UPDATE
|
|
297
|
+
- INSERT OR IGNORE -> INSERT ... ON CONFLICT DO NOTHING
|
|
298
|
+
- datetime('now') -> NOW()
|
|
299
|
+
"""
|
|
300
|
+
sql = _translate_placeholders(sql)
|
|
301
|
+
|
|
302
|
+
# INSERT OR IGNORE -> INSERT ... ON CONFLICT DO NOTHING
|
|
303
|
+
if _INSERT_OR_IGNORE_RE.search(sql):
|
|
304
|
+
sql = _INSERT_OR_IGNORE_RE.sub("INSERT", sql)
|
|
305
|
+
# Add ON CONFLICT DO NOTHING before any RETURNING clause or at end
|
|
306
|
+
if "RETURNING" in sql.upper():
|
|
307
|
+
sql = re.sub(
|
|
308
|
+
r"(\s+RETURNING)", r" ON CONFLICT DO NOTHING\1",
|
|
309
|
+
sql, count=1, flags=re.IGNORECASE,
|
|
310
|
+
)
|
|
311
|
+
else:
|
|
312
|
+
sql = sql.rstrip().rstrip(";") + " ON CONFLICT DO NOTHING"
|
|
313
|
+
|
|
314
|
+
# INSERT OR REPLACE -> INSERT ... ON CONFLICT DO UPDATE
|
|
315
|
+
elif _INSERT_OR_REPLACE_RE.search(sql):
|
|
316
|
+
sql = _INSERT_OR_REPLACE_RE.sub("INSERT", sql)
|
|
317
|
+
# Extract table name and column names from INSERT INTO table (col1, col2, ...)
|
|
318
|
+
table_match = re.search(
|
|
319
|
+
r"INSERT\s+INTO\s+(\w+)\s*\(([^)]+)\)", sql, re.IGNORECASE
|
|
320
|
+
)
|
|
321
|
+
if table_match:
|
|
322
|
+
table_name = table_match.group(1)
|
|
323
|
+
cols = [c.strip() for c in table_match.group(2).split(",")]
|
|
324
|
+
# Determine conflict target:
|
|
325
|
+
# If first column is 'id', use it as PK.
|
|
326
|
+
# Otherwise, look for unique constraint columns among the inserted cols.
|
|
327
|
+
conflict_cols = None
|
|
328
|
+
if cols[0].lower() == "id":
|
|
329
|
+
conflict_cols = [cols[0]]
|
|
330
|
+
else:
|
|
331
|
+
# Try to find unique constraint columns from the connection cache
|
|
332
|
+
conflict_cols = _get_unique_constraint_cols(table_name, cols)
|
|
333
|
+
if not conflict_cols:
|
|
334
|
+
# Fallback: use first column
|
|
335
|
+
conflict_cols = [cols[0]]
|
|
336
|
+
|
|
337
|
+
conflict_str = ", ".join(conflict_cols)
|
|
338
|
+
update_cols = [c for c in cols if c.lower() not in
|
|
339
|
+
{cc.lower() for cc in conflict_cols}]
|
|
340
|
+
if update_cols:
|
|
341
|
+
set_clause = ", ".join(
|
|
342
|
+
f"{c} = EXCLUDED.{c}" for c in update_cols
|
|
343
|
+
)
|
|
344
|
+
conflict = f" ON CONFLICT ({conflict_str}) DO UPDATE SET {set_clause}"
|
|
345
|
+
else:
|
|
346
|
+
conflict = f" ON CONFLICT ({conflict_str}) DO NOTHING"
|
|
347
|
+
sql = sql.rstrip().rstrip(";") + conflict
|
|
348
|
+
|
|
349
|
+
# datetime('now', '-N days/hours/...') -> NOW() - INTERVAL 'N days/hours/...'
|
|
350
|
+
def _datetime_replace(m):
|
|
351
|
+
modifiers = re.findall(r"'([^']*)'", m.group(0))
|
|
352
|
+
# First arg is 'now'; remaining are modifiers like '-30 days'
|
|
353
|
+
result = "NOW()"
|
|
354
|
+
for mod in modifiers[1:]:
|
|
355
|
+
mod = mod.strip()
|
|
356
|
+
if mod.startswith("-"):
|
|
357
|
+
result += f" - INTERVAL '{mod[1:]}'"
|
|
358
|
+
elif mod.startswith("+"):
|
|
359
|
+
result += f" + INTERVAL '{mod[1:]}'"
|
|
360
|
+
else:
|
|
361
|
+
result += f" + INTERVAL '{mod}'"
|
|
362
|
+
return result
|
|
363
|
+
|
|
364
|
+
sql = re.sub(
|
|
365
|
+
r"datetime\s*\(\s*'now'\s*(?:,\s*'[^']*'\s*)*\)",
|
|
366
|
+
_datetime_replace,
|
|
367
|
+
sql,
|
|
368
|
+
flags=re.IGNORECASE,
|
|
369
|
+
)
|
|
370
|
+
|
|
371
|
+
# AUTOINCREMENT -> removed (PostgreSQL SERIAL handles this natively)
|
|
372
|
+
sql = re.sub(r"\s+AUTOINCREMENT\b", "", sql, flags=re.IGNORECASE)
|
|
373
|
+
|
|
374
|
+
# INTEGER PRIMARY KEY -> SERIAL PRIMARY KEY (for CREATE TABLE)
|
|
375
|
+
if re.search(r"\bCREATE\s+TABLE\b", sql, re.IGNORECASE):
|
|
376
|
+
sql = re.sub(
|
|
377
|
+
r"\bINTEGER\s+PRIMARY\s+KEY\b",
|
|
378
|
+
"SERIAL PRIMARY KEY",
|
|
379
|
+
sql,
|
|
380
|
+
count=1,
|
|
381
|
+
flags=re.IGNORECASE,
|
|
382
|
+
)
|
|
383
|
+
|
|
384
|
+
# ROUND(double_precision, int) needs cast in PostgreSQL
|
|
385
|
+
sql = re.sub(
|
|
386
|
+
r"\bROUND\(([^,)]+),\s*(\d+)\)",
|
|
387
|
+
r"ROUND((\1)::numeric, \2)",
|
|
388
|
+
sql,
|
|
389
|
+
flags=re.IGNORECASE,
|
|
390
|
+
)
|
|
391
|
+
|
|
392
|
+
# GROUP_CONCAT(x) -> STRING_AGG(x, ',')
|
|
393
|
+
# GROUP_CONCAT(DISTINCT x) -> STRING_AGG(DISTINCT x, ',')
|
|
394
|
+
sql = re.sub(
|
|
395
|
+
r"\bGROUP_CONCAT\(\s*DISTINCT\s+(\w+)\s*\)",
|
|
396
|
+
r"STRING_AGG(DISTINCT \1::text, ',')",
|
|
397
|
+
sql,
|
|
398
|
+
flags=re.IGNORECASE,
|
|
399
|
+
)
|
|
400
|
+
sql = re.sub(
|
|
401
|
+
r"\bGROUP_CONCAT\(([^)]+)\)",
|
|
402
|
+
r"STRING_AGG(\1::text, ',')",
|
|
403
|
+
sql,
|
|
404
|
+
flags=re.IGNORECASE,
|
|
405
|
+
)
|
|
406
|
+
|
|
407
|
+
return sql
|
|
408
|
+
|
|
409
|
+
|
|
410
|
+
# ---------------------------------------------------------------------------
|
|
411
|
+
# Row wrapper — supports both integer index and key access
|
|
412
|
+
# ---------------------------------------------------------------------------
|
|
413
|
+
|
|
414
|
+
class _DualAccessRow:
|
|
415
|
+
"""Wrap a PostgreSQL RealDictRow to support both dict-like and index access.
|
|
416
|
+
|
|
417
|
+
SQLite's Row supports row[0] and row["col"]. PostgreSQL's RealDictRow
|
|
418
|
+
only supports row["col"]. This wrapper adds row[0] support for backward
|
|
419
|
+
compatibility with code that uses integer indexing.
|
|
420
|
+
"""
|
|
421
|
+
__slots__ = ("_data", "_keys")
|
|
422
|
+
|
|
423
|
+
def __init__(self, row_dict):
|
|
424
|
+
self._data = row_dict
|
|
425
|
+
self._keys = list(row_dict.keys())
|
|
426
|
+
|
|
427
|
+
def __getitem__(self, key):
|
|
428
|
+
if isinstance(key, int):
|
|
429
|
+
return self._data[self._keys[key]]
|
|
430
|
+
return self._data[key]
|
|
431
|
+
|
|
432
|
+
def __contains__(self, key):
|
|
433
|
+
return key in self._data
|
|
434
|
+
|
|
435
|
+
def __len__(self):
|
|
436
|
+
return len(self._data)
|
|
437
|
+
|
|
438
|
+
def __iter__(self):
|
|
439
|
+
return iter(self._data.values())
|
|
440
|
+
|
|
441
|
+
def __repr__(self):
|
|
442
|
+
return repr(self._data)
|
|
443
|
+
|
|
444
|
+
def keys(self):
|
|
445
|
+
return self._data.keys()
|
|
446
|
+
|
|
447
|
+
def values(self):
|
|
448
|
+
return self._data.values()
|
|
449
|
+
|
|
450
|
+
def items(self):
|
|
451
|
+
return self._data.items()
|
|
452
|
+
|
|
453
|
+
def get(self, key, default=None):
|
|
454
|
+
return self._data.get(key, default)
|
|
455
|
+
|
|
456
|
+
|
|
457
|
+
def _wrap_row(row, backend):
|
|
458
|
+
"""Wrap a row for dual access if PostgreSQL backend."""
|
|
459
|
+
if row is None or backend == StorageBackend.SQLITE:
|
|
460
|
+
return row
|
|
461
|
+
return _DualAccessRow(row)
|
|
462
|
+
|
|
463
|
+
|
|
464
|
+
def _wrap_rows(rows, backend):
|
|
465
|
+
"""Wrap multiple rows for dual access if PostgreSQL backend."""
|
|
466
|
+
if backend == StorageBackend.SQLITE:
|
|
467
|
+
return rows
|
|
468
|
+
return [_DualAccessRow(r) for r in rows]
|
|
469
|
+
|
|
470
|
+
|
|
471
|
+
# ---------------------------------------------------------------------------
|
|
472
|
+
# Cursor wrapper
|
|
473
|
+
# ---------------------------------------------------------------------------
|
|
474
|
+
|
|
475
|
+
class _CursorWrapper:
|
|
476
|
+
"""Wraps a database cursor to provide a uniform API.
|
|
477
|
+
|
|
478
|
+
For SQLite, this wraps sqlite3.Cursor directly.
|
|
479
|
+
For PostgreSQL, this wraps psycopg2 cursor with dict-like Row access
|
|
480
|
+
and sqlite3-compatible execute (with ? placeholder translation).
|
|
481
|
+
"""
|
|
482
|
+
|
|
483
|
+
def __init__(self, cursor: Any, backend: StorageBackend):
|
|
484
|
+
self._cursor = cursor
|
|
485
|
+
self._backend = backend
|
|
486
|
+
self._description = None
|
|
487
|
+
|
|
488
|
+
def execute(self, sql: str, params: Any = None) -> "_CursorWrapper":
|
|
489
|
+
"""Execute SQL. Translates SQLite syntax to PostgreSQL."""
|
|
490
|
+
if self._backend == StorageBackend.POSTGRESQL:
|
|
491
|
+
sql = _translate_sqlite_sql(sql)
|
|
492
|
+
if params is None:
|
|
493
|
+
self._cursor.execute(sql)
|
|
494
|
+
else:
|
|
495
|
+
# Convert list to tuple for psycopg2 compatibility
|
|
496
|
+
if isinstance(params, list):
|
|
497
|
+
params = tuple(params)
|
|
498
|
+
self._cursor.execute(sql, params)
|
|
499
|
+
self._description = self._cursor.description
|
|
500
|
+
return self
|
|
501
|
+
|
|
502
|
+
def executemany(self, sql: str, param_seq: Any) -> "_CursorWrapper":
|
|
503
|
+
"""Execute SQL with multiple parameter sets."""
|
|
504
|
+
if self._backend == StorageBackend.POSTGRESQL:
|
|
505
|
+
sql = _translate_sqlite_sql(sql)
|
|
506
|
+
self._cursor.executemany(sql, param_seq)
|
|
507
|
+
self._description = self._cursor.description
|
|
508
|
+
return self
|
|
509
|
+
|
|
510
|
+
def executescript(self, sql: str) -> "_CursorWrapper":
|
|
511
|
+
"""Execute a SQL script (multiple statements).
|
|
512
|
+
|
|
513
|
+
SQLite has native executescript; PostgreSQL splits on semicolons
|
|
514
|
+
and executes each statement individually, skipping statements
|
|
515
|
+
that contain SQLite-specific syntax (AUTOINCREMENT, etc.) since
|
|
516
|
+
the tables already exist via pg_migrate.py.
|
|
517
|
+
"""
|
|
518
|
+
if self._backend == StorageBackend.SQLITE:
|
|
519
|
+
self._cursor.executescript(sql)
|
|
520
|
+
else:
|
|
521
|
+
# Split into individual statements and execute each
|
|
522
|
+
for stmt in sql.split(";"):
|
|
523
|
+
stmt = stmt.strip()
|
|
524
|
+
if not stmt:
|
|
525
|
+
continue
|
|
526
|
+
# Translate SQLite-specific syntax to PostgreSQL
|
|
527
|
+
stmt = _translate_sqlite_sql(stmt)
|
|
528
|
+
try:
|
|
529
|
+
self._cursor.execute(stmt)
|
|
530
|
+
except Exception as exc:
|
|
531
|
+
# Skip "already exists" errors for IF NOT EXISTS
|
|
532
|
+
err_msg = str(exc).lower()
|
|
533
|
+
if "already exists" in err_msg:
|
|
534
|
+
continue
|
|
535
|
+
logger.warning("executescript statement failed: %s", exc)
|
|
536
|
+
return self
|
|
537
|
+
|
|
538
|
+
def fetchall(self) -> List[Any]:
|
|
539
|
+
"""Fetch all rows. Supports both integer and key access."""
|
|
540
|
+
return _wrap_rows(self._cursor.fetchall(), self._backend)
|
|
541
|
+
|
|
542
|
+
def fetchone(self) -> Any:
|
|
543
|
+
"""Fetch one row. Supports both integer and key access."""
|
|
544
|
+
return _wrap_row(self._cursor.fetchone(), self._backend)
|
|
545
|
+
|
|
546
|
+
def fetchmany(self, size: int = 100) -> List[Any]:
|
|
547
|
+
"""Fetch many rows. Supports both integer and key access."""
|
|
548
|
+
return _wrap_rows(self._cursor.fetchmany(size), self._backend)
|
|
549
|
+
|
|
550
|
+
@property
|
|
551
|
+
def description(self):
|
|
552
|
+
return self._cursor.description
|
|
553
|
+
|
|
554
|
+
@property
|
|
555
|
+
def rowcount(self) -> int:
|
|
556
|
+
return self._cursor.rowcount
|
|
557
|
+
|
|
558
|
+
@property
|
|
559
|
+
def lastrowid(self) -> Optional[int]:
|
|
560
|
+
"""Last inserted row ID. Native on SQLite; emulated on PostgreSQL."""
|
|
561
|
+
if self._backend == StorageBackend.SQLITE:
|
|
562
|
+
return self._cursor.lastrowid
|
|
563
|
+
# PostgreSQL doesn't have lastrowid on cursor; use RETURNING in SQL
|
|
564
|
+
return getattr(self._cursor, "lastrowid", None)
|
|
565
|
+
|
|
566
|
+
def close(self) -> None:
|
|
567
|
+
self._cursor.close()
|
|
568
|
+
|
|
569
|
+
def __iter__(self):
|
|
570
|
+
if self._backend == StorageBackend.SQLITE:
|
|
571
|
+
return iter(self._cursor)
|
|
572
|
+
return (_DualAccessRow(row) for row in self._cursor)
|
|
573
|
+
|
|
574
|
+
|
|
575
|
+
# ---------------------------------------------------------------------------
|
|
576
|
+
# Connection wrapper
|
|
577
|
+
# ---------------------------------------------------------------------------
|
|
578
|
+
|
|
579
|
+
class StorageConnection:
|
|
580
|
+
"""Database-agnostic connection wrapper.
|
|
581
|
+
|
|
582
|
+
Provides the same API as sqlite3.Connection:
|
|
583
|
+
- execute(sql, params) -> CursorWrapper
|
|
584
|
+
- commit()
|
|
585
|
+
- rollback()
|
|
586
|
+
- close()
|
|
587
|
+
- cursor()
|
|
588
|
+
- Context manager support (__enter__ / __exit__)
|
|
589
|
+
|
|
590
|
+
Row access is dict-like (column names as keys) for both backends.
|
|
591
|
+
"""
|
|
592
|
+
|
|
593
|
+
def __init__(self, raw_conn: Any, backend: StorageBackend,
|
|
594
|
+
pool_return=None):
|
|
595
|
+
self._conn = raw_conn
|
|
596
|
+
self._backend = backend
|
|
597
|
+
self._closed = False
|
|
598
|
+
self._pool_return = pool_return # callback to return conn to pool
|
|
599
|
+
|
|
600
|
+
@property
|
|
601
|
+
def backend(self) -> StorageBackend:
|
|
602
|
+
return self._backend
|
|
603
|
+
|
|
604
|
+
@property
|
|
605
|
+
def raw(self) -> Any:
|
|
606
|
+
"""Access the underlying raw connection (sqlite3.Connection or psycopg2 conn).
|
|
607
|
+
|
|
608
|
+
Use sparingly — only for backend-specific operations like
|
|
609
|
+
sqlite3.backup() or PostgreSQL COPY.
|
|
610
|
+
"""
|
|
611
|
+
return self._conn
|
|
612
|
+
|
|
613
|
+
def execute(self, sql: str, params: Any = None) -> _CursorWrapper:
|
|
614
|
+
"""Execute SQL and return a cursor wrapper."""
|
|
615
|
+
cursor = self.cursor()
|
|
616
|
+
return cursor.execute(sql, params)
|
|
617
|
+
|
|
618
|
+
def executemany(self, sql: str, param_seq: Any) -> _CursorWrapper:
|
|
619
|
+
"""Execute SQL with multiple parameter sets."""
|
|
620
|
+
cursor = self.cursor()
|
|
621
|
+
return cursor.executemany(sql, param_seq)
|
|
622
|
+
|
|
623
|
+
def executescript(self, sql: str) -> _CursorWrapper:
|
|
624
|
+
"""Execute a SQL script."""
|
|
625
|
+
cursor = self.cursor()
|
|
626
|
+
return cursor.executescript(sql)
|
|
627
|
+
|
|
628
|
+
def cursor(self) -> _CursorWrapper:
|
|
629
|
+
"""Create a new cursor wrapper."""
|
|
630
|
+
if self._backend == StorageBackend.POSTGRESQL:
|
|
631
|
+
import psycopg2.extras
|
|
632
|
+
raw_cursor = self._conn.cursor(
|
|
633
|
+
cursor_factory=psycopg2.extras.RealDictCursor
|
|
634
|
+
)
|
|
635
|
+
else:
|
|
636
|
+
raw_cursor = self._conn.cursor()
|
|
637
|
+
return _CursorWrapper(raw_cursor, self._backend)
|
|
638
|
+
|
|
639
|
+
def commit(self) -> None:
|
|
640
|
+
self._conn.commit()
|
|
641
|
+
|
|
642
|
+
def rollback(self) -> None:
|
|
643
|
+
self._conn.rollback()
|
|
644
|
+
|
|
645
|
+
def close(self) -> None:
|
|
646
|
+
if not self._closed:
|
|
647
|
+
if self._pool_return:
|
|
648
|
+
self._pool_return(self._conn)
|
|
649
|
+
else:
|
|
650
|
+
self._conn.close()
|
|
651
|
+
self._closed = True
|
|
652
|
+
|
|
653
|
+
def __enter__(self) -> "StorageConnection":
|
|
654
|
+
return self
|
|
655
|
+
|
|
656
|
+
def __exit__(self, exc_type, exc_val, exc_tb) -> None:
|
|
657
|
+
if exc_type:
|
|
658
|
+
self.rollback()
|
|
659
|
+
self.close()
|
|
660
|
+
|
|
661
|
+
# Compatibility: allow conn.row_factory reads (no-op setter for PG)
|
|
662
|
+
@property
|
|
663
|
+
def row_factory(self):
|
|
664
|
+
if self._backend == StorageBackend.SQLITE:
|
|
665
|
+
return self._conn.row_factory
|
|
666
|
+
return None
|
|
667
|
+
|
|
668
|
+
@row_factory.setter
|
|
669
|
+
def row_factory(self, factory):
|
|
670
|
+
if self._backend == StorageBackend.SQLITE:
|
|
671
|
+
self._conn.row_factory = factory
|
|
672
|
+
|
|
673
|
+
|
|
674
|
+
# ---------------------------------------------------------------------------
|
|
675
|
+
# PostgreSQL type adapters — return strings for datetime (SQLite compat)
|
|
676
|
+
# ---------------------------------------------------------------------------
|
|
677
|
+
|
|
678
|
+
_pg_dates_registered = False
|
|
679
|
+
|
|
680
|
+
|
|
681
|
+
def _register_pg_string_dates():
|
|
682
|
+
"""Register psycopg2 type casters that return datetime as ISO strings.
|
|
683
|
+
|
|
684
|
+
SQLite returns datetime columns as strings. Without this adapter,
|
|
685
|
+
PostgreSQL returns datetime objects which break json.dumps() across
|
|
686
|
+
100+ tool files. Registering globally ensures consistent behavior.
|
|
687
|
+
"""
|
|
688
|
+
global _pg_dates_registered
|
|
689
|
+
if _pg_dates_registered:
|
|
690
|
+
return
|
|
691
|
+
try:
|
|
692
|
+
import psycopg2.extensions as ext
|
|
693
|
+
|
|
694
|
+
# TIMESTAMP / TIMESTAMPTZ → ISO string
|
|
695
|
+
def _cast_timestamp(value, cur):
|
|
696
|
+
if value is None:
|
|
697
|
+
return None
|
|
698
|
+
return str(value)
|
|
699
|
+
|
|
700
|
+
# DATE → ISO string
|
|
701
|
+
def _cast_date(value, cur):
|
|
702
|
+
if value is None:
|
|
703
|
+
return None
|
|
704
|
+
return str(value)
|
|
705
|
+
|
|
706
|
+
# OIDs: 1114=TIMESTAMP, 1184=TIMESTAMPTZ, 1082=DATE
|
|
707
|
+
TIMESTAMP = ext.new_type((1114,), "TIMESTAMP_STR", _cast_timestamp)
|
|
708
|
+
TIMESTAMPTZ = ext.new_type((1184,), "TIMESTAMPTZ_STR", _cast_timestamp)
|
|
709
|
+
DATE = ext.new_type((1082,), "DATE_STR", _cast_date)
|
|
710
|
+
|
|
711
|
+
ext.register_type(TIMESTAMP)
|
|
712
|
+
ext.register_type(TIMESTAMPTZ)
|
|
713
|
+
ext.register_type(DATE)
|
|
714
|
+
|
|
715
|
+
_pg_dates_registered = True
|
|
716
|
+
logger.debug("Registered psycopg2 string date adapters")
|
|
717
|
+
except ImportError:
|
|
718
|
+
pass
|
|
719
|
+
|
|
720
|
+
|
|
721
|
+
# ---------------------------------------------------------------------------
|
|
722
|
+
# Connection pool (PostgreSQL only)
|
|
723
|
+
# ---------------------------------------------------------------------------
|
|
724
|
+
|
|
725
|
+
class _PGPool:
|
|
726
|
+
"""Simple PostgreSQL connection pool using psycopg2.pool.
|
|
727
|
+
|
|
728
|
+
Thread-safe. Falls back to direct connections if pooling unavailable.
|
|
729
|
+
"""
|
|
730
|
+
|
|
731
|
+
def __init__(self, config: StorageConfig):
|
|
732
|
+
self._config = config
|
|
733
|
+
self._pool = None
|
|
734
|
+
self._lock = threading.Lock()
|
|
735
|
+
|
|
736
|
+
def _ensure_pool(self):
|
|
737
|
+
if self._pool is not None:
|
|
738
|
+
return
|
|
739
|
+
with self._lock:
|
|
740
|
+
if self._pool is not None:
|
|
741
|
+
return
|
|
742
|
+
try:
|
|
743
|
+
from psycopg2 import pool as pg_pool
|
|
744
|
+
import psycopg2.extensions
|
|
745
|
+
# Register type adapters so PostgreSQL returns strings for
|
|
746
|
+
# datetime/date columns, matching SQLite behavior. Without
|
|
747
|
+
# this, json.dumps() fails across 100+ tools.
|
|
748
|
+
_register_pg_string_dates()
|
|
749
|
+
self._pool = pg_pool.ThreadedConnectionPool(
|
|
750
|
+
minconn=1,
|
|
751
|
+
maxconn=self._config.pg_pool_size,
|
|
752
|
+
dsn=self._config.pg_dsn(),
|
|
753
|
+
)
|
|
754
|
+
logger.info(
|
|
755
|
+
"PostgreSQL pool created: %s:%s/%s (pool_size=%d)",
|
|
756
|
+
self._config.pg_host,
|
|
757
|
+
self._config.pg_port,
|
|
758
|
+
self._config.pg_database,
|
|
759
|
+
self._config.pg_pool_size,
|
|
760
|
+
)
|
|
761
|
+
except Exception as exc:
|
|
762
|
+
logger.error("Failed to create PG pool: %s", exc)
|
|
763
|
+
raise
|
|
764
|
+
|
|
765
|
+
def get_connection(self) -> Any:
|
|
766
|
+
"""Get a connection from the pool.
|
|
767
|
+
|
|
768
|
+
Sets autocommit=True by default to match SQLite behavior where
|
|
769
|
+
each statement auto-commits unless wrapped in BEGIN/COMMIT.
|
|
770
|
+
Without this, psycopg2 wraps everything in a transaction and
|
|
771
|
+
a single error aborts all subsequent statements.
|
|
772
|
+
"""
|
|
773
|
+
self._ensure_pool()
|
|
774
|
+
conn = self._pool.getconn()
|
|
775
|
+
conn.autocommit = True
|
|
776
|
+
return conn
|
|
777
|
+
|
|
778
|
+
def put_connection(self, conn: Any) -> None:
|
|
779
|
+
"""Return a connection to the pool."""
|
|
780
|
+
if self._pool:
|
|
781
|
+
self._pool.putconn(conn)
|
|
782
|
+
|
|
783
|
+
def close_all(self) -> None:
|
|
784
|
+
"""Close all pooled connections."""
|
|
785
|
+
if self._pool:
|
|
786
|
+
self._pool.closeall()
|
|
787
|
+
self._pool = None
|
|
788
|
+
|
|
789
|
+
|
|
790
|
+
# ---------------------------------------------------------------------------
|
|
791
|
+
# Engine (singleton per database)
|
|
792
|
+
# ---------------------------------------------------------------------------
|
|
793
|
+
|
|
794
|
+
class StorageEngine:
|
|
795
|
+
"""Database engine managing configuration and connection lifecycle.
|
|
796
|
+
|
|
797
|
+
Typically used as a singleton via ``get_engine()``.
|
|
798
|
+
"""
|
|
799
|
+
|
|
800
|
+
def __init__(self, config: Optional[StorageConfig] = None):
|
|
801
|
+
self._config = config or StorageConfig()
|
|
802
|
+
self._pg_pool: Optional[_PGPool] = None
|
|
803
|
+
|
|
804
|
+
if self._config.backend == StorageBackend.POSTGRESQL:
|
|
805
|
+
self._pg_pool = _PGPool(self._config)
|
|
806
|
+
logger.info("StorageEngine initialized: PostgreSQL")
|
|
807
|
+
else:
|
|
808
|
+
logger.info(
|
|
809
|
+
"StorageEngine initialized: SQLite (%s)", self._config.db_path
|
|
810
|
+
)
|
|
811
|
+
|
|
812
|
+
@property
|
|
813
|
+
def backend(self) -> StorageBackend:
|
|
814
|
+
return self._config.backend
|
|
815
|
+
|
|
816
|
+
@property
|
|
817
|
+
def config(self) -> StorageConfig:
|
|
818
|
+
return self._config
|
|
819
|
+
|
|
820
|
+
def connect(
|
|
821
|
+
self,
|
|
822
|
+
db_path: Optional[Union[str, Path]] = None,
|
|
823
|
+
row_factory: bool = True,
|
|
824
|
+
validate: bool = False,
|
|
825
|
+
) -> StorageConnection:
|
|
826
|
+
"""Create a new database connection.
|
|
827
|
+
|
|
828
|
+
Args:
|
|
829
|
+
db_path: Override SQLite path (ignored for PostgreSQL).
|
|
830
|
+
row_factory: Enable dict-like row access (default True).
|
|
831
|
+
validate: Raise FileNotFoundError if SQLite DB missing.
|
|
832
|
+
|
|
833
|
+
Returns:
|
|
834
|
+
StorageConnection wrapping the appropriate backend.
|
|
835
|
+
"""
|
|
836
|
+
if self._config.backend == StorageBackend.POSTGRESQL:
|
|
837
|
+
return self._connect_pg()
|
|
838
|
+
return self._connect_sqlite(db_path, row_factory, validate)
|
|
839
|
+
|
|
840
|
+
def _connect_sqlite(
|
|
841
|
+
self,
|
|
842
|
+
db_path: Optional[Union[str, Path]] = None,
|
|
843
|
+
row_factory: bool = True,
|
|
844
|
+
validate: bool = False,
|
|
845
|
+
) -> StorageConnection:
|
|
846
|
+
"""Create a SQLite connection."""
|
|
847
|
+
path = Path(db_path) if db_path else self._config.db_path
|
|
848
|
+
if validate and not path.exists():
|
|
849
|
+
raise FileNotFoundError(
|
|
850
|
+
f"Database not found: {path}\n"
|
|
851
|
+
"Run: python tools/db/init_icdev_db.py"
|
|
852
|
+
)
|
|
853
|
+
conn = sqlite3.connect(str(path))
|
|
854
|
+
if row_factory:
|
|
855
|
+
conn.row_factory = sqlite3.Row
|
|
856
|
+
return StorageConnection(conn, StorageBackend.SQLITE)
|
|
857
|
+
|
|
858
|
+
def _connect_pg(self) -> StorageConnection:
|
|
859
|
+
"""Create a PostgreSQL connection from the pool."""
|
|
860
|
+
if not self._pg_pool:
|
|
861
|
+
raise RuntimeError("PostgreSQL pool not initialized")
|
|
862
|
+
raw_conn = self._pg_pool.get_connection()
|
|
863
|
+
return StorageConnection(
|
|
864
|
+
raw_conn, StorageBackend.POSTGRESQL,
|
|
865
|
+
pool_return=self._pg_pool.put_connection,
|
|
866
|
+
)
|
|
867
|
+
|
|
868
|
+
def close(self) -> None:
|
|
869
|
+
"""Shut down the engine and release all resources."""
|
|
870
|
+
if self._pg_pool:
|
|
871
|
+
self._pg_pool.close_all()
|
|
872
|
+
self._pg_pool = None
|
|
873
|
+
|
|
874
|
+
def health_check(self) -> Dict[str, Any]:
|
|
875
|
+
"""Check backend health."""
|
|
876
|
+
import time
|
|
877
|
+
start = time.time()
|
|
878
|
+
try:
|
|
879
|
+
with self.connect() as conn:
|
|
880
|
+
conn.execute("SELECT 1")
|
|
881
|
+
latency_ms = int((time.time() - start) * 1000)
|
|
882
|
+
return {
|
|
883
|
+
"status": "healthy",
|
|
884
|
+
"backend": self._config.backend.value,
|
|
885
|
+
"latency_ms": latency_ms,
|
|
886
|
+
}
|
|
887
|
+
except Exception as exc:
|
|
888
|
+
return {
|
|
889
|
+
"status": "unhealthy",
|
|
890
|
+
"backend": self._config.backend.value,
|
|
891
|
+
"error": str(exc),
|
|
892
|
+
}
|
|
893
|
+
|
|
894
|
+
|
|
895
|
+
# ---------------------------------------------------------------------------
|
|
896
|
+
# Singleton engine + public API
|
|
897
|
+
# ---------------------------------------------------------------------------
|
|
898
|
+
|
|
899
|
+
_engine: Optional[StorageEngine] = None
|
|
900
|
+
_engine_lock = threading.Lock()
|
|
901
|
+
|
|
902
|
+
|
|
903
|
+
def get_engine(config: Optional[StorageConfig] = None) -> StorageEngine:
|
|
904
|
+
"""Get or create the singleton StorageEngine.
|
|
905
|
+
|
|
906
|
+
Args:
|
|
907
|
+
config: Optional explicit config. If provided on first call, it
|
|
908
|
+
becomes the engine config. Subsequent calls ignore it.
|
|
909
|
+
|
|
910
|
+
Returns:
|
|
911
|
+
The global StorageEngine instance.
|
|
912
|
+
"""
|
|
913
|
+
global _engine
|
|
914
|
+
if _engine is not None:
|
|
915
|
+
return _engine
|
|
916
|
+
with _engine_lock:
|
|
917
|
+
if _engine is not None:
|
|
918
|
+
return _engine
|
|
919
|
+
_engine = StorageEngine(config)
|
|
920
|
+
return _engine
|
|
921
|
+
|
|
922
|
+
|
|
923
|
+
def reset_engine() -> None:
|
|
924
|
+
"""Shut down and reset the global engine. Primarily for testing."""
|
|
925
|
+
global _engine
|
|
926
|
+
with _engine_lock:
|
|
927
|
+
if _engine:
|
|
928
|
+
_engine.close()
|
|
929
|
+
_engine = None
|
|
930
|
+
|
|
931
|
+
|
|
932
|
+
def get_connection(
|
|
933
|
+
db_path: Optional[Union[str, Path]] = None,
|
|
934
|
+
row_factory: bool = True,
|
|
935
|
+
validate: bool = False,
|
|
936
|
+
) -> StorageConnection:
|
|
937
|
+
"""Get a database connection from the global engine.
|
|
938
|
+
|
|
939
|
+
This is the primary public API — a drop-in replacement for
|
|
940
|
+
``sqlite3.connect()`` and ``tools.compat.db_utils.get_db_connection()``.
|
|
941
|
+
|
|
942
|
+
Args:
|
|
943
|
+
db_path: Override SQLite path (ignored for PostgreSQL).
|
|
944
|
+
row_factory: Enable dict-like row access (default True).
|
|
945
|
+
validate: Raise FileNotFoundError if SQLite DB missing.
|
|
946
|
+
|
|
947
|
+
Returns:
|
|
948
|
+
StorageConnection (context manager) wrapping the configured backend.
|
|
949
|
+
|
|
950
|
+
Example:
|
|
951
|
+
with get_connection() as conn:
|
|
952
|
+
rows = conn.execute("SELECT * FROM projects").fetchall()
|
|
953
|
+
"""
|
|
954
|
+
return get_engine().connect(db_path=db_path, row_factory=row_factory, validate=validate)
|
|
955
|
+
|
|
956
|
+
|
|
957
|
+
# ---------------------------------------------------------------------------
|
|
958
|
+
# Convenience: per-database connections (matching db_utils.py API)
|
|
959
|
+
# ---------------------------------------------------------------------------
|
|
960
|
+
|
|
961
|
+
def get_icdev_connection(
|
|
962
|
+
db_path: Optional[Union[str, Path]] = None,
|
|
963
|
+
validate: bool = False,
|
|
964
|
+
row_factory: bool = True,
|
|
965
|
+
) -> StorageConnection:
|
|
966
|
+
"""Get connection to the ICDEV database.
|
|
967
|
+
|
|
968
|
+
For PostgreSQL backend, connects to the configured PG database.
|
|
969
|
+
For SQLite backend, connects to data/icdev.db (or override).
|
|
970
|
+
"""
|
|
971
|
+
engine = get_engine()
|
|
972
|
+
if engine.backend == StorageBackend.POSTGRESQL:
|
|
973
|
+
return engine.connect()
|
|
974
|
+
|
|
975
|
+
# SQLite: resolve path with env var fallback
|
|
976
|
+
path = Path(
|
|
977
|
+
db_path
|
|
978
|
+
or os.environ.get("ICDEV_DB_PATH")
|
|
979
|
+
or str(_PROJECT_ROOT / "data" / "icdev.db")
|
|
980
|
+
)
|
|
981
|
+
return engine.connect(db_path=path, row_factory=row_factory, validate=validate)
|
|
982
|
+
|
|
983
|
+
|
|
984
|
+
def get_sparkpilot_connection(
|
|
985
|
+
db_path: Optional[Union[str, Path]] = None,
|
|
986
|
+
validate: bool = False,
|
|
987
|
+
row_factory: bool = True,
|
|
988
|
+
) -> StorageConnection:
|
|
989
|
+
"""Get connection to the SparkPilot database.
|
|
990
|
+
|
|
991
|
+
For PostgreSQL backend, connects to the configured PG database
|
|
992
|
+
(all tables live in one PG database with schema separation).
|
|
993
|
+
For SQLite backend, connects to data/icdev.db.
|
|
994
|
+
"""
|
|
995
|
+
engine = get_engine()
|
|
996
|
+
if engine.backend == StorageBackend.POSTGRESQL:
|
|
997
|
+
return engine.connect()
|
|
998
|
+
|
|
999
|
+
path = Path(
|
|
1000
|
+
db_path
|
|
1001
|
+
or os.environ.get("SPARKPILOT_DB_PATH")
|
|
1002
|
+
or str(_PROJECT_ROOT / "data" / "icdev.db")
|
|
1003
|
+
)
|
|
1004
|
+
return engine.connect(db_path=path, row_factory=row_factory, validate=validate)
|
|
1005
|
+
|
|
1006
|
+
|
|
1007
|
+
def get_memory_connection(
|
|
1008
|
+
db_path: Optional[Union[str, Path]] = None,
|
|
1009
|
+
validate: bool = False,
|
|
1010
|
+
row_factory: bool = True,
|
|
1011
|
+
) -> StorageConnection:
|
|
1012
|
+
"""Get connection to the memory database."""
|
|
1013
|
+
engine = get_engine()
|
|
1014
|
+
if engine.backend == StorageBackend.POSTGRESQL:
|
|
1015
|
+
return engine.connect()
|
|
1016
|
+
|
|
1017
|
+
path = Path(
|
|
1018
|
+
db_path
|
|
1019
|
+
or os.environ.get("ICDEV_MEMORY_DB_PATH")
|
|
1020
|
+
or str(_PROJECT_ROOT / "data" / "memory.db")
|
|
1021
|
+
)
|
|
1022
|
+
return engine.connect(db_path=path, row_factory=row_factory, validate=validate)
|
|
1023
|
+
|
|
1024
|
+
|
|
1025
|
+
# ---------------------------------------------------------------------------
|
|
1026
|
+
# CLI: health check and info
|
|
1027
|
+
# ---------------------------------------------------------------------------
|
|
1028
|
+
|
|
1029
|
+
def _main():
|
|
1030
|
+
"""CLI entry point for health check and configuration display."""
|
|
1031
|
+
import argparse
|
|
1032
|
+
import json
|
|
1033
|
+
|
|
1034
|
+
parser = argparse.ArgumentParser(description="ICDEV Storage Layer (D-DB-20)")
|
|
1035
|
+
parser.add_argument("--health", action="store_true", help="Run health check")
|
|
1036
|
+
parser.add_argument("--info", action="store_true", help="Show configuration")
|
|
1037
|
+
parser.add_argument("--json", action="store_true", help="JSON output")
|
|
1038
|
+
args = parser.parse_args()
|
|
1039
|
+
|
|
1040
|
+
engine = get_engine()
|
|
1041
|
+
|
|
1042
|
+
if args.health:
|
|
1043
|
+
result = engine.health_check()
|
|
1044
|
+
if args.json:
|
|
1045
|
+
print(json.dumps(result, indent=2))
|
|
1046
|
+
else:
|
|
1047
|
+
status = result["status"]
|
|
1048
|
+
backend = result["backend"]
|
|
1049
|
+
latency = result.get("latency_ms", "N/A")
|
|
1050
|
+
print(f"Backend: {backend}")
|
|
1051
|
+
print(f"Status: {status}")
|
|
1052
|
+
print(f"Latency: {latency}ms")
|
|
1053
|
+
return
|
|
1054
|
+
|
|
1055
|
+
if args.info:
|
|
1056
|
+
cfg = engine.config
|
|
1057
|
+
info = {
|
|
1058
|
+
"backend": cfg.backend.value,
|
|
1059
|
+
"sqlite_path": str(cfg.db_path),
|
|
1060
|
+
"pg_host": cfg.pg_host,
|
|
1061
|
+
"pg_port": cfg.pg_port,
|
|
1062
|
+
"pg_database": cfg.pg_database,
|
|
1063
|
+
"pg_username": cfg.pg_username,
|
|
1064
|
+
"pg_pool_size": cfg.pg_pool_size,
|
|
1065
|
+
"pg_has_connection_string": bool(cfg.pg_connection_string),
|
|
1066
|
+
}
|
|
1067
|
+
if args.json:
|
|
1068
|
+
print(json.dumps(info, indent=2))
|
|
1069
|
+
else:
|
|
1070
|
+
for k, v in info.items():
|
|
1071
|
+
print(f"{k}: {v}")
|
|
1072
|
+
return
|
|
1073
|
+
|
|
1074
|
+
# Default: show backend
|
|
1075
|
+
print(f"Storage backend: {engine.config.backend.value}")
|
|
1076
|
+
print(f"Run --health for connectivity test, --info for configuration")
|
|
1077
|
+
|
|
1078
|
+
|
|
1079
|
+
if __name__ == "__main__":
|
|
1080
|
+
_main()
|