kiri-cli 1.0.0b3__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.
Potentially problematic release.
This version of kiri-cli might be problematic. Click here for more details.
- kiri_adapters/__init__.py +193 -0
- kiri_adapters/backends/__init__.py +0 -0
- kiri_adapters/backends/databricks.manifest.yml +55 -0
- kiri_adapters/backends/databricks.py +2980 -0
- kiri_adapters/backends/fabric_dw.manifest.yml +17 -0
- kiri_adapters/backends/fabric_dw.py +307 -0
- kiri_adapters/backends/fabric_lakehouse.manifest.yml +19 -0
- kiri_adapters/backends/fabric_lakehouse.py +365 -0
- kiri_adapters/backends/iceberg.manifest.yml +17 -0
- kiri_adapters/backends/iceberg.py +451 -0
- kiri_adapters/backends/localduckdb.manifest.yml +43 -0
- kiri_adapters/backends/localduckdb.py +1295 -0
- kiri_adapters/backends/mssql.manifest.yml +17 -0
- kiri_adapters/backends/mssql.py +366 -0
- kiri_adapters/backends/postgres.manifest.yml +17 -0
- kiri_adapters/backends/postgres.py +434 -0
- kiri_adapters/backends/trino.manifest.yml +17 -0
- kiri_adapters/backends/trino.py +542 -0
- kiri_adapters/base.py +892 -0
- kiri_adapters/doctor_checks.py +81 -0
- kiri_adapters/dv_planner.py +1205 -0
- kiri_adapters/dv_planner_bridge.py +265 -0
- kiri_adapters/dv_planner_pit.py +265 -0
- kiri_adapters/dv_planner_ref.py +403 -0
- kiri_adapters/dv_planner_sat_variants.py +668 -0
- kiri_adapters/framework.py +112 -0
- kiri_adapters/incident/__init__.py +30 -0
- kiri_adapters/incident/_http.py +121 -0
- kiri_adapters/incident/base.py +89 -0
- kiri_adapters/incident/jira.py +365 -0
- kiri_adapters/incident/servicenow.py +314 -0
- kiri_adapters/incident/zendesk.py +285 -0
- kiri_adapters/manifest.py +466 -0
- kiri_adapters/orchestration/__init__.py +30 -0
- kiri_adapters/orchestration/airflow.py +117 -0
- kiri_adapters/orchestration/artifact_emitter.py +99 -0
- kiri_adapters/orchestration/autoloader_emitter.py +141 -0
- kiri_adapters/orchestration/azure_data_factory.py +137 -0
- kiri_adapters/orchestration/base.py +626 -0
- kiri_adapters/orchestration/databricks_workflows.py +1768 -0
- kiri_adapters/orchestration/k8s_cron.py +555 -0
- kiri_adapters/orchestration/lifecycle.py +222 -0
- kiri_adapters/orchestration/pipeline_compat.py +145 -0
- kiri_adapters/orchestration/runtime_python.py +188 -0
- kiri_adapters/paths.py +38 -0
- kiri_adapters/protocols/__init__.py +64 -0
- kiri_adapters/protocols/ai_service/__init__.py +7 -0
- kiri_adapters/protocols/ai_service/conformance.py +120 -0
- kiri_adapters/protocols/base.py +220 -0
- kiri_adapters/protocols/catalog/__init__.py +7 -0
- kiri_adapters/protocols/catalog/conformance.py +138 -0
- kiri_adapters/protocols/framework/__init__.py +7 -0
- kiri_adapters/protocols/framework/conformance.py +98 -0
- kiri_adapters/protocols/incident/__init__.py +7 -0
- kiri_adapters/protocols/incident/conformance.py +64 -0
- kiri_adapters/protocols/orchestration/__init__.py +7 -0
- kiri_adapters/protocols/orchestration/conformance.py +84 -0
- kiri_adapters/protocols/platform/__init__.py +7 -0
- kiri_adapters/protocols/platform/conformance.py +120 -0
- kiri_adapters/protocols/sql_dialect/__init__.py +7 -0
- kiri_adapters/protocols/sql_dialect/conformance.py +91 -0
- kiri_adapters/protocols/vault/__init__.py +7 -0
- kiri_adapters/protocols/vault/conformance.py +65 -0
- kiri_adapters/py.typed +0 -0
- kiri_adapters/registry.py +104 -0
- kiri_adapters/semantic/__init__.py +60 -0
- kiri_adapters/semantic/apply.py +274 -0
- kiri_adapters/semantic/base.py +530 -0
- kiri_adapters/semantic/cube.py +280 -0
- kiri_adapters/semantic/dbt_semantic.py +92 -0
- kiri_adapters/semantic/metric_views.py +249 -0
- kiri_adapters/semantic/metricflow.py +345 -0
- kiri_adapters/semantic/snowflake_views.py +75 -0
- kiri_adapters/sql_dialect.py +1592 -0
- kiri_adapters/tsql_to_spark.py +653 -0
- kiri_ai_gateway/__init__.py +147 -0
- kiri_ai_gateway/audit.py +334 -0
- kiri_ai_gateway/batch.py +293 -0
- kiri_ai_gateway/config.py +165 -0
- kiri_ai_gateway/gateway.py +642 -0
- kiri_ai_gateway/placeholder_guard.py +145 -0
- kiri_ai_gateway/prices/__init__.py +77 -0
- kiri_ai_gateway/prices/prices.yaml +64 -0
- kiri_ai_gateway/prompts/__init__.py +54 -0
- kiri_ai_gateway/prompts/airbyte_source_setup.yaml +212 -0
- kiri_ai_gateway/prompts/bdv_translation.yaml +90 -0
- kiri_ai_gateway/prompts/contract_draft_mvp.yaml +117 -0
- kiri_ai_gateway/prompts/describe_table.yaml +19 -0
- kiri_ai_gateway/prompts/design_gold_from_question.yaml +130 -0
- kiri_ai_gateway/prompts/detect_pii.yaml +59 -0
- kiri_ai_gateway/prompts/discover_classify_column.yaml +69 -0
- kiri_ai_gateway/prompts/discover_describe_table.yaml +61 -0
- kiri_ai_gateway/prompts/dv_quality_summary.yaml +36 -0
- kiri_ai_gateway/prompts/incident_assist.yaml +58 -0
- kiri_ai_gateway/prompts/infer_source_schema.yaml +31 -0
- kiri_ai_gateway/prompts/ingest_backend_chooser.yaml +116 -0
- kiri_ai_gateway/prompts/interactive_source_edit.yaml +94 -0
- kiri_ai_gateway/prompts/map_needed_entity_to_source.yaml +72 -0
- kiri_ai_gateway/prompts/meet_kiri.yaml +127 -0
- kiri_ai_gateway/prompts/native_source_setup.yaml +211 -0
- kiri_ai_gateway/prompts/source_grouping_reason.yaml +29 -0
- kiri_ai_gateway/prompts/suggest_attribute_classification.yaml +42 -0
- kiri_ai_gateway/prompts/suggest_data_tests.yaml +71 -0
- kiri_ai_gateway/prompts/suggest_gold_dimension.yaml +108 -0
- kiri_ai_gateway/prompts/suggest_gold_fact.yaml +107 -0
- kiri_ai_gateway/prompts/suggest_gold_model.yaml +70 -0
- kiri_ai_gateway/prompts/suggest_semantic_intent.yaml +91 -0
- kiri_ai_gateway/prompts/suggest_silver_model.yaml +140 -0
- kiri_ai_gateway/providers/__init__.py +7 -0
- kiri_ai_gateway/providers/anthropic_provider.py +26 -0
- kiri_ai_gateway/providers/gateway_provider.py +188 -0
- kiri_ai_gateway/providers/local_provider.py +177 -0
- kiri_ai_gateway/py.typed +0 -0
- kiri_ai_gateway/service.py +283 -0
- kiri_ai_gateway/transport.py +103 -0
- kiri_ai_gateway/transports/__init__.py +12 -0
- kiri_ai_gateway/transports/anthropic_transport.py +73 -0
- kiri_ai_gateway/transports/ollama_transport.py +82 -0
- kiri_ai_gateway/transports/openai_compatible_transport.py +125 -0
- kiri_ai_gateway/workspace_policy.py +52 -0
- kiri_catalog/__init__.py +199 -0
- kiri_catalog/annotation_plan.py +183 -0
- kiri_catalog/appearances.py +155 -0
- kiri_catalog/backends/__init__.py +79 -0
- kiri_catalog/backends/base.py +167 -0
- kiri_catalog/backends/memory.py +241 -0
- kiri_catalog/backends/postgres.py +497 -0
- kiri_catalog/backends/redacting.py +195 -0
- kiri_catalog/backends/sqlite.py +480 -0
- kiri_catalog/context.py +94 -0
- kiri_catalog/federation/__init__.py +114 -0
- kiri_catalog/federation/api.py +200 -0
- kiri_catalog/federation/cache.py +149 -0
- kiri_catalog/federation/emit.py +209 -0
- kiri_catalog/federation/fs_resolver.py +295 -0
- kiri_catalog/federation/http_resolver.py +486 -0
- kiri_catalog/federation/metrics.py +123 -0
- kiri_catalog/federation/release.py +342 -0
- kiri_catalog/federation/resolver.py +332 -0
- kiri_catalog/federation/schema.py +460 -0
- kiri_catalog/importer.py +668 -0
- kiri_catalog/lineage_drift.py +280 -0
- kiri_catalog/lineage_graph.py +176 -0
- kiri_catalog/lineage_graph_explain.py +96 -0
- kiri_catalog/lineage_graph_mermaid.py +230 -0
- kiri_catalog/observed_uc_lineage.py +216 -0
- kiri_catalog/pii_detect.py +176 -0
- kiri_catalog/push.py +122 -0
- kiri_catalog/py.typed +0 -0
- kiri_catalog/redact.py +101 -0
- kiri_catalog/review_actions.py +119 -0
- kiri_catalog/schema/__init__.py +70 -0
- kiri_catalog/schema/asset.py +142 -0
- kiri_catalog/schema/column.py +247 -0
- kiri_catalog/schema/enums.py +195 -0
- kiri_catalog/schema/job.py +51 -0
- kiri_catalog/schema/lineage.py +33 -0
- kiri_catalog/schema/policy.py +69 -0
- kiri_catalog/schema/primitives.py +90 -0
- kiri_catalog/schema/review_state.py +111 -0
- kiri_catalog/schema/urn.py +45 -0
- kiri_catalog/schema_discovery.py +160 -0
- kiri_catalog/sinks/__init__.py +64 -0
- kiri_catalog/sinks/base.py +178 -0
- kiri_catalog/sinks/horizon.py +195 -0
- kiri_catalog/sinks/purview.py +216 -0
- kiri_catalog/sinks/unity.py +352 -0
- kiri_catalog/sinks/unity_executor.py +543 -0
- kiri_catalog/sources/__init__.py +42 -0
- kiri_catalog/sources/base.py +104 -0
- kiri_catalog/sources/unity.py +465 -0
- kiri_catalog/steward_writeback.py +158 -0
- kiri_catalog/tag_sql.py +99 -0
- kiri_cli/LICENSE +105 -0
- kiri_cli/__init__.py +1 -0
- kiri_cli/_apply_cli.py +63 -0
- kiri_cli/_apply_reporter.py +38 -0
- kiri_cli/_closed_menu.py +36 -0
- kiri_cli/_fixture_option.py +18 -0
- kiri_cli/_layer_detect.py +158 -0
- kiri_cli/_project_option.py +79 -0
- kiri_cli/adapter_cmd.py +192 -0
- kiri_cli/ai_batch_cmd.py +258 -0
- kiri_cli/ai_cmd.py +352 -0
- kiri_cli/approve_cmd.py +659 -0
- kiri_cli/attr_cmd.py +575 -0
- kiri_cli/audit_cmd.py +348 -0
- kiri_cli/audit_correlate_cmd.py +648 -0
- kiri_cli/audit_join.py +437 -0
- kiri_cli/audit_join_cmd.py +160 -0
- kiri_cli/auto_cmd.py +97 -0
- kiri_cli/azure_cmd.py +243 -0
- kiri_cli/catalog.py +1293 -0
- kiri_cli/cli_reference_live.py +147 -0
- kiri_cli/compile_cmd.py +493 -0
- kiri_cli/compliance_cmd.py +221 -0
- kiri_cli/contract_approval_cmd.py +94 -0
- kiri_cli/contract_cmd.py +64 -0
- kiri_cli/contract_codeowners_cmd.py +162 -0
- kiri_cli/contract_diff.py +111 -0
- kiri_cli/contract_emit.py +90 -0
- kiri_cli/contract_export.py +192 -0
- kiri_cli/contract_lint.py +1505 -0
- kiri_cli/contract_merge_cmd.py +264 -0
- kiri_cli/contract_new.py +326 -0
- kiri_cli/contract_promote.py +216 -0
- kiri_cli/contract_scaffold_cmd.py +134 -0
- kiri_cli/contract_schema_export.py +69 -0
- kiri_cli/contract_show.py +150 -0
- kiri_cli/contract_stubs.py +60 -0
- kiri_cli/contract_sync_from_live.py +327 -0
- kiri_cli/contract_validate.py +154 -0
- kiri_cli/contract_verify_live.py +242 -0
- kiri_cli/cost_cmd.py +243 -0
- kiri_cli/databricks_health.py +1254 -0
- kiri_cli/databricks_setup_cmd.py +712 -0
- kiri_cli/discover_cmd.py +600 -0
- kiri_cli/docs_cmd.py +35 -0
- kiri_cli/doctor_cmd.py +145 -0
- kiri_cli/dv_cmd.py +814 -0
- kiri_cli/dv_conformance_cmd.py +154 -0
- kiri_cli/dv_install.py +493 -0
- kiri_cli/dv_migrate_cmd.py +183 -0
- kiri_cli/dv_test_cmd.py +175 -0
- kiri_cli/federation_cmd.py +315 -0
- kiri_cli/fk_cmd.py +160 -0
- kiri_cli/flows_cmd.py +910 -0
- kiri_cli/governance_cmd.py +358 -0
- kiri_cli/incident_api_cmd.py +180 -0
- kiri_cli/incident_cmd.py +410 -0
- kiri_cli/incident_setup_cmd.py +239 -0
- kiri_cli/ingest.py +427 -0
- kiri_cli/ingest_discover.py +480 -0
- kiri_cli/ingest_new_source.py +386 -0
- kiri_cli/intent_cmd.py +122 -0
- kiri_cli/invariants_cmd.py +118 -0
- kiri_cli/kiri_banner.py +161 -0
- kiri_cli/kpi_cmd.py +222 -0
- kiri_cli/layer_cmd.py +464 -0
- kiri_cli/lineage.py +601 -0
- kiri_cli/main.py +2472 -0
- kiri_cli/maturity_cmd.py +222 -0
- kiri_cli/migrate_cmd.py +1347 -0
- kiri_cli/onboard_cmd.py +223 -0
- kiri_cli/pack_cmd.py +496 -0
- kiri_cli/pipeline_cmd.py +127 -0
- kiri_cli/pre_classify_cmd.py +274 -0
- kiri_cli/project_cmd.py +592 -0
- kiri_cli/py.typed +0 -0
- kiri_cli/quality_cmd.py +295 -0
- kiri_cli/rbac.py +139 -0
- kiri_cli/rbac_cmd.py +291 -0
- kiri_cli/release.py +850 -0
- kiri_cli/release_semver.py +265 -0
- kiri_cli/repo_cmd.py +155 -0
- kiri_cli/retention_cmd.py +333 -0
- kiri_cli/sample_data/__init__.py +12 -0
- kiri_cli/sample_data/customers.contract.yml +47 -0
- kiri_cli/sample_data/customers.csv +25 -0
- kiri_cli/scaffold_cmd.py +721 -0
- kiri_cli/schema_cmd.py +185 -0
- kiri_cli/semantic_cmd.py +605 -0
- kiri_cli/silver_cmd.py +348 -0
- kiri_cli/skills_cmd.py +243 -0
- kiri_cli/skills_data/README.md +51 -0
- kiri_cli/skills_data/add-source/SKILL.md +116 -0
- kiri_cli/skills_data/azure-deploy/SKILL.md +261 -0
- kiri_cli/skills_data/azure-status/SKILL.md +111 -0
- kiri_cli/skills_data/compliance-check/SKILL.md +99 -0
- kiri_cli/skills_data/databricks-build-pipeline/SKILL.md +112 -0
- kiri_cli/skills_data/databricks-project-setup/SKILL.md +157 -0
- kiri_cli/skills_data/debug-apply/SKILL.md +186 -0
- kiri_cli/skills_data/draft-gold-model/SKILL.md +203 -0
- kiri_cli/skills_data/explain-kpi/SKILL.md +158 -0
- kiri_cli/skills_data/incident-coach/SKILL.md +135 -0
- kiri_cli/skills_data/incident-setup/SKILL.md +112 -0
- kiri_cli/skills_data/maturity-coach/SKILL.md +120 -0
- kiri_cli/skills_data/promote-contract/SKILL.md +91 -0
- kiri_cli/skills_data/review-attributes/SKILL.md +94 -0
- kiri_cli/sla_cmd.py +769 -0
- kiri_cli/source_cmd.py +909 -0
- kiri_cli/templates/__init__.py +6 -0
- kiri_cli/templates/dv4dbt_packages.yml.j2 +11 -0
- kiri_cli/upgrade_cmd.py +896 -0
- kiri_cli/use_cmd.py +120 -0
- kiri_cli/vault_cmd.py +241 -0
- kiri_cli/verify_generated.py +241 -0
- kiri_cli-1.0.0b3.dist-info/METADATA +212 -0
- kiri_cli-1.0.0b3.dist-info/RECORD +743 -0
- kiri_cli-1.0.0b3.dist-info/WHEEL +4 -0
- kiri_cli-1.0.0b3.dist-info/entry_points.txt +4 -0
- kiri_compliance/__init__.py +33 -0
- kiri_compliance/audit_coverage.py +354 -0
- kiri_compliance/controls.py +763 -0
- kiri_compliance/evidence.py +155 -0
- kiri_compliance/py.typed +0 -0
- kiri_compliance/quality_engine.py +246 -0
- kiri_compliance/renderer.py +195 -0
- kiri_compliance/report.py +44 -0
- kiri_compliance/schema.py +129 -0
- kiri_core/__init__.py +185 -0
- kiri_core/adapter_surfaces.py +149 -0
- kiri_core/ai_query_gate/__init__.py +92 -0
- kiri_core/ai_query_gate/audit.py +153 -0
- kiri_core/ai_query_gate/evaluator.py +484 -0
- kiri_core/ai_query_gate/models.py +162 -0
- kiri_core/ai_query_gate/parser.py +436 -0
- kiri_core/ai_query_gate/resolver.py +274 -0
- kiri_core/ai_query_gate/store.py +234 -0
- kiri_core/ai_query_gate/store_postgres.py +409 -0
- kiri_core/api_keys.py +243 -0
- kiri_core/apply/__init__.py +27 -0
- kiri_core/apply/conformance.py +99 -0
- kiri_core/apply/incident_router.py +176 -0
- kiri_core/apply/models.py +140 -0
- kiri_core/apply/reporter.py +46 -0
- kiri_core/apply/runner.py +3083 -0
- kiri_core/apply_log_store.py +155 -0
- kiri_core/approval_flow/__init__.py +133 -0
- kiri_core/approval_flow/integration.py +156 -0
- kiri_core/approval_flow/models.py +443 -0
- kiri_core/approval_flow/notifier.py +263 -0
- kiri_core/approval_flow/orchestrator.py +660 -0
- kiri_core/approval_flow/reconcile_signoff.py +53 -0
- kiri_core/approval_flow/store.py +220 -0
- kiri_core/approval_flow/store_jsonl.py +217 -0
- kiri_core/approval_flow/store_postgres.py +580 -0
- kiri_core/approval_policy.py +116 -0
- kiri_core/asgi_trace.py +160 -0
- kiri_core/compliance.py +802 -0
- kiri_core/cutover_approval.py +187 -0
- kiri_core/discovery/__init__.py +109 -0
- kiri_core/discovery/ai/__init__.py +37 -0
- kiri_core/discovery/ai/_payload.py +115 -0
- kiri_core/discovery/ai/budget.py +83 -0
- kiri_core/discovery/ai/classify.py +223 -0
- kiri_core/discovery/ai/describe.py +200 -0
- kiri_core/discovery/ai/errors.py +49 -0
- kiri_core/discovery/discover.py +648 -0
- kiri_core/discovery/heuristics/__init__.py +42 -0
- kiri_core/discovery/heuristics/classification.py +174 -0
- kiri_core/discovery/heuristics/medallion.py +157 -0
- kiri_core/discovery/heuristics/pii.py +92 -0
- kiri_core/discovery/introspector.py +636 -0
- kiri_core/discovery/introspector_databricks.py +396 -0
- kiri_core/discovery/introspector_postgres.py +394 -0
- kiri_core/discovery/introspector_snowflake.py +383 -0
- kiri_core/discovery/reporting/__init__.py +17 -0
- kiri_core/discovery/reporting/discovery_report.py +190 -0
- kiri_core/discovery/scaffold/__init__.py +41 -0
- kiri_core/discovery/scaffold/contract_writer.py +632 -0
- kiri_core/discovery/scaffold/provenance_overlay.py +143 -0
- kiri_core/discovery/scaffold/source_writer.py +211 -0
- kiri_core/discovery/scaffold/todo_marker.py +98 -0
- kiri_core/domain_policy.py +122 -0
- kiri_core/goal_coverage.py +82 -0
- kiri_core/gold_registry.py +211 -0
- kiri_core/health.py +192 -0
- kiri_core/incident.py +204 -0
- kiri_core/incident_builders.py +256 -0
- kiri_core/incident_log.py +418 -0
- kiri_core/intent/__init__.py +46 -0
- kiri_core/intent/agent_runner.py +82 -0
- kiri_core/intent/project_dv.py +261 -0
- kiri_core/intent/propose.py +132 -0
- kiri_core/intent/semantic_intent.py +446 -0
- kiri_core/invariants.py +301 -0
- kiri_core/kpi.py +373 -0
- kiri_core/latency.py +150 -0
- kiri_core/lifecycle/__init__.py +555 -0
- kiri_core/lifecycle/ai_index.py +453 -0
- kiri_core/lifecycle/auto_mode.py +172 -0
- kiri_core/lifecycle/bootstrap.py +316 -0
- kiri_core/lifecycle/cli_reference.py +1064 -0
- kiri_core/lifecycle/commit.py +258 -0
- kiri_core/lifecycle/compile_dispatch.py +296 -0
- kiri_core/lifecycle/compile_range.py +665 -0
- kiri_core/lifecycle/contract_inventory.py +354 -0
- kiri_core/lifecycle/contracts.py +250 -0
- kiri_core/lifecycle/doctor.py +412 -0
- kiri_core/lifecycle/git_remote.py +948 -0
- kiri_core/lifecycle/identity.py +147 -0
- kiri_core/lifecycle/ingestion_modes.py +215 -0
- kiri_core/lifecycle/layer_contract.py +452 -0
- kiri_core/lifecycle/layer_contract_migrate.py +969 -0
- kiri_core/lifecycle/layer_contract_parser.py +436 -0
- kiri_core/lifecycle/layer_contract_validator.py +2291 -0
- kiri_core/lifecycle/layout.py +298 -0
- kiri_core/lifecycle/onboarding.py +306 -0
- kiri_core/lifecycle/openlineage_emit.py +153 -0
- kiri_core/lifecycle/patch.py +231 -0
- kiri_core/lifecycle/plan.py +241 -0
- kiri_core/lifecycle/probe.py +204 -0
- kiri_core/lifecycle/project_context.py +334 -0
- kiri_core/lifecycle/project_status.py +532 -0
- kiri_core/lifecycle/scaffold.py +455 -0
- kiri_core/lifecycle/source_grouping.py +725 -0
- kiri_core/lifecycle/source_introspection.py +488 -0
- kiri_core/lifecycle/source_inventory.py +561 -0
- kiri_core/lifecycle/source_patch.py +353 -0
- kiri_core/lifecycle/sources.py +470 -0
- kiri_core/lifecycle/sync_from_live.py +727 -0
- kiri_core/lifecycle/validate.py +116 -0
- kiri_core/lifecycle/verify_annotations.py +391 -0
- kiri_core/lifecycle/verify_live.py +389 -0
- kiri_core/lifecycle/verify_metric_views.py +321 -0
- kiri_core/lifecycle/watermark.py +371 -0
- kiri_core/lifecycle/watermark_apply.py +656 -0
- kiri_core/lineage.py +418 -0
- kiri_core/loader.py +377 -0
- kiri_core/logging.py +374 -0
- kiri_core/maturity.py +592 -0
- kiri_core/migration_layer_policy.py +285 -0
- kiri_core/names.py +163 -0
- kiri_core/observed_lineage.py +62 -0
- kiri_core/odcs/__init__.py +15 -0
- kiri_core/otel_bootstrap.py +326 -0
- kiri_core/pipeline_ir.py +332 -0
- kiri_core/pipeline_loader.py +82 -0
- kiri_core/planning.py +110 -0
- kiri_core/project.py +1576 -0
- kiri_core/project_docs.py +199 -0
- kiri_core/py.typed +0 -0
- kiri_core/rate_limit.py +456 -0
- kiri_core/release_state/__init__.py +37 -0
- kiri_core/release_state/models.py +108 -0
- kiri_core/release_state/store.py +634 -0
- kiri_core/resilience/__init__.py +288 -0
- kiri_core/resilience/retry.py +173 -0
- kiri_core/runtime.py +40 -0
- kiri_core/schema/__init__.py +62 -0
- kiri_core/schema/column.py +140 -0
- kiri_core/schema/ingestion.py +665 -0
- kiri_core/schema/model.py +44 -0
- kiri_core/schema/reporting_goal.py +160 -0
- kiri_core/schema/source.py +188 -0
- kiri_core/silver/__init__.py +148 -0
- kiri_core/silver/apply_runner.py +830 -0
- kiri_core/silver/backfill.py +280 -0
- kiri_core/silver/dv_backend.py +426 -0
- kiri_core/silver/dv_graph.py +932 -0
- kiri_core/silver/dv_graph_mermaid.py +443 -0
- kiri_core/silver/dv_graph_payload.py +79 -0
- kiri_core/silver/dv_graph_views.py +416 -0
- kiri_core/silver/graph_projector.py +566 -0
- kiri_core/silver/hash_canonicaliser.py +172 -0
- kiri_core/silver/hash_policy.py +195 -0
- kiri_core/silver/load_ts_policy.py +202 -0
- kiri_core/silver/schema_evolution.py +410 -0
- kiri_core/silver/zone_rules.py +81 -0
- kiri_core/sizing.py +134 -0
- kiri_core/storage_paths.py +130 -0
- kiri_core/technique_validator.py +700 -0
- kiri_core/trace.py +409 -0
- kiri_core/types.py +41 -0
- kiri_core/urn.py +161 -0
- kiri_dbt_bridge/__init__.py +22 -0
- kiri_dbt_bridge/_schema_resolve.py +38 -0
- kiri_dbt_bridge/bridge.py +94 -0
- kiri_dbt_bridge/dbt.manifest.yml +26 -0
- kiri_dbt_bridge/dv4dbt/__init__.py +143 -0
- kiri_dbt_bridge/dv4dbt/backend.py +204 -0
- kiri_dbt_bridge/dv4dbt/bridge_generator.py +289 -0
- kiri_dbt_bridge/dv4dbt/compat.py +232 -0
- kiri_dbt_bridge/dv4dbt/hub_generator.py +282 -0
- kiri_dbt_bridge/dv4dbt/link_generator.py +306 -0
- kiri_dbt_bridge/dv4dbt/manifest.py +203 -0
- kiri_dbt_bridge/dv4dbt/pit_generator.py +318 -0
- kiri_dbt_bridge/dv4dbt/policy_translation.py +249 -0
- kiri_dbt_bridge/dv4dbt/project_writer.py +588 -0
- kiri_dbt_bridge/dv4dbt/ref_generator.py +404 -0
- kiri_dbt_bridge/dv4dbt/render.py +180 -0
- kiri_dbt_bridge/dv4dbt/sat_generator.py +480 -0
- kiri_dbt_bridge/dv4dbt/sat_variant_generator.py +529 -0
- kiri_dbt_bridge/dv4dbt/templates/__init__.py +16 -0
- kiri_dbt_bridge/dv4dbt/templates/bridge.sql.j2 +9 -0
- kiri_dbt_bridge/dv4dbt/templates/dbt_project.yml.j2 +37 -0
- kiri_dbt_bridge/dv4dbt/templates/hub.sql.j2 +9 -0
- kiri_dbt_bridge/dv4dbt/templates/link.sql.j2 +9 -0
- kiri_dbt_bridge/dv4dbt/templates/packages.yml.j2 +10 -0
- kiri_dbt_bridge/dv4dbt/templates/pit.sql.j2 +9 -0
- kiri_dbt_bridge/dv4dbt/templates/ref_historised.sql.j2 +9 -0
- kiri_dbt_bridge/dv4dbt/templates/ref_non_historised.sql.j2 +9 -0
- kiri_dbt_bridge/dv4dbt/templates/sat.sql.j2 +9 -0
- kiri_dbt_bridge/dv4dbt/templates/sat_effectivity.sql.j2 +9 -0
- kiri_dbt_bridge/dv4dbt/templates/sat_multi_active.sql.j2 +9 -0
- kiri_dbt_bridge/dv4dbt/templates/sat_non_historised.sql.j2 +9 -0
- kiri_dbt_bridge/dv4dbt/templates/sat_record_tracking.sql.j2 +9 -0
- kiri_dbt_bridge/dv4dbt/templates/sat_virtual_end_dated.sql.j2 +9 -0
- kiri_dbt_bridge/dv4dbt/templates/sources.yml.j2 +39 -0
- kiri_dbt_bridge/framework_adapter.py +310 -0
- kiri_dbt_bridge/generate.py +372 -0
- kiri_dbt_bridge/gold/__init__.py +21 -0
- kiri_dbt_bridge/gold/_sql_emit.py +128 -0
- kiri_dbt_bridge/gold/assemble.py +90 -0
- kiri_dbt_bridge/gold/canonical_columns.py +168 -0
- kiri_dbt_bridge/gold/computed_expressions.py +179 -0
- kiri_dbt_bridge/gold/dialects/__init__.py +69 -0
- kiri_dbt_bridge/gold/dialects/databricks.py +150 -0
- kiri_dbt_bridge/gold/dialects/duckdb.py +120 -0
- kiri_dbt_bridge/gold/dialects/trino.py +136 -0
- kiri_dbt_bridge/gold/dim_generator.py +688 -0
- kiri_dbt_bridge/gold/dimension_refs.py +255 -0
- kiri_dbt_bridge/gold/dispatch.py +69 -0
- kiri_dbt_bridge/gold/fact_generator.py +534 -0
- kiri_dbt_bridge/gold/flat_generator.py +144 -0
- kiri_dbt_bridge/gold/lineage.py +484 -0
- kiri_dbt_bridge/gold/localized_twins.py +218 -0
- kiri_dbt_bridge/gold/measures.py +49 -0
- kiri_dbt_bridge/gold/role_playing.py +75 -0
- kiri_dbt_bridge/gold/surrogate_key.py +114 -0
- kiri_dbt_bridge/macros/kiri_scd1_dim.sql +57 -0
- kiri_dbt_bridge/macros/kiri_scd2_dim.sql +188 -0
- kiri_dbt_bridge/macros/kiri_sha256.sql +32 -0
- kiri_dbt_bridge/macros/kiri_string_cast.sql +37 -0
- kiri_dbt_bridge/macros/kiri_surrogate_key.sql +43 -0
- kiri_dbt_bridge/profiles.py +162 -0
- kiri_dbt_bridge/py.typed +0 -0
- kiri_dbt_bridge/safe_ident.py +44 -0
- kiri_dbt_bridge/silver_conformance.py +188 -0
- kiri_dbt_bridge/silver_kimball/__init__.py +27 -0
- kiri_dbt_bridge/silver_kimball/assemble.py +174 -0
- kiri_dbt_bridge/silver_kimball/dim_generator.py +602 -0
- kiri_dbt_bridge/silver_kimball/dispatch.py +86 -0
- kiri_dbt_bridge/silver_kimball/fact_generator.py +320 -0
- kiri_dbt_bridge/silver_kimball/flat_generator.py +156 -0
- kiri_dbt_bridge/silver_kimball/lineage.py +464 -0
- kiri_dbt_bridge/silver_kimball/watermark_window.py +199 -0
- kiri_dbt_bridge/silver_kimball/zone.py +68 -0
- kiri_dbt_bridge/silver_scd2_curated/__init__.py +46 -0
- kiri_dbt_bridge/silver_scd2_curated/assemble.py +213 -0
- kiri_dbt_bridge/silver_scd2_curated/curated_generator.py +351 -0
- kiri_dbt_bridge/silver_scd2_curated/standardized_generator.py +261 -0
- kiri_dbt_bridge/silver_scd2_curated/standardized_plan.py +254 -0
- kiri_iceberg/__init__.py +11 -0
- kiri_iceberg/catalog.py +128 -0
- kiri_iceberg/profile.py +131 -0
- kiri_iceberg/py.typed +0 -0
- kiri_ingestion/__init__.py +63 -0
- kiri_ingestion/airbyte_client.py +428 -0
- kiri_ingestion/database_fetcher.py +224 -0
- kiri_ingestion/layout.py +71 -0
- kiri_ingestion/mssql_introspect.py +565 -0
- kiri_ingestion/py.typed +0 -0
- kiri_ingestion/rest_fetcher.py +472 -0
- kiri_ingestion/runner.py +341 -0
- kiri_ingestion/schema_drift.py +222 -0
- kiri_ingestion/storage.py +621 -0
- kiri_mcp/__init__.py +5 -0
- kiri_mcp/audit.py +99 -0
- kiri_mcp/audit_decorators.py +252 -0
- kiri_mcp/main.py +22 -0
- kiri_mcp/py.typed +0 -0
- kiri_mcp/rbac.py +110 -0
- kiri_mcp/server.py +766 -0
- kiri_mcp/skills.py +297 -0
- kiri_mcp/tools/__init__.py +70 -0
- kiri_mcp/tools/_shared.py +58 -0
- kiri_mcp/tools/adapter.py +150 -0
- kiri_mcp/tools/cli_reference.py +59 -0
- kiri_mcp/tools/compliance.py +119 -0
- kiri_mcp/tools/contract.py +374 -0
- kiri_mcp/tools/dv.py +62 -0
- kiri_mcp/tools/federation.py +152 -0
- kiri_mcp/tools/intent.py +71 -0
- kiri_mcp/tools/lineage.py +204 -0
- kiri_mcp/tools/lineage_drift.py +75 -0
- kiri_mcp/tools/plan.py +66 -0
- kiri_mcp/tools/project.py +311 -0
- kiri_migration/__init__.py +56 -0
- kiri_migration/name_preservation.py +271 -0
- kiri_migration/predeploy.py +311 -0
- kiri_migration/py.typed +0 -0
- kiri_migration/reconcile.py +1891 -0
- kiri_migration/scaffold/__init__.py +90 -0
- kiri_migration/scaffold/_audit_columns.py +74 -0
- kiri_migration/scaffold/_classify.py +100 -0
- kiri_migration/scaffold/_typemap.py +71 -0
- kiri_migration/scaffold/_yaml.py +33 -0
- kiri_migration/scaffold/bronze.py +279 -0
- kiri_migration/scaffold/compile_targets/__init__.py +271 -0
- kiri_migration/scaffold/compile_targets/_common.py +193 -0
- kiri_migration/scaffold/compile_targets/copy_into.py +33 -0
- kiri_migration/scaffold/compile_targets/dlt.py +125 -0
- kiri_migration/scaffold/compile_targets/dlt_with_append_flows.py +163 -0
- kiri_migration/scaffold/compile_targets/dlt_with_apply_changes.py +192 -0
- kiri_migration/scaffold/compile_targets/dlt_with_expectations.py +276 -0
- kiri_migration/scaffold/compile_targets/layer_dispatch.py +258 -0
- kiri_migration/scaffold/compile_targets/sql_full_snapshot.py +385 -0
- kiri_migration/scaffold/compile_targets/streaming_table.py +37 -0
- kiri_migration/scaffold/mart.py +234 -0
- kiri_migration/scaffold/plans/__init__.py +49 -0
- kiri_migration/scaffold/plans/_registry.py +308 -0
- kiri_migration/scaffold/plans/minimal.py +550 -0
- kiri_migration/scaffold/plans/plan_b_layer.py +640 -0
- kiri_migration/scaffold/silver_from_marts.py +256 -0
- kiri_migration/scaffold/silver_pattern.py +451 -0
- kiri_migration/sp_translator.py +398 -0
- kiri_odcs/LICENSE +204 -0
- kiri_odcs/__init__.py +234 -0
- kiri_odcs/ai_trust_tier.py +475 -0
- kiri_odcs/canonical.py +263 -0
- kiri_odcs/diff.py +63 -0
- kiri_odcs/errors.py +51 -0
- kiri_odcs/invariant_sql.py +136 -0
- kiri_odcs/kiri_bridge.py +71 -0
- kiri_odcs/kiri_ext.py +3166 -0
- kiri_odcs/kiri_fk.py +447 -0
- kiri_odcs/kiri_pit.py +121 -0
- kiri_odcs/kiri_ref.py +57 -0
- kiri_odcs/openlineage_adapter.py +105 -0
- kiri_odcs/parser.py +1213 -0
- kiri_odcs/pii_guardrails.py +159 -0
- kiri_odcs/py.typed +0 -0
- kiri_odcs/schema_export.py +223 -0
- kiri_openlineage/__init__.py +58 -0
- kiri_openlineage/builder.py +146 -0
- kiri_openlineage/cost.py +190 -0
- kiri_openlineage/emitter.py +165 -0
- kiri_openlineage/py.typed +0 -0
- kiri_openlineage/schema.py +121 -0
- kiri_packs/__init__.py +67 -0
- kiri_packs/installer.py +500 -0
- kiri_packs/manifest.py +134 -0
- kiri_packs/publisher.py +605 -0
- kiri_packs/py.typed +0 -0
- kiri_packs/validator.py +295 -0
- kiri_quality/__init__.py +108 -0
- kiri_quality/ai_audit_correlator.py +293 -0
- kiri_quality/ai_audit_correlator_apply.py +526 -0
- kiri_quality/conformance_evidence.py +272 -0
- kiri_quality/dbt_test_collector.py +214 -0
- kiri_quality/dq_codegen.py +300 -0
- kiri_quality/dv_checks.py +393 -0
- kiri_quality/dv_column_lineage.py +279 -0
- kiri_quality/dv_column_lineage_apply.py +856 -0
- kiri_quality/dv_conformance.py +841 -0
- kiri_quality/dv_fixture_runner.py +478 -0
- kiri_quality/dv_invariants.py +986 -0
- kiri_quality/dv_kiri.py +97 -0
- kiri_quality/dv_markdown.py +169 -0
- kiri_quality/dv_observed_uc_reader.py +187 -0
- kiri_quality/dv_runner.py +389 -0
- kiri_quality/evidence_status.py +337 -0
- kiri_quality/fk_checks.py +800 -0
- kiri_quality/fk_ddl.py +402 -0
- kiri_quality/fk_pit_contract.py +168 -0
- kiri_quality/fk_runner.py +950 -0
- kiri_quality/kernel.py +376 -0
- kiri_quality/pii.py +60 -0
- kiri_quality/pre_classify/__init__.py +55 -0
- kiri_quality/pre_classify/engine.py +346 -0
- kiri_quality/pre_classify/locale_packs/en.yaml +117 -0
- kiri_quality/pre_classify/locale_packs/sv.yaml +73 -0
- kiri_quality/pre_classify/merge.py +297 -0
- kiri_quality/pre_classify/model.py +163 -0
- kiri_quality/py.typed +0 -0
- kiri_quality/reject_lane.py +261 -0
- kiri_quality/rule_runner.py +565 -0
- kiri_quality/schema_evolution_check.py +88 -0
- kiri_quality/silver_quality.py +494 -0
- kiri_quality/silver_quality_markdown.py +187 -0
- kiri_quality/uc_security_compile.py +951 -0
- kiri_quality/uc_security_compile_apply.py +1186 -0
- kiri_rbac/__init__.py +87 -0
- kiri_rbac/access.py +371 -0
- kiri_rbac/loader.py +104 -0
- kiri_rbac/pr_gate.py +213 -0
- kiri_rbac/providers/__init__.py +29 -0
- kiri_rbac/providers/_jwt.py +121 -0
- kiri_rbac/providers/azure_ad.py +93 -0
- kiri_rbac/providers/base.py +107 -0
- kiri_rbac/providers/factory.py +48 -0
- kiri_rbac/providers/github.py +134 -0
- kiri_rbac/providers/oidc_generic.py +99 -0
- kiri_rbac/py.typed +0 -0
- kiri_rbac/resolver.py +118 -0
- kiri_rbac/schema.py +219 -0
- kiri_schema_registry/__init__.py +22 -0
- kiri_schema_registry/avro.py +127 -0
- kiri_schema_registry/base.py +71 -0
- kiri_schema_registry/confluent.py +184 -0
- kiri_schema_registry/glue.py +178 -0
- kiri_schema_registry/py.typed +0 -0
- kiri_slack_bot/__init__.py +24 -0
- kiri_slack_bot/handler.py +248 -0
- kiri_slack_bot/py.typed +0 -0
- kiri_slack_bot/queries.py +90 -0
- kiri_slack_bot/signature.py +94 -0
- kiri_vault/__init__.py +59 -0
- kiri_vault/audit.py +64 -0
- kiri_vault/aws_vault.py +123 -0
- kiri_vault/azure_vault.py +236 -0
- kiri_vault/boundaries.py +219 -0
- kiri_vault/client.py +54 -0
- kiri_vault/databricks_vault.py +292 -0
- kiri_vault/env_vault.py +77 -0
- kiri_vault/py.typed +0 -0
- kiri_vault/ref.py +84 -0
- kirimana_automation/__init__.py +0 -0
- kirimana_automation/business_calendar.py +179 -0
- kirimana_automation/dag_snapshot.py +252 -0
- kirimana_automation/db/__init__.py +5 -0
- kirimana_automation/db/migrate.py +571 -0
- kirimana_automation/db/migrations/0001_release_state.down.duckdb.sql +29 -0
- kirimana_automation/db/migrations/0001_release_state.down.postgres.sql +37 -0
- kirimana_automation/db/migrations/0001_release_state.duckdb.sql +170 -0
- kirimana_automation/db/migrations/0001_release_state.postgres.sql +176 -0
- kirimana_automation/db/migrations/0002_rate_limit_buckets.down.duckdb.sql +9 -0
- kirimana_automation/db/migrations/0002_rate_limit_buckets.down.postgres.sql +9 -0
- kirimana_automation/db/migrations/0002_rate_limit_buckets.duckdb.sql +14 -0
- kirimana_automation/db/migrations/0002_rate_limit_buckets.postgres.sql +18 -0
- kirimana_automation/db/migrations/0003_flow_runs.down.duckdb.sql +18 -0
- kirimana_automation/db/migrations/0003_flow_runs.down.postgres.sql +19 -0
- kirimana_automation/db/migrations/0003_flow_runs.duckdb.sql +85 -0
- kirimana_automation/db/migrations/0003_flow_runs.postgres.sql +102 -0
- kirimana_automation/db/migrations/0004_flow_dag_snapshots.down.duckdb.sql +11 -0
- kirimana_automation/db/migrations/0004_flow_dag_snapshots.down.postgres.sql +11 -0
- kirimana_automation/db/migrations/0004_flow_dag_snapshots.duckdb.sql +15 -0
- kirimana_automation/db/migrations/0004_flow_dag_snapshots.postgres.sql +31 -0
- kirimana_automation/db/migrations/0005_calendar_skip.down.duckdb.sql +109 -0
- kirimana_automation/db/migrations/0005_calendar_skip.down.postgres.sql +42 -0
- kirimana_automation/db/migrations/0005_calendar_skip.duckdb.sql +113 -0
- kirimana_automation/db/migrations/0005_calendar_skip.postgres.sql +42 -0
- kirimana_automation/db/migrations/0006_flow_apply_log.down.duckdb.sql +9 -0
- kirimana_automation/db/migrations/0006_flow_apply_log.down.postgres.sql +11 -0
- kirimana_automation/db/migrations/0006_flow_apply_log.duckdb.sql +45 -0
- kirimana_automation/db/migrations/0006_flow_apply_log.postgres.sql +49 -0
- kirimana_automation/db/migrations/0007_flow_task_error_redacted.down.duckdb.sql +14 -0
- kirimana_automation/db/migrations/0007_flow_task_error_redacted.down.postgres.sql +2 -0
- kirimana_automation/db/migrations/0007_flow_task_error_redacted.duckdb.sql +7 -0
- kirimana_automation/db/migrations/0007_flow_task_error_redacted.postgres.sql +11 -0
- kirimana_automation/db/migrations/README.md +84 -0
- kirimana_automation/flow_loader.py +220 -0
- kirimana_automation/freeze.py +133 -0
- kirimana_automation/kiri_recommender.py +313 -0
- kirimana_automation/main.py +195 -0
- kirimana_automation/py.typed +0 -0
- kirimana_automation/run_history.py +593 -0
- kirimana_automation/run_history_analysis.py +607 -0
- kirimana_automation/schedule.py +343 -0
- kirimana_automation/schedule_validator.py +572 -0
- kirimana_automation/workflow_compiler.py +1288 -0
|
@@ -0,0 +1,488 @@
|
|
|
1
|
+
"""Deterministic source-schema introspection helpers (ADR 0090)."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import json
|
|
6
|
+
import re
|
|
7
|
+
from collections import Counter
|
|
8
|
+
from datetime import datetime
|
|
9
|
+
from pathlib import Path
|
|
10
|
+
from typing import Any, Protocol
|
|
11
|
+
|
|
12
|
+
import yaml
|
|
13
|
+
from pydantic import BaseModel, ConfigDict
|
|
14
|
+
|
|
15
|
+
from kiri_core.schema.ingestion import (
|
|
16
|
+
DatabaseSpec,
|
|
17
|
+
IngestionFormat,
|
|
18
|
+
IngestionMode,
|
|
19
|
+
RestApiIncremental,
|
|
20
|
+
RestApiPagination,
|
|
21
|
+
RestApiSpec,
|
|
22
|
+
)
|
|
23
|
+
from kiri_core.schema.source import Source
|
|
24
|
+
from kiri_core.types import Classification
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class InferredColumn(BaseModel):
|
|
28
|
+
model_config = ConfigDict(extra="forbid", frozen=True)
|
|
29
|
+
|
|
30
|
+
name: str
|
|
31
|
+
data_type: str = "string"
|
|
32
|
+
nullable: bool = True
|
|
33
|
+
description: str | None = None
|
|
34
|
+
pii: bool = False
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
class RestApiIntrospectionResult(BaseModel):
|
|
38
|
+
model_config = ConfigDict(extra="forbid", frozen=True)
|
|
39
|
+
|
|
40
|
+
record_path: str | None
|
|
41
|
+
columns: tuple[InferredColumn, ...]
|
|
42
|
+
pagination: RestApiPagination
|
|
43
|
+
incremental: RestApiIncremental | None = None
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
class SourceIntrospectionDraft(BaseModel):
|
|
47
|
+
model_config = ConfigDict(extra="forbid", frozen=True)
|
|
48
|
+
|
|
49
|
+
yaml_text: str
|
|
50
|
+
source_path: Path | None = None
|
|
51
|
+
note: str
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
class AIColumnAnnotation(BaseModel):
|
|
55
|
+
model_config = ConfigDict(extra="forbid", frozen=True)
|
|
56
|
+
|
|
57
|
+
name: str
|
|
58
|
+
data_type: str | None = None
|
|
59
|
+
description: str | None = None
|
|
60
|
+
pii: bool | None = None
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
class AISchemaAnnotations(BaseModel):
|
|
64
|
+
model_config = ConfigDict(extra="forbid", frozen=True)
|
|
65
|
+
|
|
66
|
+
columns: tuple[AIColumnAnnotation, ...] = ()
|
|
67
|
+
watermark_column: str | None = None
|
|
68
|
+
watermark_reason: str | None = None
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
class AIGatewayLike(Protocol):
|
|
72
|
+
def generate(
|
|
73
|
+
self,
|
|
74
|
+
*,
|
|
75
|
+
prompt_name: str,
|
|
76
|
+
variables: dict[str, Any],
|
|
77
|
+
caller: str,
|
|
78
|
+
classification: str,
|
|
79
|
+
) -> Any: ...
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
_TIMESTAMP_NAME_RE = re.compile(
|
|
83
|
+
r"(updated|modified|created|time|timestamp|date|datum|systemdatum)",
|
|
84
|
+
re.I,
|
|
85
|
+
)
|
|
86
|
+
_ID_NAME_RE = re.compile(r"(^id$|_id$|id$|nummer$|nr$)", re.I)
|
|
87
|
+
_PII_NAME_RE = re.compile(r"(email|e[-_]?post|phone|telefon|personnummer|ssn|name|namn)", re.I)
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
def infer_rest_api_schema(payload: Any) -> RestApiIntrospectionResult:
|
|
91
|
+
"""Infer record path, columns, pagination, and watermark from one JSON payload."""
|
|
92
|
+
record_path, rows = _best_record_array(payload)
|
|
93
|
+
if not rows:
|
|
94
|
+
raise ValueError("sample JSON contains no array of object records to introspect")
|
|
95
|
+
columns = _infer_columns(rows)
|
|
96
|
+
pagination = _infer_pagination(payload)
|
|
97
|
+
incremental = _infer_incremental(columns)
|
|
98
|
+
return RestApiIntrospectionResult(
|
|
99
|
+
record_path=record_path,
|
|
100
|
+
columns=tuple(columns),
|
|
101
|
+
pagination=pagination,
|
|
102
|
+
incremental=incremental,
|
|
103
|
+
)
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
def apply_ai_annotations(
|
|
107
|
+
deterministic: RestApiIntrospectionResult,
|
|
108
|
+
annotations: AISchemaAnnotations,
|
|
109
|
+
) -> RestApiIntrospectionResult:
|
|
110
|
+
"""Apply AI annotations only to columns that exist in the deterministic sample."""
|
|
111
|
+
by_name = {col.name: col for col in deterministic.columns}
|
|
112
|
+
updated = dict(by_name)
|
|
113
|
+
for ann in annotations.columns:
|
|
114
|
+
current = updated.get(ann.name)
|
|
115
|
+
if current is None:
|
|
116
|
+
continue
|
|
117
|
+
data_type = ann.data_type if ann.data_type in _TYPE_VOCAB else current.data_type
|
|
118
|
+
updated[ann.name] = current.model_copy(
|
|
119
|
+
update={
|
|
120
|
+
"data_type": data_type,
|
|
121
|
+
"description": ann.description or current.description,
|
|
122
|
+
"pii": current.pii if ann.pii is None else bool(ann.pii),
|
|
123
|
+
}
|
|
124
|
+
)
|
|
125
|
+
incremental = deterministic.incremental
|
|
126
|
+
if annotations.watermark_column in by_name:
|
|
127
|
+
incremental = RestApiIncremental(
|
|
128
|
+
incremental_column=str(annotations.watermark_column),
|
|
129
|
+
request_param="since",
|
|
130
|
+
)
|
|
131
|
+
return deterministic.model_copy(
|
|
132
|
+
update={
|
|
133
|
+
"columns": tuple(updated[name] for name in by_name),
|
|
134
|
+
"incremental": incremental,
|
|
135
|
+
}
|
|
136
|
+
)
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
def infer_with_ai(
|
|
140
|
+
deterministic: RestApiIntrospectionResult,
|
|
141
|
+
sample_rows: list[dict[str, Any]],
|
|
142
|
+
*,
|
|
143
|
+
gateway: AIGatewayLike,
|
|
144
|
+
source_name: str,
|
|
145
|
+
table_name: str,
|
|
146
|
+
classification: str,
|
|
147
|
+
) -> RestApiIntrospectionResult:
|
|
148
|
+
"""Ask the audited AI gateway for semantic annotations over sampled columns."""
|
|
149
|
+
response = gateway.generate(
|
|
150
|
+
prompt_name="infer_source_schema",
|
|
151
|
+
variables={
|
|
152
|
+
"source_name": source_name,
|
|
153
|
+
"table_name": table_name,
|
|
154
|
+
"columns": json.dumps([c.model_dump() for c in deterministic.columns], sort_keys=True),
|
|
155
|
+
"sample_rows": json.dumps(sample_rows[:20], default=str, sort_keys=True),
|
|
156
|
+
},
|
|
157
|
+
caller="cli.source.introspect.ai",
|
|
158
|
+
classification=classification,
|
|
159
|
+
)
|
|
160
|
+
raw = _strip_fences(str(response.content))
|
|
161
|
+
data = json.loads(raw)
|
|
162
|
+
annotations = AISchemaAnnotations.model_validate(data)
|
|
163
|
+
return apply_ai_annotations(deterministic, annotations)
|
|
164
|
+
|
|
165
|
+
|
|
166
|
+
def render_rest_api_source_draft(
|
|
167
|
+
*,
|
|
168
|
+
project_root: Path,
|
|
169
|
+
source_name: str,
|
|
170
|
+
owner: str,
|
|
171
|
+
classification: Classification,
|
|
172
|
+
domain: str,
|
|
173
|
+
table_name: str,
|
|
174
|
+
rest_api: RestApiSpec,
|
|
175
|
+
inference: RestApiIntrospectionResult,
|
|
176
|
+
ai_used: bool = False,
|
|
177
|
+
) -> SourceIntrospectionDraft:
|
|
178
|
+
"""Render an unreviewed source YAML draft from REST introspection."""
|
|
179
|
+
rest_payload = rest_api.model_copy(
|
|
180
|
+
update={
|
|
181
|
+
"record_path": inference.record_path,
|
|
182
|
+
"pagination": inference.pagination,
|
|
183
|
+
"incremental": inference.incremental,
|
|
184
|
+
}
|
|
185
|
+
).model_dump(mode="python", exclude_none=True)
|
|
186
|
+
payload = _source_payload(
|
|
187
|
+
source_name=source_name,
|
|
188
|
+
owner=owner,
|
|
189
|
+
classification=classification,
|
|
190
|
+
domain=domain,
|
|
191
|
+
table_name=table_name,
|
|
192
|
+
columns=inference.columns,
|
|
193
|
+
ingestion={
|
|
194
|
+
"mode": IngestionMode.REST_API.value,
|
|
195
|
+
"format": IngestionFormat.JSON.value,
|
|
196
|
+
"rest_api": rest_payload,
|
|
197
|
+
},
|
|
198
|
+
note=(
|
|
199
|
+
"UNREVIEWED source-introspection draft. Review inferred columns/types and "
|
|
200
|
+
"remove kiri.introspection_unreviewed to accept."
|
|
201
|
+
+ (" AI annotations were applied through AIGateway." if ai_used else "")
|
|
202
|
+
),
|
|
203
|
+
)
|
|
204
|
+
Source.model_validate(payload["sources"][0])
|
|
205
|
+
return SourceIntrospectionDraft(
|
|
206
|
+
yaml_text=str(yaml.safe_dump(payload, sort_keys=False, default_flow_style=False)),
|
|
207
|
+
source_path=project_root / "sources" / f"{source_name}.yml",
|
|
208
|
+
note=payload["sources"][0]["kiri"]["introspection_note"],
|
|
209
|
+
)
|
|
210
|
+
|
|
211
|
+
|
|
212
|
+
def render_database_source_draft(
|
|
213
|
+
*,
|
|
214
|
+
project_root: Path,
|
|
215
|
+
source_name: str,
|
|
216
|
+
owner: str,
|
|
217
|
+
classification: Classification,
|
|
218
|
+
domain: str,
|
|
219
|
+
table_name: str,
|
|
220
|
+
database: DatabaseSpec,
|
|
221
|
+
columns: tuple[InferredColumn, ...],
|
|
222
|
+
) -> SourceIntrospectionDraft:
|
|
223
|
+
payload = _source_payload(
|
|
224
|
+
source_name=source_name,
|
|
225
|
+
owner=owner,
|
|
226
|
+
classification=classification,
|
|
227
|
+
domain=domain,
|
|
228
|
+
table_name=table_name,
|
|
229
|
+
columns=columns,
|
|
230
|
+
ingestion={
|
|
231
|
+
"mode": IngestionMode.DATABASE.value,
|
|
232
|
+
"format": IngestionFormat.JSON.value,
|
|
233
|
+
"database": database.model_dump(mode="python", exclude_none=True),
|
|
234
|
+
},
|
|
235
|
+
note=(
|
|
236
|
+
"UNREVIEWED database-catalog introspection draft. Review inferred "
|
|
237
|
+
"columns/types and remove kiri.introspection_unreviewed to accept."
|
|
238
|
+
),
|
|
239
|
+
)
|
|
240
|
+
Source.model_validate(payload["sources"][0])
|
|
241
|
+
return SourceIntrospectionDraft(
|
|
242
|
+
yaml_text=str(yaml.safe_dump(payload, sort_keys=False, default_flow_style=False)),
|
|
243
|
+
source_path=project_root / "sources" / f"{source_name}.yml",
|
|
244
|
+
note=payload["sources"][0]["kiri"]["introspection_note"],
|
|
245
|
+
)
|
|
246
|
+
|
|
247
|
+
|
|
248
|
+
def _source_payload(
|
|
249
|
+
*,
|
|
250
|
+
source_name: str,
|
|
251
|
+
owner: str,
|
|
252
|
+
classification: Classification,
|
|
253
|
+
domain: str,
|
|
254
|
+
table_name: str,
|
|
255
|
+
columns: tuple[InferredColumn, ...],
|
|
256
|
+
ingestion: dict[str, Any],
|
|
257
|
+
note: str,
|
|
258
|
+
) -> dict[str, Any]:
|
|
259
|
+
return {
|
|
260
|
+
"sources": [
|
|
261
|
+
{
|
|
262
|
+
"name": source_name,
|
|
263
|
+
"owner": owner,
|
|
264
|
+
"classification": classification.value,
|
|
265
|
+
"domain": domain,
|
|
266
|
+
"kiri": {
|
|
267
|
+
"introspection_unreviewed": True,
|
|
268
|
+
"introspection_note": note,
|
|
269
|
+
},
|
|
270
|
+
"tables": [
|
|
271
|
+
{
|
|
272
|
+
"name": table_name,
|
|
273
|
+
"columns": [_column_source_payload(col) for col in columns],
|
|
274
|
+
"ingestion": ingestion,
|
|
275
|
+
}
|
|
276
|
+
],
|
|
277
|
+
}
|
|
278
|
+
]
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
|
|
282
|
+
def _column_source_payload(col: InferredColumn) -> dict[str, Any]:
|
|
283
|
+
payload: dict[str, Any] = {
|
|
284
|
+
"name": col.name,
|
|
285
|
+
"data_type": col.data_type,
|
|
286
|
+
}
|
|
287
|
+
if col.description:
|
|
288
|
+
payload["description"] = col.description
|
|
289
|
+
if col.pii:
|
|
290
|
+
payload["pii"] = True
|
|
291
|
+
return payload
|
|
292
|
+
|
|
293
|
+
|
|
294
|
+
def _best_record_array(payload: Any) -> tuple[str | None, list[dict[str, Any]]]:
|
|
295
|
+
candidates: list[tuple[str | None, list[dict[str, Any]], int]] = []
|
|
296
|
+
|
|
297
|
+
def walk(value: Any, path: str | None) -> None:
|
|
298
|
+
if isinstance(value, list):
|
|
299
|
+
if value and all(isinstance(item, dict) for item in value):
|
|
300
|
+
rows = [item for item in value if isinstance(item, dict)]
|
|
301
|
+
if _has_homogeneous_keys(rows) and _has_record_scalar(rows):
|
|
302
|
+
common_keys = set(rows[0])
|
|
303
|
+
for row in rows[1:]:
|
|
304
|
+
common_keys &= set(row)
|
|
305
|
+
candidates.append((path, rows, len(rows) * 10 + len(common_keys)))
|
|
306
|
+
return
|
|
307
|
+
if isinstance(value, dict):
|
|
308
|
+
for key, child in value.items():
|
|
309
|
+
child_path = key if path in (None, "") else f"{path}.{key}"
|
|
310
|
+
walk(child, child_path)
|
|
311
|
+
|
|
312
|
+
walk(payload, None)
|
|
313
|
+
if not candidates:
|
|
314
|
+
return None, []
|
|
315
|
+
path, rows, _score = max(
|
|
316
|
+
candidates,
|
|
317
|
+
key=lambda c: (c[2], 0 if c[0] is None else c[0].count(".")),
|
|
318
|
+
)
|
|
319
|
+
return path, rows
|
|
320
|
+
|
|
321
|
+
|
|
322
|
+
def _has_homogeneous_keys(rows: list[dict[str, Any]]) -> bool:
|
|
323
|
+
"""Accept record arrays only when every object has the same key set.
|
|
324
|
+
|
|
325
|
+
ADR 0090 drafts must be deterministic and must not union unrelated object
|
|
326
|
+
shapes. Exact key-set equality keeps array detection explainable and fail
|
|
327
|
+
closed for mixed envelopes such as ``[{id, name}, {error, message}]``.
|
|
328
|
+
"""
|
|
329
|
+
if not rows:
|
|
330
|
+
return False
|
|
331
|
+
modal = set(rows[0])
|
|
332
|
+
return all(set(row) == modal for row in rows[1:])
|
|
333
|
+
|
|
334
|
+
|
|
335
|
+
def _has_record_scalar(rows: list[dict[str, Any]]) -> bool:
|
|
336
|
+
return any(
|
|
337
|
+
not isinstance(value, (dict, list))
|
|
338
|
+
for row in rows
|
|
339
|
+
for value in row.values()
|
|
340
|
+
if value is not None
|
|
341
|
+
)
|
|
342
|
+
|
|
343
|
+
|
|
344
|
+
def _infer_pagination(payload: Any) -> RestApiPagination:
|
|
345
|
+
"""Infer pagination only from explicit payload evidence.
|
|
346
|
+
|
|
347
|
+
Non-paginated payloads default to ``kind='none'`` so unreviewed drafts do
|
|
348
|
+
not fabricate page parameters.
|
|
349
|
+
"""
|
|
350
|
+
keys = _payload_keys(payload)
|
|
351
|
+
cursor_markers = {"cursor", "next_cursor", "nextCursor", "next"}
|
|
352
|
+
page_markers = {"page", "total_pages", "totalPages", "offset", "limit", "per_page"}
|
|
353
|
+
if keys & cursor_markers:
|
|
354
|
+
return RestApiPagination(kind="cursor", cursor_param="cursor")
|
|
355
|
+
if keys & page_markers:
|
|
356
|
+
return RestApiPagination(kind="page", page_param="page", limit_param="limit")
|
|
357
|
+
return RestApiPagination(kind="none")
|
|
358
|
+
|
|
359
|
+
|
|
360
|
+
def _payload_keys(payload: Any) -> set[str]:
|
|
361
|
+
keys: set[str] = set()
|
|
362
|
+
|
|
363
|
+
def walk(value: Any) -> None:
|
|
364
|
+
if isinstance(value, dict):
|
|
365
|
+
keys.update(str(key) for key in value)
|
|
366
|
+
for child in value.values():
|
|
367
|
+
walk(child)
|
|
368
|
+
elif isinstance(value, list):
|
|
369
|
+
for item in value[:5]:
|
|
370
|
+
walk(item)
|
|
371
|
+
|
|
372
|
+
walk(payload)
|
|
373
|
+
return keys
|
|
374
|
+
|
|
375
|
+
|
|
376
|
+
_TYPE_VOCAB = {"boolean", "int64", "float64", "timestamp", "date", "string"}
|
|
377
|
+
|
|
378
|
+
|
|
379
|
+
def _infer_columns(rows: list[dict[str, Any]]) -> list[InferredColumn]:
|
|
380
|
+
names = sorted({name for row in rows for name in row})
|
|
381
|
+
columns: list[InferredColumn] = []
|
|
382
|
+
for name in names:
|
|
383
|
+
values = [row.get(name) for row in rows]
|
|
384
|
+
non_null = [v for v in values if v is not None]
|
|
385
|
+
columns.append(
|
|
386
|
+
InferredColumn(
|
|
387
|
+
name=name,
|
|
388
|
+
data_type=_infer_type(name, non_null),
|
|
389
|
+
nullable=len(non_null) != len(values),
|
|
390
|
+
pii=bool(_PII_NAME_RE.search(name)),
|
|
391
|
+
)
|
|
392
|
+
)
|
|
393
|
+
return columns
|
|
394
|
+
|
|
395
|
+
|
|
396
|
+
def _infer_type(name: str, values: list[Any]) -> str:
|
|
397
|
+
if not values:
|
|
398
|
+
return "string"
|
|
399
|
+
if all(isinstance(v, bool) for v in values):
|
|
400
|
+
return "boolean"
|
|
401
|
+
if all(isinstance(v, int) and not isinstance(v, bool) for v in values):
|
|
402
|
+
return "int64"
|
|
403
|
+
if all(isinstance(v, (int, float)) and not isinstance(v, bool) for v in values):
|
|
404
|
+
return "float64"
|
|
405
|
+
if all(isinstance(v, str) and _looks_like_datetime(v) for v in values):
|
|
406
|
+
return "timestamp" if _TIMESTAMP_NAME_RE.search(name) else "date"
|
|
407
|
+
return "string"
|
|
408
|
+
|
|
409
|
+
|
|
410
|
+
def _looks_like_datetime(value: str) -> bool:
|
|
411
|
+
s = value.strip()
|
|
412
|
+
if not s:
|
|
413
|
+
return False
|
|
414
|
+
for candidate in (s, s.replace("Z", "+00:00")):
|
|
415
|
+
try:
|
|
416
|
+
datetime.fromisoformat(candidate)
|
|
417
|
+
return True
|
|
418
|
+
except ValueError:
|
|
419
|
+
continue
|
|
420
|
+
return False
|
|
421
|
+
|
|
422
|
+
|
|
423
|
+
def _infer_incremental(columns: list[InferredColumn]) -> RestApiIncremental | None:
|
|
424
|
+
by_type = [c for c in columns if c.data_type in {"timestamp", "date"}]
|
|
425
|
+
named_time = [c for c in by_type if _TIMESTAMP_NAME_RE.search(c.name)]
|
|
426
|
+
if named_time:
|
|
427
|
+
return RestApiIncremental(incremental_column=named_time[0].name, request_param="since")
|
|
428
|
+
named_id = [c for c in columns if c.data_type == "int64" and _ID_NAME_RE.search(c.name)]
|
|
429
|
+
if named_id:
|
|
430
|
+
return RestApiIncremental(incremental_column=named_id[0].name, request_param="since")
|
|
431
|
+
return None
|
|
432
|
+
|
|
433
|
+
|
|
434
|
+
def columns_from_database_catalog(rows: list[tuple[str, str, bool]]) -> tuple[InferredColumn, ...]:
|
|
435
|
+
"""Convert database catalog rows ``(name, db_type, nullable)`` to source columns."""
|
|
436
|
+
return tuple(
|
|
437
|
+
InferredColumn(name=name, data_type=_map_database_type(db_type), nullable=nullable)
|
|
438
|
+
for name, db_type, nullable in rows
|
|
439
|
+
)
|
|
440
|
+
|
|
441
|
+
|
|
442
|
+
def _map_database_type(db_type: str) -> str:
|
|
443
|
+
t = db_type.lower()
|
|
444
|
+
if any(part in t for part in ("int", "serial", "number")):
|
|
445
|
+
return "int64"
|
|
446
|
+
if any(part in t for part in ("double", "float", "real", "decimal", "numeric")):
|
|
447
|
+
return "float64"
|
|
448
|
+
if "bool" in t:
|
|
449
|
+
return "boolean"
|
|
450
|
+
if any(part in t for part in ("timestamp", "datetime")):
|
|
451
|
+
return "timestamp"
|
|
452
|
+
if "date" in t:
|
|
453
|
+
return "date"
|
|
454
|
+
return "string"
|
|
455
|
+
|
|
456
|
+
|
|
457
|
+
def _strip_fences(text: str) -> str:
|
|
458
|
+
s = text.strip()
|
|
459
|
+
if not s.startswith("```"):
|
|
460
|
+
return s
|
|
461
|
+
lines = s.splitlines()
|
|
462
|
+
if lines and lines[0].startswith("```"):
|
|
463
|
+
lines = lines[1:]
|
|
464
|
+
if lines and lines[-1].startswith("```"):
|
|
465
|
+
lines = lines[:-1]
|
|
466
|
+
return "\n".join(lines).strip()
|
|
467
|
+
|
|
468
|
+
|
|
469
|
+
def sample_rows_at_path(payload: Any, record_path: str | None) -> list[dict[str, Any]]:
|
|
470
|
+
current = resolve_dotted_path(payload, record_path)
|
|
471
|
+
if isinstance(current, list):
|
|
472
|
+
return [item for item in current if isinstance(item, dict)]
|
|
473
|
+
return []
|
|
474
|
+
|
|
475
|
+
|
|
476
|
+
def resolve_dotted_path(payload: Any, record_path: str | None) -> Any:
|
|
477
|
+
"""Resolve an ADR 0090 dotted JSON object path using literal key names."""
|
|
478
|
+
current = payload
|
|
479
|
+
if record_path:
|
|
480
|
+
for segment in record_path.split("."):
|
|
481
|
+
if not isinstance(current, dict):
|
|
482
|
+
return None
|
|
483
|
+
current = current.get(segment)
|
|
484
|
+
return current
|
|
485
|
+
|
|
486
|
+
|
|
487
|
+
def column_type_counts(columns: tuple[InferredColumn, ...]) -> Counter[str]:
|
|
488
|
+
return Counter(col.data_type for col in columns)
|