xp-libs-analytics 0.4.0__tar.gz → 0.5.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {xp_libs_analytics-0.4.0 → xp_libs_analytics-0.5.0}/.github/pull_request_template.md +2 -2
- {xp_libs_analytics-0.4.0 → xp_libs_analytics-0.5.0}/.github/workflows/ci.yml +1 -0
- {xp_libs_analytics-0.4.0 → xp_libs_analytics-0.5.0}/CHANGELOG.md +7 -1
- {xp_libs_analytics-0.4.0 → xp_libs_analytics-0.5.0}/PKG-INFO +45 -4
- {xp_libs_analytics-0.4.0 → xp_libs_analytics-0.5.0}/README.md +44 -3
- xp_libs_analytics-0.5.0/SECURITY.md +15 -0
- {xp_libs_analytics-0.4.0 → xp_libs_analytics-0.5.0}/docs/architecture.md +2 -0
- xp_libs_analytics-0.5.0/docs/registered-queries.md +64 -0
- {xp_libs_analytics-0.4.0 → xp_libs_analytics-0.5.0}/docs/security-review.md +18 -4
- {xp_libs_analytics-0.4.0 → xp_libs_analytics-0.5.0}/docs/yaml-security.md +2 -2
- {xp_libs_analytics-0.4.0 → xp_libs_analytics-0.5.0}/evals/agent_skill/README.md +2 -2
- {xp_libs_analytics-0.4.0 → xp_libs_analytics-0.5.0}/examples/README.md +1 -1
- xp_libs_analytics-0.5.0/examples/registered_queries.py +60 -0
- {xp_libs_analytics-0.4.0 → xp_libs_analytics-0.5.0}/pyproject.toml +1 -1
- {xp_libs_analytics-0.4.0 → xp_libs_analytics-0.5.0}/scripts/smoke.py +20 -2
- {xp_libs_analytics-0.4.0 → xp_libs_analytics-0.5.0}/src/xp_libs_analytics/__init__.py +5 -1
- xp_libs_analytics-0.5.0/src/xp_libs_analytics/engine/native.py +202 -0
- {xp_libs_analytics-0.4.0 → xp_libs_analytics-0.5.0}/src/xp_libs_analytics/engine/worker.py +22 -8
- {xp_libs_analytics-0.4.0 → xp_libs_analytics-0.5.0}/src/xp_libs_analytics/errors/catalog.json +6 -0
- {xp_libs_analytics-0.4.0 → xp_libs_analytics-0.5.0}/src/xp_libs_analytics/errors/codes.py +6 -0
- {xp_libs_analytics-0.4.0 → xp_libs_analytics-0.5.0}/src/xp_libs_analytics/mcp/server.py +138 -14
- {xp_libs_analytics-0.4.0 → xp_libs_analytics-0.5.0}/src/xp_libs_analytics/models/__init__.py +2 -1
- xp_libs_analytics-0.5.0/src/xp_libs_analytics/registered.py +144 -0
- {xp_libs_analytics-0.4.0 → xp_libs_analytics-0.5.0}/src/xp_libs_analytics/sdk/__init__.py +147 -27
- xp_libs_analytics-0.5.0/tests/test_import_separation.py +75 -0
- {xp_libs_analytics-0.4.0 → xp_libs_analytics-0.5.0}/tests/test_packaging_contract.py +1 -1
- xp_libs_analytics-0.5.0/tests/test_parameterized_queries.py +238 -0
- xp_libs_analytics-0.5.0/tests/test_registered_queries.py +132 -0
- xp_libs_analytics-0.4.0/SECURITY.md +0 -15
- xp_libs_analytics-0.4.0/tests/test_import_separation.py +0 -31
- {xp_libs_analytics-0.4.0 → xp_libs_analytics-0.5.0}/.coveragerc +0 -0
- {xp_libs_analytics-0.4.0 → xp_libs_analytics-0.5.0}/.dockerignore +0 -0
- {xp_libs_analytics-0.4.0 → xp_libs_analytics-0.5.0}/.github/CODEOWNERS.template +0 -0
- {xp_libs_analytics-0.4.0 → xp_libs_analytics-0.5.0}/.github/dependabot.yml +0 -0
- {xp_libs_analytics-0.4.0 → xp_libs_analytics-0.5.0}/.github/workflows/release.yml +0 -0
- {xp_libs_analytics-0.4.0 → xp_libs_analytics-0.5.0}/.github/workflows/security.yml +0 -0
- {xp_libs_analytics-0.4.0 → xp_libs_analytics-0.5.0}/.gitignore +0 -0
- {xp_libs_analytics-0.4.0 → xp_libs_analytics-0.5.0}/CONTRIBUTING.md +0 -0
- {xp_libs_analytics-0.4.0 → xp_libs_analytics-0.5.0}/Dockerfile +0 -0
- {xp_libs_analytics-0.4.0 → xp_libs_analytics-0.5.0}/LICENSE +0 -0
- {xp_libs_analytics-0.4.0 → xp_libs_analytics-0.5.0}/THIRD_PARTY_NOTICES.md +0 -0
- {xp_libs_analytics-0.4.0 → xp_libs_analytics-0.5.0}/docs/GITHUB_SETUP.md +0 -0
- {xp_libs_analytics-0.4.0 → xp_libs_analytics-0.5.0}/docs/adr/ADR-001-schema.md +0 -0
- {xp_libs_analytics-0.4.0 → xp_libs_analytics-0.5.0}/docs/adr/ADR-002-errors.md +0 -0
- {xp_libs_analytics-0.4.0 → xp_libs_analytics-0.5.0}/docs/adr/ADR-003-snapshots.md +0 -0
- {xp_libs_analytics-0.4.0 → xp_libs_analytics-0.5.0}/docs/adr/ADR-004-execution.md +0 -0
- {xp_libs_analytics-0.4.0 → xp_libs_analytics-0.5.0}/docs/adr/ADR-005-YAML-DSL.md +0 -0
- {xp_libs_analytics-0.4.0 → xp_libs_analytics-0.5.0}/docs/adr/entity-inference-deferred.md +0 -0
- {xp_libs_analytics-0.4.0 → xp_libs_analytics-0.5.0}/docs/errors.md +0 -0
- {xp_libs_analytics-0.4.0 → xp_libs_analytics-0.5.0}/docs/query-language.md +0 -0
- {xp_libs_analytics-0.4.0 → xp_libs_analytics-0.5.0}/docs/releasing.md +0 -0
- {xp_libs_analytics-0.4.0 → xp_libs_analytics-0.5.0}/docs/string-matching.md +0 -0
- {xp_libs_analytics-0.4.0 → xp_libs_analytics-0.5.0}/evals/__init__.py +0 -0
- {xp_libs_analytics-0.4.0 → xp_libs_analytics-0.5.0}/evals/agent_skill/__init__.py +0 -0
- {xp_libs_analytics-0.4.0 → xp_libs_analytics-0.5.0}/evals/agent_skill/cli.py +0 -0
- {xp_libs_analytics-0.4.0 → xp_libs_analytics-0.5.0}/evals/agent_skill/datasets/empty.jsonl +0 -0
- {xp_libs_analytics-0.4.0 → xp_libs_analytics-0.5.0}/evals/agent_skill/datasets/epoch.jsonl +0 -0
- {xp_libs_analytics-0.4.0 → xp_libs_analytics-0.5.0}/evals/agent_skill/datasets/events.jsonl +0 -0
- {xp_libs_analytics-0.4.0 → xp_libs_analytics-0.5.0}/evals/agent_skill/datasets/extreme.jsonl +0 -0
- {xp_libs_analytics-0.4.0 → xp_libs_analytics-0.5.0}/evals/agent_skill/datasets/injection.jsonl +0 -0
- {xp_libs_analytics-0.4.0 → xp_libs_analytics-0.5.0}/evals/agent_skill/datasets/invalid_time.jsonl +0 -0
- {xp_libs_analytics-0.4.0 → xp_libs_analytics-0.5.0}/evals/agent_skill/datasets/matching.jsonl +0 -0
- {xp_libs_analytics-0.4.0 → xp_libs_analytics-0.5.0}/evals/agent_skill/datasets/mixed.jsonl +0 -0
- {xp_libs_analytics-0.4.0 → xp_libs_analytics-0.5.0}/evals/agent_skill/datasets/nested_records.jsonl +0 -0
- {xp_libs_analytics-0.4.0 → xp_libs_analytics-0.5.0}/evals/agent_skill/datasets/sales.jsonl +0 -0
- {xp_libs_analytics-0.4.0 → xp_libs_analytics-0.5.0}/evals/agent_skill/datasets/sparse_records.jsonl +0 -0
- {xp_libs_analytics-0.4.0 → xp_libs_analytics-0.5.0}/evals/agent_skill/datasets/telemetry.jsonl +0 -0
- {xp_libs_analytics-0.4.0 → xp_libs_analytics-0.5.0}/evals/agent_skill/datasets/timezone.jsonl +0 -0
- {xp_libs_analytics-0.4.0 → xp_libs_analytics-0.5.0}/evals/agent_skill/endpoint_security.py +0 -0
- {xp_libs_analytics-0.4.0 → xp_libs_analytics-0.5.0}/evals/agent_skill/fixtures.py +0 -0
- {xp_libs_analytics-0.4.0 → xp_libs_analytics-0.5.0}/evals/agent_skill/generate.py +0 -0
- {xp_libs_analytics-0.4.0 → xp_libs_analytics-0.5.0}/evals/agent_skill/prompts/agent_system.md +0 -0
- {xp_libs_analytics-0.4.0 → xp_libs_analytics-0.5.0}/evals/agent_skill/recovery.py +0 -0
- {xp_libs_analytics-0.4.0 → xp_libs_analytics-0.5.0}/evals/agent_skill/reporting.py +0 -0
- {xp_libs_analytics-0.4.0 → xp_libs_analytics-0.5.0}/evals/agent_skill/reports/.gitignore +0 -0
- {xp_libs_analytics-0.4.0 → xp_libs_analytics-0.5.0}/evals/agent_skill/scenarios/adversarial.yaml +0 -0
- {xp_libs_analytics-0.4.0 → xp_libs_analytics-0.5.0}/evals/agent_skill/scenarios/negative.yaml +0 -0
- {xp_libs_analytics-0.4.0 → xp_libs_analytics-0.5.0}/evals/agent_skill/scenarios/positive.yaml +0 -0
- {xp_libs_analytics-0.4.0 → xp_libs_analytics-0.5.0}/evals/agent_skill/scenarios/recovery.yaml +0 -0
- {xp_libs_analytics-0.4.0 → xp_libs_analytics-0.5.0}/evals/agent_skill/scenarios/skill_comparison.yaml +0 -0
- {xp_libs_analytics-0.4.0 → xp_libs_analytics-0.5.0}/evals/agent_skill/scenarios/temporal.yaml +0 -0
- {xp_libs_analytics-0.4.0 → xp_libs_analytics-0.5.0}/evals/agent_skill/scenarios.py +0 -0
- {xp_libs_analytics-0.4.0 → xp_libs_analytics-0.5.0}/evals/agent_skill/scoring.py +0 -0
- {xp_libs_analytics-0.4.0 → xp_libs_analytics-0.5.0}/evals/agent_skill/server.py +0 -0
- {xp_libs_analytics-0.4.0 → xp_libs_analytics-0.5.0}/evals/agent_skill/skill_loader.py +0 -0
- {xp_libs_analytics-0.4.0 → xp_libs_analytics-0.5.0}/evals/agent_skill/task.py +0 -0
- {xp_libs_analytics-0.4.0 → xp_libs_analytics-0.5.0}/examples/data/events.json +0 -0
- {xp_libs_analytics-0.4.0 → xp_libs_analytics-0.5.0}/examples/data/events.ndjson +0 -0
- {xp_libs_analytics-0.4.0 → xp_libs_analytics-0.5.0}/examples/data/matching_events.json +0 -0
- {xp_libs_analytics-0.4.0 → xp_libs_analytics-0.5.0}/examples/data/mixed_types.json +0 -0
- {xp_libs_analytics-0.4.0 → xp_libs_analytics-0.5.0}/examples/queries/01_count_all.yaml +0 -0
- {xp_libs_analytics-0.4.0 → xp_libs_analytics-0.5.0}/examples/queries/02_filter_and_count.yaml +0 -0
- {xp_libs_analytics-0.4.0 → xp_libs_analytics-0.5.0}/examples/queries/03_group_stats_and_topk.yaml +0 -0
- {xp_libs_analytics-0.4.0 → xp_libs_analytics-0.5.0}/examples/queries/04_nested_or_predicates.yaml +0 -0
- {xp_libs_analytics-0.4.0 → xp_libs_analytics-0.5.0}/examples/queries/05_is_missing.yaml +0 -0
- {xp_libs_analytics-0.4.0 → xp_libs_analytics-0.5.0}/examples/queries/06_missing_vs_null.yaml +0 -0
- {xp_libs_analytics-0.4.0 → xp_libs_analytics-0.5.0}/examples/queries/07_conditional_aggregation.yaml +0 -0
- {xp_libs_analytics-0.4.0 → xp_libs_analytics-0.5.0}/examples/queries/08_ratio_if.yaml +0 -0
- {xp_libs_analytics-0.4.0 → xp_libs_analytics-0.5.0}/examples/queries/09_tumbling_window.yaml +0 -0
- {xp_libs_analytics-0.4.0 → xp_libs_analytics-0.5.0}/examples/queries/10_sliding_window.yaml +0 -0
- {xp_libs_analytics-0.4.0 → xp_libs_analytics-0.5.0}/examples/queries/11_explicit_cast.yaml +0 -0
- {xp_libs_analytics-0.4.0 → xp_libs_analytics-0.5.0}/examples/queries/12_text_range_distinct.yaml +0 -0
- {xp_libs_analytics-0.4.0 → xp_libs_analytics-0.5.0}/examples/queries/13_summary_statistics.yaml +0 -0
- {xp_libs_analytics-0.4.0 → xp_libs_analytics-0.5.0}/examples/queries/14_nested_and_not.yaml +0 -0
- {xp_libs_analytics-0.4.0 → xp_libs_analytics-0.5.0}/examples/queries/15_conditional_sum.yaml +0 -0
- {xp_libs_analytics-0.4.0 → xp_libs_analytics-0.5.0}/examples/queries/16_mixed_types_cast.yaml +0 -0
- {xp_libs_analytics-0.4.0 → xp_libs_analytics-0.5.0}/examples/queries/17_relative_last_hour.yaml +0 -0
- {xp_libs_analytics-0.4.0 → xp_libs_analytics-0.5.0}/examples/queries/18_string_exact.yaml +0 -0
- {xp_libs_analytics-0.4.0 → xp_libs_analytics-0.5.0}/examples/queries/19_string_normalization.yaml +0 -0
- {xp_libs_analytics-0.4.0 → xp_libs_analytics-0.5.0}/examples/queries/20_prefix_suffix.yaml +0 -0
- {xp_libs_analytics-0.4.0 → xp_libs_analytics-0.5.0}/examples/queries/21_whole_word.yaml +0 -0
- {xp_libs_analytics-0.4.0 → xp_libs_analytics-0.5.0}/examples/queries/22_ilike.yaml +0 -0
- {xp_libs_analytics-0.4.0 → xp_libs_analytics-0.5.0}/examples/queries/23_rust_regex.yaml +0 -0
- {xp_libs_analytics-0.4.0 → xp_libs_analytics-0.5.0}/examples/queries/24_fuzzy.yaml +0 -0
- {xp_libs_analytics-0.4.0 → xp_libs_analytics-0.5.0}/examples/queries/25_multi_field_matching.yaml +0 -0
- {xp_libs_analytics-0.4.0 → xp_libs_analytics-0.5.0}/examples/queries/26_conditional_fuzzy.yaml +0 -0
- {xp_libs_analytics-0.4.0 → xp_libs_analytics-0.5.0}/examples/queries/27_temporal_fuzzy.yaml +0 -0
- {xp_libs_analytics-0.4.0 → xp_libs_analytics-0.5.0}/examples/queries/28_safe_matching_diagnostics.yaml +0 -0
- {xp_libs_analytics-0.4.0 → xp_libs_analytics-0.5.0}/examples/run_examples.py +0 -0
- {xp_libs_analytics-0.4.0 → xp_libs_analytics-0.5.0}/requirements-lock.txt +0 -0
- {xp_libs_analytics-0.4.0 → xp_libs_analytics-0.5.0}/scripts/benchmark.py +0 -0
- {xp_libs_analytics-0.4.0 → xp_libs_analytics-0.5.0}/scripts/benchmark_matching.py +0 -0
- {xp_libs_analytics-0.4.0 → xp_libs_analytics-0.5.0}/scripts/check_critical_coverage.py +0 -0
- {xp_libs_analytics-0.4.0 → xp_libs_analytics-0.5.0}/scripts/check_name.py +0 -0
- {xp_libs_analytics-0.4.0 → xp_libs_analytics-0.5.0}/scripts/inventory.py +0 -0
- {xp_libs_analytics-0.4.0 → xp_libs_analytics-0.5.0}/scripts/resource_probe.py +0 -0
- {xp_libs_analytics-0.4.0 → xp_libs_analytics-0.5.0}/scripts/verify_dist.py +0 -0
- {xp_libs_analytics-0.4.0 → xp_libs_analytics-0.5.0}/skills/generic-analytics/SKILL.md +0 -0
- {xp_libs_analytics-0.4.0 → xp_libs_analytics-0.5.0}/skills/generic-analytics/references/errors.md +0 -0
- {xp_libs_analytics-0.4.0 → xp_libs_analytics-0.5.0}/skills/generic-analytics/references/query-language.md +0 -0
- {xp_libs_analytics-0.4.0 → xp_libs_analytics-0.5.0}/skills/generic-analytics/references/string-matching.md +0 -0
- {xp_libs_analytics-0.4.0 → xp_libs_analytics-0.5.0}/src/xp_libs_analytics/engine/__init__.py +0 -0
- {xp_libs_analytics-0.4.0 → xp_libs_analytics-0.5.0}/src/xp_libs_analytics/engine/backend.py +0 -0
- {xp_libs_analytics-0.4.0 → xp_libs_analytics-0.5.0}/src/xp_libs_analytics/engine/planner.py +0 -0
- {xp_libs_analytics-0.4.0 → xp_libs_analytics-0.5.0}/src/xp_libs_analytics/errors/__init__.py +0 -0
- {xp_libs_analytics-0.4.0 → xp_libs_analytics-0.5.0}/src/xp_libs_analytics/ingest/__init__.py +0 -0
- {xp_libs_analytics-0.4.0 → xp_libs_analytics-0.5.0}/src/xp_libs_analytics/interfaces.py +0 -0
- {xp_libs_analytics-0.4.0 → xp_libs_analytics-0.5.0}/src/xp_libs_analytics/matching/__init__.py +0 -0
- {xp_libs_analytics-0.4.0 → xp_libs_analytics-0.5.0}/src/xp_libs_analytics/matching/compiler.py +0 -0
- {xp_libs_analytics-0.4.0 → xp_libs_analytics-0.5.0}/src/xp_libs_analytics/matching/diagnostics.py +0 -0
- {xp_libs_analytics-0.4.0 → xp_libs_analytics-0.5.0}/src/xp_libs_analytics/matching/models.py +0 -0
- {xp_libs_analytics-0.4.0 → xp_libs_analytics-0.5.0}/src/xp_libs_analytics/matching/normalize.py +0 -0
- {xp_libs_analytics-0.4.0 → xp_libs_analytics-0.5.0}/src/xp_libs_analytics/matching/patterns.py +0 -0
- {xp_libs_analytics-0.4.0 → xp_libs_analytics-0.5.0}/src/xp_libs_analytics/mcp/__init__.py +0 -0
- {xp_libs_analytics-0.4.0 → xp_libs_analytics-0.5.0}/src/xp_libs_analytics/parser/__init__.py +0 -0
- {xp_libs_analytics-0.4.0 → xp_libs_analytics-0.5.0}/src/xp_libs_analytics/policy/__init__.py +0 -0
- {xp_libs_analytics-0.4.0 → xp_libs_analytics-0.5.0}/src/xp_libs_analytics/py.typed +0 -0
- {xp_libs_analytics-0.4.0 → xp_libs_analytics-0.5.0}/src/xp_libs_analytics/registry.py +0 -0
- {xp_libs_analytics-0.4.0 → xp_libs_analytics-0.5.0}/tests/agent_eval_live/__init__.py +0 -0
- {xp_libs_analytics-0.4.0 → xp_libs_analytics-0.5.0}/tests/agent_eval_live/conftest.py +0 -0
- {xp_libs_analytics-0.4.0 → xp_libs_analytics-0.5.0}/tests/agent_eval_live/test_live.py +0 -0
- {xp_libs_analytics-0.4.0 → xp_libs_analytics-0.5.0}/tests/agent_eval_offline/__init__.py +0 -0
- {xp_libs_analytics-0.4.0 → xp_libs_analytics-0.5.0}/tests/agent_eval_offline/test_contract_failures.py +0 -0
- {xp_libs_analytics-0.4.0 → xp_libs_analytics-0.5.0}/tests/agent_eval_offline/test_controls.py +0 -0
- {xp_libs_analytics-0.4.0 → xp_libs_analytics-0.5.0}/tests/agent_eval_offline/test_endpoint_privacy.py +0 -0
- {xp_libs_analytics-0.4.0 → xp_libs_analytics-0.5.0}/tests/agent_eval_offline/test_equivalence.py +0 -0
- {xp_libs_analytics-0.4.0 → xp_libs_analytics-0.5.0}/tests/agent_eval_offline/test_fixture_oracles.py +0 -0
- {xp_libs_analytics-0.4.0 → xp_libs_analytics-0.5.0}/tests/agent_eval_offline/test_native_agent.py +0 -0
- {xp_libs_analytics-0.4.0 → xp_libs_analytics-0.5.0}/tests/agent_eval_offline/test_preflight_failures.py +0 -0
- {xp_libs_analytics-0.4.0 → xp_libs_analytics-0.5.0}/tests/agent_eval_offline/test_privacy_runtime.py +0 -0
- {xp_libs_analytics-0.4.0 → xp_libs_analytics-0.5.0}/tests/agent_eval_offline/test_recovery_seeding.py +0 -0
- {xp_libs_analytics-0.4.0 → xp_libs_analytics-0.5.0}/tests/agent_eval_offline/test_runtime_failures.py +0 -0
- {xp_libs_analytics-0.4.0 → xp_libs_analytics-0.5.0}/tests/agent_eval_offline/test_scenario_contracts.py +0 -0
- {xp_libs_analytics-0.4.0 → xp_libs_analytics-0.5.0}/tests/agent_eval_offline/test_scoring.py +0 -0
- {xp_libs_analytics-0.4.0 → xp_libs_analytics-0.5.0}/tests/agent_eval_offline/test_skill_loading.py +0 -0
- {xp_libs_analytics-0.4.0 → xp_libs_analytics-0.5.0}/tests/agent_eval_offline/test_variations.py +0 -0
- {xp_libs_analytics-0.4.0 → xp_libs_analytics-0.5.0}/tests/conftest.py +0 -0
- {xp_libs_analytics-0.4.0 → xp_libs_analytics-0.5.0}/tests/fixtures/matching_events.jsonl +0 -0
- {xp_libs_analytics-0.4.0 → xp_libs_analytics-0.5.0}/tests/fixtures/query-schema.json +0 -0
- {xp_libs_analytics-0.4.0 → xp_libs_analytics-0.5.0}/tests/matching/__init__.py +0 -0
- {xp_libs_analytics-0.4.0 → xp_libs_analytics-0.5.0}/tests/matching/support.py +0 -0
- {xp_libs_analytics-0.4.0 → xp_libs_analytics-0.5.0}/tests/matching/test_agent_contract.py +0 -0
- {xp_libs_analytics-0.4.0 → xp_libs_analytics-0.5.0}/tests/matching/test_budgets_diagnostics.py +0 -0
- {xp_libs_analytics-0.4.0 → xp_libs_analytics-0.5.0}/tests/matching/test_components_security.py +0 -0
- {xp_libs_analytics-0.4.0 → xp_libs_analytics-0.5.0}/tests/matching/test_composition.py +0 -0
- {xp_libs_analytics-0.4.0 → xp_libs_analytics-0.5.0}/tests/matching/test_operators.py +0 -0
- {xp_libs_analytics-0.4.0 → xp_libs_analytics-0.5.0}/tests/matching/test_properties.py +0 -0
- {xp_libs_analytics-0.4.0 → xp_libs_analytics-0.5.0}/tests/oracle.py +0 -0
- {xp_libs_analytics-0.4.0 → xp_libs_analytics-0.5.0}/tests/test_agent_workflows.py +0 -0
- {xp_libs_analytics-0.4.0 → xp_libs_analytics-0.5.0}/tests/test_contracts.py +0 -0
- {xp_libs_analytics-0.4.0 → xp_libs_analytics-0.5.0}/tests/test_edge_cases.py +0 -0
- {xp_libs_analytics-0.4.0 → xp_libs_analytics-0.5.0}/tests/test_ingest_policy.py +0 -0
- {xp_libs_analytics-0.4.0 → xp_libs_analytics-0.5.0}/tests/test_mcp.py +0 -0
- {xp_libs_analytics-0.4.0 → xp_libs_analytics-0.5.0}/tests/test_operations.py +0 -0
- {xp_libs_analytics-0.4.0 → xp_libs_analytics-0.5.0}/tests/test_registry.py +0 -0
- {xp_libs_analytics-0.4.0 → xp_libs_analytics-0.5.0}/tests/test_security.py +0 -0
- {xp_libs_analytics-0.4.0 → xp_libs_analytics-0.5.0}/tests/test_telemetry.py +0 -0
- {xp_libs_analytics-0.4.0 → xp_libs_analytics-0.5.0}/tests/test_time.py +0 -0
- {xp_libs_analytics-0.4.0 → xp_libs_analytics-0.5.0}/tests/test_validation.py +0 -0
- {xp_libs_analytics-0.4.0 → xp_libs_analytics-0.5.0}/tests/test_worker_limits.py +0 -0
- {xp_libs_analytics-0.4.0 → xp_libs_analytics-0.5.0}/tests/test_workflows.py +0 -0
- {xp_libs_analytics-0.4.0 → xp_libs_analytics-0.5.0}/tests/test_yaml_security.py +0 -0
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
## Description and motivation
|
|
2
2
|
|
|
3
3
|
## Affected contracts / V1 operators / public API
|
|
4
|
-
- [ ] Updated `
|
|
4
|
+
- [ ] Updated `docs/query-language.md` and reviewed versioning if the query contract changed
|
|
5
5
|
- [ ] New/changed error codes and catalog parity (single `catalog.json`)
|
|
6
6
|
- [ ] Updated agent skill and machine-valid examples if behavior changed
|
|
7
7
|
|
|
8
8
|
## Safety and threat model
|
|
9
9
|
- [ ] No unsafe SQL/Python/UDF/network/file access introduced
|
|
10
|
-
- [ ]
|
|
10
|
+
- [ ] Reviewed authorization and context-local dataset access
|
|
11
11
|
- [ ] Resource budgets and input/output leakage considered
|
|
12
12
|
- [ ] No real records/secrets in fixtures, logs or artifacts
|
|
13
13
|
|
|
@@ -44,6 +44,7 @@ jobs:
|
|
|
44
44
|
- run: python scripts/check_critical_coverage.py coverage.json
|
|
45
45
|
- run: python examples/run_examples.py --lint-only
|
|
46
46
|
- run: python examples/run_examples.py --all
|
|
47
|
+
- run: python -m examples.registered_queries
|
|
47
48
|
- run: python scripts/resource_probe.py
|
|
48
49
|
- uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2
|
|
49
50
|
if: always()
|
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.5.0 — Parameterized registered queries
|
|
4
|
+
|
|
5
|
+
- Add key-only MCP execution for host-registered YAML or typed queries. Add a typed SDK entry point that bypasses YAML parsing while retaining snapshot planning and authorization.
|
|
6
|
+
- Bind strict runtime parameters into fixed YAML value positions and support host-owned Polars transformations over authorized projections.
|
|
7
|
+
- Expose parameter schema discovery and optional generated MCP tools. Support native execution in the OS-limited worker using a host-provided transformation registry.
|
|
8
|
+
|
|
3
9
|
|
|
4
10
|
## 0.4.0 — Bounded string and fuzzy query matching
|
|
5
11
|
|
|
@@ -24,7 +30,7 @@
|
|
|
24
30
|
## 0.2.0 — local build
|
|
25
31
|
|
|
26
32
|
- Rename the distribution/CLI to `xp-libs-analytics` and imports to `xp_libs_analytics`.
|
|
27
|
-
- Preserve the independently usable YAML
|
|
33
|
+
- Preserve the independently usable YAML analytics core and optional FastMCP adapter. Add a custom MCP adapter example and an import isolation regression test.
|
|
28
34
|
- Add optional Inspect AI agent evaluations, deterministic scenario/oracle contracts, real MCP recovery seeding, semantic/result scorers, memory-only Inspect logs, sanitized reporting and skill/baseline comparison.
|
|
29
35
|
- Add 105 synthetic scenarios and offline/live test separation. Live results are recorded separately from deterministic test evidence.
|
|
30
36
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: xp-libs-analytics
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.5.0
|
|
4
4
|
Summary: Schema-on-read, in-memory analytics for agent-generated YAML queries
|
|
5
5
|
Project-URL: Source, https://github.com/rrodrigoa/xp-libs-analytics
|
|
6
6
|
Project-URL: Issues, https://github.com/rrodrigoa/xp-libs-analytics/issues
|
|
@@ -45,7 +45,7 @@ Description-Content-Type: text/markdown
|
|
|
45
45
|
|
|
46
46
|
# XP Libs Analytics
|
|
47
47
|
|
|
48
|
-
A Python library for bounded, in-memory snapshot analytics over JSON records with unknown, heterogeneous schemas.
|
|
48
|
+
A Python library for bounded, in-memory snapshot analytics over JSON records with unknown, heterogeneous schemas. The engine accepts restricted YAML and host-authored typed query definitions, then plans and executes them using fixed Polars expressions. The optional standalone FastMCP adapter supports YAML authoring or execution by registered query key.
|
|
49
49
|
|
|
50
50
|
Python 3.12+ · MIT · Import: `xp_libs_analytics` · Distribution: `xp-libs-analytics`
|
|
51
51
|
|
|
@@ -109,7 +109,7 @@ Input accepts JSON objects, arrays of objects, NDJSON, or Python iterables of JS
|
|
|
109
109
|
| Grouping | Up to four scalar dimensions; missing and explicit null groups remain separate |
|
|
110
110
|
| Time | Explicit RFC3339/epoch casts, absolute or relative UTC ranges, epoch-aligned tumbling/sliding windows |
|
|
111
111
|
| Results | HAVING, stable sorting, limit/truncation, strict JSON and quality/provenance evidence |
|
|
112
|
-
| Errors |
|
|
112
|
+
| Errors | 79 catalog codes, one canonical packaged message catalog, safe diagnostic locations |
|
|
113
113
|
|
|
114
114
|
See the [query language](docs/query-language.md), [examples](examples/README.md), and [agent skill](skills/generic-analytics/SKILL.md). `NO_DATA` reports absent matching observations. `INSUFFICIENT_DATA` applies the host's minimum sample policy. Unknown sampling cadence yields `coverage: UNKNOWN`; counts do not imply duration.
|
|
115
115
|
|
|
@@ -166,7 +166,7 @@ def validation_tool(query_yaml: str) -> dict:
|
|
|
166
166
|
return analytics.validate_query(query_yaml)
|
|
167
167
|
```
|
|
168
168
|
|
|
169
|
-
|
|
169
|
+
A custom MCP adapter can register these callables. The host selects the authorized execution context and identity, enforces transport authentication, and uses `isolated=True` for untrusted execution. The library also includes an optional FastMCP adapter.
|
|
170
170
|
|
|
171
171
|
```sh
|
|
172
172
|
xp-libs-analytics --help
|
|
@@ -178,6 +178,47 @@ Tools: `analytics_capabilities`, `analytics_list_datasets`, `analytics_load_json
|
|
|
178
178
|
|
|
179
179
|
The local stdio server inherits its launcher's authority; one process is one local context. The default backend uses isolated workers. HTTP access fails closed unless a host supplies an authenticated `principal_resolver` and configures FastMCP transport authentication. A host-supplied `engine` deliberately selects that engine's context for local use. Hosted sessions can supply `context_resolver`, a trusted callable returning their authorized engine/context. No tool accepts a context ID or principal. The identity resolver creates distinct namespaces per authenticated principal and reapplies current field/scopes policy per request. The adapter caps cached principal contexts at 128; restarting clears local state. It is not a turnkey public multi-tenant service.
|
|
180
180
|
|
|
181
|
+
### Registered query keys
|
|
182
|
+
|
|
183
|
+
Hosts can expose a fixed set of queries through `create_server(registered_queries=...)`.
|
|
184
|
+
The host registers the datasets and supplies the query definitions at startup.
|
|
185
|
+
|
|
186
|
+
```python
|
|
187
|
+
from xp_libs_analytics import AnalyticsEngine, AnalyticsQuery
|
|
188
|
+
from xp_libs_analytics.mcp.server import create_server
|
|
189
|
+
|
|
190
|
+
if __name__ == "__main__":
|
|
191
|
+
engine = AnalyticsEngine(isolated=True)
|
|
192
|
+
engine.register_records("observations", [{"value": 2}, {"value": 4}])
|
|
193
|
+
|
|
194
|
+
count_yaml = """version: analytics.query/v1
|
|
195
|
+
dataset: {name: observations}
|
|
196
|
+
aggregations: [{op: count, as: rows}]
|
|
197
|
+
"""
|
|
198
|
+
sum_query = AnalyticsQuery.model_validate(
|
|
199
|
+
{
|
|
200
|
+
"version": "analytics.query/v1",
|
|
201
|
+
"dataset": {"name": "observations"},
|
|
202
|
+
"aggregations": [{"op": "sum", "field": "/value", "as": "total"}],
|
|
203
|
+
}
|
|
204
|
+
)
|
|
205
|
+
|
|
206
|
+
server = create_server(engine, registered_queries={"count": count_yaml, "sum": sum_query})
|
|
207
|
+
server.run(transport="stdio")
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
The client calls `analytics_query_by_key` with `{"key": "sum"}` and receives the standard analytics result or error. Keys are case-sensitive, start with an ASCII letter, and contain up to 64 ASCII letters, digits, underscores, or hyphens. The host distributes the allowed keys to clients. For HTTP, the host configures transport authentication and supplies a trusted context resolver that selects an engine with the registered datasets.
|
|
211
|
+
|
|
212
|
+
YAML entries use restricted parsing on each call. Typed entries and direct SDK calls to `engine.query_typed(sum_query)` bypass the analytics YAML parser. FastMCP independently imports PyYAML for its skill provider. Both query forms resolve the current dataset snapshot and run authorization, cost planning, and execution on each request.
|
|
213
|
+
|
|
214
|
+
Registered queries can also accept a host-defined Pydantic parameter model. Use
|
|
215
|
+
`ParameterizedYamlQuery` for fixed YAML or `PolarsQuery` for a trusted Polars
|
|
216
|
+
transformation. Clients pass values as `{"key": "sum", "parameters": {"minimum": 3}}`
|
|
217
|
+
and can retrieve the input schema with `analytics_describe_query`. Set
|
|
218
|
+
`generated_tools=True` to expose each query as an `analytics_registered_<key>` tool
|
|
219
|
+
with named arguments. See [registered query configuration](docs/registered-queries.md)
|
|
220
|
+
and run `python -m examples.registered_queries` for an end-to-end example.
|
|
221
|
+
|
|
181
222
|
## Development and evidence
|
|
182
223
|
|
|
183
224
|
Optional real-model evaluations use Inspect AI and the actual MCP adapter. From this checkout, install `python -m pip install -e '.[dev,mcp,agent-eval]'`, then run `python -m evals.agent_skill.cli validate`. Live evaluation accepts the model connection only at execution time; see [evaluation usage](evals/agent_skill/README.md). Normal CI runs offline evaluator tests and excludes live model tests.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# XP Libs Analytics
|
|
2
2
|
|
|
3
|
-
A Python library for bounded, in-memory snapshot analytics over JSON records with unknown, heterogeneous schemas.
|
|
3
|
+
A Python library for bounded, in-memory snapshot analytics over JSON records with unknown, heterogeneous schemas. The engine accepts restricted YAML and host-authored typed query definitions, then plans and executes them using fixed Polars expressions. The optional standalone FastMCP adapter supports YAML authoring or execution by registered query key.
|
|
4
4
|
|
|
5
5
|
Python 3.12+ · MIT · Import: `xp_libs_analytics` · Distribution: `xp-libs-analytics`
|
|
6
6
|
|
|
@@ -64,7 +64,7 @@ Input accepts JSON objects, arrays of objects, NDJSON, or Python iterables of JS
|
|
|
64
64
|
| Grouping | Up to four scalar dimensions; missing and explicit null groups remain separate |
|
|
65
65
|
| Time | Explicit RFC3339/epoch casts, absolute or relative UTC ranges, epoch-aligned tumbling/sliding windows |
|
|
66
66
|
| Results | HAVING, stable sorting, limit/truncation, strict JSON and quality/provenance evidence |
|
|
67
|
-
| Errors |
|
|
67
|
+
| Errors | 79 catalog codes, one canonical packaged message catalog, safe diagnostic locations |
|
|
68
68
|
|
|
69
69
|
See the [query language](docs/query-language.md), [examples](examples/README.md), and [agent skill](skills/generic-analytics/SKILL.md). `NO_DATA` reports absent matching observations. `INSUFFICIENT_DATA` applies the host's minimum sample policy. Unknown sampling cadence yields `coverage: UNKNOWN`; counts do not imply duration.
|
|
70
70
|
|
|
@@ -121,7 +121,7 @@ def validation_tool(query_yaml: str) -> dict:
|
|
|
121
121
|
return analytics.validate_query(query_yaml)
|
|
122
122
|
```
|
|
123
123
|
|
|
124
|
-
|
|
124
|
+
A custom MCP adapter can register these callables. The host selects the authorized execution context and identity, enforces transport authentication, and uses `isolated=True` for untrusted execution. The library also includes an optional FastMCP adapter.
|
|
125
125
|
|
|
126
126
|
```sh
|
|
127
127
|
xp-libs-analytics --help
|
|
@@ -133,6 +133,47 @@ Tools: `analytics_capabilities`, `analytics_list_datasets`, `analytics_load_json
|
|
|
133
133
|
|
|
134
134
|
The local stdio server inherits its launcher's authority; one process is one local context. The default backend uses isolated workers. HTTP access fails closed unless a host supplies an authenticated `principal_resolver` and configures FastMCP transport authentication. A host-supplied `engine` deliberately selects that engine's context for local use. Hosted sessions can supply `context_resolver`, a trusted callable returning their authorized engine/context. No tool accepts a context ID or principal. The identity resolver creates distinct namespaces per authenticated principal and reapplies current field/scopes policy per request. The adapter caps cached principal contexts at 128; restarting clears local state. It is not a turnkey public multi-tenant service.
|
|
135
135
|
|
|
136
|
+
### Registered query keys
|
|
137
|
+
|
|
138
|
+
Hosts can expose a fixed set of queries through `create_server(registered_queries=...)`.
|
|
139
|
+
The host registers the datasets and supplies the query definitions at startup.
|
|
140
|
+
|
|
141
|
+
```python
|
|
142
|
+
from xp_libs_analytics import AnalyticsEngine, AnalyticsQuery
|
|
143
|
+
from xp_libs_analytics.mcp.server import create_server
|
|
144
|
+
|
|
145
|
+
if __name__ == "__main__":
|
|
146
|
+
engine = AnalyticsEngine(isolated=True)
|
|
147
|
+
engine.register_records("observations", [{"value": 2}, {"value": 4}])
|
|
148
|
+
|
|
149
|
+
count_yaml = """version: analytics.query/v1
|
|
150
|
+
dataset: {name: observations}
|
|
151
|
+
aggregations: [{op: count, as: rows}]
|
|
152
|
+
"""
|
|
153
|
+
sum_query = AnalyticsQuery.model_validate(
|
|
154
|
+
{
|
|
155
|
+
"version": "analytics.query/v1",
|
|
156
|
+
"dataset": {"name": "observations"},
|
|
157
|
+
"aggregations": [{"op": "sum", "field": "/value", "as": "total"}],
|
|
158
|
+
}
|
|
159
|
+
)
|
|
160
|
+
|
|
161
|
+
server = create_server(engine, registered_queries={"count": count_yaml, "sum": sum_query})
|
|
162
|
+
server.run(transport="stdio")
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
The client calls `analytics_query_by_key` with `{"key": "sum"}` and receives the standard analytics result or error. Keys are case-sensitive, start with an ASCII letter, and contain up to 64 ASCII letters, digits, underscores, or hyphens. The host distributes the allowed keys to clients. For HTTP, the host configures transport authentication and supplies a trusted context resolver that selects an engine with the registered datasets.
|
|
166
|
+
|
|
167
|
+
YAML entries use restricted parsing on each call. Typed entries and direct SDK calls to `engine.query_typed(sum_query)` bypass the analytics YAML parser. FastMCP independently imports PyYAML for its skill provider. Both query forms resolve the current dataset snapshot and run authorization, cost planning, and execution on each request.
|
|
168
|
+
|
|
169
|
+
Registered queries can also accept a host-defined Pydantic parameter model. Use
|
|
170
|
+
`ParameterizedYamlQuery` for fixed YAML or `PolarsQuery` for a trusted Polars
|
|
171
|
+
transformation. Clients pass values as `{"key": "sum", "parameters": {"minimum": 3}}`
|
|
172
|
+
and can retrieve the input schema with `analytics_describe_query`. Set
|
|
173
|
+
`generated_tools=True` to expose each query as an `analytics_registered_<key>` tool
|
|
174
|
+
with named arguments. See [registered query configuration](docs/registered-queries.md)
|
|
175
|
+
and run `python -m examples.registered_queries` for an end-to-end example.
|
|
176
|
+
|
|
136
177
|
## Development and evidence
|
|
137
178
|
|
|
138
179
|
Optional real-model evaluations use Inspect AI and the actual MCP adapter. From this checkout, install `python -m pip install -e '.[dev,mcp,agent-eval]'`, then run `python -m evals.agent_skill.cli validate`. Live evaluation accepts the model connection only at execution time; see [evaluation usage](evals/agent_skill/README.md). Normal CI runs offline evaluator tests and excludes live model tests.
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
# Security Policy
|
|
2
|
+
|
|
3
|
+
XP Libs Analytics executes analytical queries over potentially sensitive JSON records. Vulnerability reports should use synthetic records and omit secrets, access tokens, personal information, private schema, and cross-context dataset metadata.
|
|
4
|
+
|
|
5
|
+
## Reporting vulnerabilities
|
|
6
|
+
|
|
7
|
+
Use the repository's private security advisory form (Security → Advisories → Report a vulnerability) when private reporting is available.
|
|
8
|
+
|
|
9
|
+
## Supported versions
|
|
10
|
+
|
|
11
|
+
Security fixes are distributed as new package versions. The [changelog](CHANGELOG.md) records version-specific changes.
|
|
12
|
+
|
|
13
|
+
## Threat model priorities
|
|
14
|
+
|
|
15
|
+
The library's threat model covers cross-context name resolution, snapshot identity, untrusted tool input, resource exhaustion, type and cast handling, metadata disclosure, dependency and package integrity, and unauthorized output. See the [end-to-end security review](docs/security-review.md) for controls and test evidence.
|
|
@@ -16,6 +16,8 @@ The worker memory ceiling is absolute. POSIX address-space limits include native
|
|
|
16
16
|
|
|
17
17
|
MCP resources are read-only and allowlisted. Under modern MCP, local requests inherit launcher identity rather than a per-request connection ID. Remote hosts must supply transport authentication and a trusted principal or execution-context resolver; default HTTP data operations are denied.
|
|
18
18
|
|
|
19
|
+
The optional key-only MCP surface resolves caller-supplied keys through a host-configured mapping. A YAML entry enters restricted parsing. A typed entry enters through strict model validation and skips the analytics YAML parser. Both paths bind the current snapshot under the trusted context, plan against its schema and budgets, and use the shared execution and result path.
|
|
20
|
+
|
|
19
21
|
MCP never accepts agent-supplied identity or context selectors. Authenticated principal contexts are isolated and policy views preserve names while applying current scopes/field restrictions. A context resolver can instead select a host-managed session/request context. Names in other contexts are indistinguishable from nonexistent names. Local stdio deliberately has one launcher context per process. Reserved register_stream rejects without consuming sources; no continuous semantics are implied.
|
|
20
22
|
|
|
21
23
|
The shared matching package owns strict rule models, Unicode normalization, bounded LIKE/Rust regex validation, per-query distinct RapidFuzz scoring and privacy-cleared diagnostics. Matching plans compile to native Polars expressions/membership without row UDFs. Typed datetime equality bypasses string matching after explicit casts. Safe independent nonfuzzy conjunctions can bound candidate work; OR/NOT branches are never pushed independently. There is no persistent derived-value cache. Regex/fuzzy and requested diagnostics default to the OS-limited worker; explain/validate counts candidates without running scorers. Optional diagnostics have an explicitly narrower post-all-filters/pre-aggregation leaf-hit scope. Entity inference is deferred; see docs/adr/entity-inference-deferred.md.
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
# Parameterized registered queries
|
|
2
|
+
|
|
3
|
+
The host supplies `registered_queries` to `create_server`. Each entry accepts fixed
|
|
4
|
+
YAML text, an `AnalyticsQuery`, a `ParameterizedYamlQuery`, or a `PolarsQuery`. The
|
|
5
|
+
adapter resolves the key within the server and selects the engine through the
|
|
6
|
+
trusted context. Dataset bindings remain local to that engine.
|
|
7
|
+
|
|
8
|
+
Use `analytics_describe_query(key)` to obtain the parameter JSON Schema. Call
|
|
9
|
+
`analytics_query_by_key(key, parameters)` with a JSON object. Omitting the object
|
|
10
|
+
applies the model defaults and still enforces required fields. Fixed queries accept
|
|
11
|
+
an omitted or empty object. With `generated_tools=True`, the tool
|
|
12
|
+
`analytics_registered_<key>` accepts the same values as named arguments. Both
|
|
13
|
+
surfaces validate requests against the same model.
|
|
14
|
+
|
|
15
|
+
Parameter models use strict JSON validation. Integer and boolean values require
|
|
16
|
+
their JSON types. Enums use their declared JSON values and datetimes use RFC 3339
|
|
17
|
+
strings. The engine forbids undeclared fields and applies byte, depth, node,
|
|
18
|
+
collection, and string limits before model validation. Effective values,
|
|
19
|
+
including defaults, are also subject to the resource limits. Host models can
|
|
20
|
+
enable Pydantic's `validate_default` setting to validate defaults against field
|
|
21
|
+
constraints. Model validators and serializers execute as trusted host code.
|
|
22
|
+
|
|
23
|
+
## Fixed YAML
|
|
24
|
+
|
|
25
|
+
Create `ParameterizedYamlQuery(yaml, parameters=Model)` and invoke it directly with
|
|
26
|
+
`engine.query_parameterized(definition, values)` or register it with the MCP
|
|
27
|
+
adapter. Place `{$param: minimum}` where the query expects a whole literal value.
|
|
28
|
+
Supported positions include predicate values, membership lists, range bounds,
|
|
29
|
+
matching pattern values and thresholds, and time range bounds. Placeholder names
|
|
30
|
+
refer to Python model fields. A bound string is a literal even if it contains YAML
|
|
31
|
+
syntax. The dataset name, field paths, operators, aggregate aliases, and other
|
|
32
|
+
structural parts come from the fixed template. The resulting query goes through
|
|
33
|
+
the normal typed planner.
|
|
34
|
+
|
|
35
|
+
## Native Polars
|
|
36
|
+
|
|
37
|
+
Create `PolarsQuery(dataset, fields, transform, parameters, output_schema)`
|
|
38
|
+
and invoke it with `engine.query_polars(definition, values)` or register it with the
|
|
39
|
+
MCP adapter. The host supplies the dataset name, JSON Pointer paths, and a callable
|
|
40
|
+
that takes a Polars DataFrame and the validated Pydantic model, then returns a
|
|
41
|
+
Polars DataFrame. The input contains every row from the leased snapshot, a
|
|
42
|
+
`row_index`, one column per authorized scalar path, and one Boolean
|
|
43
|
+
`present:<path>` column per path. Missing values have a false presence flag and a
|
|
44
|
+
null cell. Explicit nulls have a true presence flag and a null cell. Container and
|
|
45
|
+
mixed-type projections are rejected.
|
|
46
|
+
|
|
47
|
+
The output must match the declared column names, order, and types. Supported
|
|
48
|
+
output types are `integer` (Int64), `float` (Float64), `boolean`, `string`,
|
|
49
|
+
`datetime` (timezone-aware), and `null`. Values can be nullable. Oversized outputs
|
|
50
|
+
return a structured error. `max_output_rows` limits returned rows after the
|
|
51
|
+
transformation, and the engine enforces its result byte limit.
|
|
52
|
+
|
|
53
|
+
For isolated execution, construct the engine with `isolated=True` and an explicit
|
|
54
|
+
`native_transforms={"count": count_values}` registry. The transformation and the
|
|
55
|
+
parameter model must be top-level definitions in importable host modules. The
|
|
56
|
+
worker resolves the host-selected references after setting its resource limits.
|
|
57
|
+
MCP arguments supply parameter values only. Native transformations have the
|
|
58
|
+
privileges of the host or worker process and must come from trusted application
|
|
59
|
+
code. Use operating-system or container controls to restrict process access as
|
|
60
|
+
needed. The OS worker limits apply to execution, and the host bounds projection
|
|
61
|
+
and parameter processing.
|
|
62
|
+
|
|
63
|
+
Run `python -m examples.registered_queries` from the source checkout with the MCP
|
|
64
|
+
extra installed. The example uses one parameter model with both query types.
|
|
@@ -1,15 +1,29 @@
|
|
|
1
|
-
#
|
|
1
|
+
# End-to-end security review for V1
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
XP Libs Analytics executes bounded snapshot queries over JSON records. The host controls which records enter the engine, which context handles each request, and what that context can read. YAML expresses the query. The Python SDK enforces the same query contract for direct callers and callers using the optional MCP adapter.
|
|
4
|
+
|
|
5
|
+
```text
|
|
6
|
+
Trusted host ── records ──► bounded admission ──► immutable snapshot
|
|
7
|
+
│ │
|
|
8
|
+
└──────── context, permissions and limits ───┐ │
|
|
9
|
+
▼ ▼
|
|
10
|
+
Caller ── YAML via SDK or MCP ──► parse and type ──► authorize and plan
|
|
11
|
+
│
|
|
12
|
+
approved plan
|
|
13
|
+
▼
|
|
14
|
+
Caller ◄── bounded JSON or coded error ◄──────────── execute
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
The review covers these boundaries from admission through response, as well as tool and release permissions. Executed tests and OS probes are recorded in [verification evidence](evidence/verification.md).
|
|
4
18
|
|
|
5
19
|
- YAML is byte/event/depth/node/collection/scalar bounded before construction. Tags, directives, anchors, aliases, merges and duplicate keys are rejected. Scalar resolvers are isolated from global PyYAML behavior. Query keys cannot introduce SQL, code, plugins, host paths or network sources.
|
|
6
20
|
- Strict disjoint AST variants forbid unused and unknown fields. Public errors use only the packaged catalog. Invalid unknown-key locations are redacted; parser excerpts, records and backend exception strings are never returned.
|
|
7
21
|
- Snapshots copy admitted records, preserve missing versus null and examine all rows for typing/casts. Integer precision/overflow and nonfinite results are checked. Opaque internal column names prevent output alias collisions. Casting precedes WHERE and cannot bypass field permissions.
|
|
8
|
-
- Ownership is enforced for
|
|
22
|
+
- Ownership is enforced for dataset discovery, description, query validation, planning, execution, and unregistering. Hidden and nonexistent paths are indistinguishable for field-restricted principals. Dataset names outside the current context are indistinguishable from unknown names. The engine applies the host's current policy without mutating shared request authority.
|
|
9
23
|
- Input bytes, rows, paths, record size, recursion, store bytes, handle count, result bytes, groups, distinct cardinality, windows, overlap and expanded rows have explicit budgets. Quotas/concurrency are shared by owner across SDK views of the same store. A low result limit cannot bypass planning.
|
|
10
24
|
- Workers have OS CPU/memory ceilings and parent-enforced deadlines; native abort/pipe loss returns a canonical error. Windows Job Object and POSIX rlimit adapters have fail-closed API tests. OS probes demonstrate memory denial and CPU termination; host/container restrictions remain necessary for host-side admission and egress.
|
|
11
25
|
- Default MCP local identity follows launcher authority under modern per-request connections. Default HTTP data operations fail closed. Authenticated remote hosting requires a trusted resolver and transport authentication; this package does not claim to configure an identity provider. Upload is disabled by default. Resource names are an allowlist, with no caller filesystem routing.
|
|
12
26
|
- Observability hooks contain only IDs/hashes/status/timing, never records, query text or field values. Hook failures cannot change query outcomes. The package performs no default payload logging.
|
|
13
27
|
- CI does not use `pull_request_target`. Actions are commit-pinned. Only protected release jobs have OIDC write authority; TestPyPI promotion compares wheel bytes and checks hashes before PyPI. Account-level protection still requires maintainers.
|
|
14
28
|
|
|
15
|
-
|
|
29
|
+
The evidence for this review comes from source inspection and local tests. The deterministic agent suite verifies tool workflows and result handling. Live model accuracy, internet-facing authentication deployments, and independent penetration testing fall outside this review. Exact decimal arithmetic, continuous queries, and multi-input execution fall outside V1.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Restricted YAML Query Parser — Normative Security Contract (V1)
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
This document defines restricted YAML decoding before typed Pydantic validation. It applies to caller-authored YAML and host-registered YAML queries. Host-authored typed queries enter through model validation. Both paths operate on snapshots of JSON or NDJSON records and return JSON results or errors.
|
|
4
4
|
|
|
5
5
|
## Decision and rationale
|
|
6
6
|
|
|
@@ -34,7 +34,7 @@
|
|
|
34
34
|
Parser fidelity:
|
|
35
35
|
|
|
36
36
|
1. Enforce UTF-8 and raw byte size **before** parser work. Suggested default `max_query_bytes=65_536`, configurable lower by trusted policy. Also bound string length (`max_scalar_chars=1_024` except perhaps approved fixed query metadata), nesting (`max_yaml_depth=16`), total nodes/events (`max_yaml_nodes=4_096`), mapping entries and sequences (`max_yaml_collection_items=1_024`); versioned server defaults are normative once selected.
|
|
37
|
-
2.
|
|
37
|
+
2. The restricted loader extends PyYAML's `SafeLoader` with the library's duplicate-key rules, scalar resolution, and allowed syntax. Query parsing excludes `yaml.Loader`, `FullLoader`, `unsafe_load`, `!!python/*`, and arbitrary constructors.
|
|
38
38
|
3. Preflight parser events **with budgets**. Reject `AliasEvent`, any `anchor` on scalar/sequence/mapping events, explicit tags, directives, multi-documents and unsupported events. Stop after the first permitted document and reject further documents. Parse deterministically, with a hard deadline/worker budget and bounded untrusted text. Avoid regex-only prechecks: anchors, aliases and tags must be detected by parsing, not substring search (quoted `"*alias"` is just a string).
|
|
39
39
|
4. Clone only the safe scalar resolver table before modifying it; don't mutate global `yaml.SafeLoader` implicit resolvers. Install a narrowly documented set of JSON-compatible scalar recognizers: lowercase true/false; exact null; JSON-style decimal integers and finite decimal/exponent floats; no timestamp or binary auto-resolution; no YAML 1.1 aliases for booleans. Treat leading-zero or plus-prefixed numerals as strings unless explicitly supported by a later version. Validate `int64` bounds and numeric finiteness at the typed semantic layer; never accept Python bool as numeric.
|
|
40
40
|
5. Override mapping constructor to reject duplicate keys **at every depth**, including flow-style maps, before converting to dict; require string keys, no merge key, and preserve source marks in a safe `{query_path → (line,column)}` index. Convert positions to 1-based line/column. Use local source-mark metadata, never return raw YAML excerpts or tokens. A duplicate-key failure may include the duplicate key's **safe path** and line/column, but must not echo field values.
|
|
@@ -18,7 +18,7 @@ For an Ollama deployment, install Ollama separately, start it with `ollama serve
|
|
|
18
18
|
|
|
19
19
|
## Supply connection settings only at runtime
|
|
20
20
|
|
|
21
|
-
Set MODEL_ID in
|
|
21
|
+
Set `MODEL_ID` in the evaluation process environment to identify an installed model with native tool support. The evaluation runner requires an explicit model ID.
|
|
22
22
|
|
|
23
23
|
The CLI prompts for an API base URL if `--endpoint` is omitted. It never saves that answer. For example:
|
|
24
24
|
|
|
@@ -32,7 +32,7 @@ python -m evals.agent_skill.cli run --model "$MODEL_ID" --suite adversarial
|
|
|
32
32
|
python -m evals.agent_skill.cli compare --model "$MODEL_ID" --scenario aggregate-count --repetitions 3
|
|
33
33
|
```
|
|
34
34
|
|
|
35
|
-
|
|
35
|
+
Pass the endpoint at runtime with `--endpoint "$API_BASE"` or enter it at the interactive prompt. The prompt keeps the address out of command history. Supply any required credential through the process environment variable `XP_ANALYTICS_EVAL_API_KEY` and clear it after use.
|
|
36
36
|
|
|
37
37
|
Other controls: `--seed`, `--temperature` (default zero), `--max-turns`, `--max-tool-calls`, `--timeout`, `--report`, `--compare-skill`, `--verbose`, and explicit `--traces` for redacted message traces. Runs are serial; concurrency defaults to one. Preflight checks model listing where available, native tool calling with a harmless echo tool, actual analytics MCP discovery, and skill references. It fails without silently emulating tools.
|
|
38
38
|
|
|
@@ -11,7 +11,7 @@ python examples/run_examples.py --all
|
|
|
11
11
|
|
|
12
12
|
`--lint-only` applies the restricted parser and strict Pydantic AST. `--all` additionally registers synthetic records, registers them under example_data, semantically validates and executes every query, and unregisters each binding. Runtime errors fail the runner.
|
|
13
13
|
|
|
14
|
-
Each query is a complete document. `dataset.name: example_data` selects the logical name registered by the runner.
|
|
14
|
+
Each query is a complete document. `dataset.name: example_data` selects the logical name registered by the runner. The same YAML can execute against any snapshot registered as `example_data` with compatible fields and types. The YAML uses the logical name throughout. Example inputs are `data/events.json`, its equivalent `events.ndjson`, `mixed_types.json`, and `matching_events.json`. The engine discovers their field paths from the admitted records.
|
|
15
15
|
|
|
16
16
|
| Example | Behavior |
|
|
17
17
|
|---|---|
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
"""Run both parameterized query forms through a local MCP client."""
|
|
2
|
+
|
|
3
|
+
import asyncio
|
|
4
|
+
from typing import Annotated
|
|
5
|
+
|
|
6
|
+
import polars as pl
|
|
7
|
+
from fastmcp import Client
|
|
8
|
+
from pydantic import BaseModel, Field
|
|
9
|
+
|
|
10
|
+
from xp_libs_analytics import AnalyticsEngine, ParameterizedYamlQuery, PolarsQuery
|
|
11
|
+
from xp_libs_analytics.mcp.server import create_server
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class Minimum(BaseModel):
|
|
15
|
+
minimum: Annotated[int, Field(ge=0, le=100)] = 2
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def count_values(frame: pl.DataFrame, parameters: BaseModel) -> pl.DataFrame:
|
|
19
|
+
assert isinstance(parameters, Minimum) # noqa: S101 -- host-owned example contract
|
|
20
|
+
return frame.filter(pl.col("/value") >= parameters.minimum).select(
|
|
21
|
+
pl.len().cast(pl.Int64).alias("count")
|
|
22
|
+
)
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
YAML_COUNT = ParameterizedYamlQuery(
|
|
26
|
+
"""version: analytics.query/v1
|
|
27
|
+
dataset: {name: observations}
|
|
28
|
+
filters:
|
|
29
|
+
- {field: /value, op: gte, value: {$param: minimum}}
|
|
30
|
+
aggregations:
|
|
31
|
+
- {op: count, as: count}
|
|
32
|
+
""",
|
|
33
|
+
Minimum,
|
|
34
|
+
)
|
|
35
|
+
POLARS_COUNT = PolarsQuery("observations", ("/value",), count_values, Minimum, {"count": "integer"})
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
async def main() -> None:
|
|
39
|
+
engine = AnalyticsEngine()
|
|
40
|
+
engine.register_records("observations", [{"value": 1}, {"value": 3}, {"value": 4}])
|
|
41
|
+
server = create_server(
|
|
42
|
+
engine,
|
|
43
|
+
registered_queries={"yaml_count": YAML_COUNT, "polars_count": POLARS_COUNT},
|
|
44
|
+
generated_tools=True,
|
|
45
|
+
)
|
|
46
|
+
async with Client(server) as client:
|
|
47
|
+
yaml_result = await client.call_tool(
|
|
48
|
+
"analytics_query_by_key", {"key": "yaml_count", "parameters": {"minimum": 3}}
|
|
49
|
+
)
|
|
50
|
+
polars_result = await client.call_tool("analytics_registered_polars_count", {"minimum": 3})
|
|
51
|
+
for result in (yaml_result, polars_result):
|
|
52
|
+
if result.structured_content is None or result.structured_content["rows"] != [
|
|
53
|
+
{"count": 2}
|
|
54
|
+
]:
|
|
55
|
+
raise RuntimeError("Unexpected registered query result")
|
|
56
|
+
print(result.structured_content["rows"])
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
if __name__ == "__main__":
|
|
60
|
+
asyncio.run(main())
|
|
@@ -6,7 +6,9 @@ from dataclasses import replace
|
|
|
6
6
|
from importlib.metadata import distribution
|
|
7
7
|
from importlib.resources import files
|
|
8
8
|
|
|
9
|
-
from
|
|
9
|
+
from pydantic import BaseModel
|
|
10
|
+
|
|
11
|
+
from xp_libs_analytics import AnalyticsEngine, ParameterizedYamlQuery
|
|
10
12
|
|
|
11
13
|
assert "fastmcp" not in sys.modules
|
|
12
14
|
assert any(
|
|
@@ -14,7 +16,7 @@ assert any(
|
|
|
14
16
|
for p in distribution("xp-libs-analytics").files
|
|
15
17
|
)
|
|
16
18
|
catalog = json.loads(files("xp_libs_analytics.errors").joinpath("catalog.json").read_text())
|
|
17
|
-
assert len(catalog) ==
|
|
19
|
+
assert len(catalog) == 79
|
|
18
20
|
bundle = files("xp_libs_analytics").joinpath("skills/generic-analytics")
|
|
19
21
|
assert bundle.joinpath("SKILL.md").is_file()
|
|
20
22
|
assert bundle.joinpath("references/query-language-full.md").is_file()
|
|
@@ -22,6 +24,22 @@ assert bundle.joinpath("references/string-matching-full.md").is_file()
|
|
|
22
24
|
assert bundle.joinpath("examples/queries/01_count_all.yaml").is_file()
|
|
23
25
|
assert len(list(bundle.joinpath("examples/queries").iterdir())) == 28
|
|
24
26
|
engine = AnalyticsEngine()
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
class Parameters(BaseModel):
|
|
30
|
+
minimum: int = 1
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
engine.register_records("registered_smoke", [{"value": 0}, {"value": 2}])
|
|
34
|
+
registered = ParameterizedYamlQuery(
|
|
35
|
+
"""version: analytics.query/v1
|
|
36
|
+
dataset: {name: registered_smoke}
|
|
37
|
+
filters: [{field: /value, op: gte, value: {$param: minimum}}]
|
|
38
|
+
aggregations: [{op: count, as: rows}]
|
|
39
|
+
""",
|
|
40
|
+
Parameters,
|
|
41
|
+
)
|
|
42
|
+
assert engine.query_parameterized(registered)["rows"] == [{"rows": 1}]
|
|
25
43
|
handle = engine.register_records("dataset_1", [{"x": 2}, {"x": 4}, {}])
|
|
26
44
|
text = f"""version: analytics.query/v1
|
|
27
45
|
dataset: {{name: {handle.name}}}
|
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
"""Bounded snapshot analytics over arbitrary JSON records and restricted YAML."""
|
|
2
2
|
|
|
3
|
+
from xp_libs_analytics.engine.native import PolarsQuery
|
|
3
4
|
from xp_libs_analytics.errors import AnalyticsError
|
|
4
5
|
from xp_libs_analytics.models import AnalyticsQuery
|
|
5
6
|
from xp_libs_analytics.policy import Limits, Principal
|
|
7
|
+
from xp_libs_analytics.registered import ParameterizedYamlQuery
|
|
6
8
|
from xp_libs_analytics.registry import DatasetInfo, DatasetRegistry
|
|
7
9
|
from xp_libs_analytics.sdk import AnalyticsEngine
|
|
8
10
|
|
|
@@ -13,6 +15,8 @@ __all__ = [
|
|
|
13
15
|
"DatasetInfo",
|
|
14
16
|
"DatasetRegistry",
|
|
15
17
|
"Limits",
|
|
18
|
+
"ParameterizedYamlQuery",
|
|
19
|
+
"PolarsQuery",
|
|
16
20
|
"Principal",
|
|
17
21
|
]
|
|
18
|
-
__version__ = "0.
|
|
22
|
+
__version__ = "0.5.0"
|