From c065957549133c557d30678333d7f2c9ad8cacd5 Mon Sep 17 00:00:00 2001 From: Jeff Schnitter Date: Mon, 12 Jan 2026 13:35:41 -0800 Subject: [PATCH 1/5] Release 1.8.0: Bug fixes and iconTag test coverage #minor (#189) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * perf: optimize test scheduling with --dist loadfile for 25% faster test runs * feat: add support for Cortex Secrets API (#161) * chore: update HISTORY.md for main * perf: optimize test scheduling with --dist loadfile for 25% faster test runs (#157) * refactor: separate trigger-evaluation test to avoid scorecard evaluation race conditions - Create dedicated cli-test-evaluation-scorecard for trigger-evaluation testing - Remove retry logic complexity from test_scorecards() and test_scorecards_drafts() - Add new test_scorecard_trigger_evaluation() that creates/deletes its own scorecard - Eliminates race condition where import triggers evaluation conflicting with tests * refactor: remove unnecessary mock decorator from _get_rule helper function The helper function doesn't need its own environment patching since it's called from fixtures that already have their own @mock.patch.dict decorators. * Revert "perf: optimize test scheduling with --dist loadfile for 25% faster test runs (#157)" This reverts commit 8879fcfa7ee30a73f023e8bbef7d799808493319. The --dist loadfile optimization caused race conditions between tests that share resources (e.g., test_custom_events_uuid and test_custom_events_list both operate on custom events and can interfere with each other when run in parallel by file). Reliability > speed. Better to have tests take 40s with no race conditions than 30s with intermittent failures. * perf: rename test_deploys.py to test_000_deploys.py for early scheduling Pytest collects tests alphabetically by filename. With pytest-xdist --dist load, tests collected earlier are more likely to be scheduled first. Since test_deploys is the longest-running test (~19s), scheduling it early maximizes parallel efficiency with 12 workers. This is our general strategy: prefix slow tests with numbers (000, 001, etc.) to control scheduling order without introducing race conditions like --dist loadfile. * feat: add support for Cortex Secrets API Add complete CLI support for managing secrets via the Cortex Secrets API: - cortex secrets list: List secrets (with optional entity tag filter) - cortex secrets get: Get secret by alias - cortex secrets create: Create new secret - cortex secrets update: Update existing secret - cortex secrets delete: Delete secret All commands support entity tags as required by the API. Tests skip gracefully if API key lacks secrets permissions. Also fixes HISTORY.md generation by using Angular convention in git-changelog, which properly recognizes feat:, fix:, and perf: commit types instead of only recognizing the basic convention (add:, fix:, change:, remove:). Closes #158 * fix: update secret test to use valid tag format Change test secret tag from 'cli-test-secret' to 'cli_test_secret' to comply with API validation that only allows alphanumeric and underscore characters. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --------- Co-authored-by: GitHub Actions Co-authored-by: Claude * feat: add entity relationships API support with optimized backup/restore (#160) * chore: update HISTORY.md for main * perf: optimize test scheduling with --dist loadfile for 25% faster test runs (#157) * refactor: separate trigger-evaluation test to avoid scorecard evaluation race conditions - Create dedicated cli-test-evaluation-scorecard for trigger-evaluation testing - Remove retry logic complexity from test_scorecards() and test_scorecards_drafts() - Add new test_scorecard_trigger_evaluation() that creates/deletes its own scorecard - Eliminates race condition where import triggers evaluation conflicting with tests * refactor: remove unnecessary mock decorator from _get_rule helper function The helper function doesn't need its own environment patching since it's called from fixtures that already have their own @mock.patch.dict decorators. * Revert "perf: optimize test scheduling with --dist loadfile for 25% faster test runs (#157)" This reverts commit 8879fcfa7ee30a73f023e8bbef7d799808493319. The --dist loadfile optimization caused race conditions between tests that share resources (e.g., test_custom_events_uuid and test_custom_events_list both operate on custom events and can interfere with each other when run in parallel by file). Reliability > speed. Better to have tests take 40s with no race conditions than 30s with intermittent failures. * perf: rename test_deploys.py to test_000_deploys.py for early scheduling Pytest collects tests alphabetically by filename. With pytest-xdist --dist load, tests collected earlier are more likely to be scheduled first. Since test_deploys is the longest-running test (~19s), scheduling it early maximizes parallel efficiency with 12 workers. This is our general strategy: prefix slow tests with numbers (000, 001, etc.) to control scheduling order without introducing race conditions like --dist loadfile. * feat: add entity relationships API support and fix backup export bug - Fix double-encoding bug in backup export for entity-types and ip-allowlist - entity-types and ip-allowlist were being converted to strings before json.dump - This caused import failures with "TypeError: string indices must be integers" - Add entity-relationship-types commands: - list: List all relationship types - get: Get relationship type by tag - create: Create new relationship type - update: Update existing relationship type - delete: Delete relationship type - Add entity-relationships commands: - list: List all relationships for a type - list-destinations: Get destinations for source entity - list-sources: Get sources for destination entity - add-destinations: Add destinations to source - add-sources: Add sources to destination - update-destinations: Replace all destinations for source - update-sources: Replace all sources for destination - add-bulk: Add multiple relationships - update-bulk: Replace all relationships for type - Integrate entity relationships into backup/restore: - Export entity-relationship-types and entity-relationships - Import with proper ordering (types before catalog, relationships after) - Transform export format to bulk update format for import 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude * fix: clean up entity relationships import output and fix bugs - Add _print parameter to entity_relationship_types.create() and entity_relationships.update_bulk() - Use _print=False when importing to suppress JSON output - Fix import to use correct keys: sourceEntity.tag and destinationEntity.tag instead of source.tag - Replace typer.unstable.TempFile with standard tempfile.NamedTemporaryFile - Improve output: show only tag names instead of full JSON when importing - Add missing tempfile import 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude * fix: support re-importing existing entity relationship types - Check if relationship type already exists before importing - Use update instead of create for existing relationship types - Add _print parameter to entity_relationship_types.update() - Matches pattern used by entity_types import This allows backup imports to be idempotent and run multiple times without encountering "already exists" errors. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude * feat: improve error handling in backup import - Add detailed error reporting for catalog imports - Show filename, error type, and error message for failures - Add total failure count at end of catalog import - Add error handling for entity relationship type imports - Wrap create/update in try/except blocks - Show which relationship type failed and why - Add total failure count - Add error handling for entity relationship imports - Wrap import operations in try/except blocks - Show which relationship type failed and why - Add total failure count This makes it much easier to diagnose import failures by showing exactly which files are failing and what the error is. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude * fix: improve catalog import error handling and make sequential - Change catalog import from parallel to sequential execution - This allows errors to be correlated with specific files - HTTP errors from cortex_client are now shown with filenames - Catch typer.Exit exceptions in catalog import - The HTTP client raises typer.Exit on errors - Now catches and reports which file caused the error - Remove unused imports added for parallel error capture - Simplify catalog import logic Note: The plugin import failures with "string indices must be integers" are due to exports created before the double-encoding bug fix. Re-export with the current code to fix these. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude * perf: parallelize entity relationships and catalog imports - Restore parallel execution for catalog import (30 workers) - Previously made sequential for error debugging - Now handles typer.Exit exceptions properly - Maintains good error reporting with filenames - Parallelize entity relationship type imports (30 workers) - Check existing types once, then import in parallel - Properly handles create vs update decision - Parallelize entity relationship imports (30 workers) - Each relationship type is independent - Can safely import in parallel All imports now use ThreadPoolExecutor with 30 workers for maximum performance while maintaining error reporting. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude * feat: add comprehensive import summary and retry commands - All import functions now return statistics (type, imported count, failures) - Import summary section shows: - Per-type import counts and failures - Total imported and failed counts - Failed imports section lists: - Full file paths for all failures - Error type and message for each - Retry commands section provides: - Ready-to-run cortex commands for each failed file - Can copy/paste directly from terminal - Commands use proper quoting for file paths with spaces - Updated all import functions to track file paths in parallel execution - Added typer.Exit exception handling to plugins, scorecards, workflows - Consistent error reporting across all import types Example output: IMPORT SUMMARY catalog: 250 imported, 5 failed TOTAL: 500 imported, 5 failed FAILED IMPORTS /path/to/file.yaml Error: HTTP - Validation or HTTP error RETRY COMMANDS cortex catalog create -f "/path/to/file.yaml" 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude * fix: show catalog filename before import attempt - Print "Importing: filename" before attempting catalog import - HTTP errors now appear immediately after the filename - Remove duplicate success messages at end - Only show failure count summary This makes it immediately clear which file is causing each HTTP 400 error: Before: Processing: .../catalog HTTP Error 400: Unknown error HTTP Error 400: Unknown error After: Processing: .../catalog Importing: docs.yaml HTTP Error 400: Unknown error Importing: another.yaml HTTP Error 400: Unknown error 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude * fix: improve test isolation for custom events list test Delete all event types (not just VALIDATE_SERVICE) at test start to prevent interference from parallel tests. The connection pooling performance improvements made tests run much faster, increasing temporal overlap between parallel tests and exposing this existing test isolation issue. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude * fix: initialize variables before conditional to prevent NameError When entity-relationship-types or entity-relationships directories don't exist (like in test data), the import functions would reference undefined `results` and `failed_count` variables, causing a NameError and preventing subsequent imports from running (including catalog import, breaking tests). This bug was causing test_catalog_delete_entity and test_custom_events_list to fail because the import would crash before importing catalog entities. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --------- Co-authored-by: GitHub Actions Co-authored-by: Claude * fix: add client-side rate limiting and make tests idempotent (#165) #minor * chore: update HISTORY.md for main * perf: optimize test scheduling with --dist loadfile for 25% faster test runs (#157) * refactor: separate trigger-evaluation test to avoid scorecard evaluation race conditions - Create dedicated cli-test-evaluation-scorecard for trigger-evaluation testing - Remove retry logic complexity from test_scorecards() and test_scorecards_drafts() - Add new test_scorecard_trigger_evaluation() that creates/deletes its own scorecard - Eliminates race condition where import triggers evaluation conflicting with tests * refactor: remove unnecessary mock decorator from _get_rule helper function The helper function doesn't need its own environment patching since it's called from fixtures that already have their own @mock.patch.dict decorators. * Revert "perf: optimize test scheduling with --dist loadfile for 25% faster test runs (#157)" This reverts commit 8879fcfa7ee30a73f023e8bbef7d799808493319. The --dist loadfile optimization caused race conditions between tests that share resources (e.g., test_custom_events_uuid and test_custom_events_list both operate on custom events and can interfere with each other when run in parallel by file). Reliability > speed. Better to have tests take 40s with no race conditions than 30s with intermittent failures. * perf: rename test_deploys.py to test_000_deploys.py for early scheduling Pytest collects tests alphabetically by filename. With pytest-xdist --dist load, tests collected earlier are more likely to be scheduled first. Since test_deploys is the longest-running test (~19s), scheduling it early maximizes parallel efficiency with 12 workers. This is our general strategy: prefix slow tests with numbers (000, 001, etc.) to control scheduling order without introducing race conditions like --dist loadfile. * feat: add entity relationships API support and fix backup export bug - Fix double-encoding bug in backup export for entity-types and ip-allowlist - entity-types and ip-allowlist were being converted to strings before json.dump - This caused import failures with "TypeError: string indices must be integers" - Add entity-relationship-types commands: - list: List all relationship types - get: Get relationship type by tag - create: Create new relationship type - update: Update existing relationship type - delete: Delete relationship type - Add entity-relationships commands: - list: List all relationships for a type - list-destinations: Get destinations for source entity - list-sources: Get sources for destination entity - add-destinations: Add destinations to source - add-sources: Add sources to destination - update-destinations: Replace all destinations for source - update-sources: Replace all sources for destination - add-bulk: Add multiple relationships - update-bulk: Replace all relationships for type - Integrate entity relationships into backup/restore: - Export entity-relationship-types and entity-relationships - Import with proper ordering (types before catalog, relationships after) - Transform export format to bulk update format for import 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude * fix: clean up entity relationships import output and fix bugs - Add _print parameter to entity_relationship_types.create() and entity_relationships.update_bulk() - Use _print=False when importing to suppress JSON output - Fix import to use correct keys: sourceEntity.tag and destinationEntity.tag instead of source.tag - Replace typer.unstable.TempFile with standard tempfile.NamedTemporaryFile - Improve output: show only tag names instead of full JSON when importing - Add missing tempfile import 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude * fix: support re-importing existing entity relationship types - Check if relationship type already exists before importing - Use update instead of create for existing relationship types - Add _print parameter to entity_relationship_types.update() - Matches pattern used by entity_types import This allows backup imports to be idempotent and run multiple times without encountering "already exists" errors. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude * feat: improve error handling in backup import - Add detailed error reporting for catalog imports - Show filename, error type, and error message for failures - Add total failure count at end of catalog import - Add error handling for entity relationship type imports - Wrap create/update in try/except blocks - Show which relationship type failed and why - Add total failure count - Add error handling for entity relationship imports - Wrap import operations in try/except blocks - Show which relationship type failed and why - Add total failure count This makes it much easier to diagnose import failures by showing exactly which files are failing and what the error is. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude * fix: improve catalog import error handling and make sequential - Change catalog import from parallel to sequential execution - This allows errors to be correlated with specific files - HTTP errors from cortex_client are now shown with filenames - Catch typer.Exit exceptions in catalog import - The HTTP client raises typer.Exit on errors - Now catches and reports which file caused the error - Remove unused imports added for parallel error capture - Simplify catalog import logic Note: The plugin import failures with "string indices must be integers" are due to exports created before the double-encoding bug fix. Re-export with the current code to fix these. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude * perf: parallelize entity relationships and catalog imports - Restore parallel execution for catalog import (30 workers) - Previously made sequential for error debugging - Now handles typer.Exit exceptions properly - Maintains good error reporting with filenames - Parallelize entity relationship type imports (30 workers) - Check existing types once, then import in parallel - Properly handles create vs update decision - Parallelize entity relationship imports (30 workers) - Each relationship type is independent - Can safely import in parallel All imports now use ThreadPoolExecutor with 30 workers for maximum performance while maintaining error reporting. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude * feat: add comprehensive import summary and retry commands - All import functions now return statistics (type, imported count, failures) - Import summary section shows: - Per-type import counts and failures - Total imported and failed counts - Failed imports section lists: - Full file paths for all failures - Error type and message for each - Retry commands section provides: - Ready-to-run cortex commands for each failed file - Can copy/paste directly from terminal - Commands use proper quoting for file paths with spaces - Updated all import functions to track file paths in parallel execution - Added typer.Exit exception handling to plugins, scorecards, workflows - Consistent error reporting across all import types Example output: IMPORT SUMMARY catalog: 250 imported, 5 failed TOTAL: 500 imported, 5 failed FAILED IMPORTS /path/to/file.yaml Error: HTTP - Validation or HTTP error RETRY COMMANDS cortex catalog create -f "/path/to/file.yaml" 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude * fix: show catalog filename before import attempt - Print "Importing: filename" before attempting catalog import - HTTP errors now appear immediately after the filename - Remove duplicate success messages at end - Only show failure count summary This makes it immediately clear which file is causing each HTTP 400 error: Before: Processing: .../catalog HTTP Error 400: Unknown error HTTP Error 400: Unknown error After: Processing: .../catalog Importing: docs.yaml HTTP Error 400: Unknown error Importing: another.yaml HTTP Error 400: Unknown error 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude * fix: improve test isolation for custom events list test Delete all event types (not just VALIDATE_SERVICE) at test start to prevent interference from parallel tests. The connection pooling performance improvements made tests run much faster, increasing temporal overlap between parallel tests and exposing this existing test isolation issue. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude * fix: initialize variables before conditional to prevent NameError When entity-relationship-types or entity-relationships directories don't exist (like in test data), the import functions would reference undefined `results` and `failed_count` variables, causing a NameError and preventing subsequent imports from running (including catalog import, breaking tests). This bug was causing test_catalog_delete_entity and test_custom_events_list to fail because the import would crash before importing catalog entities. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude * chore: merge staging to main #minor (#162) * perf: optimize test scheduling with --dist loadfile for 25% faster test runs * feat: add support for Cortex Secrets API (#161) * chore: update HISTORY.md for main * perf: optimize test scheduling with --dist loadfile for 25% faster test runs (#157) * refactor: separate trigger-evaluation test to avoid scorecard evaluation race conditions - Create dedicated cli-test-evaluation-scorecard for trigger-evaluation testing - Remove retry logic complexity from test_scorecards() and test_scorecards_drafts() - Add new test_scorecard_trigger_evaluation() that creates/deletes its own scorecard - Eliminates race condition where import triggers evaluation conflicting with tests * refactor: remove unnecessary mock decorator from _get_rule helper function The helper function doesn't need its own environment patching since it's called from fixtures that already have their own @mock.patch.dict decorators. * Revert "perf: optimize test scheduling with --dist loadfile for 25% faster test runs (#157)" This reverts commit 8879fcfa7ee30a73f023e8bbef7d799808493319. The --dist loadfile optimization caused race conditions between tests that share resources (e.g., test_custom_events_uuid and test_custom_events_list both operate on custom events and can interfere with each other when run in parallel by file). Reliability > speed. Better to have tests take 40s with no race conditions than 30s with intermittent failures. * perf: rename test_deploys.py to test_000_deploys.py for early scheduling Pytest collects tests alphabetically by filename. With pytest-xdist --dist load, tests collected earlier are more likely to be scheduled first. Since test_deploys is the longest-running test (~19s), scheduling it early maximizes parallel efficiency with 12 workers. This is our general strategy: prefix slow tests with numbers (000, 001, etc.) to control scheduling order without introducing race conditions like --dist loadfile. * feat: add support for Cortex Secrets API Add complete CLI support for managing secrets via the Cortex Secrets API: - cortex secrets list: List secrets (with optional entity tag filter) - cortex secrets get: Get secret by alias - cortex secrets create: Create new secret - cortex secrets update: Update existing secret - cortex secrets delete: Delete secret All commands support entity tags as required by the API. Tests skip gracefully if API key lacks secrets permissions. Also fixes HISTORY.md generation by using Angular convention in git-changelog, which properly recognizes feat:, fix:, and perf: commit types instead of only recognizing the basic convention (add:, fix:, change:, remove:). Closes #158 * fix: update secret test to use valid tag format Change test secret tag from 'cli-test-secret' to 'cli_test_secret' to comply with API validation that only allows alphanumeric and underscore characters. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --------- Co-authored-by: GitHub Actions Co-authored-by: Claude * feat: add entity relationships API support with optimized backup/restore (#160) * chore: update HISTORY.md for main * perf: optimize test scheduling with --dist loadfile for 25% faster test runs (#157) * refactor: separate trigger-evaluation test to avoid scorecard evaluation race conditions - Create dedicated cli-test-evaluation-scorecard for trigger-evaluation testing - Remove retry logic complexity from test_scorecards() and test_scorecards_drafts() - Add new test_scorecard_trigger_evaluation() that creates/deletes its own scorecard - Eliminates race condition where import triggers evaluation conflicting with tests * refactor: remove unnecessary mock decorator from _get_rule helper function The helper function doesn't need its own environment patching since it's called from fixtures that already have their own @mock.patch.dict decorators. * Revert "perf: optimize test scheduling with --dist loadfile for 25% faster test runs (#157)" This reverts commit 8879fcfa7ee30a73f023e8bbef7d799808493319. The --dist loadfile optimization caused race conditions between tests that share resources (e.g., test_custom_events_uuid and test_custom_events_list both operate on custom events and can interfere with each other when run in parallel by file). Reliability > speed. Better to have tests take 40s with no race conditions than 30s with intermittent failures. * perf: rename test_deploys.py to test_000_deploys.py for early scheduling Pytest collects tests alphabetically by filename. With pytest-xdist --dist load, tests collected earlier are more likely to be scheduled first. Since test_deploys is the longest-running test (~19s), scheduling it early maximizes parallel efficiency with 12 workers. This is our general strategy: prefix slow tests with numbers (000, 001, etc.) to control scheduling order without introducing race conditions like --dist loadfile. * feat: add entity relationships API support and fix backup export bug - Fix double-encoding bug in backup export for entity-types and ip-allowlist - entity-types and ip-allowlist were being converted to strings before json.dump - This caused import failures with "TypeError: string indices must be integers" - Add entity-relationship-types commands: - list: List all relationship types - get: Get relationship type by tag - create: Create new relationship type - update: Update existing relationship type - delete: Delete relationship type - Add entity-relationships commands: - list: List all relationships for a type - list-destinations: Get destinations for source entity - list-sources: Get sources for destination entity - add-destinations: Add destinations to source - add-sources: Add sources to destination - update-destinations: Replace all destinations for source - update-sources: Replace all sources for destination - add-bulk: Add multiple relationships - update-bulk: Replace all relationships for type - Integrate entity relationships into backup/restore: - Export entity-relationship-types and entity-relationships - Import with proper ordering (types before catalog, relationships after) - Transform export format to bulk update format for import 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude * fix: clean up entity relationships import output and fix bugs - Add _print parameter to entity_relationship_types.create() and entity_relationships.update_bulk() - Use _print=False when importing to suppress JSON output - Fix import to use correct keys: sourceEntity.tag and destinationEntity.tag instead of source.tag - Replace typer.unstable.TempFile with standard tempfile.NamedTemporaryFile - Improve output: show only tag names instead of full JSON when importing - Add missing tempfile import 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude * fix: support re-importing existing entity relationship types - Check if relationship type already exists before importing - Use update instead of create for existing relationship types - Add _print parameter to entity_relationship_types.update() - Matches pattern used by entity_types import This allows backup imports to be idempotent and run multiple times without encountering "already exists" errors. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude * feat: improve error handling in backup import - Add detailed error reporting for catalog imports - Show filename, error type, and error message for failures - Add total failure count at end of catalog import - Add error handling for entity relationship type imports - Wrap create/update in try/except blocks - Show which relationship type failed and why - Add total failure count - Add error handling for entity relationship imports - Wrap import operations in try/except blocks - Show which relationship type failed and why - Add total failure count This makes it much easier to diagnose import failures by showing exactly which files are failing and what the error is. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude * fix: improve catalog import error handling and make sequential - Change catalog import from parallel to sequential execution - This allows errors to be correlated with specific files - HTTP errors from cortex_client are now shown with filenames - Catch typer.Exit exceptions in catalog import - The HTTP client raises typer.Exit on errors - Now catches and reports which file caused the error - Remove unused imports added for parallel error capture - Simplify catalog import logic Note: The plugin import failures with "string indices must be integers" are due to exports created before the double-encoding bug fix. Re-export with the current code to fix these. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude * perf: parallelize entity relationships and catalog imports - Restore parallel execution for catalog import (30 workers) - Previously made sequential for error debugging - Now handles typer.Exit exceptions properly - Maintains good error reporting with filenames - Parallelize entity relationship type imports (30 workers) - Check existing types once, then import in parallel - Properly handles create vs update decision - Parallelize entity relationship imports (30 workers) - Each relationship type is independent - Can safely import in parallel All imports now use ThreadPoolExecutor with 30 workers for maximum performance while maintaining error reporting. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude * feat: add comprehensive import summary and retry commands - All import functions now return statistics (type, imported count, failures) - Import summary section shows: - Per-type import counts and failures - Total imported and failed counts - Failed imports section lists: - Full file paths for all failures - Error type and message for each - Retry commands section provides: - Ready-to-run cortex commands for each failed file - Can copy/paste directly from terminal - Commands use proper quoting for file paths with spaces - Updated all import functions to track file paths in parallel execution - Added typer.Exit exception handling to plugins, scorecards, workflows - Consistent error reporting across all import types Example output: IMPORT SUMMARY catalog: 250 imported, 5 failed TOTAL: 500 imported, 5 failed FAILED IMPORTS /path/to/file.yaml Error: HTTP - Validation or HTTP error RETRY COMMANDS cortex catalog create -f "/path/to/file.yaml" 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude * fix: show catalog filename before import attempt - Print "Importing: filename" before attempting catalog import - HTTP errors now appear immediately after the filename - Remove duplicate success messages at end - Only show failure count summary This makes it immediately clear which file is causing each HTTP 400 error: Before: Processing: .../catalog HTTP Error 400: Unknown error HTTP Error 400: Unknown error After: Processing: .../catalog Importing: docs.yaml HTTP Error 400: Unknown error Importing: another.yaml HTTP Error 400: Unknown error 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude * fix: improve test isolation for custom events list test Delete all event types (not just VALIDATE_SERVICE) at test start to prevent interference from parallel tests. The connection pooling performance improvements made tests run much faster, increasing temporal overlap between parallel tests and exposing this existing test isolation issue. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude * fix: initialize variables before conditional to prevent NameError When entity-relationship-types or entity-relationships directories don't exist (like in test data), the import functions would reference undefined `results` and `failed_count` variables, causing a NameError and preventing subsequent imports from running (including catalog import, breaking tests). This bug was causing test_catalog_delete_entity and test_custom_events_list to fail because the import would crash before importing catalog entities. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --------- Co-authored-by: GitHub Actions Co-authored-by: Claude --------- Co-authored-by: GitHub Actions Co-authored-by: Claude * chore: update HISTORY.md for main * add: pytest configuration with perf marker Add pytest.ini to configure test markers: - perf: for performance tests excluded from regular runs - setup: for setup tests that run before other tests The perf marker is excluded by default via addopts, ensuring performance tests only run via 'just test-perf'. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude * fix: exclude perf tests from test-all command Update Justfile to exclude both 'setup' and 'perf' markers from test-all and _test-all-individual recipes. Previously only excluded 'setup', which caused performance tests to run and slow down the regular test suite. Also add test-perf recipe to explicitly run performance tests. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude * fix: allow 'just test' to run perf tests Override pytest.ini's default marker exclusion in the 'test' recipe by passing -m "" (empty marker filter). This allows users to run individual test files including perf tests via 'just test '. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude * add: client-side rate limiting with TokenBucket algorithm Implements proactive rate limiting to avoid 429 errors rather than reactively retrying them. Key changes: - Add TokenBucket class for thread-safe rate limiting (1000 req/min) - Configure 50-token burst capacity for initial request batches - Remove 429 from retry status_forcelist (now prevented by rate limiter) - Add performance test validating rate limiter with 250 parallel workers - Test confirms 100% success rate at ~1049 req/min with no 429 errors This approach is more efficient than retry-based rate limiting as it prevents rate limit errors proactively, reducing overall request latency and eliminating wasted retry attempts. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude * add: configurable rate limit via CORTEX_RATE_LIMIT env var Allows testing higher rate limits to determine if actual API limit is higher than documented 1000 req/min. Changes: - Read rate limit from CORTEX_RATE_LIMIT environment variable - Default to 1000 req/min if not set - Allows runtime testing of different rate limits without code changes Usage: export CORTEX_RATE_LIMIT=1500 cortex backup catalog --export-dir ./test 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude * add: --rate-limit CLI flag for configurable rate limiting Adds global CLI flag to configure API rate limit alongside existing CORTEX_RATE_LIMIT environment variable. Changes: - Add --rate-limit/-r flag to global options - Pass rate_limit to CortexClient initialization - Help text shows environment variable and default (1000 req/min) Usage: cortex --rate-limit 1500 backup catalog --export-dir ./test Or via environment variable: export CORTEX_RATE_LIMIT=1500 cortex backup catalog --export-dir ./test 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude * fix: make scorecard exemption tests idempotent - Moved CORTEX_API_KEY_VIEWER patch from decorator to context manager - Added cleanup logic to revoke existing exemptions before creating new ones - Cleanup runs with admin key (has revoke permission) while request uses viewer key (creates PENDING exemptions) - Tests now pass even when exemptions exist from previous runs 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --------- Co-authored-by: GitHub Actions Co-authored-by: Claude * fix: remove rate limiter initialization log message (#168) Removed INFO log message that appears on every CLI invocation which could impact applications parsing CLI output. Fixes #167 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: Claude * fix: change default logging level from INFO to WARNING - Users no longer see INFO log messages by default - Still shows WARNING (slow requests) and ERROR/CRITICAL if they occur - CLI output is cleaner and more suitable for parsing by other tools - Users can enable full diagnostics with -l DEBUG or -l INFO Fixes #170 * fix: initialize results and failed_count before directory check in import functions All _import_* functions now initialize results=[] and failed_count=0 before checking if directory exists. This prevents UnboundLocalError when importing partial exports that don't include all resource types. Fixed functions: - _import_catalog: Added results=[] and failed_count=0 - _import_plugins: Added results=[] and failed_count=0 - _import_scorecards: Added results=[] and failed_count=0 - _import_workflows: Added results=[] and failed_count=0 Fixes #171 * fix: only retry on 429 rate limit errors, not 5xx server errors Changed status_forcelist from [500, 502, 503, 504] to [429] to avoid wasting time retrying requests that are unlikely to succeed. Client errors (4xx) and server errors (5xx) typically won't succeed on retry, while rate limit errors (429) benefit from retry with backoff. Fixes #172 * Revert: Undo direct merges to staging (#176) * Revert "Merge branch '172-retry-only-429' into staging" This reverts commit 052796ee6c85a7d0dea59cc386c57fbbfdb1eb45, reversing changes made to 5be1748d5f4212f650147e0674716860c26a628d. * Revert "Merge branch '171-import-partial-export' into staging" This reverts commit 5be1748d5f4212f650147e0674716860c26a628d, reversing changes made to b095f88dba65651cf2b9a00a35cabadf3cf5c3f8. * Revert "Merge branch '170-default-logging-none' into staging" This reverts commit b095f88dba65651cf2b9a00a35cabadf3cf5c3f8, reversing changes made to 7b203576499cb3a07308de9b5eb7395dfdc02c29. * fix: change default logging level from INFO to WARNING (#177) - Users no longer see INFO log messages by default - Still shows WARNING (slow requests) and ERROR/CRITICAL if they occur - CLI output is cleaner and more suitable for parsing by other tools - Users can enable full diagnostics with -l DEBUG or -l INFO Fixes #170 * fix: initialize results and failed_count before directory check in import functions (#178) All _import_* functions now initialize results=[] and failed_count=0 before checking if directory exists. This prevents UnboundLocalError when importing partial exports that don't include all resource types. Fixed functions: - _import_catalog: Added results=[] and failed_count=0 - _import_plugins: Added results=[] and failed_count=0 - _import_scorecards: Added results=[] and failed_count=0 - _import_workflows: Added results=[] and failed_count=0 Fixes #171 * fix: only retry on 429 rate limit errors, not 5xx server errors (#179) Changed status_forcelist from [500, 502, 503, 504] to [429] to avoid wasting time retrying requests that are unlikely to succeed. Client errors (4xx) and server errors (5xx) typically won't succeed on retry, while rate limit errors (429) benefit from retry with backoff. Fixes #172 * Add tests for iconTag parameter in entity-types create (#183) * Release 1.6.1: Three bug fixes #patch (#180) * perf: optimize test scheduling with --dist loadfile for 25% faster test runs * feat: add support for Cortex Secrets API (#161) * chore: update HISTORY.md for main * perf: optimize test scheduling with --dist loadfile for 25% faster test runs (#157) * refactor: separate trigger-evaluation test to avoid scorecard evaluation race conditions - Create dedicated cli-test-evaluation-scorecard for trigger-evaluation testing - Remove retry logic complexity from test_scorecards() and test_scorecards_drafts() - Add new test_scorecard_trigger_evaluation() that creates/deletes its own scorecard - Eliminates race condition where import triggers evaluation conflicting with tests * refactor: remove unnecessary mock decorator from _get_rule helper function The helper function doesn't need its own environment patching since it's called from fixtures that already have their own @mock.patch.dict decorators. * Revert "perf: optimize test scheduling with --dist loadfile for 25% faster test runs (#157)" This reverts commit 8879fcfa7ee30a73f023e8bbef7d799808493319. The --dist loadfile optimization caused race conditions between tests that share resources (e.g., test_custom_events_uuid and test_custom_events_list both operate on custom events and can interfere with each other when run in parallel by file). Reliability > speed. Better to have tests take 40s with no race conditions than 30s with intermittent failures. * perf: rename test_deploys.py to test_000_deploys.py for early scheduling Pytest collects tests alphabetically by filename. With pytest-xdist --dist load, tests collected earlier are more likely to be scheduled first. Since test_deploys is the longest-running test (~19s), scheduling it early maximizes parallel efficiency with 12 workers. This is our general strategy: prefix slow tests with numbers (000, 001, etc.) to control scheduling order without introducing race conditions like --dist loadfile. * feat: add support for Cortex Secrets API Add complete CLI support for managing secrets via the Cortex Secrets API: - cortex secrets list: List secrets (with optional entity tag filter) - cortex secrets get: Get secret by alias - cortex secrets create: Create new secret - cortex secrets update: Update existing secret - cortex secrets delete: Delete secret All commands support entity tags as required by the API. Tests skip gracefully if API key lacks secrets permissions. Also fixes HISTORY.md generation by using Angular convention in git-changelog, which properly recognizes feat:, fix:, and perf: commit types instead of only recognizing the basic convention (add:, fix:, change:, remove:). Closes #158 * fix: update secret test to use valid tag format Change test secret tag from 'cli-test-secret' to 'cli_test_secret' to comply with API validation that only allows alphanumeric and underscore characters. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --------- Co-authored-by: GitHub Actions Co-authored-by: Claude * feat: add entity relationships API support with optimized backup/restore (#160) * chore: update HISTORY.md for main * perf: optimize test scheduling with --dist loadfile for 25% faster test runs (#157) * refactor: separate trigger-evaluation test to avoid scorecard evaluation race conditions - Create dedicated cli-test-evaluation-scorecard for trigger-evaluation testing - Remove retry logic complexity from test_scorecards() and test_scorecards_drafts() - Add new test_scorecard_trigger_evaluation() that creates/deletes its own scorecard - Eliminates race condition where import triggers evaluation conflicting with tests * refactor: remove unnecessary mock decorator from _get_rule helper function The helper function doesn't need its own environment patching since it's called from fixtures that already have their own @mock.patch.dict decorators. * Revert "perf: optimize test scheduling with --dist loadfile for 25% faster test runs (#157)" This reverts commit 8879fcfa7ee30a73f023e8bbef7d799808493319. The --dist loadfile optimization caused race conditions between tests that share resources (e.g., test_custom_events_uuid and test_custom_events_list both operate on custom events and can interfere with each other when run in parallel by file). Reliability > speed. Better to have tests take 40s with no race conditions than 30s with intermittent failures. * perf: rename test_deploys.py to test_000_deploys.py for early scheduling Pytest collects tests alphabetically by filename. With pytest-xdist --dist load, tests collected earlier are more likely to be scheduled first. Since test_deploys is the longest-running test (~19s), scheduling it early maximizes parallel efficiency with 12 workers. This is our general strategy: prefix slow tests with numbers (000, 001, etc.) to control scheduling order without introducing race conditions like --dist loadfile. * feat: add entity relationships API support and fix backup export bug - Fix double-encoding bug in backup export for entity-types and ip-allowlist - entity-types and ip-allowlist were being converted to strings before json.dump - This caused import failures with "TypeError: string indices must be integers" - Add entity-relationship-types commands: - list: List all relationship types - get: Get relationship type by tag - create: Create new relationship type - update: Update existing relationship type - delete: Delete relationship type - Add entity-relationships commands: - list: List all relationships for a type - list-destinations: Get destinations for source entity - list-sources: Get sources for destination entity - add-destinations: Add destinations to source - add-sources: Add sources to destination - update-destinations: Replace all destinations for source - update-sources: Replace all sources for destination - add-bulk: Add multiple relationships - update-bulk: Replace all relationships for type - Integrate entity relationships into backup/restore: - Export entity-relationship-types and entity-relationships - Import with proper ordering (types before catalog, relationships after) - Transform export format to bulk update format for import 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude * fix: clean up entity relationships import output and fix bugs - Add _print parameter to entity_relationship_types.create() and entity_relationships.update_bulk() - Use _print=False when importing to suppress JSON output - Fix import to use correct keys: sourceEntity.tag and destinationEntity.tag instead of source.tag - Replace typer.unstable.TempFile with standard tempfile.NamedTemporaryFile - Improve output: show only tag names instead of full JSON when importing - Add missing tempfile import 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude * fix: support re-importing existing entity relationship types - Check if relationship type already exists before importing - Use update instead of create for existing relationship types - Add _print parameter to entity_relationship_types.update() - Matches pattern used by entity_types import This allows backup imports to be idempotent and run multiple times without encountering "already exists" errors. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude * feat: improve error handling in backup import - Add detailed error reporting for catalog imports - Show filename, error type, and error message for failures - Add total failure count at end of catalog import - Add error handling for entity relationship type imports - Wrap create/update in try/except blocks - Show which relationship type failed and why - Add total failure count - Add error handling for entity relationship imports - Wrap import operations in try/except blocks - Show which relationship type failed and why - Add total failure count This makes it much easier to diagnose import failures by showing exactly which files are failing and what the error is. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude * fix: improve catalog import error handling and make sequential - Change catalog import from parallel to sequential execution - This allows errors to be correlated with specific files - HTTP errors from cortex_client are now shown with filenames - Catch typer.Exit exceptions in catalog import - The HTTP client raises typer.Exit on errors - Now catches and reports which file caused the error - Remove unused imports added for parallel error capture - Simplify catalog import logic Note: The plugin import failures with "string indices must be integers" are due to exports created before the double-encoding bug fix. Re-export with the current code to fix these. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude * perf: parallelize entity relationships and catalog imports - Restore parallel execution for catalog import (30 workers) - Previously made sequential for error debugging - Now handles typer.Exit exceptions properly - Maintains good error reporting with filenames - Parallelize entity relationship type imports (30 workers) - Check existing types once, then import in parallel - Properly handles create vs update decision - Parallelize entity relationship imports (30 workers) - Each relationship type is independent - Can safely import in parallel All imports now use ThreadPoolExecutor with 30 workers for maximum performance while maintaining error reporting. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude * feat: add comprehensive import summary and retry commands - All import functions now return statistics (type, imported count, failures) - Import summary section shows: - Per-type import counts and failures - Total imported and failed counts - Failed imports section lists: - Full file paths for all failures - Error type and message for each - Retry commands section provides: - Ready-to-run cortex commands for each failed file - Can copy/paste directly from terminal - Commands use proper quoting for file paths with spaces - Updated all import functions to track file paths in parallel execution - Added typer.Exit exception handling to plugins, scorecards, workflows - Consistent error reporting across all import types Example output: IMPORT SUMMARY catalog: 250 imported, 5 failed TOTAL: 500 imported, 5 failed FAILED IMPORTS /path/to/file.yaml Error: HTTP - Validation or HTTP error RETRY COMMANDS cortex catalog create -f "/path/to/file.yaml" 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude * fix: show catalog filename before import attempt - Print "Importing: filename" before attempting catalog import - HTTP errors now appear immediately after the filename - Remove duplicate success messages at end - Only show failure count summary This makes it immediately clear which file is causing each HTTP 400 error: Before: Processing: .../catalog HTTP Error 400: Unknown error HTTP Error 400: Unknown error After: Processing: .../catalog Importing: docs.yaml HTTP Error 400: Unknown error Importing: another.yaml HTTP Error 400: Unknown error 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude * fix: improve test isolation for custom events list test Delete all event types (not just VALIDATE_SERVICE) at test start to prevent interference from parallel tests. The connection pooling performance improvements made tests run much faster, increasing temporal overlap between parallel tests and exposing this existing test isolation issue. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude * fix: initialize variables before conditional to prevent NameError When entity-relationship-types or entity-relationships directories don't exist (like in test data), the import functions would reference undefined `results` and `failed_count` variables, causing a NameError and preventing subsequent imports from running (including catalog import, breaking tests). This bug was causing test_catalog_delete_entity and test_custom_events_list to fail because the import would crash before importing catalog entities. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --------- Co-authored-by: GitHub Actions Co-authored-by: Claude * fix: add client-side rate limiting and make tests idempotent (#165) #minor * chore: update HISTORY.md for main * perf: optimize test scheduling with --dist loadfile for 25% faster test runs (#157) * refactor: separate trigger-evaluation test to avoid scorecard evaluation race conditions - Create dedicated cli-test-evaluation-scorecard for trigger-evaluation testing - Remove retry logic complexity from test_scorecards() and test_scorecards_drafts() - Add new test_scorecard_trigger_evaluation() that creates/deletes its own scorecard - Eliminates race condition where import triggers evaluation conflicting with tests * refactor: remove unnecessary mock decorator from _get_rule helper function The helper function doesn't need its own environment patching since it's called from fixtures that already have their own @mock.patch.dict decorators. * Revert "perf: optimize test scheduling with --dist loadfile for 25% faster test runs (#157)" This reverts commit 8879fcfa7ee30a73f023e8bbef7d799808493319. The --dist loadfile optimization caused race conditions between tests that share resources (e.g., test_custom_events_uuid and test_custom_events_list both operate on custom events and can interfere with each other when run in parallel by file). Reliability > speed. Better to have tests take 40s with no race conditions than 30s with intermittent failures. * perf: rename test_deploys.py to test_000_deploys.py for early scheduling Pytest collects tests alphabetically by filename. With pytest-xdist --dist load, tests collected earlier are more likely to be scheduled first. Since test_deploys is the longest-running test (~19s), scheduling it early maximizes parallel efficiency with 12 workers. This is our general strategy: prefix slow tests with numbers (000, 001, etc.) to control scheduling order without introducing race conditions like --dist loadfile. * feat: add entity relationships API support and fix backup export bug - Fix double-encoding bug in backup export for entity-types and ip-allowlist - entity-types and ip-allowlist were being converted to strings before json.dump - This caused import failures with "TypeError: string indices must be integers" - Add entity-relationship-types commands: - list: List all relationship types - get: Get relationship type by tag - create: Create new relationship type - update: Update existing relationship type - delete: Delete relationship type - Add entity-relationships commands: - list: List all relationships for a type - list-destinations: Get destinations for source entity - list-sources: Get sources for destination entity - add-destinations: Add destinations to source - add-sources: Add sources to destination - update-destinations: Replace all destinations for source - update-sources: Replace all sources for destination - add-bulk: Add multiple relationships - update-bulk: Replace all relationships for type - Integrate entity relationships into backup/restore: - Export entity-relationship-types and entity-relationships - Import with proper ordering (types before catalog, relationships after) - Transform export format to bulk update format for import 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude * fix: clean up entity relationships import output and fix bugs - Add _print parameter to entity_relationship_types.create() and entity_relationships.update_bulk() - Use _print=False when importing to suppress JSON output - Fix import to use correct keys: sourceEntity.tag and destinationEntity.tag instead of source.tag - Replace typer.unstable.TempFile with standard tempfile.NamedTemporaryFile - Improve output: show only tag names instead of full JSON when importing - Add missing tempfile import 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude * fix: support re-importing existing entity relationship types - Check if relationship type already exists before importing - Use update instead of create for existing relationship types - Add _print parameter to entity_relationship_types.update() - Matches pattern used by entity_types import This allows backup imports to be idempotent and run multiple times without encountering "already exists" errors. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude * feat: improve error handling in backup import - Add detailed error reporting for catalog imports - Show filename, error type, and error message for failures - Add total failure count at end of catalog import - Add error handling for entity relationship type imports - Wrap create/update in try/except blocks - Show which relationship type failed and why - Add total failure count - Add error handling for entity relationship imports - Wrap import operations in try/except blocks - Show which relationship type failed and why - Add total failure count This makes it much easier to diagnose import failures by showing exactly which files are failing and what the error is. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude * fix: improve catalog import error handling and make sequential - Change catalog import from parallel to sequential execution - This allows errors to be correlated with specific files - HTTP errors from cortex_client are now shown with filenames - Catch typer.Exit exceptions in catalog import - The HTTP client raises typer.Exit on errors - Now catches and reports which file caused the error - Remove unused imports added for parallel error capture - Simplify catalog import logic Note: The plugin import failures with "string indices must be integers" are due to exports created before the double-encoding bug fix. Re-export with the current code to fix these. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude * perf: parallelize entity relationships and catalog imports - Restore parallel execution for catalog import (30 workers) - Previously made sequential for error debugging - Now handles typer.Exit exceptions properly - Maintains good error reporting with filenames - Parallelize entity relationship type imports (30 workers) - Check existing types once, then import in parallel - Properly handles create vs update decision - Parallelize entity relationship imports (30 workers) - Each relationship type is independent - Can safely import in parallel All imports now use ThreadPoolExecutor with 30 workers for maximum performance while maintaining error reporting. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude * feat: add comprehensive import summary and retry commands - All import functions now return statistics (type, imported count, failures) - Import summary section shows: - Per-type import counts and failures - Total imported and failed counts - Failed imports section lists: - Full file paths for all failures - Error type and message for each - Retry commands section provides: - Ready-to-run cortex commands for each failed file - Can copy/paste directly from terminal - Commands use proper quoting for file paths with spaces - Updated all import functions to track file paths in parallel execution - Added typer.Exit exception handling to plugins, scorecards, workflows - Consistent error reporting across all import types Example output: IMPORT SUMMARY catalog: 250 imported, 5 failed TOTAL: 500 imported, 5 failed FAILED IMPORTS /path/to/file.yaml Error: HTTP - Validation or HTTP error RETRY COMMANDS cortex catalog create -f "/path/to/file.yaml" 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude * fix: show catalog filename before import attempt - Print "Importing: filename" before attempting catalog import - HTTP errors now appear immediately after the filename - Remove duplicate success messages at end - Only show failure count summary This makes it immediately clear which file is causing each HTTP 400 error: Before: Processing: .../catalog HTTP Error 400: Unknown error HTTP Error 400: Unknown error After: Processing: .../catalog Importing: docs.yaml HTTP Error 400: Unknown error Importing: another.yaml HTTP Error 400: Unknown error 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude * fix: improve test isolation for custom events list test Delete all event types (not just VALIDATE_SERVICE) at test start to prevent interference from parallel tests. The connection pooling performance improvements made tests run much faster, increasing temporal overlap between parallel tests and exposing this existing test isolation issue. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude * fix: initialize variables before conditional to prevent NameError When entity-relationship-types or entity-relationships directories don't exist (like in test data), the import functions would reference undefined `results` and `failed_count` variables, causing a NameError and preventing subsequent imports from running (including catalog import, breaking tests). This bug was causing test_catalog_delete_entity and test_custom_events_list to fail because the import would crash before importing catalog entities. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude * chore: merge staging to main #minor (#162) * perf: optimize test scheduling with --dist loadfile for 25% faster test runs * feat: add support for Cortex Secrets API (#161) * chore: update HISTORY.md for main * perf: optimize test scheduling with --dist loadfile for 25% faster test runs (#157) * refactor: separate trigger-evaluation test to avoid scorecard evaluation race conditions - Create dedicated cli-test-evaluation-scorecard for trigger-evaluation testing - Remove retry logic complexity from test_scorecards() and test_scorecards_drafts() - Add new test_scorecard_trigger_evaluation() that creates/deletes its own scorecard - Eliminates race condition where import triggers evaluation conflicting with tests * refactor: remove unnecessary mock decorator from _get_rule helper function The helper function doesn't need its own environment patching since it's called from fixtures that already have their own @mock.patch.dict decorators. * Revert "perf: optimize test scheduling with --dist loadfile for 25% faster test runs (#157)" This reverts commit 8879fcfa7ee30a73f023e8bbef7d799808493319. The --dist loadfile optimization caused race conditions between tests that share resources (e.g., test_custom_events_uuid and test_custom_events_list both operate on custom events and can interfere with each other when run in parallel by file). Reliability > speed. Better to have tests take 40s with no race conditions than 30s with intermittent failures. * perf: rename test_deploys.py to test_000_deploys.py for early scheduling Pytest collects tests alphabetically by filename. With pytest-xdist --dist load, tests collected earlier are more likely to be scheduled first. Since test_deploys is the longest-running test (~19s), scheduling it early maximizes parallel efficiency with 12 workers. This is our general strategy: prefix slow tests with numbers (000, 001, etc.) to control scheduling order without introducing race conditions like --dist loadfile. * feat: add support for Cortex Secrets API Add complete CLI support for managing secrets via the Cortex Secrets API: - cortex secrets list: List secrets (with optional entity tag filter) - cortex secrets get: Get secret by alias - cortex secrets create: Create new secret - cortex secrets update: Update existing secret - cortex secrets delete: Delete secret All commands support entity tags as required by the API. Tests skip gracefully if API key lacks secrets permissions. Also fixes HISTORY.md generation by using Angular convention in git-changelog, which properly recognizes feat:, fix:, and perf: commit types instead of only recognizing the basic convention (add:, fix:, change:, remove:). Closes #158 * fix: update secret test to use valid tag format Change test secret tag from 'cli-test-secret' to 'cli_test_secret' to comply with API validation that only allows alphanumeric and underscore characters. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --------- Co-authored-by: GitHub Actions Co-authored-by: Claude * feat: add entity relationships API support with optimized backup/restore (#160) * chore: update HISTORY.md for main * perf: optimize test scheduling with --dist loadfile for 25% faster test runs (#157) * refactor: separate trigger-evaluation test to avoid scorecard evaluation race conditions - Create dedicated cli-test-evaluation-scorecard for trigger-evaluation testing - Remove retry logic complexity from test_scorecards() and test_scorecards_drafts() - Add new test_scorecard_trigger_evaluation() that creates/deletes its own scorecard - Eliminates race condition where import triggers evaluation conflicting with tests * refactor: remove unnecessary mock decorator from _get_rule helper function The helper function doesn't need its own environment patching since it's called from fixtures that already have their own @mock.patch.dict decorators. * Revert "perf: optimize test scheduling with --dist loadfile for 25% faster test runs (#157)" This reverts commit 8879fcfa7ee30a73f023e8bbef7d799808493319. The --dist loadfile optimization caused race conditions between tests that share resources (e.g., test_custom_events_uuid and test_custom_events_list both operate on custom events and can interfere with each other when run in parallel by file). Reliability > speed. Better to have tests take 40s with no race conditions than 30s with intermittent failures. * perf: rename test_deploys.py to test_000_deploys.py for early scheduling Pytest collects tests alphabetically by filename. With pytest-xdist --dist load, tests collected earlier are more likely to be scheduled first. Since test_deploys is the longest-running test (~19s), scheduling it early maximizes parallel efficiency with 12 workers. This is our general strategy: prefix slow tests with numbers (000, 001, etc.) to control scheduling order without introducing race conditions like --dist loadfile. * feat: add entity relationships API support and fix backup export bug - Fix double-encoding bug in backup export for entity-types and ip-allowlist - entity-types and ip-allowlist were being converted to strings before json.dump - This caused import failures with "TypeError: string indices must be integers" - Add entity-relationship-types commands: - list: List all relationship types - get: Get relationship type by tag - create: Create new relationship type - update: Update existing relationship type - delete: Delete relationship type - Add entity-relationships commands: - list: List all relationships for a type - list-destinations: Get destinations for source entity - list-sources: Get sources for destination entity - add-destinations: Add destinations to source - add-sources: Add sources to destination - update-destinations: Replace all destinations for source - update-sources: Replace all sources for destination - add-bulk: Add multiple relationships - update-bulk: Replace all relationships for type - Integrate entity relationships into backup/restore: - Export entity-relationship-types and entity-relationships - Import with proper ordering (types before catalog, relationships after) - Transform export format to bulk update format for import 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude * fix: clean up entity relationships import output and fix bugs - Add _print parameter to entity_relationship_types.create() and entity_relationships.update_bulk() - Use _print=False when importing to suppress JSON output - Fix import to use correct keys: sourceEntity.tag and destinationEntity.tag instead of source.tag - Replace typer.unstable.TempFile with standard tempfile.NamedTemporaryFile - Improve output: show only tag names instead of full JSON when importing - Add missing tempfile import 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude * fix: support re-importing existing entity relationship types - Check if relationship type already exists before importing - Use update instead of create for existing relationship types - Add _print parameter to entity_relationship_types.update() - Matches pattern used by entity_types import This allows backup imports to be idempotent and run multiple times without encountering "already exists" errors. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude * feat: improve error handling in backup import - Add detailed error reporting for catalog imports - Show filename, error type, and error message for failures - Add total failure count at end of catalog import - Add error handling for entity relationship type imports - Wrap create/update in try/except blocks - Show which relationship type failed and why - Add total failure count - Add error handling for entity relationship imports - Wrap import operations in try/except blocks - Show which relationship type failed and why - Add total failure count This makes it much easier to diagnose import failures by showing exactly which files are failing and what the error is. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude * fix: improve catalog import error handling and make sequential - Change catalog import from parallel to sequential execution - This allows errors to be correlated with specific files - HTTP errors from cortex_client are now shown with filenames - Catch typer.Exit exceptions in catalog import - The HTTP client raises typer.Exit on errors - Now catches and reports which file caused the error - Remove unused imports added for parallel error capture - Simplify catalog import logic Note: The plugin import failures with "string indices must be integers" are due to exports created before the double-encoding bug fix. Re-export with the current code to fix these. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude * perf: parallelize entity relationships and catalog imports - Restore parallel execution for catalog import (30 workers) - Previously made sequential for error debugging - Now handles typer.Exit exceptions properly - Maintains good error reporting with filenames - Parallelize entity relationship type imports (30 workers) - Check existing types once, then import in parallel - Properly handles create vs update decision - Parallelize entity relationship imports (30 workers) - Each relationship type is independent - Can safely import in parallel All imports now use ThreadPoolExecutor with 30 workers for maximum performance while maintaining error reporting. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude * feat: add comprehensive import summary and retry commands - All import functions now return statistics (type, imported count, failures) - Import summary section shows: - Per-type import counts and failures - Total imported and failed counts - Failed imports section lists: - Full file paths for all failures - Error type and message for each - Retry commands section provides: - Ready-to-run cortex commands for each failed file - Can copy/paste directly from terminal - Commands use proper quoting for file paths with spaces - Updated all import functions to track file paths in parallel execution - Added typer.Exit exception handling to plugins, scorecards, workflows - Consistent error reporting across all import types Example output: IMPORT SUMMARY catalog: 250 imported, 5 failed TOTAL: 500 imported, 5 failed FAILED IMPORTS /path/to/file.yaml Error: HTTP - Validation or HTTP error RETRY COMMANDS cortex catalog create -f "/path/to/file.yaml" 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude * fix: show catalog filename before import attempt - Print "Importing: filename" before attempting catalog import - HTTP errors now appear immediately after the filename - Remove duplicate success messages at end - Only show failure count summary This makes it immediately clear which file is causing each HTTP 400 error: Before: Processing: .../catalog HTTP Error 400: Unknown error HTTP Error 400: Unknown error After: Processing: .../catalog Importing: docs.yaml HTTP Error 400: Unknown error Importing: another.yaml HTTP Error 400: Unknown error 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude * fix: improve test isolation for custom events list test Delete all event types (not just VALIDATE_SERVICE) at test start to prevent interference from parallel tests. The connection pooling performance improvements made tests run much faster, increasing temporal overlap between parallel tests and exposing this existing test isolation issue. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude * fix: initialize variables before conditional to prevent NameError When entity-relationship-types or entity-relationships directories don't exist (like in test data), the import functions would reference undefined `results` and `failed_count` variables, causing a NameError and preventing subsequent imports from running (including catalog import, breaking tests). This bug was causing test_catalog_delete_entity and test_custom_events_list to fail because the import would crash before importing catalog entities. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --------- Co-authored-by: GitHub Actions Co-authored-by: Claude --------- Co-authored-by: GitHub Actions Co-authored-by: Claude * chore: update HISTORY.md for main * add: pytest configuration with perf marker Add pytest.ini to configure test markers: - perf: for performance tests excluded from regular runs - setup: for setup tests that run before other tests The perf marker is excluded by default via addopts, ensuring performance tests only run via 'just test-perf'. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude * fix: exclude perf tests from test-all command Update Justfile to exclude both 'setup' and 'perf' markers from test-all and _test-all-individual recipes. Previously only excluded 'setup', which caused performance tests to run and slow down the regular test suite. Also add test-perf recipe to explicitly run performance tests. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude * fix: allow 'just test' to run perf tests Override pytest.ini's default marker exclusion in the 'test' recipe by passing -m "" (empty marker filter). This allows users to run individual test files including perf tests via 'just test '. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude * add: client-side rate limiting with TokenBucket algorithm Implements proactive rate limiting to avoid 429 errors rather than reactively retrying them. Key changes: - Add TokenBucket class for thread-safe rate limiting (1000 req/min) - Configure 50-token burst capacity for initial request batches - Remove 429 from retry status_forcelist (now prevented by rate limiter) - Add performance test validating rate limiter with 250 parallel workers - Test confirms 100% success rate at ~1049 req/min with no 429 errors This approach is more efficient than retry-based rate limiting as it prevents rate limit errors proactively, reducing overall request latency and eliminating wasted retry attempts. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude * add: configurable rate limit via CORTEX_RATE_LIMIT env var Allows testing higher rate limits to determine if actual API limit is higher than documented 1000 req/min. Changes: - Read rate limit from CORTEX_RATE_LIMIT environment variable - Default to 1000 req/min if not set - Allows runtime testing of different rate limits without code changes Usage: export CORTEX_RATE_LIMIT=1500 cortex backup catalog --export-dir ./test 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude * add: --rate-limit CLI flag for configurable rate limiting Adds global CLI flag to configure API rate limit alongside existing CORTEX_RATE_LIMIT environment variable. Changes: - Add --rate-limit/-r flag to global options - Pass rate_limit to CortexClient initialization - Help text shows environment variable and default (1000 req/min) Usage: cortex --rate-limit 1500 backup catalog --export-dir ./test Or via environment variable: export CORTEX_RATE_LIMIT=1500 cortex backup catalog --export-dir ./test 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude * fix: make scorecard exemption tests idempotent - Moved CORTEX_API_KEY_VIEWER patch from decorator to context manager - Added cleanup logic to revoke existing exemptions before creating new ones - Cleanup runs with admin key (has revoke permission) while request uses viewer key (creates PENDING exemptions) - Tests now pass even when exemptions exist from previous runs 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --------- Co-authored-by: GitHub Actions Co-authored-by: Claude * fix: remove rate limiter initialization log message (#168) Removed INFO log message that appears on every CLI invocation which could impact applications parsing CLI output. Fixes #167 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: Claude * fix: change default logging level from INFO to WARNING - Users no longer see INFO log messages by default - Still shows WARNING (slow requests) and ERROR/CRITICAL if they occur - CLI output is cleaner and more suitable for parsing by other tools - Users can enable full diagnostics with -l DEBUG or -l INFO Fixes #170 * fix: initialize results and failed_count before directory check in import functions All _import_* functions now initialize results=[] and failed_count=0 before checking if directory exists. This prevents UnboundLocalError when importing partial exports that don't include all resource types. Fixed functions: - _import_catalog: Added results=[] and failed_count=0 - _import_plugins: Added results=[] and failed_count=0 - _import_scorecards: Added results=[] and failed_count=0 - _import_workflows: Added results=[] and failed_count=0 Fixes #171 * fix: only retry on 429 rate limit errors, not 5xx server errors Changed status_forcelist from [500, 502, 503, 504] to [429] to avoid wasting time retrying requests that are unlikely to succeed. Client errors (4xx) and server errors (5xx) typically won't succeed on retry, while rate limit errors (429) benefit from retry with backoff. Fixes #172 * Revert: Undo direct merges to staging (#176) * Revert "Merge branch '172-retry-only-429' into staging" This reverts commit 052796ee6c85a7d0dea59cc386c57fbbfdb1eb45, reversing changes made to 5be1748d5f4212f650147e0674716860c26a628d. * Revert "Merge branch '171-import-partial-export' into staging" This reverts commit 5be1748d5f4212f650147e0674716860c26a628d, reversing changes made to b095f88dba65651cf2b9a00a35cabadf3cf5c3f8. * Revert "Merge branch '170-default-logging-none' into staging" This reverts commit b095f88dba65651cf2b9a00a35cabadf3cf5c3f8, reversing changes made to 7b203576499cb3a07308de9b5eb7395dfdc02c29. * fix: change default logging level from INFO to WARNING (#177) - Users no longer see INFO log messages by default - Still shows WARNING (slow requests) and ERROR/CRITICAL if they occur - CLI output is cleaner and more suitable for parsing by other tools - Users can enable full diagnostics with -l DEBUG or -l INFO Fixes #170 * fix: initialize results and failed_count before directory check in import functions (#178) All _import_* functions now initialize results=[] and failed_count=0 before checking if directory exists. This prevents UnboundLocalError when importing partial exports that don't include all resource types. Fixed functions: - _import_catalog: Added results=[] and failed_count=0 - _import_plugins: Added results=[] and failed_count=0 - _import_scorecards: Added results=[] and failed_count=0 - _import_workflows: Added results=[] and failed_count=0 Fixes #171 * fix: only retry on 429 rate limit errors, not 5xx server errors (#179) Changed status_forcelist from [500, 502, 503, 504] to [429] to avoid wasting time retrying requests that are unlikely to succeed. Client errors (4xx) and server errors (5xx) typically won't succeed on retry, while rate limit errors (429) benefit from retry with backoff. Fixes #172 --------- Co-authored-by: GitHub Actions Co-authored-by: Claude * chore: update HISTORY.md for main * add: tests for iconTag parameter in entity-types create - Add iconTag to cli-test.json with valid value (Cortex-builtin::Basketball) - Add test to verify iconTag is returned correctly after create - Add test for invalid iconTag (API accepts and uses default icon) - Add test data file for invalid icon scenario 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 * add: document HISTORY.md merge conflict resolution in CLAUDE.md 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 * fix: Update urllib3 to >= 2.6.0 for CVE-2025-66418 and CVE-2025-66471 Addresses security vulnerabilities in urllib3 versions < 2.6.0. Closes #186 Co-Authored-By: Claude Opus 4.5 * add: Document branch naming convention in CLAUDE.md Co-Authored-By: Claude Opus 4.5 * fix: Sync homebrew formula with tap and update urllib3 to 2.6.3 - Updated homebrew/cortexapps-cli.rb to match current tap formula - Updated urllib3 resource from 2.4.0 to 2.6.3 (addresses CVEs) - Added documentation about homebrew dependency update limitations Co-Authored-By: Claude Opus 4.5 --------- Co-authored-by: GitHub Actions Co-authored-by: Claude * Fix backup import/export error handling (#185) * Release 1.6.1: Three bug fixes #patch (#180) * perf: optimize test scheduling with --dist loadfile for 25% faster test runs * feat: add support for Cortex Secrets API (#161) * chore: update HISTORY.md for main * perf: optimize test scheduling with --dist loadfile for 25% faster test runs (#157) * refactor: separate trigger-evaluation test to avoid scorecard evaluation race conditions - Create dedicated cli-test-evaluation-scorecard for trigger-evaluation testing - Remove retry logic complexity from test_scorecards() and test_scorecards_drafts() - Add new test_scorecard_trigger_evaluation() that creates/deletes its own scorecard - Eliminates race condition where import triggers evaluation conflicting with tests * refactor: remove unnecessary mock decorator from _get_rule helper function The helper function doesn't need its own environment patching since it's called from fixtures that already have their own @mock.patch.dict decorators. * Revert "perf: optimize test scheduling with --dist loadfile for 25% faster test runs (#157)" This reverts commit 8879fcfa7ee30a73f023e8bbef7d799808493319. The --dist loadfile optimization caused race conditions between tests that share resources (e.g., test_custom_events_uuid and test_custom_events_list both operate on custom events and can interfere with each other when run in parallel by file). Reliability > speed. Better to have tests take 40s with no race conditions than 30s with intermittent failures. * perf: rename test_deploys.py to test_000_deploys.py for early scheduling Pytest collects tests alphabetically by filename. With pytest-xdist --dist load, tests collected earlier are more likely to be scheduled first. Since test_deploys is the longest-running test (~19s), scheduling it early maximizes parallel efficiency with 12 workers. This is our general strategy: prefix slow tests with numbers (000, 001, etc.) to control scheduling order without introducing race conditions like --dist loadfile. * feat: add support for Cortex Secrets API Add complete CLI support for managing secrets via the Cortex Secrets API: - cortex secrets list: List secrets (with optional entity tag filter) - cortex secrets get: Get secret by alias - cortex secrets create: Create new secret - cortex secrets update: Update existing secret - cortex secrets delete: Delete secret All commands support entity tags as required by the API. Tests skip gracefully if API key lacks secrets permissions. Also fixes HISTORY.md generation by using Angular convention in git-changelog, which properly recognizes feat:, fix:, and perf: commit types instead of only recognizing the basic convention (add:, fix:, change:, remove:). Closes #158 * fix: update secret test to use valid tag format Change test secret tag from 'cli-test-secret' to 'cli_test_secret' to comply with API validation that only allows alphanumeric and underscore characters. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --------- Co-authored-by: GitHub Actions Co-authored-by: Claude * feat: add entity relationships API support with optimized backup/restore (#160) * chore: update HISTORY.md for main * perf: optimize test scheduling with --dist loadfile for 25% faster test runs (#157) * refactor: separate trigger-evaluation test to avoid scorecard evaluation race conditions - Create dedicated cli-test-evaluation-scorecard for trigger-evaluation testing - Remove retry logic complexity from test_scorecards() and test_scorecards_drafts() - Add new test_scorecard_trigger_evaluation() that creates/deletes its own scorecard - Eliminates race condition where import triggers evaluation conflicting with tests * refactor: remove unnecessary mock decorator from _get_rule helper function The helper function doesn't need its own environment patching since it's called from fixtures that already have their own @mock.patch.dict decorators. * Revert "perf: optimize test scheduling with --dist loadfile for 25% faster test runs (#157)" This reverts commit 8879fcfa7ee30a73f023e8bbef7d799808493319. The --dist loadfile optimization caused race conditions between tests that share resources (e.g., test_custom_events_uuid and test_custom_events_list both operate on custom events and can interfere with each other when run in parallel by file). Reliability > speed. Better to have tests take 40s with no race conditions than 30s with intermittent failures. * perf: rename test_deploys.py to test_000_deploys.py for early scheduling Pytest collects tests alphabetically by filename. With pytest-xdist --dist load, tests collected earlier are more likely to be scheduled first. Since test_deploys is the longest-running test (~19s), scheduling it early maximizes parallel efficiency with 12 workers. This is our general strategy: prefix slow tests with numbers (000, 001, etc.) to control scheduling order without introducing race conditions like --dist loadfile. * feat: add entity relationships API support and fix backup export bug - Fix double-encoding bug in backup export for entity-types and ip-allowlist - entity-types and ip-allowlist were being converted to strings before json.dump - This caused import failures with "TypeError: string indices must be integers" - Add entity-relationship-types commands: - list: List all relationship types - get: Get relationship type by tag - create: Create new relationship type - update: Update existing relationship type - delete: Delete relationship type - Add entity-relationships commands: - list: List all relationships for a type - list-destinations: Get destinations for source entity - list-sources: Get sources for destination entity - add-destinations: Add destinations to source - add-sources: Add sources to destination - update-destinations: Replace all destinations for source - update-sources: Replace all sources for destination - add-bulk: Add multiple relationships - update-bulk: Replace all relationships for type - Integrate entity relationships into backup/restore: - Export entity-relationship-types and entity-relationships - Import with proper ordering (types before catalog, relationships after) - Transform export format to bulk update format for import 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude * fix: clean up entity relationships import output and fix bugs - Add _print parameter to entity_relationship_types.create() and entity_relationships.update_bulk() - Use _print=False when importing to suppress JSON output - Fix import to use correct keys: sourceEntity.tag and destinationEntity.tag instead of source.tag - Replace typer.unstable.TempFile with standard tempfile.NamedTemporaryFile - Improve output: show only tag names instead of full JSON when importing - Add missing tempfile import 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude * fix: support re-importing existing entity relationship types - Check if relationship type already exists before importing - Use update instead of create for existing relationship types - Add _print parameter to entity_relationship_types.update() - Matches pattern used by entity_types import This allows backup imports to be idempotent and run multiple times without encountering "already exists" errors. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude * feat: improve error handling in backup import - Add detailed error reporting for catalog imports - Show filename, error type, and error message for failures - Add total failure count at end of catalog import - Add error handling for entity relationship type imports - Wrap create/update in try/except blocks - Show which relationship type failed and why - Add total failure count - Add error handling for entity relationship imports - Wrap import operations in try/except blocks - Show which relationship type failed and why - Add total failure count This makes it much easier to diagnose import failures by showing exactly which files are failing and what the error is. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude * fix: improve catalog import error handling and make sequential - Change catalog import from parallel to sequential execution - This allows errors to be correlated with specific files - HTTP errors from cortex_client are now shown with filenames - Catch typer.Exit exceptions in catalog import - The HTTP client raises typer.Exit on errors - Now catches and reports which file caused the error - Remove unused imports added for parallel error capture - Simplify catalog import logic Note: The plugin import failures with "string indices must be integers" are due to exports created before the double-encoding bug fix. Re-export with the current code to fix these. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude * perf: parallelize entity relationships and catalog imports - Restore parallel execution for catalog import (30 workers) - Previously made sequential for error debugging - Now handles typer.Exit exceptions properly - Maintains good error reporting with filenames - Parallelize entity relationship type imports (30 workers) - Check existing types once, then import in parallel - Properly handles create vs update decision - Parallelize entity relationship imports (30 workers) - Each relationship type is independent - Can safely import in parallel All imports now use ThreadPoolExecutor with 30 workers for maximum performance while maintaining error reporting. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude * feat: add comprehensive import summary and retry commands - All import functions now return statistics (type, imported count, failures) - Import summary section shows: - Per-type import counts and failures - Total imported and failed counts - Failed imports section lists: - Full file paths for all failures - Error type and message for each - Retry commands section provides: - Ready-to-run cortex commands for each failed file - Can copy/paste directly from terminal - Commands use proper quoting for file paths with spaces - Updated all import functions to track file paths in parallel execution - Added typer.Exit exception handling to plugins, scorecards, workflows - Consistent error reporting across all import types Example output: IMPORT SUMMARY catalog: 250 imported, 5 failed TOTAL: 500 imported, 5 failed FAILED IMPORTS /path/to/file.yaml Error: HTTP - Validation or HTTP error RETRY COMMANDS cortex catalog create -f "/path/to/file.yaml" 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude * fix: show catalog filename before import attempt - Print "Importing: filename" before attempting catalog import - HTTP errors now appear immediately after the filename - Remove duplicate success messages at end - Only show failure count summary This makes it immediately clear which file is causing each HTTP 400 error: Before: Processing: .../catalog HTTP Error 400: Unknown error HTTP Error 400: Unknown error After: Processing: .../catalog Importing: docs.yaml HTTP Error 400: Unknown error Importing: another.yaml HTTP Error 400: Unknown error 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude * fix: improve test isolation for custom events list test Delete all event types (not just VALIDATE_SERVICE) at test start to prevent interference from parallel tests. The connection pooling performance improvements made tests run much faster, increasing temporal overlap between parallel tests and exposing this existing test isolation issue. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude * fix: initialize variables before conditional to prevent NameError When entity-relationship-types or entity-relationships directories don't exist (like in test data), the import functions would reference undefined `results` and `failed_count` variables, causing a NameError and preventing subsequent imports from running (including catalog import, breaking tests). This bug was causing test_catalog_delete_entity and test_custom_events_list to fail because the import would crash before importing catalog entities. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --------- Co-authored-by: GitHub Actions Co-authored-by: Claude * fix: add client-side rate limiting and make tests idempotent (#165) #minor * chore: update HISTORY.md for main * perf: optimize test scheduling with --dist loadfile for 25% faster test runs (#157) * refactor: separate trigger-evaluation test to avoid scorecard evaluation race conditions - Create dedicated cli-test-evaluation-scorecard for trigger-evaluation testing - Remove retry logic complexity from test_scorecards() and test_scorecards_drafts() - Add new test_scorecard_trigger_evaluation() that creates/deletes its own scorecard - Eliminates race condition where import triggers evaluation conflicting with tests * refactor: remove unnecessary mock decorator from _get_rule helper function The helper function doesn't need its own environment patching since it's called from fixtures that already have their own @mock.patch.dict decorators. * Revert "perf: optimize test scheduling with --dist loadfile for 25% faster test runs (#157)" This reverts commit 8879fcfa7ee30a73f023e8bbef7d799808493319. The --dist loadfile optimization caused race conditions between tests that share resources (e.g., test_custom_events_uuid and test_custom_events_list both operate on custom events and can interfere with each other when run in parallel by file). Reliability > speed. Better to have tests take 40s with no race conditions than 30s with intermittent failures. * perf: rename test_deploys.py to test_000_deploys.py for early scheduling Pytest collects tests alphabetically by filename. With pytest-xdist --dist load, tests collected earlier are more likely to be scheduled first. Since test_deploys is the longest-running test (~19s), scheduling it early maximizes parallel efficiency with 12 workers. This is our general strategy: prefix slow tests with numbers (000, 001, etc.) to control scheduling order without introducing race conditions like --dist loadfile. * feat: add entity relationships API support and fix backup export bug - Fix double-encoding bug in backup export for entity-types and ip-allowlist - entity-types and ip-allowlist were being converted to strings before json.dump - This caused import failures with "TypeError: string indices must be integers" - Add entity-relationship-types commands: - list: List all relationship types - get: Get relationship type by tag - create: Create new relationship type - update: Update existing relationship type - delete: Delete relationship type - Add entity-relationships commands: - list: List all relationships for a type - list-destinations: Get destinations for source entity - list-sources: Get sources for destination entity - add-destinations: Add destinations to source - add-sources: Add sources to destination - update-destinations: Replace all destinations for source - update-sources: Replace all sources for destination - add-bulk: Add multiple relationships - update-bulk: Replace all relationships for type - Integrate entity relationships into backup/restore: - Export entity-relationship-types and entity-relationships - Import with proper ordering (types before catalog, relationships after) - Transform export format to bulk update format for import 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude * fix: clean up entity relationships import output and fix bugs - Add _print parameter to entity_relationship_types.create() and entity_relationships.update_bulk() - Use _print=False when importing to suppress JSON output - Fix import to use correct keys: sourceEntity.tag and destinationEntity.tag instead of source.tag - Replace typer.unstable.TempFile with standard tempfile.NamedTemporaryFile - Improve output: show only tag names instead of full JSON when importing - Add missing tempfile import 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude * fix: support re-importing existing entity relationship types - Check if relationship type already exists before importing - Use update instead of create for existing relationship types - Add _print parameter to entity_relationship_types.update() - Matches pattern used by entity_types import This allows backup imports to be idempotent and run multiple times without encountering "already exists" errors. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude * feat: improve error handling in backup import - Add detailed error reporting for catalog imports - Show filename, error type, and error message for failures - Add total failure count at end of catalog import - Add error handling for entity relationship type imports - Wrap create/update in try/except blocks - Show which relationship type failed and why - Add total failure count - Add error handling for entity relationship imports - Wrap import operations in try/except blocks - Show which relationship type failed and why - Add total failure count This makes it much easier to diagnose import failures by showing exactly which files are failing and what the error is. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude * fix: improve catalog import error handling and make sequential - Change catalog import from parallel to sequential execution - This allows errors to be correlated with specific files - HTTP errors from cortex_client are now shown with filenames - Catch typer.Exit exceptions in catalog import - The HTTP client raises typer.Exit on errors - Now catches and reports which file caused the error - Remove unused imports added for parallel error capture - Simplify catalog import logic Note: The plugin import failures with "string indices must be integers" are due to exports created before the double-encoding bug fix. Re-export with the current code to fix these. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude * perf: parallelize entity relationships and catalog imports - Restore parallel execution for catalog import (30 workers) - Previously made sequential for error debugging - Now handles typer.Exit exceptions properly - Maintains good error reporting with filenames - Parallelize entity relationship type imports (30 workers) - Check existing types once, then import in parallel - Properly handles create vs update decision - Parallelize entity relationship imports (30 workers) - Each relationship type is independent - Can safely import in parallel All imports now use ThreadPoolExecutor with 30 workers for maximum performance while maintaining error reporting. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude * feat: add comprehensive import summary and retry commands - All import functions now return statistics (type, imported count, failures) - Import summary section shows: - Per-type import counts and failures - Total imported and failed counts - Failed imports section lists: - Full file paths for all failures - Error type and message for each - Retry commands section provides: - Ready-to-run cortex commands for each failed file - Can copy/paste directly from terminal - Commands use proper quoting for file paths with spaces - Updated all import functions to track file paths in parallel execution - Added typer.Exit exception handling to plugins, scorecards, workflows - Consistent error reporting across all import types Example output: IMPORT SUMMARY catalog: 250 imported, 5 failed TOTAL: 500 imported, 5 failed FAILED IMPORTS /path/to/file.yaml Error: HTTP - Validation or HTTP error RETRY COMMANDS cortex catalog create -f "/path/to/file.yaml" 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude * fix: show catalog filename before import attempt - Print "Importing: filename" before attempting catalog import - HTTP errors now appear immediately after the filename - Remove duplicate success messages at end - Only show failure count summary This makes it immediately clear which file is causing each HTTP 400 error: Before: Processing: .../catalog HTTP Error 400: Unknown error HTTP Error 400: Unknown error After: Processing: .../catalog Importing: docs.yaml HTTP Error 400: Unknown error Importing: another.yaml HTTP Error 400: Unknown error 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude * fix: improve test isolation for custom events list test Delete all event types (not just VALIDATE_SERVICE) at test start to prevent interference from parallel tests. The connection pooling performance improvements made tests run much faster, increasing temporal overlap between parallel tests and exposing this existing test isolation issue. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude * fix: initialize variables before conditional to prevent NameError When entity-relationship-types or entity-relationships directories don't exist (like in test data), the import functions would reference undefined `results` and `failed_count` variables, causing a NameError and preventing subsequent imports from running (including catalog import, breaking tests). This bug was causing test_catalog_delete_entity and test_custom_events_list to fail because the import would crash before importing catalog entities. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude * chore: merge staging to main #minor (#162) * perf: optimize test scheduling with --dist loadfile for 25% faster test runs * feat: add support for Cortex Secrets API (#161) * chore: update HISTORY.md for main * perf: optimize test scheduling with --dist loadfile for 25% faster test runs (#157) * refactor: separate trigger-evaluation test to avoid scorecard evaluation race conditions - Create dedicated cli-test-evaluation-scorecard for trigger-evaluation testing - Remove retry logic complexity from test_scorecards() and test_scorecards_drafts() - Add new test_scorecard_trigger_evaluation() that creates/deletes its own scorecard - Eliminates race condition where import triggers evaluation conflicting with tests * refactor: remove unnecessary mock decorator from _get_rule helper function The helper function doesn't need its own environment patching since it's called from fixtures that already have their own @mock.patch.dict decorators. * Revert "perf: optimize test scheduling with --dist loadfile for 25% faster test runs (#157)" This reverts commit 8879fcfa7ee30a73f023e8bbef7d799808493319. The --dist loadfile optimization caused race conditions between tests that share resources (e.g., test_custom_events_uuid and test_custom_events_list both operate on custom events and can interfere with each other when run in parallel by file). Reliability > speed. Better to have tests take 40s with no race conditions than 30s with intermittent failures. * perf: rename test_deploys.py to test_000_deploys.py for early scheduling Pytest collects tests alphabetically by filename. With pytest-xdist --dist load, tests collected earlier are more likely to be scheduled first. Since test_deploys is the longest-running test (~19s), scheduling it early maximizes parallel efficiency with 12 workers. This is our general strategy: prefix slow tests with numbers (000, 001, etc.) to control scheduling order without introducing race conditions like --dist loadfile. * feat: add support for Cortex Secrets API Add complete CLI support for managing secrets via the Cortex Secrets API: - cortex secrets list: List secrets (with optional entity tag filter) - cortex secrets get: Get secret by alias - cortex secrets create: Create new secret - cortex secrets update: Update existing secret - cortex secrets delete: Delete secret All commands support entity tags as required by the API. Tests skip gracefully if API key lacks secrets permissions. Also fixes HISTORY.md generation by using Angular convention in git-changelog, which properly recognizes feat:, fix:, and perf: commit types instead of only recognizing the basic convention (add:, fix:, change:, remove:). Closes #158 * fix: update secret test to use valid tag format Change test secret tag from 'cli-test-secret' to 'cli_test_secret' to comply with API validation that only allows alphanumeric and underscore characters. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --------- Co-authored-by: GitHub Actions Co-authored-by: Claude * feat: add entity relationships API support with optimized backup/restore (#160) * chore: update HISTORY.md for main * perf: optimize test scheduling with --dist loadfile for 25% faster test runs (#157) * refactor: separate trigger-evaluation test to avoid scorecard evaluation race conditions - Create dedicated cli-test-evaluation-scorecard for trigger-evaluation testing - Remove retry logic complexity from test_scorecards() and test_scorecards_drafts() - Add new test_scorecard_trigger_evaluation() that creates/deletes its own scorecard - Eliminates race condition where import triggers evaluation conflicting with tests * refactor: remove unnecessary mock decorator from _get_rule helper function The helper function doesn't need its own environment patching since it's called from fixtures that already have their own @mock.patch.dict decorators. * Revert "perf: optimize test scheduling with --dist loadfile for 25% faster test runs (#157)" This reverts commit 8879fcfa7ee30a73f023e8bbef7d799808493319. The --dist loadfile optimization caused race conditions between tests that share resources (e.g., test_custom_events_uuid and test_custom_events_list both operate on custom events and can interfere with each other when run in parallel by file). Reliability > speed. Better to have tests take 40s with no race conditions than 30s with intermittent failures. * perf: rename test_deploys.py to test_000_deploys.py for early scheduling Pytest collects tests alphabetically by filename. With pytest-xdist --dist load, tests collected earlier are more likely to be scheduled first. Since test_deploys is the longest-running test (~19s), scheduling it early maximizes parallel efficiency with 12 workers. This is our general strategy: prefix slow tests with numbers (000, 001, etc.) to control scheduling order without introducing race conditions like --dist loadfile. * feat: add entity relationships API support and fix backup export bug - Fix double-encoding bug in backup export for entity-types and ip-allowlist - entity-types and ip-allowlist were being converted to strings before json.dump - This caused import failures with "TypeError: string indices must be integers" - Add entity-relationship-types commands: - list: List all relationship types - get: Get relationship type by tag - create: Create new relationship type - update: Update existing relationship type - delete: Delete relationship type - Add entity-relationships commands: - list: List all relationships for a type - list-destinations: Get destinations for source entity - list-sources: Get sources for destination entity - add-destinations: Add destinations to source - add-sources: Add sources to destination - update-destinations: Replace all destinations for source - update-sources: Replace all sources for destination - add-bulk: Add multiple relationships - update-bulk: Replace all relationships for type - Integrate entity relationships into backup/restore: - Export entity-relationship-types and entity-relationships - Import with proper ordering (types before catalog, relationships after) - Transform export format to bulk update format for import 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude * fix: clean up entity relationships import output and fix bugs - Add _print parameter to entity_relationship_types.create() and entity_relationships.update_bulk() - Use _print=False when importing to suppress JSON output - Fix import to use correct keys: sourceEntity.tag and destinationEntity.tag instead of source.tag - Replace typer.unstable.TempFile with standard tempfile.NamedTemporaryFile - Improve output: show only tag names instead of full JSON when importing - Add missing tempfile import 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude * fix: support re-importing existing entity relationship types - Check if relationship type already exists before importing - Use update instead of create for existing relationship types - Add _print parameter to entity_relationship_types.update() - Matches pattern used by entity_types import This allows backup imports to be idempotent and run multiple times without encountering "already exists" errors. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude * feat: improve error handling in backup import - Add detailed error reporting for catalog imports - Show filename, error type, and error message for failures - Add total failure count at end of catalog import - Add error handling for entity relationship type imports - Wrap create/update in try/except blocks - Show which relationship type failed and why - Add total failure count - Add error handling for entity relationship imports - Wrap import operations in try/except blocks - Show which relationship type failed and why - Add total failure count This makes it much easier to diagnose import failures by showing exactly which files are failing and what the error is. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude * fix: improve catalog import error handling and make sequential - Change catalog import from parallel to sequential execution - This allows errors to be correlated with specific files - HTTP errors from cortex_client are now shown with filenames - Catch typer.Exit exceptions in catalog import - The HTTP client raises typer.Exit on errors - Now catches and reports which file caused the error - Remove unused imports added for parallel error capture - Simplify catalog import logic Note: The plugin import failures with "string indices must be integers" are due to exports created before the double-encoding bug fix. Re-export with the current code to fix these. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude * perf: parallelize entity relationships and catalog imports - Restore parallel execution for catalog import (30 workers) - Previously made sequential for error debugging - Now handles typer.Exit exceptions properly - Maintains good error reporting with filenames - Parallelize entity relationship type imports (30 workers) - Check existing types once, then import in parallel - Properly handles create vs update decision - Parallelize entity relationship imports (30 workers) - Each relationship type is independent - Can safely import in parallel All imports now use ThreadPoolExecutor with 30 workers for maximum performance while maintaining error reporting. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude * feat: add comprehensive import summary and retry commands - All import functions now return statistics (type, imported count, failures) - Import summary section shows: - Per-type import counts and failures - Total imported and failed counts - Failed imports section lists: - Full file paths for all failures - Error type and message for each - Retry commands section provides: - Ready-to-run cortex commands for each failed file - Can copy/paste directly from terminal - Commands use proper quoting for file paths with spaces - Updated all import functions to track file paths in parallel execution - Added typer.Exit exception handling to plugins, scorecards, workflows - Consistent error reporting across all import types Example output: IMPORT SUMMARY catalog: 250 imported, 5 failed TOTAL: 500 imported, 5 failed FAILED IMPORTS /path/to/file.yaml Error: HTTP - Validation or HTTP error RETRY COMMANDS cortex catalog create -f "/path/to/file.yaml" 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude * fix: show catalog filename before import attempt - Print "Importing: filename" before attempting catalog import - HTTP errors now appear immediately after the filename - Remove duplicate success messages at end - Only show failure count summary This makes it immediately clear which file is causing each HTTP 400 error: Before: Processing: .../catalog HTTP Error 400: Unknown error HTTP Error 400: Unknown error After: Processing: .../catalog Importing: docs.yaml HTTP Error 400: Unknown error Importing: another.yaml HTTP Error 400: Unknown error 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude * fix: improve test isolation for custom events list test Delete all event types (not just VALIDATE_SERVICE) at test start to prevent interference from parallel tests. The connection pooling performance improvements made tests run much faster, increasing temporal overlap between parallel tests and exposing this existing test isolation issue. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude * fix: initialize variables before conditional to prevent NameError When entity-relationship-types or entity-relationships directories don't exist (like in test data), the import functions would reference undefined `results` and `failed_count` variables, causing a NameError and preventing subsequent imports from running (including catalog import, breaking tests). This bug was causing test_catalog_delete_entity and test_custom_events_list to fail because the import would crash before importing catalog entities. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --------- Co-authored-by: GitHub Actions Co-authored-by: Claude --------- Co-authored-by: GitHub Actions Co-authored-by: Claude * chore: update HISTORY.md for main * add: pytest configuration with perf marker Add pytest.ini to configure test markers: - perf: for performance tests excluded from regular runs - setup: for setup tests that run before other tests The perf marker is excluded by default via addopts, ensuring performance tests only run via 'just test-perf'. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude * fix: exclude perf tests from test-all command Update Justfile to exclude both 'setup' and 'perf' markers from test-all and _test-all-individual recipes. Previously only excluded 'setup', which caused performance tests to run and slow down the regular test suite. Also add test-perf recipe to explicitly run performance tests. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude * fix: allow 'just test' to run perf tests Override pytest.ini's default marker exclusion in the 'test' recipe by passing -m "" (empty marker filter). This allows users to run individual test files including perf tests via 'just test '. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude * add: client-side rate limiting with TokenBucket algorithm Implements proactive rate limiting to avoid 429 errors rather than reactively retrying them. Key changes: - Add TokenBucket class for thread-safe rate limiting (1000 req/min) - Configure 50-token burst capacity for initial request batches - Remove 429 from retry status_forcelist (now prevented by rate limiter) - Add performance test validating rate limiter with 250 parallel workers - Test confirms 100% success rate at ~1049 req/min with no 429 errors This approach is more efficient than retry-based rate limiting as it prevents rate limit errors proactively, reducing overall request latency and eliminating wasted retry attempts. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude * add: configurable rate limit via CORTEX_RATE_LIMIT env var Allows testing higher rate limits to determine if actual API limit is higher than documented 1000 req/min. Changes: - Read rate limit from CORTEX_RATE_LIMIT environment variable - Default to 1000 req/min if not set - Allows runtime testing of different rate limits without code changes Usage: export CORTEX_RATE_LIMIT=1500 cortex backup catalog --export-dir ./test 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude * add: --rate-limit CLI flag for configurable rate limiting Adds global CLI flag to configure API rate limit alongside existing CORTEX_RATE_LIMIT environment variable. Changes: - Add --rate-limit/-r flag to global options - Pass rate_limit to CortexClient initialization - Help text shows environment variable and default (1000 req/min) Usage: cortex --rate-limit 1500 backup catalog --export-dir ./test Or via environment variable: export CORTEX_RATE_LIMIT=1500 cortex backup catalog --export-dir ./test 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude * fix: make scorecard exemption tests idempotent - Moved CORTEX_API_KEY_VIEWER patch from decorator to context manager - Added cleanup logic to revoke existing exemptions before creating new ones - Cleanup runs with admin key (has revoke permission) while request uses viewer key (creates PENDING exemptions) - Tests now pass even when exemptions exist from previous runs 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --------- Co-authored-by: GitHub Actions Co-authored-by: Claude * fix: remove rate limiter initialization log message (#168) Removed INFO log message that appears on every CLI invocation which could impact applications parsing CLI output. Fixes #167 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: Claude * fix: change default logging level from INFO to WARNING - Users no longer see INFO log messages by default - Still shows WARNING (slow requests) and ERROR/CRITICAL if they occur - CLI output is cleaner and more suitable for parsing by other tools - Users can enable full diagnostics with -l DEBUG or -l INFO Fixes #170 * fix: initialize results and failed_count before directory check in import functions All _import_* functions now initialize results=[] and failed_count=0 before checking if directory exists. This prevents UnboundLocalError when importing partial exports that don't include all resource types. Fixed functions: - _import_catalog: Added results=[] and failed_count=0 - _import_plugins: Added results=[] and failed_count=0 - _import_scorecards: Added results=[] and failed_count=0 - _import_workflows: Added results=[] and failed_count=0 Fixes #171 * fix: only retry on 429 rate limit errors, not 5xx server errors Changed status_forcelist from [500, 502, 503, 504] to [429] to avoid wasting time retrying requests that are unlikely to succeed. Client errors (4xx) and server errors (5xx) typically won't succeed on retry, while rate limit errors (429) benefit from retry with backoff. Fixes #172 * Revert: Undo direct merges to staging (#176) * Revert "Merge branch '172-retry-only-429' into staging" This reverts commit 052796ee6c85a7d0dea59cc386c57fbbfdb1eb45, reversing changes made to 5be1748d5f4212f650147e0674716860c26a628d. * Revert "Merge branch '171-import-partial-export' into staging" This reverts commit 5be1748d5f4212f650147e0674716860c26a628d, reversing changes made to b095f88dba65651cf2b9a00a35cabadf3cf5c3f8. * Revert "Merge branch '170-default-logging-none' into staging" This reverts commit b095f88dba65651cf2b9a00a35cabadf3cf5c3f8, reversing changes made to 7b203576499cb3a07308de9b5eb7395dfdc02c29. * fix: change default logging level from INFO to WARNING (#177) - Users no longer see INFO log messages by default - Still shows WARNING (slow requests) and ERROR/CRITICAL if they occur - CLI output is cleaner and more suitable for parsing by other tools - Users can enable full diagnostics with -l DEBUG or -l INFO Fixes #170 * fix: initialize results and failed_count before directory check in import functions (#178) All _import_* functions now initialize results=[] and failed_count=0 before checking if directory exists. This prevents UnboundLocalError when importing partial exports that don't include all resource types. Fixed functions: - _import_catalog: Added results=[] and failed_count=0 - _import_plugins: Added results=[] and failed_count=0 - _import_scorecards: Added results=[] and failed_count=0 - _import_workflows: Added results=[] and failed_count=0 Fixes #171 * fix: only retry on 429 rate limit errors, not 5xx server errors (#179) Changed status_forcelist from [500, 502, 503, 504] to [429] to avoid wasting time retrying requests that are unlikely to succeed. Client errors (4xx) and server errors (5xx) typically won't succeed on retry, while rate limit errors (429) benefit from retry with backoff. Fixes #172 --------- Co-authored-by: GitHub Actions Co-authored-by: Claude * chore: update HISTORY.md for main * fix: backup import/export error handling - backup import now exits with non-zero code when imports fail - backup export no longer shows Python stack trace on API errors - Added tests for both error handling scenarios Closes #184 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 * Skip test until CET-23082 is resolved. * fix: Update urllib3 to >= 2.6.0 for CVE-2025-66418 and CVE-2025-66471 Addresses security vulnerabilities in urllib3 versions < 2.6.0. Closes #186 Co-Authored-By: Claude Opus 4.5 * add: Document branch naming convention in CLAUDE.md Co-Authored-By: Claude Opus 4.5 * fix: Sync homebrew formula with tap and update urllib3 to 2.6.3 - Updated homebrew/cortexapps-cli.rb to match current tap formula - Updated urllib3 resource from 2.4.0 to 2.6.3 (addresses CVEs) - Added documentation about homebrew dependency update limitations Co-Authored-By: Claude Opus 4.5 --------- Co-authored-by: GitHub Actions Co-authored-by: Claude --------- Co-authored-by: GitHub Actions Co-authored-by: Claude --- CLAUDE.md | 12 +++++++ HISTORY.md | 2 ++ cortexapps_cli/commands/backup.py | 4 +++ cortexapps_cli/cortex_client.py | 8 +++-- data/import/entity-types/cli-test.json | 1 + data/run-time/entity-type-invalid-icon.json | 7 ++++ tests/test_backup.py | 40 +++++++++++++++++++++ tests/test_config_file.py | 4 +-- tests/test_entity_types.py | 14 +++++++- tests/test_scim.py | 1 + 10 files changed, 88 insertions(+), 5 deletions(-) create mode 100644 data/run-time/entity-type-invalid-icon.json create mode 100644 tests/test_backup.py diff --git a/CLAUDE.md b/CLAUDE.md index ec9250f..66e441b 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -171,6 +171,18 @@ Commits should be prefixed with: Only commits with these prefixes appear in the auto-generated `HISTORY.md`. +### HISTORY.md Merge Conflicts +The `HISTORY.md` file is auto-generated when `staging` is merged to `main`. This means: +- `main` always has the latest HISTORY.md +- `staging` lags behind until the next release +- Feature branches created from `main` have the updated history + +When merging feature branches to `staging`, conflicts in HISTORY.md are expected. Resolve by accepting the incoming version: +```bash +git checkout --theirs HISTORY.md +git add HISTORY.md +``` + ### GitHub Actions - **`publish.yml`**: Triggered on push to `main`, handles versioning and multi-platform publishing - **`test-pr.yml`**: Runs tests on pull requests diff --git a/HISTORY.md b/HISTORY.md index d6bce84..28f2e86 100644 --- a/HISTORY.md +++ b/HISTORY.md @@ -6,6 +6,7 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html). + ## [1.7.0](https://github.com/cortexapps/cli/releases/tag/1.7.0) - 2025-11-19 [Compare with 1.6.0](https://github.com/cortexapps/cli/compare/1.6.0...1.7.0) @@ -18,6 +19,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. - remove rate limiter initialization log message (#169) #patch ([015107a](https://github.com/cortexapps/cli/commit/015107aca15d5a4cf4eb746834bcbb7dac607e1d) by Jeff Schnitter). + ## [1.5.0](https://github.com/cortexapps/cli/releases/tag/1.5.0) - 2025-11-13 [Compare with 1.4.0](https://github.com/cortexapps/cli/compare/1.4.0...1.5.0) diff --git a/cortexapps_cli/commands/backup.py b/cortexapps_cli/commands/backup.py index 0b39592..2c4fe9d 100644 --- a/cortexapps_cli/commands/backup.py +++ b/cortexapps_cli/commands/backup.py @@ -698,3 +698,7 @@ def import_tenant( print(f"cortex scorecards create -f \"{file_path}\"") elif import_type == "workflows": print(f"cortex workflows create -f \"{file_path}\"") + + # Exit with non-zero code if any imports failed + if total_failed > 0: + raise typer.Exit(1) diff --git a/cortexapps_cli/cortex_client.py b/cortexapps_cli/cortex_client.py index 9692efd..abc014f 100644 --- a/cortexapps_cli/cortex_client.py +++ b/cortexapps_cli/cortex_client.py @@ -156,8 +156,12 @@ def request(self, method, endpoint, params={}, headers={}, data=None, raw_body=F print(error_str) raise typer.Exit(code=1) except json.JSONDecodeError: - # if we can't parse the error message, just raise the HTTP error - response.raise_for_status() + # if we can't parse the error message, print a clean error and exit + status = response.status_code + reason = response.reason or 'Unknown error' + error_str = f'[red][bold]HTTP Error {status}[/bold][/red]: {reason}' + print(error_str) + raise typer.Exit(code=1) if raw_response: return response diff --git a/data/import/entity-types/cli-test.json b/data/import/entity-types/cli-test.json index 132a29e..0628c4c 100644 --- a/data/import/entity-types/cli-test.json +++ b/data/import/entity-types/cli-test.json @@ -1,5 +1,6 @@ { "description": "This is a test entity type definition.", + "iconTag": "Cortex-builtin::Basketball", "name": "CLI Test With Empty Schema", "schema": {}, "type": "cli-test" diff --git a/data/run-time/entity-type-invalid-icon.json b/data/run-time/entity-type-invalid-icon.json new file mode 100644 index 0000000..7d37ddf --- /dev/null +++ b/data/run-time/entity-type-invalid-icon.json @@ -0,0 +1,7 @@ +{ + "description": "This is a test entity type definition with invalid icon.", + "iconTag": "invalidIcon", + "name": "CLI Test With Invalid Icon", + "schema": {}, + "type": "cli-test-invalid-icon" +} diff --git a/tests/test_backup.py b/tests/test_backup.py new file mode 100644 index 0000000..be9c138 --- /dev/null +++ b/tests/test_backup.py @@ -0,0 +1,40 @@ +from tests.helpers.utils import * +import os +import tempfile + +def test_backup_import_invalid_api_key(monkeypatch): + """ + Test that backup import exits with non-zero return code when API calls fail. + """ + monkeypatch.setenv("CORTEX_API_KEY", "invalidKey") + + # Create a temp directory with a catalog subdirectory and a simple yaml file + with tempfile.TemporaryDirectory() as tmpdir: + catalog_dir = os.path.join(tmpdir, "catalog") + os.makedirs(catalog_dir) + + # Create a minimal catalog entity file + entity_file = os.path.join(catalog_dir, "test-entity.yaml") + with open(entity_file, "w") as f: + f.write(""" +info: + x-cortex-tag: test-entity + title: Test Entity + x-cortex-type: service +""") + + result = cli(["backup", "import", "-d", tmpdir], return_type=ReturnType.RAW) + assert result.exit_code != 0, f"backup import should exit with non-zero code on failure, got exit_code={result.exit_code}" + + +def test_backup_export_invalid_api_key(monkeypatch): + """ + Test that backup export exits with non-zero return code and clean error message when API calls fail. + """ + monkeypatch.setenv("CORTEX_API_KEY", "invalidKey") + + with tempfile.TemporaryDirectory() as tmpdir: + result = cli(["backup", "export", "-d", tmpdir], return_type=ReturnType.RAW) + assert result.exit_code != 0, f"backup export should exit with non-zero code on failure, got exit_code={result.exit_code}" + assert "HTTP Error 401" in result.stdout, "Should show HTTP 401 error message" + assert "Traceback" not in result.stdout, "Should not show Python traceback" diff --git a/tests/test_config_file.py b/tests/test_config_file.py index f37cc81..1f86c97 100644 --- a/tests/test_config_file.py +++ b/tests/test_config_file.py @@ -31,12 +31,12 @@ def test_config_file_bad_api_key(monkeypatch, tmp_path): monkeypatch.setattr('sys.stdin', io.StringIO('y')) f = tmp_path / "test-config-bad-api-key.txt" response = cli(["-c", str(f), "-k", "invalidApiKey", "scorecards", "list"], return_type=ReturnType.RAW) - assert "401 Client Error: Unauthorized" in str(response), "should get Unauthorized error" + assert "HTTP Error 401" in response.stdout, "should get Unauthorized error" def test_environment_variable_invalid_key(monkeypatch): monkeypatch.setenv("CORTEX_API_KEY", "invalidKey") response = cli(["scorecards", "list"], return_type=ReturnType.RAW) - assert "401 Client Error: Unauthorized" in str(response), "should get Unauthorized error" + assert "HTTP Error 401" in response.stdout, "should get Unauthorized error" def test_config_file_bad_url(monkeypatch, tmp_path): monkeypatch.delenv("CORTEX_BASE_URL") diff --git a/tests/test_entity_types.py b/tests/test_entity_types.py index 8b2d65b..c5b59fd 100644 --- a/tests/test_entity_types.py +++ b/tests/test_entity_types.py @@ -12,6 +12,18 @@ def test_resource_definitions(capsys): response = cli(["entity-types", "list"]) assert any(definition['type'] == 'cli-test' for definition in response['definitions']), "Should find entity type named 'cli-test'" - cli(["entity-types", "get", "-t", "cli-test"]) + # Verify iconTag was set correctly + response = cli(["entity-types", "get", "-t", "cli-test"]) + assert response.get('iconTag') == "Cortex-builtin::Basketball", "iconTag should be set to Cortex-builtin::Basketball" cli(["entity-types", "update", "-t", "cli-test", "-f", "data/run-time/entity-type-update.json"]) + + +def test_resource_definitions_invalid_icon(): + # API does not reject invalid iconTag values - it uses a default icon instead + # This test verifies that behavior and will catch if the API changes to reject invalid icons + response = cli(["entity-types", "create", "-f", "data/run-time/entity-type-invalid-icon.json"], return_type=ReturnType.RAW) + assert response.exit_code == 0, "Creation should succeed even with invalid iconTag (API uses default icon)" + + # Clean up the test entity type + cli(["entity-types", "delete", "-t", "cli-test-invalid-icon"]) diff --git a/tests/test_scim.py b/tests/test_scim.py index 3215747..ed5bda7 100644 --- a/tests/test_scim.py +++ b/tests/test_scim.py @@ -2,6 +2,7 @@ from urllib.error import HTTPError import pytest +@pytest.mark.skip(reason="Disabled until CET-23082 is resolved.") def test(): response = cli(["scim", "list"], ReturnType.STDOUT) From f8d55bde9ba231f4d767348d59058f00d8e395b5 Mon Sep 17 00:00:00 2001 From: GitHub Actions Date: Mon, 12 Jan 2026 21:36:10 +0000 Subject: [PATCH 2/5] chore: update HISTORY.md for main --- HISTORY.md | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/HISTORY.md b/HISTORY.md index 28f2e86..cc66fe4 100644 --- a/HISTORY.md +++ b/HISTORY.md @@ -6,6 +6,13 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html). +## [1.8.0](https://github.com/cortexapps/cli/releases/tag/1.8.0) - 2026-01-12 + +[Compare with 1.7.0](https://github.com/cortexapps/cli/compare/1.7.0...1.8.0) + +### Bug Fixes + +- Update urllib3 to address CVE-2025-66418 and CVE-2025-66471 #patch (#188) ([4fba98b](https://github.com/cortexapps/cli/commit/4fba98bf12083faa030dfb84b2db325d55ae9afc) by Jeff Schnitter). ## [1.7.0](https://github.com/cortexapps/cli/releases/tag/1.7.0) - 2025-11-19 @@ -19,7 +26,6 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. - remove rate limiter initialization log message (#169) #patch ([015107a](https://github.com/cortexapps/cli/commit/015107aca15d5a4cf4eb746834bcbb7dac607e1d) by Jeff Schnitter). - ## [1.5.0](https://github.com/cortexapps/cli/releases/tag/1.5.0) - 2025-11-13 [Compare with 1.4.0](https://github.com/cortexapps/cli/compare/1.4.0...1.5.0) From 04624a306ee68515ce85085ead403b23efc8b0d7 Mon Sep 17 00:00:00 2001 From: Jeff Schnitter Date: Mon, 12 Jan 2026 13:48:16 -0800 Subject: [PATCH 3/5] 187 fix catalog validation errors (#190) #minor MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix: Display validation errors from catalog API responses The error handling now detects and displays the violations array format returned by validation endpoints. Previously, these errors showed as "Unknown error - No details" because the code only handled the standard message/details format. Closes #187 Co-Authored-By: Claude Opus 4.5 * add: document staging-to-main release PR process in CLAUDE.md 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 * fix: sync HISTORY.md to staging after release to prevent merge conflicts After a release, HISTORY.md is updated on main but staging doesn't get the update. This causes merge conflicts when feature branches (created from main) are merged to staging. This fix adds a step to the publish workflow that syncs HISTORY.md from main to staging after each release. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 * fix: avoid ARG_MAX limit by using GitHub expressions instead of env var The GITHUB_CONTEXT env var containing toJson(github) was too large when merging many commits, causing "Argument list too long" error. Fixed by accessing pusher.name and pusher.email directly via GitHub expressions instead of passing the entire context as an environment variable. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 --------- Co-authored-by: Claude Opus 4.5 --- .github/workflows/publish.yml | 24 ++++++++++++++++++------ CLAUDE.md | 16 +++++++++++----- cortexapps_cli/cortex_client.py | 17 +++++++++++++++++ 3 files changed, 46 insertions(+), 11 deletions(-) diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 8ef0dc3..9bec499 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -66,18 +66,30 @@ jobs: git push fi + - name: Sync HISTORY.md to staging + run: | + # Sync the HISTORY.md update to staging to prevent merge conflicts + # when feature branches (created from main) are merged to staging + git fetch origin staging + git checkout staging + git checkout main -- HISTORY.md + if git diff --exit-code HISTORY.md; then + echo "staging HISTORY.md already up to date" + else + git add HISTORY.md + git commit -m "chore: sync HISTORY.md from main" + git push origin staging + fi + git checkout main + - name: Git details about version id: git-details - env: - GITHUB_CONTEXT: ${{ toJson(github) }} run: | version=$(git describe --tags --abbrev=0) echo "VERSION=${version}" >> $GITHUB_ENV echo "VERSION=${version}" >> $GITHUB_OUTPUT - pusher=$(echo "$GITHUB_CONTEXT" | jq -r ".event.pusher.name") - email=$(echo "$GITHUB_CONTEXT" | jq -r ".event.pusher.email") - echo "PUSHER=${pusher}" >> $GITHUB_OUTPUT - echo "EMAIL=${email}" >> $GITHUB_OUTPUT + echo "PUSHER=${{ github.event.pusher.name }}" >> $GITHUB_OUTPUT + echo "EMAIL=${{ github.event.pusher.email }}" >> $GITHUB_OUTPUT echo "URL=https://pypi.org/project/cortexapps-cli/${version}/" >> $GITHUB_OUTPUT - name: Publish diff --git a/CLAUDE.md b/CLAUDE.md index 66e441b..1cd9996 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -141,12 +141,18 @@ Use the GitHub-recommended format: `-` ### Release Workflow 1. Create feature branch for changes -2. Merge to `staging` branch for testing -3. Merge `staging` to `main` to trigger release -4. Version bumping: +2. Create PR to merge feature branch to `staging` for testing +3. Create PR to merge `staging` to `main` to trigger release: + ```bash + gh pr create --base main --head staging --title "Release X.Y.Z: Description #patch|#minor|#major" + ``` + - Include version number and brief description in title + - Use `#patch`, `#minor`, or `#major` in the title to control version bump + - List all changes in the PR body +4. Version bumping (based on hashtag in PR title or commit message): - Default: Patch version bump - - `#minor` in commit message: Minor version bump - - `#major` in commit message: Major version bump + - `#minor`: Minor version bump + - `#major`: Major version bump 5. Release publishes to: - PyPI - Docker Hub (`cortexapp/cli:VERSION` and `cortexapp/cli:latest`) diff --git a/cortexapps_cli/cortex_client.py b/cortexapps_cli/cortex_client.py index abc014f..5a7e349 100644 --- a/cortexapps_cli/cortex_client.py +++ b/cortexapps_cli/cortex_client.py @@ -149,6 +149,23 @@ def request(self, method, endpoint, params={}, headers={}, data=None, raw_body=F # try to parse the error message error = response.json() status = response.status_code + + # Check for validation error format with violations array + if 'violations' in error and isinstance(error['violations'], list): + print(f'[red][bold]HTTP Error {status}[/bold][/red]: Validation failed') + for violation in error['violations']: + title = violation.get('title', 'Validation Error') + description = violation.get('description', 'No description') + violation_type = violation.get('violationType', '') + pointer = violation.get('pointer', '') + print(f' [yellow]{title}[/yellow]: {description}') + if pointer: + print(f' [dim]Location: {pointer}[/dim]') + if violation_type: + print(f' [dim]Type: {violation_type}[/dim]') + raise typer.Exit(code=1) + + # Standard error format with message/details message = error.get('message', 'Unknown error') details = error.get('details', 'No details') request_id = error.get('requestId', 'No request ID') From e82c66912fce7e917da368d9854bdecc56f0414b Mon Sep 17 00:00:00 2001 From: GitHub Actions Date: Mon, 12 Jan 2026 21:48:38 +0000 Subject: [PATCH 4/5] chore: update HISTORY.md for main --- HISTORY.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/HISTORY.md b/HISTORY.md index cc66fe4..b79f63c 100644 --- a/HISTORY.md +++ b/HISTORY.md @@ -6,6 +6,10 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html). +## [1.9.0](https://github.com/cortexapps/cli/releases/tag/1.9.0) - 2026-01-12 + +[Compare with 1.8.0](https://github.com/cortexapps/cli/compare/1.8.0...1.9.0) + ## [1.8.0](https://github.com/cortexapps/cli/releases/tag/1.8.0) - 2026-01-12 [Compare with 1.7.0](https://github.com/cortexapps/cli/compare/1.7.0...1.8.0) From 03910a68b58c155f908c3dba77393e87b0317db7 Mon Sep 17 00:00:00 2001 From: Jeff Schnitter Date: Fri, 23 Jan 2026 14:08:31 -0800 Subject: [PATCH 5/5] fix: correct New Relic integration commands to match API - Fix add command: use --account-id, --personal-key (NRAK prefix), --region (US/EU) - Add NRAK prefix validation for personal key - Add --table/--csv output options to list command - Add no_args_is_help to all commands with required parameters - Fix API endpoints (validate, validate_all, add_multiple) - Add JSON format example to add-multiple help - Update tests for new parameters and add coverage for validation Fixes #191 Co-Authored-By: Claude Opus 4.5 --- .../integrations_commands/newrelic.py | 80 ++++++++++++++----- tests/test_integrations_newrelic.py | 31 ++++++- 2 files changed, 89 insertions(+), 22 deletions(-) diff --git a/cortexapps_cli/commands/integrations_commands/newrelic.py b/cortexapps_cli/commands/integrations_commands/newrelic.py index ae0dd46..80d113f 100644 --- a/cortexapps_cli/commands/integrations_commands/newrelic.py +++ b/cortexapps_cli/commands/integrations_commands/newrelic.py @@ -1,16 +1,24 @@ +from enum import Enum import json from rich import print_json import typer from typing_extensions import Annotated +from cortexapps_cli.command_options import ListCommandOptions +from cortexapps_cli.utils import print_output_with_context app = typer.Typer(help="New Relic commands", no_args_is_help=True) -@app.command() +class Region(str, Enum): + US = "US" + EU = "EU" + +@app.command(no_args_is_help=True) def add( ctx: typer.Context, - alias: str = typer.Option(..., "--alias", "-a", help="Alias for this configuration"), - api_key: str = typer.Option(..., "--api-key", "-api", help="API key"), - host: str = typer.Option(None, "--host", "-h", help="Optional host name"), + alias: str = typer.Option(None, "--alias", "-a", help="Alias for this configuration"), + account_id: str = typer.Option(None, "--account-id", "-acc", help="New Relic account ID"), + personal_key: str = typer.Option(None, "--personal-key", "-pk", help="New Relic personal API key"), + region: Region = typer.Option(Region.US, "--region", "-r", help="Region (US or EU)"), is_default: bool = typer.Option(False, "--is-default", "-i", help="If this is the default configuration"), file_input: Annotated[typer.FileText, typer.Option("--file", "-f", help="JSON file containing configurations, if command line options not used; can be passed as stdin with -, example: -f-")] = None, ): @@ -21,40 +29,56 @@ def add( client = ctx.obj["client"] if file_input: - if alias or api_key or is_default or host: - raise typer.BadParameter("When providing a custom event definition file, do not specify any other custom event attributes") + if alias or account_id or personal_key: + raise typer.BadParameter("When providing a file, do not specify --alias, --account-id, or --personal-key") data = json.loads("".join([line for line in file_input])) else: + if not alias or not account_id or not personal_key: + raise typer.BadParameter("--alias, --account-id, and --personal-key are required when not using --file") + if not personal_key.startswith("NRAK"): + raise typer.BadParameter("--personal-key must start with 'NRAK'") data = { "alias": alias, - "apiKey": api_key, - "host": host, + "accountId": account_id, + "personalKey": personal_key, + "region": region.value, "isDefault": is_default, - } + } - # remove any data elements that are None - can only be is_default - data = {k: v for k, v in data.items() if v is not None} - r = client.post("api/v1/newrelic/configuration", data=data) print_json(data=r) -@app.command() +@app.command(no_args_is_help=True) def add_multiple( ctx: typer.Context, file_input: Annotated[typer.FileText, typer.Option("--file", "-f", help="JSON file containing configurations; can be passed as stdin with -, example: -f-")] = None, ): """ Add multiple configurations + + JSON file format: + \b + { + "configurations": [ + { + "accountId": 1, + "alias": "text", + "isDefault": true, + "personalKey": "text", + "region": "US" + } + ] + } """ client = ctx.obj["client"] data = json.loads("".join([line for line in file_input])) - r = client.put("api/v1/newrelic/configurations", data=data) + r = client.post("api/v1/newrelic/configurations", data=data) print_json(data=r) -@app.command() +@app.command(no_args_is_help=True) def delete( ctx: typer.Context, alias: str = typer.Option(..., "--alias", "-a", help="The alias of the configuration"), @@ -81,7 +105,7 @@ def delete_all( r = client.delete("api/v1/newrelic/configurations") print_json(data=r) -@app.command() +@app.command(no_args_is_help=True) def get( ctx: typer.Context, alias: str = typer.Option(..., "--alias", "-a", help="The alias of the configuration"), @@ -98,6 +122,12 @@ def get( @app.command() def list( ctx: typer.Context, + table_output: ListCommandOptions.table_output = False, + csv_output: ListCommandOptions.csv_output = False, + columns: ListCommandOptions.columns = [], + no_headers: ListCommandOptions.no_headers = False, + filters: ListCommandOptions.filters = [], + sort: ListCommandOptions.sort = [], ): """ Get all configurations @@ -105,8 +135,16 @@ def list( client = ctx.obj["client"] + if (table_output or csv_output) and not ctx.params.get('columns'): + ctx.params['columns'] = [ + "Alias=alias", + "AccountId=accountId", + "Region=region", + "IsDefault=isDefault", + ] + r = client.get("api/v1/newrelic/configurations") - print_json(data=r) + print_output_with_context(ctx, r) @app.command() def get_default( @@ -122,7 +160,7 @@ def get_default( print_json(data=r) -@app.command() +@app.command(no_args_is_help=True) def update( ctx: typer.Context, alias: str = typer.Option(..., "--alias", "-a", help="The alias of the configuration"), @@ -142,7 +180,7 @@ def update( r = client.put("api/v1/newrelic/configuration/" + alias, data=data) print_json(data=r) -@app.command() +@app.command(no_args_is_help=True) def validate( ctx: typer.Context, alias: str = typer.Option(..., "--alias", "-a", help="The alias of the configuration"), @@ -153,7 +191,7 @@ def validate( client = ctx.obj["client"] - r = client.post("api/v1/newrelic/configurations/validate" + alias) + r = client.post("api/v1/newrelic/configuration/validate/" + alias) print_json(data=r) @app.command() @@ -166,5 +204,5 @@ def validate_all( client = ctx.obj["client"] - r = client.post("api/v1/newrelic/configurations") + r = client.post("api/v1/newrelic/configuration/validate") print_json(data=r) diff --git a/tests/test_integrations_newrelic.py b/tests/test_integrations_newrelic.py index b54e205..0af6040 100644 --- a/tests/test_integrations_newrelic.py +++ b/tests/test_integrations_newrelic.py @@ -10,7 +10,29 @@ def _dummy_file(tmp_path): @responses.activate def test_integrations_newrelic_add(): responses.add(responses.POST, os.getenv("CORTEX_BASE_URL") + "/api/v1/newrelic/configuration", json={}, status=200) - cli(["integrations", "newrelic", "add", "-a", "myAlias", "-h", "my.host.com", "--api-key", "123456", "-i"]) + cli(["integrations", "newrelic", "add", "-a", "myAlias", "--account-id", "12345", "--personal-key", "NRAK-123456", "-i"]) + +@responses.activate +def test_integrations_newrelic_add_with_region(): + responses.add(responses.POST, os.getenv("CORTEX_BASE_URL") + "/api/v1/newrelic/configuration", json={}, status=200) + cli(["integrations", "newrelic", "add", "-a", "myAlias", "-acc", "12345", "-pk", "NRAK-123456", "-r", "EU"]) + +def test_integrations_newrelic_add_invalid_key(): + result = cli(["integrations", "newrelic", "add", "-a", "myAlias", "--account-id", "12345", "--personal-key", "invalid-key"], return_type=ReturnType.RAW) + assert result.exit_code != 0 + assert "must start with 'NRAK'" in result.output + +def test_integrations_newrelic_add_missing_required(): + result = cli(["integrations", "newrelic", "add", "-a", "myAlias"], return_type=ReturnType.RAW) + assert result.exit_code != 0 + assert "are required" in result.output + +@responses.activate +def test_integrations_newrelic_add_with_file(tmp_path): + f = tmp_path / "test_integrations_newrelic_add.json" + f.write_text('{"alias": "test", "accountId": "12345", "personalKey": "NRAK-123", "region": "US"}') + responses.add(responses.POST, os.getenv("CORTEX_BASE_URL") + "/api/v1/newrelic/configuration", json={}, status=200) + cli(["integrations", "newrelic", "add", "-f", str(f)]) @responses.activate def test_integrations_newrelic_add_multiple(tmp_path): @@ -38,6 +60,13 @@ def test_integrations_newrelic_list(): responses.add(responses.GET, os.getenv("CORTEX_BASE_URL") + "/api/v1/newrelic/configurations", json={}, status=200) cli(["integrations", "newrelic", "list"]) +@responses.activate +def test_integrations_newrelic_list_table(): + responses.add(responses.GET, os.getenv("CORTEX_BASE_URL") + "/api/v1/newrelic/configurations", json={"configurations": [{"alias": "test", "accountId": "123", "region": "US", "isDefault": True}]}, status=200) + result = cli(["integrations", "newrelic", "list", "--table"], return_type=ReturnType.RAW) + assert "Alias" in result.output + assert "test" in result.output + @responses.activate def test_integrations_newrelic_get_default(): responses.add(responses.GET, os.getenv("CORTEX_BASE_URL") + "/api/v1/newrelic/default-configuration", json={}, status=200)