graphcheck 0.2.0__tar.gz → 0.3.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.
- graphcheck-0.3.0/.gitattributes +2 -0
- graphcheck-0.3.0/.github/CODEOWNERS +2 -0
- {graphcheck-0.2.0 → graphcheck-0.3.0}/.github/pull_request_template.md +1 -1
- graphcheck-0.3.0/.github/scripts/first-run.sh +107 -0
- graphcheck-0.3.0/.github/scripts/first_run_evidence.py +403 -0
- graphcheck-0.3.0/.github/workflows/ci.yml +144 -0
- graphcheck-0.3.0/.github/workflows/extended-ci.yml +232 -0
- {graphcheck-0.2.0 → graphcheck-0.3.0}/.github/workflows/graphcheck.yml +4 -2
- graphcheck-0.3.0/.github/workflows/hostile-scale.yml +26 -0
- {graphcheck-0.2.0 → graphcheck-0.3.0}/.github/workflows/release.yml +1 -1
- {graphcheck-0.2.0 → graphcheck-0.3.0}/.gitignore +1 -1
- graphcheck-0.3.0/.gitmodules +3 -0
- {graphcheck-0.2.0 → graphcheck-0.3.0}/CHANGELOG.md +65 -4
- {graphcheck-0.2.0 → graphcheck-0.3.0}/CONTRIBUTING.md +25 -5
- {graphcheck-0.2.0 → graphcheck-0.3.0}/PKG-INFO +27 -11
- {graphcheck-0.2.0 → graphcheck-0.3.0}/README.md +25 -10
- graphcheck-0.3.0/docs/README.md +9 -0
- {graphcheck-0.2.0/docs → graphcheck-0.3.0/docs/getting-started}/quickstart.md +7 -4
- {graphcheck-0.2.0/docs → graphcheck-0.3.0/docs/guides}/agents.md +14 -8
- graphcheck-0.3.0/docs/guides/ci-cd.md +400 -0
- graphcheck-0.2.0/docs/ci-setup.md → graphcheck-0.3.0/docs/guides/github-actions.md +26 -28
- {graphcheck-0.2.0/docs → graphcheck-0.3.0/docs/guides}/troubleshooting.md +7 -5
- {graphcheck-0.2.0/docs → graphcheck-0.3.0/docs/guides}/user-guide.md +19 -16
- {graphcheck-0.2.0 → graphcheck-0.3.0}/docs/llms.txt +2 -1
- graphcheck-0.3.0/docs/maintainers/ci.md +97 -0
- graphcheck-0.2.0/docs/clean-machine-test-protocol.md → graphcheck-0.3.0/docs/maintainers/clean-machine-test.md +1 -1
- {graphcheck-0.2.0/docs → graphcheck-0.3.0/docs/maintainers}/releasing.md +5 -2
- graphcheck-0.2.0/docs/check-reference.md → graphcheck-0.3.0/docs/reference/checks.md +2 -2
- {graphcheck-0.2.0/docs → graphcheck-0.3.0/docs/reference}/compatibility.md +15 -10
- {graphcheck-0.2.0/docs → graphcheck-0.3.0/docs/reference}/telemetry.md +1 -1
- graphcheck-0.3.0/docs/samples/report-clean.html +12 -0
- graphcheck-0.3.0/docs/samples/report-findings.html +12 -0
- {graphcheck-0.2.0/docs/samples → graphcheck-0.3.0/docs/samples/reports}/report-clean.html +6 -4
- {graphcheck-0.2.0/docs/samples → graphcheck-0.3.0/docs/samples/reports}/report-findings.html +6 -4
- {graphcheck-0.2.0/docs/specs → graphcheck-0.3.0/docs/schemas}/results.schema.json +5 -5
- graphcheck-0.2.0/docs/specs/SPEC-01-results-json.md → graphcheck-0.3.0/docs/specifications/spec-01-results-json.md +23 -16
- graphcheck-0.2.0/docs/specs/SPEC-02-check-yaml.md → graphcheck-0.3.0/docs/specifications/spec-02-check-yaml.md +4 -4
- graphcheck-0.2.0/docs/specs/SPEC-03-connector.md → graphcheck-0.3.0/docs/specifications/spec-03-connector.md +3 -2
- graphcheck-0.2.0/docs/specs/SPEC-04 Engine.md → graphcheck-0.3.0/docs/specifications/spec-04-engine.md +5 -4
- graphcheck-0.2.0/docs/specs/SPEC-05-profiler-baseline.md → graphcheck-0.3.0/docs/specifications/spec-05-profiler-baseline.md +5 -5
- graphcheck-0.2.0/docs/specs/SPEC-06-scorer-html-report.md → graphcheck-0.3.0/docs/specifications/spec-06-scorer-html-report.md +24 -16
- graphcheck-0.2.0/docs/specs/SPEC-09-packs.md → graphcheck-0.3.0/docs/specifications/spec-09-packs.md +8 -8
- graphcheck-0.2.0/docs/specs/SPEC-11-generate.md → graphcheck-0.3.0/docs/specifications/spec-11-generate.md +11 -11
- graphcheck-0.2.0/docs/specs/SPEC-12-observability-support → graphcheck-0.3.0/docs/specifications/spec-12-observability-support.md +3 -3
- graphcheck-0.3.0/examples/README.md +8 -0
- graphcheck-0.2.0/docs/docker-quickstart.md → graphcheck-0.3.0/examples/fraud-ring/README.md +7 -2
- graphcheck-0.3.0/examples/fraud-ring/checks/fraud-ring-conformance.yml +19 -0
- graphcheck-0.3.0/examples/fraud-ring/checks/smoke.yml +7 -0
- graphcheck-0.3.0/examples/fraud-ring/graphcheck.yml +4 -0
- graphcheck-0.2.0/profiles.yml → graphcheck-0.3.0/examples/fraud-ring/profiles.example.yml +2 -1
- graphcheck-0.3.0/examples/minimal/README.md +17 -0
- {graphcheck-0.2.0/examples → graphcheck-0.3.0/examples/minimal}/checks/example.yml +0 -7
- graphcheck-0.3.0/examples/minimal/graphcheck.yml +4 -0
- graphcheck-0.3.0/examples/minimal/optional-checks/customer-count-drift.yml +8 -0
- graphcheck-0.3.0/examples/minimal/profiles.example.yml +8 -0
- {graphcheck-0.2.0 → graphcheck-0.3.0/examples}/monitoring/README.md +1 -1
- {graphcheck-0.2.0 → graphcheck-0.3.0/examples}/monitoring/prometheus/README.md +3 -3
- {graphcheck-0.2.0 → graphcheck-0.3.0}/pyproject.toml +4 -1
- {graphcheck-0.2.0 → graphcheck-0.3.0}/src/graphcheck/__init__.py +1 -1
- {graphcheck-0.2.0 → graphcheck-0.3.0}/src/graphcheck/cli.py +28 -9
- {graphcheck-0.2.0 → graphcheck-0.3.0}/src/graphcheck/contracts/results.py +14 -8
- {graphcheck-0.2.0 → graphcheck-0.3.0}/src/graphcheck/contracts/schemas.py +10 -10
- {graphcheck-0.2.0 → graphcheck-0.3.0}/src/graphcheck/engine/runner.py +5 -5
- {graphcheck-0.2.0 → graphcheck-0.3.0}/src/graphcheck/neo4j_adapter.py +1 -1
- {graphcheck-0.2.0 → graphcheck-0.3.0}/src/graphcheck/project.py +2 -2
- graphcheck-0.3.0/src/graphcheck/reporting/coverage.py +18 -0
- {graphcheck-0.2.0 → graphcheck-0.3.0}/src/graphcheck/reporting/explorer.py +1 -1
- {graphcheck-0.2.0 → graphcheck-0.3.0}/src/graphcheck/reporting/history.py +15 -22
- {graphcheck-0.2.0 → graphcheck-0.3.0}/src/graphcheck/reporting/html.py +24 -15
- {graphcheck-0.2.0 → graphcheck-0.3.0}/src/graphcheck/reporting/presentation.py +7 -7
- {graphcheck-0.2.0 → graphcheck-0.3.0}/src/graphcheck/reporting/redaction.py +1 -1
- {graphcheck-0.2.0 → graphcheck-0.3.0}/src/graphcheck/reporting/writer.py +17 -11
- {graphcheck-0.2.0 → graphcheck-0.3.0}/tests/conftest.py +2 -0
- {graphcheck-0.2.0 → graphcheck-0.3.0}/tests/integration/conftest.py +1 -1
- graphcheck-0.3.0/tests/integration/hostile/README.md +41 -0
- graphcheck-0.3.0/tests/integration/hostile/apoc-less.yml +10 -0
- graphcheck-0.3.0/tests/integration/hostile/cases.yml +42 -0
- graphcheck-0.3.0/tests/integration/hostile/empty.yml +6 -0
- graphcheck-0.3.0/tests/integration/hostile/llm-kg-builder.cypher +71 -0
- graphcheck-0.3.0/tests/integration/hostile/llm-kg-builder.yml +14 -0
- graphcheck-0.3.0/tests/integration/hostile/neo4j-44-cluster.yml +62 -0
- graphcheck-0.3.0/tests/integration/hostile/public-scale.yml +6 -0
- {graphcheck-0.2.0 → graphcheck-0.3.0}/tests/integration/test_generate_integration.py +1 -1
- {graphcheck-0.2.0 → graphcheck-0.3.0}/tests/integration/test_generate_live.py +1 -1
- graphcheck-0.3.0/tests/integration/test_hostile_graphs.py +376 -0
- {graphcheck-0.2.0 → graphcheck-0.3.0}/tests/integration/test_integration_neo4j_adapter.py +1 -1
- {graphcheck-0.2.0 → graphcheck-0.3.0}/tests/integration/test_mcp_agent_surface.py +3 -1
- {graphcheck-0.2.0 → graphcheck-0.3.0}/tests/integration/test_mcp_failure_isolation.py +1 -1
- {graphcheck-0.2.0 → graphcheck-0.3.0}/tests/integration/test_mcp_neo4j_workflow.py +1 -1
- {graphcheck-0.2.0 → graphcheck-0.3.0}/tests/performance/test_engine_budget.py +1 -1
- {graphcheck-0.2.0 → graphcheck-0.3.0}/tests/property/test_evaluator_properties.py +2 -1
- graphcheck-0.3.0/tests/repository/test_ci_action_boundary.py +19 -0
- {graphcheck-0.2.0/tests → graphcheck-0.3.0/tests/repository}/test_docker_quickstart.py +11 -8
- graphcheck-0.3.0/tests/repository/test_first_run_evidence.py +174 -0
- graphcheck-0.3.0/tests/repository/test_hostile_manifest.py +66 -0
- graphcheck-0.3.0/tests/repository/test_minimal_example.py +31 -0
- {graphcheck-0.2.0/tests → graphcheck-0.3.0/tests/repository}/test_packaging.py +1 -1
- {graphcheck-0.2.0/tests → graphcheck-0.3.0/tests/repository}/test_sample_reports.py +15 -3
- {graphcheck-0.2.0/tests → graphcheck-0.3.0/tests/unit/application}/test_baselines.py +1 -1
- {graphcheck-0.2.0/tests → graphcheck-0.3.0/tests/unit/application}/test_diff.py +1 -1
- {graphcheck-0.2.0/tests → graphcheck-0.3.0/tests/unit/application}/test_project_profiles.py +7 -1
- {graphcheck-0.2.0/tests → graphcheck-0.3.0/tests/unit/cli}/test_cli.py +75 -13
- {graphcheck-0.2.0/tests → graphcheck-0.3.0/tests/unit/cli}/test_cli_lazy_imports.py +2 -2
- {graphcheck-0.2.0/tests → graphcheck-0.3.0/tests/unit/cli}/test_generate_cli.py +1 -1
- {graphcheck-0.2.0/tests → graphcheck-0.3.0/tests/unit/cli}/test_run_cli.py +27 -7
- {graphcheck-0.2.0/tests → graphcheck-0.3.0/tests/unit/connectors}/test_neo4j_adapter.py +6 -3
- {graphcheck-0.2.0/tests → graphcheck-0.3.0/tests/unit}/contracts/fixtures/results.clean.json +2 -2
- {graphcheck-0.2.0/tests → graphcheck-0.3.0/tests/unit}/contracts/fixtures/results.complete.json +2 -2
- {graphcheck-0.2.0/tests → graphcheck-0.3.0/tests/unit}/contracts/fixtures/results.complete.rendered.json +2 -2
- {graphcheck-0.2.0/tests → graphcheck-0.3.0/tests/unit}/contracts/fixtures/results.failed.json +2 -2
- {graphcheck-0.2.0/tests → graphcheck-0.3.0/tests/unit}/contracts/fixtures/results.generated-only.json +2 -2
- {graphcheck-0.2.0/tests → graphcheck-0.3.0/tests/unit}/contracts/fixtures/results.partial.json +2 -2
- {graphcheck-0.2.0/tests → graphcheck-0.3.0/tests/unit}/contracts/test_agents_example.py +2 -2
- {graphcheck-0.2.0/tests → graphcheck-0.3.0/tests/unit}/contracts/test_check_validation.py +3 -7
- {graphcheck-0.2.0/tests → graphcheck-0.3.0/tests/unit}/contracts/test_profile.py +2 -2
- {graphcheck-0.2.0/tests → graphcheck-0.3.0/tests/unit}/contracts/test_results.py +8 -20
- {graphcheck-0.2.0/tests → graphcheck-0.3.0/tests/unit/engine}/test_packs.py +3 -3
- {graphcheck-0.2.0/tests → graphcheck-0.3.0/tests/unit}/engine/test_pii_pack_runtime.py +1 -1
- {graphcheck-0.2.0/tests → graphcheck-0.3.0/tests/unit}/engine/test_runner.py +20 -16
- {graphcheck-0.2.0/tests → graphcheck-0.3.0/tests/unit/mcp}/test_mcp_adapter.py +1 -1
- {graphcheck-0.2.0/tests → graphcheck-0.3.0/tests/unit}/observability/test_monitoring_config.py +4 -4
- graphcheck-0.3.0/tests/unit/observability/test_runner.py +30 -0
- graphcheck-0.3.0/tests/unit/observability/test_server.py +23 -0
- graphcheck-0.3.0/tests/unit/reporting/test_coverage.py +25 -0
- {graphcheck-0.2.0/tests → graphcheck-0.3.0/tests/unit/reporting}/test_redaction.py +1 -1
- {graphcheck-0.2.0/tests → graphcheck-0.3.0/tests/unit/reporting}/test_report_explorer.py +32 -1
- {graphcheck-0.2.0/tests → graphcheck-0.3.0/tests/unit/reporting}/test_report_history.py +100 -8
- {graphcheck-0.2.0/tests → graphcheck-0.3.0/tests/unit/reporting}/test_reporting.py +31 -15
- {graphcheck-0.2.0/tests → graphcheck-0.3.0/tests/unit/reporting}/test_result_presentation.py +63 -4
- {graphcheck-0.2.0/tests → graphcheck-0.3.0/tests/unit}/telemetry/test_cli_boundary_integration.py +8 -4
- {graphcheck-0.2.0/tests → graphcheck-0.3.0/tests/unit}/telemetry/test_transparency_doc.py +4 -4
- {graphcheck-0.2.0/scripts → graphcheck-0.3.0/tools}/generate_sample_reports.py +6 -6
- graphcheck-0.3.0/tools/run_hostile_graphs.py +54 -0
- graphcheck-0.2.0/.gitattributes +0 -2
- graphcheck-0.2.0/.github/CODEOWNERS +0 -5
- graphcheck-0.2.0/.github/actions/graphcheck-action/README.md +0 -94
- graphcheck-0.2.0/.github/actions/graphcheck-action/action.yml +0 -127
- graphcheck-0.2.0/.github/actions/graphcheck-action/generate_profile.py +0 -53
- graphcheck-0.2.0/.github/actions/graphcheck-action/write_summary.py +0 -102
- graphcheck-0.2.0/.github/scripts/first-run.sh +0 -43
- graphcheck-0.2.0/.github/workflows/ci.yml +0 -269
- graphcheck-0.2.0/.gitmodules +0 -3
- graphcheck-0.2.0/graphcheck.yml +0 -4
- graphcheck-0.2.0/tests/observability/test_runner.py +0 -109
- graphcheck-0.2.0/tests/observability/test_server.py +0 -63
- {graphcheck-0.2.0 → graphcheck-0.3.0}/.github/ISSUE_TEMPLATE/bug_report.md +0 -0
- {graphcheck-0.2.0 → graphcheck-0.3.0}/.github/ISSUE_TEMPLATE/deliverable.md +0 -0
- {graphcheck-0.2.0 → graphcheck-0.3.0}/.github/ISSUE_TEMPLATE/docs.md +0 -0
- {graphcheck-0.2.0 → graphcheck-0.3.0}/.github/ISSUE_TEMPLATE/feature_request.md +0 -0
- {graphcheck-0.2.0 → graphcheck-0.3.0}/.pre-commit-config.yaml +0 -0
- {graphcheck-0.2.0 → graphcheck-0.3.0}/.python-version +0 -0
- {graphcheck-0.2.0 → graphcheck-0.3.0}/LICENSE +0 -0
- {graphcheck-0.2.0/docs → graphcheck-0.3.0/docs/assets}/graphcheck-demo.gif +0 -0
- {graphcheck-0.2.0/docs/specs → graphcheck-0.3.0/docs/schemas}/check.envelope.schema.json +0 -0
- {graphcheck-0.2.0/docs/specs → graphcheck-0.3.0/docs/schemas}/check.schema.json +0 -0
- {graphcheck-0.2.0/docs/specs → graphcheck-0.3.0/docs/schemas}/pack.schema.json +0 -0
- {graphcheck-0.2.0/docs/specs → graphcheck-0.3.0/docs/schemas}/profile.schema.json +0 -0
- /graphcheck-0.2.0/docs/specs/SPEC-10-telemetry-events.md → /graphcheck-0.3.0/docs/specifications/spec-10-telemetry-events.md +0 -0
- /graphcheck-0.2.0/docker-compose.yml → /graphcheck-0.3.0/examples/fraud-ring/compose.yml +0 -0
- /graphcheck-0.2.0/monitoring/docker-compose.yml → /graphcheck-0.3.0/examples/monitoring/compose.yml +0 -0
- {graphcheck-0.2.0 → graphcheck-0.3.0/examples}/monitoring/grafana/dashboards/graphcheck-database-health.json +0 -0
- {graphcheck-0.2.0 → graphcheck-0.3.0/examples}/monitoring/grafana/provisioning/dashboards/dashboards.yml +0 -0
- {graphcheck-0.2.0 → graphcheck-0.3.0/examples}/monitoring/grafana/provisioning/datasources/prometheus-datasource.yml +0 -0
- {graphcheck-0.2.0 → graphcheck-0.3.0/examples}/monitoring/prometheus/prometheus.yml +0 -0
- {graphcheck-0.2.0 → graphcheck-0.3.0}/src/graphcheck/application/__init__.py +0 -0
- {graphcheck-0.2.0 → graphcheck-0.3.0}/src/graphcheck/application/artifacts.py +0 -0
- {graphcheck-0.2.0 → graphcheck-0.3.0}/src/graphcheck/application/paths.py +0 -0
- {graphcheck-0.2.0 → graphcheck-0.3.0}/src/graphcheck/application/run.py +0 -0
- {graphcheck-0.2.0 → graphcheck-0.3.0}/src/graphcheck/application/suites.py +0 -0
- {graphcheck-0.2.0 → graphcheck-0.3.0}/src/graphcheck/baselines.py +0 -0
- {graphcheck-0.2.0 → graphcheck-0.3.0}/src/graphcheck/bootstrap.py +0 -0
- {graphcheck-0.2.0 → graphcheck-0.3.0}/src/graphcheck/connection_profiles.py +0 -0
- {graphcheck-0.2.0 → graphcheck-0.3.0}/src/graphcheck/contracts/__init__.py +0 -0
- {graphcheck-0.2.0 → graphcheck-0.3.0}/src/graphcheck/contracts/check.py +0 -0
- {graphcheck-0.2.0 → graphcheck-0.3.0}/src/graphcheck/contracts/profile.py +0 -0
- {graphcheck-0.2.0 → graphcheck-0.3.0}/src/graphcheck/contracts/scalars.py +0 -0
- {graphcheck-0.2.0 → graphcheck-0.3.0}/src/graphcheck/debug_diagnostics.py +0 -0
- {graphcheck-0.2.0 → graphcheck-0.3.0}/src/graphcheck/diff.py +0 -0
- {graphcheck-0.2.0 → graphcheck-0.3.0}/src/graphcheck/engine/__init__.py +0 -0
- {graphcheck-0.2.0 → graphcheck-0.3.0}/src/graphcheck/engine/baseline.py +0 -0
- {graphcheck-0.2.0 → graphcheck-0.3.0}/src/graphcheck/engine/compiler.py +0 -0
- {graphcheck-0.2.0 → graphcheck-0.3.0}/src/graphcheck/engine/core_pack.py +0 -0
- {graphcheck-0.2.0 → graphcheck-0.3.0}/src/graphcheck/engine/evaluator.py +0 -0
- {graphcheck-0.2.0 → graphcheck-0.3.0}/src/graphcheck/engine/executor.py +0 -0
- {graphcheck-0.2.0 → graphcheck-0.3.0}/src/graphcheck/engine/identifiers.py +0 -0
- {graphcheck-0.2.0 → graphcheck-0.3.0}/src/graphcheck/engine/parameters.py +0 -0
- {graphcheck-0.2.0 → graphcheck-0.3.0}/src/graphcheck/engine/pii_pack.py +0 -0
- {graphcheck-0.2.0 → graphcheck-0.3.0}/src/graphcheck/engine/sampling.py +0 -0
- {graphcheck-0.2.0 → graphcheck-0.3.0}/src/graphcheck/errors.py +0 -0
- {graphcheck-0.2.0 → graphcheck-0.3.0}/src/graphcheck/generation/__init__.py +0 -0
- {graphcheck-0.2.0 → graphcheck-0.3.0}/src/graphcheck/generation/client.py +0 -0
- {graphcheck-0.2.0 → graphcheck-0.3.0}/src/graphcheck/generation/config.py +0 -0
- {graphcheck-0.2.0 → graphcheck-0.3.0}/src/graphcheck/generation/disclosure.py +0 -0
- {graphcheck-0.2.0 → graphcheck-0.3.0}/src/graphcheck/generation/prompts.py +0 -0
- {graphcheck-0.2.0 → graphcheck-0.3.0}/src/graphcheck/generation/proposals.py +0 -0
- {graphcheck-0.2.0 → graphcheck-0.3.0}/src/graphcheck/generation/service.py +0 -0
- {graphcheck-0.2.0 → graphcheck-0.3.0}/src/graphcheck/generation/transmission.py +0 -0
- {graphcheck-0.2.0 → graphcheck-0.3.0}/src/graphcheck/generation/writer.py +0 -0
- {graphcheck-0.2.0 → graphcheck-0.3.0}/src/graphcheck/mcp/adapter.py +0 -0
- {graphcheck-0.2.0 → graphcheck-0.3.0}/src/graphcheck/mcp/server.py +0 -0
- {graphcheck-0.2.0 → graphcheck-0.3.0}/src/graphcheck/observability/__init__.py +0 -0
- {graphcheck-0.2.0 → graphcheck-0.3.0}/src/graphcheck/observability/collector.py +0 -0
- {graphcheck-0.2.0 → graphcheck-0.3.0}/src/graphcheck/observability/health.py +0 -0
- {graphcheck-0.2.0 → graphcheck-0.3.0}/src/graphcheck/observability/metrics.py +0 -0
- {graphcheck-0.2.0 → graphcheck-0.3.0}/src/graphcheck/observability/runner.py +0 -0
- {graphcheck-0.2.0 → graphcheck-0.3.0}/src/graphcheck/observability/server.py +0 -0
- {graphcheck-0.2.0 → graphcheck-0.3.0}/src/graphcheck/packs/__init__.py +0 -0
- {graphcheck-0.2.0 → graphcheck-0.3.0}/src/graphcheck/packs/catalog.py +0 -0
- {graphcheck-0.2.0 → graphcheck-0.3.0}/src/graphcheck/packs/core.yml +0 -0
- {graphcheck-0.2.0 → graphcheck-0.3.0}/src/graphcheck/packs/metadata.py +0 -0
- {graphcheck-0.2.0 → graphcheck-0.3.0}/src/graphcheck/packs/pii.yml +0 -0
- {graphcheck-0.2.0 → graphcheck-0.3.0}/src/graphcheck/profiler.py +0 -0
- {graphcheck-0.2.0 → graphcheck-0.3.0}/src/graphcheck/reporting/__init__.py +0 -0
- {graphcheck-0.2.0 → graphcheck-0.3.0}/src/graphcheck/scoring.py +0 -0
- {graphcheck-0.2.0 → graphcheck-0.3.0}/src/graphcheck/telemetry/__init__.py +0 -0
- {graphcheck-0.2.0 → graphcheck-0.3.0}/src/graphcheck/telemetry/collector.py +0 -0
- {graphcheck-0.2.0 → graphcheck-0.3.0}/src/graphcheck/telemetry/consent.py +0 -0
- {graphcheck-0.2.0 → graphcheck-0.3.0}/src/graphcheck/telemetry/events.py +0 -0
- {graphcheck-0.2.0 → graphcheck-0.3.0}/src/graphcheck/telemetry/inactive.py +0 -0
- {graphcheck-0.2.0 → graphcheck-0.3.0}/src/graphcheck/telemetry/policy.py +0 -0
- {graphcheck-0.2.0 → graphcheck-0.3.0}/src/graphcheck/telemetry/posthog.py +0 -0
- {graphcheck-0.2.0 → graphcheck-0.3.0}/src/graphcheck/telemetry/release.py +0 -0
- {graphcheck-0.2.0 → graphcheck-0.3.0}/src/graphcheck/telemetry/runtime.py +0 -0
- {graphcheck-0.2.0 → graphcheck-0.3.0}/src/graphcheck/telemetry/types.py +0 -0
- {graphcheck-0.2.0 → graphcheck-0.3.0}/src/graphcheck/yaml_loader.py +0 -0
- {graphcheck-0.2.0 → graphcheck-0.3.0}/tests/__init__.py +0 -0
- {graphcheck-0.2.0 → graphcheck-0.3.0}/tests/integration/test_integration_engine.py +0 -0
- {graphcheck-0.2.0 → graphcheck-0.3.0}/tests/integration/test_performance_plans.py +0 -0
- {graphcheck-0.2.0 → graphcheck-0.3.0}/tests/performance/__init__.py +0 -0
- {graphcheck-0.2.0 → graphcheck-0.3.0}/tests/performance/budgets.json +0 -0
- {graphcheck-0.2.0 → graphcheck-0.3.0}/tests/performance/gates.py +0 -0
- {graphcheck-0.2.0 → graphcheck-0.3.0}/tests/performance/helpers.py +0 -0
- {graphcheck-0.2.0 → graphcheck-0.3.0}/tests/performance/test_cli_startup.py +0 -0
- {graphcheck-0.2.0 → graphcheck-0.3.0}/tests/performance/test_measurement_helpers.py +0 -0
- {graphcheck-0.2.0 → graphcheck-0.3.0}/tests/performance/test_memory_gates.py +0 -0
- {graphcheck-0.2.0 → graphcheck-0.3.0}/tests/performance/test_regression_gates.py +0 -0
- {graphcheck-0.2.0/tests → graphcheck-0.3.0/tests/unit/application}/test_debug_diagnostics.py +0 -0
- {graphcheck-0.2.0/tests → graphcheck-0.3.0/tests/unit/application}/test_profiler.py +0 -0
- {graphcheck-0.2.0/tests → graphcheck-0.3.0/tests/unit/cli}/test_monitor_cli.py +0 -0
- {graphcheck-0.2.0/tests → graphcheck-0.3.0/tests/unit}/contracts/fixtures/agent-suite.yml +0 -0
- {graphcheck-0.2.0/tests → graphcheck-0.3.0/tests/unit}/contracts/fixtures/baseline.json +0 -0
- {graphcheck-0.2.0/tests → graphcheck-0.3.0/tests/unit}/contracts/fixtures/suite.invalid-bad-expect.yml +0 -0
- {graphcheck-0.2.0/tests → graphcheck-0.3.0/tests/unit}/contracts/fixtures/suite.invalid-duplicate-key.yml +0 -0
- {graphcheck-0.2.0/tests → graphcheck-0.3.0/tests/unit}/contracts/fixtures/suite.invalid-unknown-check.yml +0 -0
- {graphcheck-0.2.0/tests → graphcheck-0.3.0/tests/unit}/contracts/fixtures/suite.invalid-unknown-key.yml +0 -0
- {graphcheck-0.2.0/tests → graphcheck-0.3.0/tests/unit}/contracts/fixtures/suite.valid.yml +0 -0
- {graphcheck-0.2.0/tests → graphcheck-0.3.0/tests/unit}/engine/test_baseline.py +0 -0
- {graphcheck-0.2.0/tests → graphcheck-0.3.0/tests/unit}/engine/test_compiler.py +0 -0
- {graphcheck-0.2.0/tests → graphcheck-0.3.0/tests/unit}/engine/test_core_pack_compiler.py +0 -0
- {graphcheck-0.2.0/tests → graphcheck-0.3.0/tests/unit}/engine/test_evaluator.py +0 -0
- {graphcheck-0.2.0/tests → graphcheck-0.3.0/tests/unit}/engine/test_executor.py +0 -0
- {graphcheck-0.2.0/tests → graphcheck-0.3.0/tests/unit}/engine/test_identifiers.py +0 -0
- {graphcheck-0.2.0/tests → graphcheck-0.3.0/tests/unit}/engine/test_parameters.py +0 -0
- {graphcheck-0.2.0/tests → graphcheck-0.3.0/tests/unit}/engine/test_sampling.py +0 -0
- {graphcheck-0.2.0/tests → graphcheck-0.3.0/tests/unit}/engine/test_sampling_runner.py +0 -0
- {graphcheck-0.2.0/tests → graphcheck-0.3.0/tests/unit/engine}/test_scoring.py +0 -0
- {graphcheck-0.2.0/tests → graphcheck-0.3.0/tests/unit}/generation/test_client.py +0 -0
- {graphcheck-0.2.0/tests → graphcheck-0.3.0/tests/unit}/generation/test_generation.py +0 -0
- {graphcheck-0.2.0/tests → graphcheck-0.3.0/tests/unit}/generation/test_service.py +0 -0
- {graphcheck-0.2.0/tests → graphcheck-0.3.0/tests/unit}/observability/__init__.py +0 -0
- {graphcheck-0.2.0/tests → graphcheck-0.3.0/tests/unit}/observability/test_collector.py +0 -0
- {graphcheck-0.2.0/tests → graphcheck-0.3.0/tests/unit}/observability/test_health.py +0 -0
- {graphcheck-0.2.0/tests → graphcheck-0.3.0/tests/unit}/telemetry/test_boundary_payloads.py +0 -0
- {graphcheck-0.2.0/tests → graphcheck-0.3.0/tests/unit}/telemetry/test_cli_controls.py +0 -0
- {graphcheck-0.2.0/tests → graphcheck-0.3.0/tests/unit}/telemetry/test_engine_events.py +0 -0
- {graphcheck-0.2.0/tests → graphcheck-0.3.0/tests/unit}/telemetry/test_events.py +0 -0
- {graphcheck-0.2.0/tests → graphcheck-0.3.0/tests/unit}/telemetry/test_policy.py +0 -0
- {graphcheck-0.2.0/tests → graphcheck-0.3.0/tests/unit}/telemetry/test_posthog.py +0 -0
- {graphcheck-0.2.0/tests → graphcheck-0.3.0/tests/unit}/telemetry/test_privacy_properties.py +0 -0
- {graphcheck-0.2.0 → graphcheck-0.3.0}/uv.lock +0 -0
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
- [ ] No `print` / `console.log`; structured logger only
|
|
11
11
|
- [ ] No swallowed exceptions (`except: pass`)
|
|
12
12
|
- [ ] No un-issued TODOs
|
|
13
|
-
- [ ] Design detailed in `docs/
|
|
13
|
+
- [ ] Design detailed in `docs/specifications/<name>.md` (where applicable)
|
|
14
14
|
- [ ] CHANGELOG.md entry
|
|
15
15
|
- [ ] No new third-party dependency without team review
|
|
16
16
|
- [ ] No agent meta-commentary or AI attribution anywhere
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
set -euo pipefail
|
|
3
|
+
|
|
4
|
+
: "${GRAPHCHECK_BOOTSTRAP_PYTHON:?Set GRAPHCHECK_BOOTSTRAP_PYTHON to the clean runner Python}"
|
|
5
|
+
: "${GRAPHCHECK_WHEEL_DIR:?Set GRAPHCHECK_WHEEL_DIR to the directory containing one wheel}"
|
|
6
|
+
: "${FIRST_RUN_PLATFORM:?Set FIRST_RUN_PLATFORM to the evidence platform name}"
|
|
7
|
+
: "${FIRST_RUN_TRIAL:?Set FIRST_RUN_TRIAL to the one-based trial number}"
|
|
8
|
+
: "${FIRST_RUN_TESTED_GITHUB_SHA:?Set FIRST_RUN_TESTED_GITHUB_SHA to github.sha}"
|
|
9
|
+
: "${FIRST_RUN_GITHUB_EVENT_NAME:?Set FIRST_RUN_GITHUB_EVENT_NAME to github.event_name}"
|
|
10
|
+
|
|
11
|
+
FIRST_RUN_DIR="${FIRST_RUN_DIR:-${RUNNER_TEMP:-/tmp}/graphcheck-first-run}"
|
|
12
|
+
FIRST_RUN_PR_HEAD_SHA="${FIRST_RUN_PR_HEAD_SHA:-}"
|
|
13
|
+
FIRST_RUN_RUNNER_OS="${FIRST_RUN_RUNNER_OS:-unknown}"
|
|
14
|
+
FIRST_RUN_RUNNER_IMAGE="${FIRST_RUN_RUNNER_IMAGE:-unknown}"
|
|
15
|
+
FIRST_RUN_VENV="${FIRST_RUN_VENV:-${RUNNER_TEMP:-/tmp}/graphcheck-first-run-venv-${FIRST_RUN_PLATFORM}-${FIRST_RUN_TRIAL}}"
|
|
16
|
+
script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
17
|
+
evidence_script="$script_dir/first_run_evidence.py"
|
|
18
|
+
mkdir -p "$FIRST_RUN_DIR"
|
|
19
|
+
|
|
20
|
+
fail_stage() {
|
|
21
|
+
local stage="$1" fix="$2"
|
|
22
|
+
printf 'First-run failed during %s.\nFix: %s\n' "$stage" "$fix" | tee "$FIRST_RUN_DIR/failure.txt" >&2
|
|
23
|
+
exit 1
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
run_stage() {
|
|
27
|
+
local stage="$1" log="$2" fix="$3"
|
|
28
|
+
shift 3
|
|
29
|
+
set +e
|
|
30
|
+
"$@" 2>&1 | tee -a "$log"
|
|
31
|
+
local status=${PIPESTATUS[0]}
|
|
32
|
+
set -e
|
|
33
|
+
test "$status" -eq 0 || fail_stage "$stage" "$fix See $log for details."
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
shopt -s nullglob
|
|
37
|
+
wheels=("$GRAPHCHECK_WHEEL_DIR"/*.whl)
|
|
38
|
+
test "${#wheels[@]}" -eq 1 || fail_stage "wheel discovery" "Place exactly one built GraphCheck wheel in $GRAPHCHECK_WHEEL_DIR."
|
|
39
|
+
wheel="${wheels[0]}"
|
|
40
|
+
|
|
41
|
+
read -r started_at_ns started_monotonic_ns < <("$GRAPHCHECK_BOOTSTRAP_PYTHON" -c "import time; print(time.time_ns(), time.monotonic_ns())")
|
|
42
|
+
install_log="$FIRST_RUN_DIR/install.log"
|
|
43
|
+
run_stage "virtual environment creation" "$install_log" "Verify that Python includes venv support and the runner temp directory is writable." "$GRAPHCHECK_BOOTSTRAP_PYTHON" -m venv "$FIRST_RUN_VENV"
|
|
44
|
+
graphcheck_python="$FIRST_RUN_VENV/bin/python"
|
|
45
|
+
graphcheck_bin="$FIRST_RUN_VENV/bin/graphcheck"
|
|
46
|
+
run_stage "GraphCheck installation" "$install_log" "Verify package-index connectivity and that the wheel supports this Python version." "$graphcheck_python" -m pip install --disable-pip-version-check --no-cache-dir "$wheel"
|
|
47
|
+
install_finished_ns=$("$GRAPHCHECK_BOOTSTRAP_PYTHON" -c "import time; print(time.monotonic_ns())")
|
|
48
|
+
|
|
49
|
+
fixture_log="$FIRST_RUN_DIR/fixture.log"
|
|
50
|
+
run_stage "Neo4j fixture preparation" "$fixture_log" "Start the pinned Neo4j service and verify Bolt credentials." "$graphcheck_python" "$evidence_script" prepare-fixture
|
|
51
|
+
fixture_finished_ns=$("$GRAPHCHECK_BOOTSTRAP_PYTHON" -c "import time; print(time.monotonic_ns())")
|
|
52
|
+
|
|
53
|
+
project="$FIRST_RUN_DIR/project"
|
|
54
|
+
mkdir -p "$project"
|
|
55
|
+
cd "$project"
|
|
56
|
+
init_started_ns=$("$GRAPHCHECK_BOOTSTRAP_PYTHON" -c "import time; print(time.monotonic_ns())")
|
|
57
|
+
run_stage "graphcheck init" "$FIRST_RUN_DIR/init.log" "Run graphcheck debug --json and correct the reported connection or project configuration problem." "$graphcheck_bin" init
|
|
58
|
+
init_finished_ns=$("$GRAPHCHECK_BOOTSTRAP_PYTHON" -c "import time; print(time.monotonic_ns())")
|
|
59
|
+
grep -q "Detected Neo4j" "$FIRST_RUN_DIR/init.log" || fail_stage "graphcheck init connectivity" "Run graphcheck debug --json and correct the reported Neo4j connection problem."
|
|
60
|
+
grep -q "Traceback (most recent call last)" "$FIRST_RUN_DIR/init.log" && fail_stage "graphcheck init diagnostics" "Use the stable diagnostic instead of exposing a Python traceback."
|
|
61
|
+
for required in graphcheck.yml profiles.yml checks/example.yml; do
|
|
62
|
+
test -f "$required" || fail_stage "graphcheck init artifact validation" "Confirm the project directory is writable and retry graphcheck init."
|
|
63
|
+
done
|
|
64
|
+
|
|
65
|
+
run_started_ns=$("$GRAPHCHECK_BOOTSTRAP_PYTHON" -c "import time; print(time.monotonic_ns())")
|
|
66
|
+
set +e
|
|
67
|
+
"$graphcheck_bin" run 2>&1 | tee "$FIRST_RUN_DIR/run.log"
|
|
68
|
+
run_exit_code=${PIPESTATUS[0]}
|
|
69
|
+
set -e
|
|
70
|
+
run_finished_ns=$("$GRAPHCHECK_BOOTSTRAP_PYTHON" -c "import time; print(time.monotonic_ns())")
|
|
71
|
+
finished_at_ns=$("$GRAPHCHECK_BOOTSTRAP_PYTHON" -c "import time; print(time.time_ns())")
|
|
72
|
+
grep -q "Traceback (most recent call last)" "$FIRST_RUN_DIR/run.log" && fail_stage "graphcheck run diagnostics" "Use the stable diagnostic instead of exposing a Python traceback."
|
|
73
|
+
|
|
74
|
+
python_version=$("$graphcheck_python" -c "import platform; print(platform.python_version())")
|
|
75
|
+
run_stage "first-result validation" "$FIRST_RUN_DIR/validation.log" "Inspect results.json, report.html, and run.log; a valid result must be complete, scored, and execute at least one check." \
|
|
76
|
+
"$GRAPHCHECK_BOOTSTRAP_PYTHON" "$evidence_script" record \
|
|
77
|
+
--results .graphcheck/runs/latest/results.json \
|
|
78
|
+
--report .graphcheck/runs/latest/report.html \
|
|
79
|
+
--output "$FIRST_RUN_DIR/timing.json" \
|
|
80
|
+
--platform "$FIRST_RUN_PLATFORM" \
|
|
81
|
+
--trial "$FIRST_RUN_TRIAL" \
|
|
82
|
+
--run-exit-code "$run_exit_code" \
|
|
83
|
+
--started-at-ns "$started_at_ns" \
|
|
84
|
+
--finished-at-ns "$finished_at_ns" \
|
|
85
|
+
--install-ns "$((install_finished_ns - started_monotonic_ns))" \
|
|
86
|
+
--fixture-ns "$((fixture_finished_ns - install_finished_ns))" \
|
|
87
|
+
--init-ns "$((init_finished_ns - init_started_ns))" \
|
|
88
|
+
--run-ns "$((run_finished_ns - run_started_ns))" \
|
|
89
|
+
--total-ns "$((run_finished_ns - started_monotonic_ns))" \
|
|
90
|
+
--python-version "$python_version" \
|
|
91
|
+
--runner-os "$FIRST_RUN_RUNNER_OS" \
|
|
92
|
+
--runner-image "$FIRST_RUN_RUNNER_IMAGE" \
|
|
93
|
+
--tested-github-sha "$FIRST_RUN_TESTED_GITHUB_SHA" \
|
|
94
|
+
--pull-request-head-sha "$FIRST_RUN_PR_HEAD_SHA" \
|
|
95
|
+
--github-event-name "$FIRST_RUN_GITHUB_EVENT_NAME"
|
|
96
|
+
|
|
97
|
+
total_seconds=$("$GRAPHCHECK_BOOTSTRAP_PYTHON" -c "print(round(($run_finished_ns - $started_monotonic_ns) / 1_000_000_000, 3))")
|
|
98
|
+
printf 'Install -> init -> first valid run completed in %.3f seconds; median budget: <900 seconds.\n' "$total_seconds" | tee "$FIRST_RUN_DIR/timing.txt" | tee -a "$FIRST_RUN_DIR/run.log"
|
|
99
|
+
|
|
100
|
+
profile_log="$FIRST_RUN_DIR/profile.log"
|
|
101
|
+
run_stage "graphcheck profile smoke check" "$profile_log" "Run graphcheck debug --json and correct the reported profiling problem." "$graphcheck_bin" profile
|
|
102
|
+
grep -q "Traceback (most recent call last)" "$profile_log" && fail_stage "graphcheck profile diagnostics" "Use the stable diagnostic instead of exposing a Python traceback."
|
|
103
|
+
baseline_found=0
|
|
104
|
+
for baseline in .graphcheck/baselines/*.json; do
|
|
105
|
+
test -f "$baseline" && baseline_found=1 && break
|
|
106
|
+
done
|
|
107
|
+
test "$baseline_found" -eq 1 || fail_stage "graphcheck profile artifact validation" "Confirm the artifact directory is writable and retry graphcheck profile."
|
|
@@ -0,0 +1,403 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""Record and aggregate clean-environment first-run evidence using only the stdlib."""
|
|
3
|
+
|
|
4
|
+
from __future__ import annotations
|
|
5
|
+
|
|
6
|
+
import argparse
|
|
7
|
+
import json
|
|
8
|
+
import math
|
|
9
|
+
import statistics
|
|
10
|
+
import sys
|
|
11
|
+
import time
|
|
12
|
+
from datetime import UTC, datetime
|
|
13
|
+
from pathlib import Path
|
|
14
|
+
|
|
15
|
+
SCHEMA_VERSION = "1"
|
|
16
|
+
DEFAULT_PLATFORMS = ("linux-container", "macos", "wsl")
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def _seconds(nanoseconds: int) -> float:
|
|
20
|
+
return round(nanoseconds / 1_000_000_000, 3)
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def _timestamp(nanoseconds: int) -> str:
|
|
24
|
+
return datetime.fromtimestamp(nanoseconds / 1_000_000_000, UTC).isoformat()
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def _read_results(path: Path, errors: list[str]) -> dict[str, object] | None:
|
|
28
|
+
try:
|
|
29
|
+
payload = json.loads(path.read_text(encoding="utf-8"))
|
|
30
|
+
except (OSError, json.JSONDecodeError) as exc:
|
|
31
|
+
errors.append(f"results.json could not be read: {type(exc).__name__}: {exc}")
|
|
32
|
+
return None
|
|
33
|
+
if not isinstance(payload, dict):
|
|
34
|
+
errors.append("results.json must contain a JSON object")
|
|
35
|
+
return None
|
|
36
|
+
return payload
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def _result_facts(
|
|
40
|
+
payload: dict[str, object] | None, run_exit_code: int, errors: list[str]
|
|
41
|
+
) -> tuple[int | None, int | float | None, str | None, str | None]:
|
|
42
|
+
if payload is None:
|
|
43
|
+
return None, None, None, None
|
|
44
|
+
run = payload.get("run")
|
|
45
|
+
totals = payload.get("totals")
|
|
46
|
+
score = payload.get("score")
|
|
47
|
+
if not isinstance(run, dict):
|
|
48
|
+
errors.append("results.json is missing the run object")
|
|
49
|
+
run = {}
|
|
50
|
+
if not isinstance(totals, dict):
|
|
51
|
+
errors.append("results.json is missing the totals object")
|
|
52
|
+
totals = {}
|
|
53
|
+
selected, skipped = totals.get("checks"), totals.get("skipped")
|
|
54
|
+
executed = (
|
|
55
|
+
selected - skipped
|
|
56
|
+
if isinstance(selected, int)
|
|
57
|
+
and not isinstance(selected, bool)
|
|
58
|
+
and isinstance(skipped, int)
|
|
59
|
+
and not isinstance(skipped, bool)
|
|
60
|
+
else None
|
|
61
|
+
)
|
|
62
|
+
if executed is None or executed < 1:
|
|
63
|
+
errors.append("results.json must contain at least one executed check")
|
|
64
|
+
run_status = run.get("run_status", run.get("status"))
|
|
65
|
+
if run_status != "complete":
|
|
66
|
+
errors.append("results.json run.run_status must be complete")
|
|
67
|
+
if run_exit_code not in {0, 1, 2}:
|
|
68
|
+
errors.append("a complete scored run must exit with code 0, 1, or 2")
|
|
69
|
+
if run.get("exit_code") != run_exit_code:
|
|
70
|
+
errors.append("the command exit code does not match results.json")
|
|
71
|
+
score_value = score.get("value") if isinstance(score, dict) else None
|
|
72
|
+
if (
|
|
73
|
+
isinstance(score_value, bool)
|
|
74
|
+
or not isinstance(score_value, (int, float))
|
|
75
|
+
or not math.isfinite(score_value)
|
|
76
|
+
):
|
|
77
|
+
errors.append("results.json must contain a finite numeric score")
|
|
78
|
+
score_value = None
|
|
79
|
+
elif not 0 <= score_value <= 100:
|
|
80
|
+
errors.append("results.json score must be between 0 and 100")
|
|
81
|
+
target = run.get("target")
|
|
82
|
+
return (
|
|
83
|
+
executed,
|
|
84
|
+
score_value,
|
|
85
|
+
run.get("graphcheck_version") if isinstance(run.get("graphcheck_version"), str) else None,
|
|
86
|
+
(
|
|
87
|
+
target.get("server_version")
|
|
88
|
+
if isinstance(target, dict) and isinstance(target.get("server_version"), str)
|
|
89
|
+
else None
|
|
90
|
+
),
|
|
91
|
+
)
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
def record(args: argparse.Namespace) -> int:
|
|
95
|
+
errors: list[str] = []
|
|
96
|
+
if args.trial < 1:
|
|
97
|
+
errors.append("trial must be a positive integer")
|
|
98
|
+
if args.budget_seconds < 1:
|
|
99
|
+
errors.append("budget_seconds must be a positive integer")
|
|
100
|
+
if args.finished_at_ns < args.started_at_ns:
|
|
101
|
+
errors.append("finished_at must not precede started_at")
|
|
102
|
+
if not args.tested_github_sha:
|
|
103
|
+
errors.append("tested_github_sha must identify the checkout tested by this trial")
|
|
104
|
+
if args.github_event_name == "pull_request" and not args.pull_request_head_sha:
|
|
105
|
+
errors.append("pull_request_head_sha is required for pull_request evidence")
|
|
106
|
+
for name in ("install", "fixture", "init", "run", "total"):
|
|
107
|
+
if getattr(args, f"{name}_ns") < 0:
|
|
108
|
+
errors.append(f"{name} timing must be non-negative")
|
|
109
|
+
results_path, report_path, output_path = map(Path, (args.results, args.report, args.output))
|
|
110
|
+
payload = _read_results(results_path, errors)
|
|
111
|
+
if not report_path.is_file() or report_path.stat().st_size < 1:
|
|
112
|
+
errors.append("report.html is missing or empty")
|
|
113
|
+
executed, score, graphcheck_version, neo4j_version = _result_facts(
|
|
114
|
+
payload, args.run_exit_code, errors
|
|
115
|
+
)
|
|
116
|
+
timings = {
|
|
117
|
+
name: _seconds(getattr(args, f"{name}_ns"))
|
|
118
|
+
for name in ("install", "fixture", "init", "run", "total")
|
|
119
|
+
}
|
|
120
|
+
evidence = {
|
|
121
|
+
"schema_version": SCHEMA_VERSION,
|
|
122
|
+
"platform": args.platform,
|
|
123
|
+
"trial": args.trial,
|
|
124
|
+
"github_event_name": args.github_event_name,
|
|
125
|
+
"tested_github_sha": args.tested_github_sha,
|
|
126
|
+
"pull_request_head_sha": args.pull_request_head_sha or None,
|
|
127
|
+
"provenance_note": (
|
|
128
|
+
"tested_github_sha is github.sha: the synthetic merge commit for pull_request events "
|
|
129
|
+
"and the pushed commit for push events"
|
|
130
|
+
),
|
|
131
|
+
"runner_os": args.runner_os,
|
|
132
|
+
"runner_image": args.runner_image,
|
|
133
|
+
"python_version": args.python_version,
|
|
134
|
+
"graphcheck_version": graphcheck_version,
|
|
135
|
+
"neo4j_version": neo4j_version,
|
|
136
|
+
"timing_boundary": "clean-wheel-install-start_to_valid-init-run-result",
|
|
137
|
+
"database_ready_before_install": False,
|
|
138
|
+
"profile_smoke_after_timing": True,
|
|
139
|
+
"started_at": _timestamp(args.started_at_ns),
|
|
140
|
+
"finished_at": _timestamp(args.finished_at_ns),
|
|
141
|
+
"timings_seconds": timings,
|
|
142
|
+
"budget_seconds": args.budget_seconds,
|
|
143
|
+
"within_individual_budget": timings["total"] < args.budget_seconds,
|
|
144
|
+
"run_exit_code": args.run_exit_code,
|
|
145
|
+
"executed_checks": executed,
|
|
146
|
+
"score": score,
|
|
147
|
+
"valid": not errors,
|
|
148
|
+
"validation_errors": errors,
|
|
149
|
+
"artifacts": {"results": str(results_path), "report": str(report_path)},
|
|
150
|
+
}
|
|
151
|
+
output_path.parent.mkdir(parents=True, exist_ok=True)
|
|
152
|
+
output_path.write_text(json.dumps(evidence, indent=2) + "\n", encoding="utf-8")
|
|
153
|
+
if errors:
|
|
154
|
+
for error in errors:
|
|
155
|
+
print(f"First-run evidence invalid: {error}", file=sys.stderr)
|
|
156
|
+
print(
|
|
157
|
+
f"Fix: inspect the command logs and {output_path}, correct the failure, and retry.",
|
|
158
|
+
file=sys.stderr,
|
|
159
|
+
)
|
|
160
|
+
return 1
|
|
161
|
+
print(
|
|
162
|
+
f"Valid first result: {executed} executed check(s), score {score}, "
|
|
163
|
+
f"install-to-result {timings['total']:.3f}s."
|
|
164
|
+
)
|
|
165
|
+
return 0
|
|
166
|
+
|
|
167
|
+
|
|
168
|
+
def prepare_fixture(args: argparse.Namespace) -> int:
|
|
169
|
+
from neo4j import GraphDatabase
|
|
170
|
+
|
|
171
|
+
deadline, last_error = time.monotonic() + args.timeout_seconds, None
|
|
172
|
+
while time.monotonic() < deadline:
|
|
173
|
+
try:
|
|
174
|
+
with GraphDatabase.driver(
|
|
175
|
+
args.uri,
|
|
176
|
+
auth=(args.user, args.password),
|
|
177
|
+
connection_timeout=1,
|
|
178
|
+
) as driver:
|
|
179
|
+
driver.verify_connectivity()
|
|
180
|
+
driver.execute_query(
|
|
181
|
+
"MERGE (:Customer {name: 'Ada'})",
|
|
182
|
+
database_=args.database,
|
|
183
|
+
)
|
|
184
|
+
print(f"Prepared first-run fixture at {args.uri}/{args.database}.")
|
|
185
|
+
return 0
|
|
186
|
+
except Exception as exc:
|
|
187
|
+
last_error = exc
|
|
188
|
+
time.sleep(2)
|
|
189
|
+
print(
|
|
190
|
+
"First-run fixture setup failed: Neo4j did not become ready "
|
|
191
|
+
f"within {args.timeout_seconds} seconds ({type(last_error).__name__}: {last_error}).\n"
|
|
192
|
+
"Fix: verify that Neo4j is running, Bolt is reachable, and the configured credentials "
|
|
193
|
+
"are correct.",
|
|
194
|
+
file=sys.stderr,
|
|
195
|
+
)
|
|
196
|
+
return 1
|
|
197
|
+
|
|
198
|
+
|
|
199
|
+
def _load_samples(root: Path) -> tuple[dict[tuple[str, int], dict[str, object]], list[str]]:
|
|
200
|
+
samples: dict[tuple[str, int], dict[str, object]] = {}
|
|
201
|
+
errors: list[str] = []
|
|
202
|
+
for path in sorted(root.rglob("timing.json")):
|
|
203
|
+
try:
|
|
204
|
+
sample = json.loads(path.read_text(encoding="utf-8"))
|
|
205
|
+
key = (str(sample["platform"]), int(sample["trial"]))
|
|
206
|
+
except (OSError, json.JSONDecodeError, KeyError, TypeError, ValueError) as exc:
|
|
207
|
+
errors.append(f"{path}: unreadable timing evidence ({type(exc).__name__}: {exc})")
|
|
208
|
+
continue
|
|
209
|
+
if key in samples:
|
|
210
|
+
errors.append(f"duplicate timing evidence for {key[0]} trial {key[1]}")
|
|
211
|
+
else:
|
|
212
|
+
samples[key] = sample
|
|
213
|
+
return samples, errors
|
|
214
|
+
|
|
215
|
+
|
|
216
|
+
def summarize(args: argparse.Namespace) -> int:
|
|
217
|
+
root, output_dir = Path(args.input), Path(args.output)
|
|
218
|
+
samples, errors = _load_samples(root)
|
|
219
|
+
tested_github_shas = sorted(
|
|
220
|
+
{
|
|
221
|
+
value
|
|
222
|
+
for sample in samples.values()
|
|
223
|
+
if isinstance((value := sample.get("tested_github_sha")), str) and value
|
|
224
|
+
}
|
|
225
|
+
)
|
|
226
|
+
pull_request_head_shas = sorted(
|
|
227
|
+
{
|
|
228
|
+
value
|
|
229
|
+
for sample in samples.values()
|
|
230
|
+
if isinstance((value := sample.get("pull_request_head_sha")), str) and value
|
|
231
|
+
}
|
|
232
|
+
)
|
|
233
|
+
github_event_names = sorted(
|
|
234
|
+
{
|
|
235
|
+
value
|
|
236
|
+
for sample in samples.values()
|
|
237
|
+
if isinstance((value := sample.get("github_event_name")), str) and value
|
|
238
|
+
}
|
|
239
|
+
)
|
|
240
|
+
for (platform_name, trial), sample in samples.items():
|
|
241
|
+
if not isinstance(sample.get("tested_github_sha"), str) or not sample["tested_github_sha"]:
|
|
242
|
+
errors.append(f"{platform_name} trial {trial} is missing tested_github_sha")
|
|
243
|
+
if sample.get("github_event_name") == "pull_request" and not sample.get(
|
|
244
|
+
"pull_request_head_sha"
|
|
245
|
+
):
|
|
246
|
+
errors.append(f"{platform_name} trial {trial} is missing pull_request_head_sha")
|
|
247
|
+
if len(tested_github_shas) != 1:
|
|
248
|
+
errors.append("timing evidence must contain exactly one tested GitHub SHA")
|
|
249
|
+
if len(pull_request_head_shas) > 1:
|
|
250
|
+
errors.append("timing evidence contains more than one pull-request head SHA")
|
|
251
|
+
if len(github_event_names) != 1:
|
|
252
|
+
errors.append("timing evidence must contain exactly one GitHub event name")
|
|
253
|
+
platforms: dict[str, dict[str, object]] = {}
|
|
254
|
+
for platform_name in args.platforms:
|
|
255
|
+
observations: list[dict[str, object]] = []
|
|
256
|
+
for trial in range(1, args.trials + 1):
|
|
257
|
+
sample = samples.get((platform_name, trial))
|
|
258
|
+
if sample is None:
|
|
259
|
+
errors.append(f"missing timing evidence for {platform_name} trial {trial}")
|
|
260
|
+
continue
|
|
261
|
+
timings = sample.get("timings_seconds")
|
|
262
|
+
total = timings.get("total") if isinstance(timings, dict) else None
|
|
263
|
+
if sample.get("valid") is not True:
|
|
264
|
+
errors.append(f"{platform_name} trial {trial} did not produce a valid result")
|
|
265
|
+
if (
|
|
266
|
+
isinstance(total, bool)
|
|
267
|
+
or not isinstance(total, (int, float))
|
|
268
|
+
or not math.isfinite(total)
|
|
269
|
+
or total < 0
|
|
270
|
+
):
|
|
271
|
+
errors.append(f"{platform_name} trial {trial} has invalid total timing")
|
|
272
|
+
continue
|
|
273
|
+
observations.append({"trial": trial, "total_seconds": total})
|
|
274
|
+
median_seconds = (
|
|
275
|
+
round(statistics.median(item["total_seconds"] for item in observations), 3)
|
|
276
|
+
if len(observations) == args.trials
|
|
277
|
+
else None
|
|
278
|
+
)
|
|
279
|
+
under_budget = median_seconds is not None and median_seconds < args.budget_seconds
|
|
280
|
+
if median_seconds is not None and not under_budget:
|
|
281
|
+
errors.append(
|
|
282
|
+
f"{platform_name} median {median_seconds:.3f}s is not under {args.budget_seconds}s"
|
|
283
|
+
)
|
|
284
|
+
platforms[platform_name] = {
|
|
285
|
+
"samples": observations,
|
|
286
|
+
"median_seconds": median_seconds,
|
|
287
|
+
"under_budget": under_budget,
|
|
288
|
+
}
|
|
289
|
+
passed = not errors
|
|
290
|
+
summary = {
|
|
291
|
+
"schema_version": SCHEMA_VERSION,
|
|
292
|
+
"metric": "clean-wheel-install-start_to_valid-init-run-result",
|
|
293
|
+
"tested_github_sha": tested_github_shas[0] if len(tested_github_shas) == 1 else None,
|
|
294
|
+
"pull_request_head_sha": (
|
|
295
|
+
pull_request_head_shas[0] if len(pull_request_head_shas) == 1 else None
|
|
296
|
+
),
|
|
297
|
+
"github_event_name": github_event_names[0] if len(github_event_names) == 1 else None,
|
|
298
|
+
"budget_seconds": args.budget_seconds,
|
|
299
|
+
"trials_per_platform": args.trials,
|
|
300
|
+
"platforms": platforms,
|
|
301
|
+
"passed": passed,
|
|
302
|
+
"errors": errors,
|
|
303
|
+
}
|
|
304
|
+
lines = ["# GraphCheck first-run evidence", ""]
|
|
305
|
+
if len(tested_github_shas) == 1:
|
|
306
|
+
lines.extend((f"Tested GitHub SHA: `{tested_github_shas[0]}`", ""))
|
|
307
|
+
if len(pull_request_head_shas) == 1:
|
|
308
|
+
lines.extend((f"Pull-request head SHA: `{pull_request_head_shas[0]}`", ""))
|
|
309
|
+
lines.extend(
|
|
310
|
+
(
|
|
311
|
+
"Budget: median install-to-first-valid-result "
|
|
312
|
+
f"**under {args.budget_seconds // 60} minutes**.",
|
|
313
|
+
"",
|
|
314
|
+
"| Platform | Samples (seconds) | Median (seconds) | Result |",
|
|
315
|
+
"| --- | ---: | ---: | --- |",
|
|
316
|
+
)
|
|
317
|
+
)
|
|
318
|
+
for name, result in platforms.items():
|
|
319
|
+
sample_text = (
|
|
320
|
+
", ".join(str(item["total_seconds"]) for item in result["samples"]) or "missing"
|
|
321
|
+
)
|
|
322
|
+
median_text = (
|
|
323
|
+
str(result["median_seconds"]) if result["median_seconds"] is not None else "n/a"
|
|
324
|
+
)
|
|
325
|
+
lines.append(
|
|
326
|
+
f"| {name} | {sample_text} | {median_text} | "
|
|
327
|
+
f"{'PASS' if result['under_budget'] else 'FAIL'} |"
|
|
328
|
+
)
|
|
329
|
+
if errors:
|
|
330
|
+
lines.extend(("", "## Errors", "", *(f"- {error}" for error in errors)))
|
|
331
|
+
lines.extend(("", f"Overall: **{'PASS' if passed else 'FAIL'}**", ""))
|
|
332
|
+
output_dir.mkdir(parents=True, exist_ok=True)
|
|
333
|
+
(output_dir / "first-run-summary.json").write_text(
|
|
334
|
+
json.dumps(summary, indent=2) + "\n", encoding="utf-8"
|
|
335
|
+
)
|
|
336
|
+
(output_dir / "first-run-summary.md").write_text("\n".join(lines), encoding="utf-8")
|
|
337
|
+
print("\n".join(lines))
|
|
338
|
+
return 0 if passed else 1
|
|
339
|
+
|
|
340
|
+
|
|
341
|
+
def parser() -> argparse.ArgumentParser:
|
|
342
|
+
root = argparse.ArgumentParser()
|
|
343
|
+
commands = root.add_subparsers(dest="command", required=True)
|
|
344
|
+
record_parser = commands.add_parser("record")
|
|
345
|
+
for name in (
|
|
346
|
+
"results",
|
|
347
|
+
"report",
|
|
348
|
+
"output",
|
|
349
|
+
"platform",
|
|
350
|
+
"python-version",
|
|
351
|
+
"runner-os",
|
|
352
|
+
"runner-image",
|
|
353
|
+
"tested-github-sha",
|
|
354
|
+
"github-event-name",
|
|
355
|
+
):
|
|
356
|
+
record_parser.add_argument(f"--{name}", required=True)
|
|
357
|
+
for name in (
|
|
358
|
+
"started-at-ns",
|
|
359
|
+
"finished-at-ns",
|
|
360
|
+
"install-ns",
|
|
361
|
+
"fixture-ns",
|
|
362
|
+
"init-ns",
|
|
363
|
+
"run-ns",
|
|
364
|
+
"total-ns",
|
|
365
|
+
):
|
|
366
|
+
record_parser.add_argument(f"--{name}", type=int, required=True)
|
|
367
|
+
record_parser.add_argument("--trial", type=int, required=True)
|
|
368
|
+
record_parser.add_argument("--run-exit-code", type=int, required=True)
|
|
369
|
+
record_parser.add_argument("--pull-request-head-sha", default="")
|
|
370
|
+
record_parser.add_argument("--budget-seconds", type=int, default=900)
|
|
371
|
+
record_parser.set_defaults(handler=record)
|
|
372
|
+
fixture_parser = commands.add_parser("prepare-fixture")
|
|
373
|
+
fixture_parser.add_argument("--uri", default="bolt://localhost:7687")
|
|
374
|
+
fixture_parser.add_argument("--user", default="neo4j")
|
|
375
|
+
fixture_parser.add_argument("--password", default="graphora")
|
|
376
|
+
fixture_parser.add_argument("--database", default="neo4j")
|
|
377
|
+
fixture_parser.add_argument("--timeout-seconds", type=int, default=120)
|
|
378
|
+
fixture_parser.set_defaults(handler=prepare_fixture)
|
|
379
|
+
summary_parser = commands.add_parser("summarize")
|
|
380
|
+
summary_parser.add_argument("--input", required=True)
|
|
381
|
+
summary_parser.add_argument("--output", required=True)
|
|
382
|
+
summary_parser.add_argument("--platforms", nargs="+", default=list(DEFAULT_PLATFORMS))
|
|
383
|
+
summary_parser.add_argument("--trials", type=int, default=3)
|
|
384
|
+
summary_parser.add_argument("--budget-seconds", type=int, default=900)
|
|
385
|
+
summary_parser.set_defaults(handler=summarize)
|
|
386
|
+
return root
|
|
387
|
+
|
|
388
|
+
|
|
389
|
+
def main() -> int:
|
|
390
|
+
try:
|
|
391
|
+
args = parser().parse_args()
|
|
392
|
+
return args.handler(args)
|
|
393
|
+
except Exception as exc:
|
|
394
|
+
print(
|
|
395
|
+
f"First-run evidence tooling failed: {type(exc).__name__}: {exc}\n"
|
|
396
|
+
"Fix: verify the evidence arguments and files, then retry.",
|
|
397
|
+
file=sys.stderr,
|
|
398
|
+
)
|
|
399
|
+
return 1
|
|
400
|
+
|
|
401
|
+
|
|
402
|
+
if __name__ == "__main__":
|
|
403
|
+
raise SystemExit(main())
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
name: CI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
pull_request:
|
|
5
|
+
push:
|
|
6
|
+
branches: [development, main]
|
|
7
|
+
|
|
8
|
+
jobs:
|
|
9
|
+
secret-scan:
|
|
10
|
+
runs-on: ubuntu-latest
|
|
11
|
+
permissions:
|
|
12
|
+
contents: read
|
|
13
|
+
steps:
|
|
14
|
+
- uses: actions/checkout@v4
|
|
15
|
+
with:
|
|
16
|
+
fetch-depth: 0
|
|
17
|
+
- name: Install checksum-verified gitleaks
|
|
18
|
+
env:
|
|
19
|
+
GITLEAKS_VERSION: 8.29.1
|
|
20
|
+
GITLEAKS_SHA256: e4eb209d04e20339d77122a3bdf9cd41351255cfb27ebcb75e85325e04f88924
|
|
21
|
+
run: >-
|
|
22
|
+
archive="$RUNNER_TEMP/gitleaks.tar.gz"; curl --fail --silent --show-error --location
|
|
23
|
+
"https://github.com/gitleaks/gitleaks/releases/download/v${GITLEAKS_VERSION}/gitleaks_${GITLEAKS_VERSION}_linux_x64.tar.gz"
|
|
24
|
+
--output "$archive"; echo "$GITLEAKS_SHA256 $archive" | sha256sum --check -;
|
|
25
|
+
tar -xzf "$archive" -C "$RUNNER_TEMP" gitleaks; echo "$RUNNER_TEMP" >> "$GITHUB_PATH"
|
|
26
|
+
- name: Scan pull-request commits
|
|
27
|
+
if: github.event_name == 'pull_request'
|
|
28
|
+
env:
|
|
29
|
+
BASE_SHA: ${{ github.event.pull_request.base.sha }}
|
|
30
|
+
HEAD_SHA: ${{ github.event.pull_request.head.sha }}
|
|
31
|
+
run: gitleaks git --redact --log-opts="${BASE_SHA}..${HEAD_SHA}" .
|
|
32
|
+
- name: Scan protected-branch history
|
|
33
|
+
if: github.event_name == 'push'
|
|
34
|
+
run: gitleaks git --redact .
|
|
35
|
+
|
|
36
|
+
lint:
|
|
37
|
+
runs-on: ubuntu-latest
|
|
38
|
+
steps:
|
|
39
|
+
- uses: actions/checkout@v4
|
|
40
|
+
- uses: astral-sh/setup-uv@v5
|
|
41
|
+
- run: uv sync --group dev
|
|
42
|
+
- run: uv run ruff check .
|
|
43
|
+
- run: uv run ruff format --check .
|
|
44
|
+
|
|
45
|
+
# Repo-wide suite on the locked driver. The `test (3.12)` / `test (3.13)` contexts are
|
|
46
|
+
# required by the branch ruleset, so this job's name and matrix must stay stable.
|
|
47
|
+
test:
|
|
48
|
+
runs-on: ubuntu-latest
|
|
49
|
+
strategy:
|
|
50
|
+
fail-fast: false
|
|
51
|
+
matrix:
|
|
52
|
+
python-version: ["3.12", "3.13", "3.14"]
|
|
53
|
+
steps:
|
|
54
|
+
- uses: actions/checkout@v4
|
|
55
|
+
with:
|
|
56
|
+
submodules: recursive
|
|
57
|
+
- uses: astral-sh/setup-uv@v5
|
|
58
|
+
with:
|
|
59
|
+
python-version: ${{ matrix.python-version }}
|
|
60
|
+
- run: uv sync --group dev
|
|
61
|
+
- run: uv run pytest --cov=graphcheck --cov-report=term-missing --cov-fail-under=80
|
|
62
|
+
|
|
63
|
+
# Neo4j Python driver compatibility across the supported range, on a separate job name so
|
|
64
|
+
# it does not collide with the required `test` contexts above.
|
|
65
|
+
driver-compat:
|
|
66
|
+
runs-on: ubuntu-latest
|
|
67
|
+
strategy:
|
|
68
|
+
fail-fast: false
|
|
69
|
+
matrix:
|
|
70
|
+
include:
|
|
71
|
+
- python-version: "3.12"
|
|
72
|
+
driver-line: min
|
|
73
|
+
- python-version: "3.14"
|
|
74
|
+
driver-line: latest-6
|
|
75
|
+
steps:
|
|
76
|
+
- uses: actions/checkout@v4
|
|
77
|
+
with:
|
|
78
|
+
submodules: recursive
|
|
79
|
+
- name: Install uv and select the matrix Python
|
|
80
|
+
uses: astral-sh/setup-uv@v5
|
|
81
|
+
with:
|
|
82
|
+
# Sets UV_PYTHON, which overrides the committed .python-version so uv
|
|
83
|
+
# runs on the matrix interpreter (not the pinned 3.12).
|
|
84
|
+
python-version: ${{ matrix.python-version }}
|
|
85
|
+
- run: uv sync --group dev
|
|
86
|
+
- name: Select tested Neo4j Python driver line
|
|
87
|
+
env:
|
|
88
|
+
DRIVER_LINE: ${{ matrix.driver-line }}
|
|
89
|
+
run: |
|
|
90
|
+
if [ "$DRIVER_LINE" = "min" ]; then
|
|
91
|
+
uv pip install "neo4j==5.20.0"
|
|
92
|
+
else
|
|
93
|
+
uv pip install --upgrade "neo4j>=6,<7"
|
|
94
|
+
fi
|
|
95
|
+
- name: Assert the interpreter matches the matrix (guards against a single pinned run)
|
|
96
|
+
env:
|
|
97
|
+
WANT: ${{ matrix.python-version }}
|
|
98
|
+
run: uv run --no-sync python -c "import os, sys; want=tuple(int(p) for p in os.environ['WANT'].split('.')); assert sys.version_info[:2]==want, sys.version"
|
|
99
|
+
- name: Test installed driver line
|
|
100
|
+
env:
|
|
101
|
+
PYTHONDEVMODE: ${{ matrix.driver-line == 'min' && '1' || '0' }}
|
|
102
|
+
PYTHONWARNINGS: ${{ matrix.driver-line == 'min' && 'error::ResourceWarning' || 'default' }}
|
|
103
|
+
run: >
|
|
104
|
+
uv run --no-sync pytest
|
|
105
|
+
tests/unit/connectors/test_neo4j_adapter.py
|
|
106
|
+
tests/unit/engine/test_executor.py
|
|
107
|
+
tests/unit/application/test_profiler.py
|
|
108
|
+
tests/unit/application/test_debug_diagnostics.py
|
|
109
|
+
|
|
110
|
+
integration:
|
|
111
|
+
runs-on: ubuntu-latest
|
|
112
|
+
timeout-minutes: 15
|
|
113
|
+
strategy:
|
|
114
|
+
fail-fast: false
|
|
115
|
+
matrix:
|
|
116
|
+
neo4j-target: [lts-cypher-5, current-cypher-5, current-cypher-25]
|
|
117
|
+
env:
|
|
118
|
+
GRAPHCHECK_NEO4J_INTEGRATION: "1"
|
|
119
|
+
GRAPHCHECK_NEO4J_TARGET: ${{ matrix.neo4j-target }}
|
|
120
|
+
steps:
|
|
121
|
+
- uses: actions/checkout@v4
|
|
122
|
+
- uses: astral-sh/setup-uv@v5
|
|
123
|
+
- run: uv sync --group dev
|
|
124
|
+
- run: >
|
|
125
|
+
uv run pytest
|
|
126
|
+
tests/integration/test_integration_neo4j_adapter.py
|
|
127
|
+
tests/integration/test_integration_engine.py
|
|
128
|
+
tests/integration/test_hostile_graphs.py
|
|
129
|
+
tests/integration/test_mcp_neo4j_workflow.py
|
|
130
|
+
tests/integration/test_performance_plans.py
|
|
131
|
+
-v
|
|
132
|
+
|
|
133
|
+
installed-wheel:
|
|
134
|
+
runs-on: ubuntu-latest
|
|
135
|
+
steps:
|
|
136
|
+
- uses: actions/checkout@v4
|
|
137
|
+
- uses: astral-sh/setup-uv@v5
|
|
138
|
+
- run: uv build
|
|
139
|
+
- run: uv venv .wheel-venv
|
|
140
|
+
- run: uv pip install --python .wheel-venv/bin/python dist/*.whl
|
|
141
|
+
- run: .wheel-venv/bin/graphcheck --version
|
|
142
|
+
- run: .wheel-venv/bin/graphcheck --help
|
|
143
|
+
- name: Verify installed package resources
|
|
144
|
+
run: .wheel-venv/bin/python -c "from importlib.resources import files; assert files('graphcheck').joinpath('packs/core.yml').is_file(); assert files('graphcheck').joinpath('packs/pii.yml').is_file()"
|