draft-board 0.1.0-beta.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/app/backend/.env.example +9 -0
- package/app/backend/.smartkanban/evidence/8b383839-cbec-45af-86ee-c7708d075cbe/bddf2ed5-2e21-4d46-a62b-10b87f1642a6_patch.txt +195 -0
- package/app/backend/.smartkanban/evidence/8b383839-cbec-45af-86ee-c7708d075cbe/bddf2ed5-2e21-4d46-a62b-10b87f1642a6_stat.txt +6 -0
- package/app/backend/CURL_EXAMPLES.md +335 -0
- package/app/backend/ENV_SETUP.md +65 -0
- package/app/backend/alembic/env.py +71 -0
- package/app/backend/alembic/script.py.mako +28 -0
- package/app/backend/alembic/versions/001_initial_schema.py +104 -0
- package/app/backend/alembic/versions/002_add_jobs_table.py +52 -0
- package/app/backend/alembic/versions/003_add_workspace_table.py +48 -0
- package/app/backend/alembic/versions/004_add_evidence_table.py +56 -0
- package/app/backend/alembic/versions/005_add_verification_commands.py +32 -0
- package/app/backend/alembic/versions/006_add_planner_lock_table.py +39 -0
- package/app/backend/alembic/versions/007_add_revision_review_tables.py +126 -0
- package/app/backend/alembic/versions/008_add_revision_idempotency_and_traceability.py +52 -0
- package/app/backend/alembic/versions/009_add_job_health_fields.py +46 -0
- package/app/backend/alembic/versions/010_add_review_comment_line_content.py +36 -0
- package/app/backend/alembic/versions/011_add_analysis_cache.py +47 -0
- package/app/backend/alembic/versions/012_add_boards_table.py +102 -0
- package/app/backend/alembic/versions/013_add_ticket_blocking.py +45 -0
- package/app/backend/alembic/versions/014_add_agent_sessions.py +220 -0
- package/app/backend/alembic/versions/015_add_ticket_sort_order.py +33 -0
- package/app/backend/alembic/versions/03220f0b93ae_add_pr_fields_to_ticket.py +49 -0
- package/app/backend/alembic/versions/0c2d89fff3b1_seed_board_configs_from_yaml.py +206 -0
- package/app/backend/alembic/versions/3348e5cf54c1_add_merge_checklist_table.py +67 -0
- package/app/backend/alembic/versions/357c780ee445_add_goal_status.py +34 -0
- package/app/backend/alembic/versions/553340b7e26c_add_autonomy_fields_to_goal.py +65 -0
- package/app/backend/alembic/versions/774dc335c679_merge_migration_heads.py +23 -0
- package/app/backend/alembic/versions/7b307e847cbd_merge_heads.py +23 -0
- package/app/backend/alembic/versions/82ecd978cc70_add_missing_indexes.py +48 -0
- package/app/backend/alembic/versions/8ef5054dc280_add_normalized_log_entries.py +173 -0
- package/app/backend/alembic/versions/8f3e2bd8ea3b_merge_migration_heads.py +23 -0
- package/app/backend/alembic/versions/9d17f0698d3b_add_config_column_to_boards_table.py +30 -0
- package/app/backend/alembic/versions/add_agent_conversation_history.py +72 -0
- package/app/backend/alembic/versions/add_job_variant.py +34 -0
- package/app/backend/alembic/versions/add_performance_indexes.py +95 -0
- package/app/backend/alembic/versions/add_repos_and_board_repos.py +174 -0
- package/app/backend/alembic/versions/add_session_id_to_jobs.py +27 -0
- package/app/backend/alembic/versions/add_sqlite_backend_tables.py +104 -0
- package/app/backend/alembic/versions/b10fb0b62240_add_diff_content_to_revisions.py +34 -0
- package/app/backend/alembic.ini +89 -0
- package/app/backend/app/__init__.py +3 -0
- package/app/backend/app/data_dir.py +85 -0
- package/app/backend/app/database.py +70 -0
- package/app/backend/app/database_sync.py +64 -0
- package/app/backend/app/dependencies/__init__.py +5 -0
- package/app/backend/app/dependencies/auth.py +80 -0
- package/app/backend/app/dependencies.py +43 -0
- package/app/backend/app/exceptions.py +178 -0
- package/app/backend/app/executors/__init__.py +1 -0
- package/app/backend/app/executors/adapters/__init__.py +1 -0
- package/app/backend/app/executors/adapters/aider.py +152 -0
- package/app/backend/app/executors/adapters/amazon_q.py +103 -0
- package/app/backend/app/executors/adapters/amp.py +123 -0
- package/app/backend/app/executors/adapters/claude.py +177 -0
- package/app/backend/app/executors/adapters/cline.py +127 -0
- package/app/backend/app/executors/adapters/codex.py +167 -0
- package/app/backend/app/executors/adapters/copilot.py +202 -0
- package/app/backend/app/executors/adapters/cursor.py +87 -0
- package/app/backend/app/executors/adapters/droid.py +123 -0
- package/app/backend/app/executors/adapters/gemini.py +132 -0
- package/app/backend/app/executors/adapters/goose.py +131 -0
- package/app/backend/app/executors/adapters/opencode.py +123 -0
- package/app/backend/app/executors/adapters/qwen.py +123 -0
- package/app/backend/app/executors/plugins/__init__.py +1 -0
- package/app/backend/app/executors/registry.py +202 -0
- package/app/backend/app/executors/spec.py +226 -0
- package/app/backend/app/main.py +486 -0
- package/app/backend/app/middleware/__init__.py +13 -0
- package/app/backend/app/middleware/idempotency.py +426 -0
- package/app/backend/app/middleware/rate_limit.py +312 -0
- package/app/backend/app/middleware/security_headers.py +43 -0
- package/app/backend/app/middleware/timeout.py +37 -0
- package/app/backend/app/models/__init__.py +56 -0
- package/app/backend/app/models/agent_conversation_history.py +56 -0
- package/app/backend/app/models/agent_session.py +127 -0
- package/app/backend/app/models/analysis_cache.py +49 -0
- package/app/backend/app/models/base.py +9 -0
- package/app/backend/app/models/board.py +79 -0
- package/app/backend/app/models/board_repo.py +68 -0
- package/app/backend/app/models/cost_budget.py +42 -0
- package/app/backend/app/models/enums.py +40 -0
- package/app/backend/app/models/evidence.py +132 -0
- package/app/backend/app/models/goal.py +102 -0
- package/app/backend/app/models/idempotency_entry.py +30 -0
- package/app/backend/app/models/job.py +163 -0
- package/app/backend/app/models/job_queue.py +39 -0
- package/app/backend/app/models/kv_store.py +28 -0
- package/app/backend/app/models/merge_checklist.py +87 -0
- package/app/backend/app/models/normalized_log.py +100 -0
- package/app/backend/app/models/planner_lock.py +43 -0
- package/app/backend/app/models/rate_limit_entry.py +25 -0
- package/app/backend/app/models/repo.py +66 -0
- package/app/backend/app/models/review_comment.py +91 -0
- package/app/backend/app/models/review_summary.py +69 -0
- package/app/backend/app/models/revision.py +130 -0
- package/app/backend/app/models/ticket.py +223 -0
- package/app/backend/app/models/ticket_event.py +83 -0
- package/app/backend/app/models/user.py +47 -0
- package/app/backend/app/models/workspace.py +71 -0
- package/app/backend/app/redis_client.py +119 -0
- package/app/backend/app/routers/__init__.py +29 -0
- package/app/backend/app/routers/agents.py +296 -0
- package/app/backend/app/routers/auth.py +94 -0
- package/app/backend/app/routers/board.py +885 -0
- package/app/backend/app/routers/dashboard.py +351 -0
- package/app/backend/app/routers/debug.py +528 -0
- package/app/backend/app/routers/evidence.py +96 -0
- package/app/backend/app/routers/executors.py +324 -0
- package/app/backend/app/routers/goals.py +574 -0
- package/app/backend/app/routers/jobs.py +448 -0
- package/app/backend/app/routers/maintenance.py +172 -0
- package/app/backend/app/routers/merge.py +360 -0
- package/app/backend/app/routers/planner.py +537 -0
- package/app/backend/app/routers/pull_requests.py +382 -0
- package/app/backend/app/routers/repos.py +263 -0
- package/app/backend/app/routers/revisions.py +939 -0
- package/app/backend/app/routers/settings.py +267 -0
- package/app/backend/app/routers/tickets.py +2003 -0
- package/app/backend/app/routers/webhooks.py +143 -0
- package/app/backend/app/routers/websocket.py +249 -0
- package/app/backend/app/schemas/__init__.py +109 -0
- package/app/backend/app/schemas/board.py +87 -0
- package/app/backend/app/schemas/common.py +33 -0
- package/app/backend/app/schemas/evidence.py +87 -0
- package/app/backend/app/schemas/goal.py +90 -0
- package/app/backend/app/schemas/job.py +97 -0
- package/app/backend/app/schemas/merge.py +139 -0
- package/app/backend/app/schemas/planner.py +500 -0
- package/app/backend/app/schemas/repo.py +187 -0
- package/app/backend/app/schemas/review.py +137 -0
- package/app/backend/app/schemas/revision.py +114 -0
- package/app/backend/app/schemas/ticket.py +238 -0
- package/app/backend/app/schemas/ticket_event.py +72 -0
- package/app/backend/app/schemas/workspace.py +19 -0
- package/app/backend/app/services/__init__.py +31 -0
- package/app/backend/app/services/agent_memory_service.py +223 -0
- package/app/backend/app/services/agent_registry.py +346 -0
- package/app/backend/app/services/agent_session_manager.py +318 -0
- package/app/backend/app/services/agent_session_service.py +219 -0
- package/app/backend/app/services/agent_tools.py +379 -0
- package/app/backend/app/services/auth_service.py +98 -0
- package/app/backend/app/services/autonomy_service.py +380 -0
- package/app/backend/app/services/board_repo_service.py +201 -0
- package/app/backend/app/services/board_service.py +326 -0
- package/app/backend/app/services/cleanup_service.py +1085 -0
- package/app/backend/app/services/config_service.py +908 -0
- package/app/backend/app/services/context_gatherer.py +557 -0
- package/app/backend/app/services/cost_tracking_service.py +293 -0
- package/app/backend/app/services/cursor_log_normalizer.py +536 -0
- package/app/backend/app/services/delivery_pipeline.py +440 -0
- package/app/backend/app/services/executor_service.py +634 -0
- package/app/backend/app/services/git_host/__init__.py +11 -0
- package/app/backend/app/services/git_host/factory.py +87 -0
- package/app/backend/app/services/git_host/github.py +270 -0
- package/app/backend/app/services/git_host/gitlab.py +194 -0
- package/app/backend/app/services/git_host/protocol.py +75 -0
- package/app/backend/app/services/git_merge_simple.py +346 -0
- package/app/backend/app/services/git_ops.py +384 -0
- package/app/backend/app/services/github_service.py +233 -0
- package/app/backend/app/services/goal_service.py +113 -0
- package/app/backend/app/services/job_service.py +423 -0
- package/app/backend/app/services/job_watchdog_service.py +424 -0
- package/app/backend/app/services/langchain_adapter.py +122 -0
- package/app/backend/app/services/llm_provider_clients.py +351 -0
- package/app/backend/app/services/llm_service.py +285 -0
- package/app/backend/app/services/log_normalizer.py +342 -0
- package/app/backend/app/services/log_stream_service.py +276 -0
- package/app/backend/app/services/merge_checklist_service.py +264 -0
- package/app/backend/app/services/merge_service.py +784 -0
- package/app/backend/app/services/orchestrator_log.py +84 -0
- package/app/backend/app/services/planner_service.py +1662 -0
- package/app/backend/app/services/planner_tick_sync.py +1040 -0
- package/app/backend/app/services/queued_message_service.py +156 -0
- package/app/backend/app/services/reliability_wrapper.py +389 -0
- package/app/backend/app/services/repo_discovery_service.py +318 -0
- package/app/backend/app/services/review_service.py +334 -0
- package/app/backend/app/services/revision_service.py +389 -0
- package/app/backend/app/services/safe_autopilot.py +510 -0
- package/app/backend/app/services/sqlite_worker.py +372 -0
- package/app/backend/app/services/task_dispatch.py +135 -0
- package/app/backend/app/services/ticket_generation_service.py +1781 -0
- package/app/backend/app/services/ticket_service.py +486 -0
- package/app/backend/app/services/udar_planner_service.py +1007 -0
- package/app/backend/app/services/webhook_service.py +126 -0
- package/app/backend/app/services/workspace_service.py +465 -0
- package/app/backend/app/services/worktree_file_service.py +92 -0
- package/app/backend/app/services/worktree_validator.py +213 -0
- package/app/backend/app/sqlite_kv.py +278 -0
- package/app/backend/app/state_machine.py +128 -0
- package/app/backend/app/templates/__init__.py +5 -0
- package/app/backend/app/templates/registry.py +243 -0
- package/app/backend/app/utils/__init__.py +5 -0
- package/app/backend/app/utils/artifact_reader.py +87 -0
- package/app/backend/app/utils/circuit_breaker.py +229 -0
- package/app/backend/app/utils/db_retry.py +136 -0
- package/app/backend/app/utils/ignored_fields.py +123 -0
- package/app/backend/app/utils/validators.py +54 -0
- package/app/backend/app/websocket/__init__.py +5 -0
- package/app/backend/app/websocket/manager.py +179 -0
- package/app/backend/app/websocket/state_tracker.py +113 -0
- package/app/backend/app/worker.py +3190 -0
- package/app/backend/calculator_tickets.json +40 -0
- package/app/backend/canary_tests.sh +591 -0
- package/app/backend/celerybeat-schedule +0 -0
- package/app/backend/celerybeat-schedule-shm +0 -0
- package/app/backend/celerybeat-schedule-wal +0 -0
- package/app/backend/logs/.gitkeep +3 -0
- package/app/backend/multiplication_division_implementation_tickets.json +55 -0
- package/app/backend/multiplication_division_tickets.json +42 -0
- package/app/backend/pyproject.toml +45 -0
- package/app/backend/requirements-dev.txt +8 -0
- package/app/backend/requirements.txt +20 -0
- package/app/backend/run.sh +30 -0
- package/app/backend/run_with_logs.sh +10 -0
- package/app/backend/scientific_calculator_tickets.json +40 -0
- package/app/backend/scripts/extract_openapi.py +21 -0
- package/app/backend/scripts/seed_demo.py +187 -0
- package/app/backend/setup_demo_review.py +302 -0
- package/app/backend/test_actual_parse.py +41 -0
- package/app/backend/test_agent_streaming.py +61 -0
- package/app/backend/test_parse.py +51 -0
- package/app/backend/test_streaming.py +51 -0
- package/app/backend/test_subprocess_streaming.py +50 -0
- package/app/backend/tests/__init__.py +1 -0
- package/app/backend/tests/conftest.py +46 -0
- package/app/backend/tests/test_auth.py +341 -0
- package/app/backend/tests/test_autonomy_service.py +391 -0
- package/app/backend/tests/test_cleanup_service_safety.py +417 -0
- package/app/backend/tests/test_middleware.py +279 -0
- package/app/backend/tests/test_planner_providers.py +290 -0
- package/app/backend/tests/test_planner_unblock.py +183 -0
- package/app/backend/tests/test_revision_invariants.py +618 -0
- package/app/backend/tests/test_sqlite_kv.py +290 -0
- package/app/backend/tests/test_sqlite_worker.py +353 -0
- package/app/backend/tests/test_task_dispatch.py +100 -0
- package/app/backend/tests/test_ticket_validation.py +304 -0
- package/app/backend/tests/test_udar_agent.py +693 -0
- package/app/backend/tests/test_webhook_service.py +184 -0
- package/app/backend/tickets_output.json +59 -0
- package/app/backend/user_management_tickets.json +50 -0
- package/app/backend/uvicorn.log +0 -0
- package/app/draft.yaml +313 -0
- package/app/frontend/dist/assets/index-LcjCczu5.js +155 -0
- package/app/frontend/dist/assets/index-_FP_279e.css +1 -0
- package/app/frontend/dist/index.html +14 -0
- package/app/frontend/dist/vite.svg +1 -0
- package/app/frontend/package.json +101 -0
- package/bin/cli.js +527 -0
- package/package.json +37 -0
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
{
|
|
2
|
+
"tickets": [
|
|
3
|
+
{
|
|
4
|
+
"title": "Implement calculator module with scientific operations",
|
|
5
|
+
"description": "Create app/utils/calculator.py with six scientific calculator functions:\n\n1. multiply(a, b) - multiply two numbers\n2. divide(a, b) - divide with ZeroDivisionError handling when b is 0\n3. power(base, exp) - raise base to exponent power\n4. sqrt(n) - square root with ValueError for negative numbers\n5. modulo(a, b) - remainder operation\n6. factorial(n) - factorial for non-negative integers (raise ValueError for negative)\n\nAll functions must:\n- Include proper type hints (use float | int for numeric types)\n- Have comprehensive docstrings following Google/NumPy style\n- Handle edge cases appropriately (division by zero, negative sqrt, negative factorial)\n- Export functions in app/utils/__init__.py\n\nAcceptance criteria:\n- File app/utils/calculator.py exists with all 6 functions\n- All functions have type hints and docstrings\n- Functions raise appropriate exceptions for invalid inputs\n- Functions are exported in app/utils/__init__.py",
|
|
6
|
+
"priority_bucket": "P1",
|
|
7
|
+
"priority_rationale": "Core feature implementation - required foundation for calculator functionality",
|
|
8
|
+
"verification": [
|
|
9
|
+
"test -f app/utils/calculator.py",
|
|
10
|
+
"python -c 'from app.utils import multiply, divide, power, sqrt, modulo, factorial; assert multiply(2, 3) == 6'",
|
|
11
|
+
"python -c 'from app.utils.calculator import divide; import pytest; pytest.raises(ZeroDivisionError, divide, 1, 0)'"
|
|
12
|
+
],
|
|
13
|
+
"notes": "Use math.sqrt and math.factorial from standard library. For modulo, use % operator. Ensure type hints support both int and float inputs."
|
|
14
|
+
},
|
|
15
|
+
{
|
|
16
|
+
"title": "Add comprehensive test coverage for calculator module",
|
|
17
|
+
"description": "Create tests/test_calculator.py with comprehensive test coverage for all calculator functions.\n\nTest requirements:\n- Test normal operation for each function with various inputs\n- Test error cases: ZeroDivisionError for divide(1, 0), ValueError for sqrt(-1) and factorial(-1)\n- Test edge cases: factorial(0) = 1, power(2, 0) = 1, modulo with negative numbers\n- Test type handling: ensure functions work with both int and float inputs\n- Use pytest fixtures where appropriate\n- Follow existing test patterns from tests/test_middleware.py\n\nAcceptance criteria:\n- File tests/test_calculator.py exists\n- All 6 functions have test coverage\n- Error cases are tested and raise correct exceptions\n- Edge cases are covered\n- Tests pass with pytest",
|
|
18
|
+
"priority_bucket": "P1",
|
|
19
|
+
"priority_rationale": "Critical for ensuring code quality and preventing regressions - tests validate correctness of core functionality",
|
|
20
|
+
"verification": [
|
|
21
|
+
"test -f tests/test_calculator.py",
|
|
22
|
+
"pytest tests/test_calculator.py -v",
|
|
23
|
+
"pytest tests/test_calculator.py --cov=app.utils.calculator --cov-report=term-missing | grep -q '100%' || pytest tests/test_calculator.py --cov=app.utils.calculator"
|
|
24
|
+
],
|
|
25
|
+
"notes": "Use pytest.raises context manager for exception testing. Consider parametrize for testing multiple input combinations."
|
|
26
|
+
},
|
|
27
|
+
{
|
|
28
|
+
"title": "Update README.md to document calculator module",
|
|
29
|
+
"description": "Create or update README.md in project root to document the calculator module and its scientific operations.\n\nDocumentation should include:\n- Overview of the calculator module\n- List of available functions with brief descriptions\n- Usage examples for each function\n- Error handling information (what exceptions are raised and when)\n- Installation/setup instructions if needed\n- Link to test file for examples\n\nAcceptance criteria:\n- README.md exists in project root\n- Calculator module is documented with all 6 functions\n- Usage examples are provided\n- Error handling is documented\n- Documentation is clear and follows project conventions",
|
|
30
|
+
"priority_bucket": "P2",
|
|
31
|
+
"priority_rationale": "Important for usability and maintainability, but not blocking for functionality - documentation helps users understand the module",
|
|
32
|
+
"verification": [
|
|
33
|
+
"test -f README.md",
|
|
34
|
+
"grep -q 'calculator' README.md",
|
|
35
|
+
"grep -q 'multiply\\|divide\\|power\\|sqrt\\|modulo\\|factorial' README.md"
|
|
36
|
+
],
|
|
37
|
+
"notes": "If README.md doesn't exist, create a new one. If it exists, add a new section for the calculator module. Include code examples using triple backticks."
|
|
38
|
+
}
|
|
39
|
+
]
|
|
40
|
+
}
|
|
@@ -0,0 +1,591 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# =============================================================================
|
|
3
|
+
# CANARY TESTS FOR ALMA KANBAN EXECUTOR INTEGRATION
|
|
4
|
+
# =============================================================================
|
|
5
|
+
#
|
|
6
|
+
# Prerequisites:
|
|
7
|
+
# 1. Redis running: redis-server
|
|
8
|
+
# 2. Backend running: cd backend && source venv/bin/activate && uvicorn app.main:app --reload
|
|
9
|
+
# 3. Worker running: cd backend && source venv/bin/activate && celery -A app.worker worker --loglevel=info
|
|
10
|
+
#
|
|
11
|
+
# Usage:
|
|
12
|
+
# ./canary_tests.sh [test_number]
|
|
13
|
+
# ./canary_tests.sh # Run all tests
|
|
14
|
+
# ./canary_tests.sh 1 # Run only Canary 1
|
|
15
|
+
# ./canary_tests.sh 4 # Run only Canary 4
|
|
16
|
+
|
|
17
|
+
set -e
|
|
18
|
+
|
|
19
|
+
API_BASE="${API_BASE:-http://localhost:8000}"
|
|
20
|
+
POLL_INTERVAL=2
|
|
21
|
+
MAX_POLLS=30
|
|
22
|
+
|
|
23
|
+
# Colors for output
|
|
24
|
+
RED='\033[0;31m'
|
|
25
|
+
GREEN='\033[0;32m'
|
|
26
|
+
YELLOW='\033[1;33m'
|
|
27
|
+
NC='\033[0m' # No Color
|
|
28
|
+
|
|
29
|
+
log_info() {
|
|
30
|
+
echo -e "${GREEN}[INFO]${NC} $1"
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
log_warn() {
|
|
34
|
+
echo -e "${YELLOW}[WARN]${NC} $1"
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
log_error() {
|
|
38
|
+
echo -e "${RED}[ERROR]${NC} $1"
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
log_step() {
|
|
42
|
+
echo -e "\n${GREEN}>>> $1${NC}"
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
# Create a goal and return its ID
|
|
46
|
+
create_goal() {
|
|
47
|
+
local title="$1"
|
|
48
|
+
local response=$(curl -s -X POST "$API_BASE/goals" \
|
|
49
|
+
-H "Content-Type: application/json" \
|
|
50
|
+
-d "{\"title\": \"$title\"}")
|
|
51
|
+
echo "$response" | jq -r '.id'
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
# Create a ticket and return its ID
|
|
55
|
+
create_ticket() {
|
|
56
|
+
local goal_id="$1"
|
|
57
|
+
local title="$2"
|
|
58
|
+
local description="${3:-}"
|
|
59
|
+
local response=$(curl -s -X POST "$API_BASE/tickets" \
|
|
60
|
+
-H "Content-Type: application/json" \
|
|
61
|
+
-d "{\"goal_id\": \"$goal_id\", \"title\": \"$title\", \"description\": \"$description\"}")
|
|
62
|
+
echo "$response" | jq -r '.id'
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
# Get ticket state
|
|
66
|
+
get_ticket_state() {
|
|
67
|
+
local ticket_id="$1"
|
|
68
|
+
curl -s "$API_BASE/tickets/$ticket_id" | jq -r '.state'
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
# Get ticket details
|
|
72
|
+
get_ticket() {
|
|
73
|
+
local ticket_id="$1"
|
|
74
|
+
curl -s "$API_BASE/tickets/$ticket_id"
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
# Run execute job
|
|
78
|
+
run_execute() {
|
|
79
|
+
local ticket_id="$1"
|
|
80
|
+
curl -s -X POST "$API_BASE/tickets/$ticket_id/run"
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
# Run verify job
|
|
84
|
+
run_verify() {
|
|
85
|
+
local ticket_id="$1"
|
|
86
|
+
curl -s -X POST "$API_BASE/tickets/$ticket_id/verify"
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
# Run resume (for interactive flow)
|
|
90
|
+
run_resume() {
|
|
91
|
+
local ticket_id="$1"
|
|
92
|
+
curl -s -X POST "$API_BASE/tickets/$ticket_id/resume"
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
# Get jobs for ticket
|
|
96
|
+
get_jobs() {
|
|
97
|
+
local ticket_id="$1"
|
|
98
|
+
curl -s "$API_BASE/tickets/$ticket_id/jobs"
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
# Get evidence for ticket
|
|
102
|
+
get_evidence() {
|
|
103
|
+
local ticket_id="$1"
|
|
104
|
+
curl -s "$API_BASE/tickets/$ticket_id/evidence"
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
# Wait for ticket to reach a state (or one of several states)
|
|
108
|
+
wait_for_state() {
|
|
109
|
+
local ticket_id="$1"
|
|
110
|
+
shift
|
|
111
|
+
local expected_states=("$@")
|
|
112
|
+
local polls=0
|
|
113
|
+
|
|
114
|
+
log_info "Waiting for ticket to reach state: ${expected_states[*]}"
|
|
115
|
+
|
|
116
|
+
while [ $polls -lt $MAX_POLLS ]; do
|
|
117
|
+
local current_state=$(get_ticket_state "$ticket_id")
|
|
118
|
+
|
|
119
|
+
for expected in "${expected_states[@]}"; do
|
|
120
|
+
if [ "$current_state" = "$expected" ]; then
|
|
121
|
+
log_info "Ticket reached state: $current_state"
|
|
122
|
+
return 0
|
|
123
|
+
fi
|
|
124
|
+
done
|
|
125
|
+
|
|
126
|
+
log_info "Current state: $current_state (poll $((polls + 1))/$MAX_POLLS)"
|
|
127
|
+
sleep $POLL_INTERVAL
|
|
128
|
+
((polls++))
|
|
129
|
+
done
|
|
130
|
+
|
|
131
|
+
log_error "Timeout waiting for state. Current: $current_state, Expected: ${expected_states[*]}"
|
|
132
|
+
return 1
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
# Verify evidence exists
|
|
136
|
+
verify_evidence() {
|
|
137
|
+
local ticket_id="$1"
|
|
138
|
+
local expected_kinds=("$@")
|
|
139
|
+
|
|
140
|
+
local evidence=$(get_evidence "$ticket_id")
|
|
141
|
+
local found_kinds=$(echo "$evidence" | jq -r '.evidence[].kind' | sort -u)
|
|
142
|
+
|
|
143
|
+
log_info "Found evidence kinds: $found_kinds"
|
|
144
|
+
|
|
145
|
+
for kind in "${expected_kinds[@]:1}"; do
|
|
146
|
+
if ! echo "$found_kinds" | grep -q "$kind"; then
|
|
147
|
+
log_warn "Missing evidence kind: $kind"
|
|
148
|
+
fi
|
|
149
|
+
done
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
# =============================================================================
|
|
153
|
+
# CANARY 1: Trivial Change (Claude Headless)
|
|
154
|
+
# =============================================================================
|
|
155
|
+
# Expected flow:
|
|
156
|
+
# proposed → executing → verifying → needs_human (default on_success)
|
|
157
|
+
# Evidence:
|
|
158
|
+
# executor_stdout, executor_meta, git_diff_stat, git_diff_patch,
|
|
159
|
+
# verify_stdout, verify_meta
|
|
160
|
+
# =============================================================================
|
|
161
|
+
canary_1() {
|
|
162
|
+
log_step "CANARY 1: Trivial Change (Claude Headless)"
|
|
163
|
+
|
|
164
|
+
log_info "Creating goal..."
|
|
165
|
+
local goal_id=$(create_goal "Canary Test Goal 1")
|
|
166
|
+
log_info "Goal ID: $goal_id"
|
|
167
|
+
|
|
168
|
+
log_info "Creating ticket..."
|
|
169
|
+
local ticket_id=$(create_ticket "$goal_id" "Add a comment to README" "Add a comment at the top of README.md")
|
|
170
|
+
log_info "Ticket ID: $ticket_id"
|
|
171
|
+
|
|
172
|
+
log_info "Transitioning to planned then executing..."
|
|
173
|
+
curl -s -X POST "$API_BASE/tickets/$ticket_id/transition" \
|
|
174
|
+
-H "Content-Type: application/json" \
|
|
175
|
+
-d '{"to_state": "planned", "actor_type": "human", "reason": "Planning canary test"}'
|
|
176
|
+
|
|
177
|
+
curl -s -X POST "$API_BASE/tickets/$ticket_id/transition" \
|
|
178
|
+
-H "Content-Type: application/json" \
|
|
179
|
+
-d '{"to_state": "executing", "actor_type": "human", "reason": "Executing canary test"}'
|
|
180
|
+
|
|
181
|
+
log_info "Running execute job..."
|
|
182
|
+
local execute_response=$(run_execute "$ticket_id")
|
|
183
|
+
log_info "Execute job: $(echo "$execute_response" | jq -c '{id, status}')"
|
|
184
|
+
|
|
185
|
+
# Wait for verification to complete (auto-triggered)
|
|
186
|
+
# First wait for verifying, then wait for needs_human (or blocked if failure)
|
|
187
|
+
wait_for_state "$ticket_id" "verifying" "blocked" "needs_human"
|
|
188
|
+
|
|
189
|
+
local state=$(get_ticket_state "$ticket_id")
|
|
190
|
+
if [ "$state" = "verifying" ]; then
|
|
191
|
+
log_info "In verifying state, waiting for verification to complete..."
|
|
192
|
+
wait_for_state "$ticket_id" "needs_human" "blocked" "done"
|
|
193
|
+
fi
|
|
194
|
+
|
|
195
|
+
# Check final state
|
|
196
|
+
local final_state=$(get_ticket_state "$ticket_id")
|
|
197
|
+
log_info "Final state: $final_state"
|
|
198
|
+
|
|
199
|
+
# Verify evidence
|
|
200
|
+
log_info "Checking evidence..."
|
|
201
|
+
verify_evidence "$ticket_id" executor_meta executor_stdout git_diff_stat git_diff_patch verify_meta verify_stdout
|
|
202
|
+
|
|
203
|
+
# Show evidence summary
|
|
204
|
+
local evidence=$(get_evidence "$ticket_id")
|
|
205
|
+
log_info "Evidence count: $(echo "$evidence" | jq '.total')"
|
|
206
|
+
|
|
207
|
+
if [ "$final_state" = "needs_human" ] || [ "$final_state" = "done" ]; then
|
|
208
|
+
log_info "✅ CANARY 1 PASSED: Ticket reached expected state ($final_state)"
|
|
209
|
+
elif [ "$final_state" = "blocked" ]; then
|
|
210
|
+
log_warn "⚠️ CANARY 1 PARTIAL: Ticket blocked (possibly no changes or executor failed)"
|
|
211
|
+
log_info "Check job logs for details"
|
|
212
|
+
else
|
|
213
|
+
log_error "❌ CANARY 1 FAILED: Unexpected state ($final_state)"
|
|
214
|
+
return 1
|
|
215
|
+
fi
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
# =============================================================================
|
|
219
|
+
# CANARY 2: No-op (No Changes)
|
|
220
|
+
# =============================================================================
|
|
221
|
+
# Expected flow:
|
|
222
|
+
# proposed → executing → blocked (reason: no changes)
|
|
223
|
+
# =============================================================================
|
|
224
|
+
canary_2() {
|
|
225
|
+
log_step "CANARY 2: No-op (No Changes)"
|
|
226
|
+
|
|
227
|
+
log_info "Creating goal..."
|
|
228
|
+
local goal_id=$(create_goal "Canary Test Goal 2")
|
|
229
|
+
log_info "Goal ID: $goal_id"
|
|
230
|
+
|
|
231
|
+
log_info "Creating ticket..."
|
|
232
|
+
local ticket_id=$(create_ticket "$goal_id" "Review code, no changes needed" "Just review the code structure, don't make any changes")
|
|
233
|
+
log_info "Ticket ID: $ticket_id"
|
|
234
|
+
|
|
235
|
+
log_info "Transitioning to planned then executing..."
|
|
236
|
+
curl -s -X POST "$API_BASE/tickets/$ticket_id/transition" \
|
|
237
|
+
-H "Content-Type: application/json" \
|
|
238
|
+
-d '{"to_state": "planned", "actor_type": "human", "reason": "Planning canary test"}'
|
|
239
|
+
|
|
240
|
+
curl -s -X POST "$API_BASE/tickets/$ticket_id/transition" \
|
|
241
|
+
-H "Content-Type: application/json" \
|
|
242
|
+
-d '{"to_state": "executing", "actor_type": "human", "reason": "Executing canary test"}'
|
|
243
|
+
|
|
244
|
+
log_info "Running execute job..."
|
|
245
|
+
local execute_response=$(run_execute "$ticket_id")
|
|
246
|
+
log_info "Execute job: $(echo "$execute_response" | jq -c '{id, status}')"
|
|
247
|
+
|
|
248
|
+
# Wait for blocked state (no changes should result in blocked)
|
|
249
|
+
wait_for_state "$ticket_id" "blocked" "verifying" "needs_human"
|
|
250
|
+
|
|
251
|
+
# Check final state
|
|
252
|
+
local final_state=$(get_ticket_state "$ticket_id")
|
|
253
|
+
log_info "Final state: $final_state"
|
|
254
|
+
|
|
255
|
+
# Get events to check reason
|
|
256
|
+
local events=$(curl -s "$API_BASE/tickets/$ticket_id/events")
|
|
257
|
+
local last_reason=$(echo "$events" | jq -r '.events[-1].reason')
|
|
258
|
+
log_info "Last transition reason: $last_reason"
|
|
259
|
+
|
|
260
|
+
if [ "$final_state" = "blocked" ]; then
|
|
261
|
+
if echo "$last_reason" | grep -qi "no.*change"; then
|
|
262
|
+
log_info "✅ CANARY 2 PASSED: Ticket blocked with 'no changes' reason"
|
|
263
|
+
else
|
|
264
|
+
log_warn "⚠️ CANARY 2 PARTIAL: Ticket blocked but reason unclear"
|
|
265
|
+
fi
|
|
266
|
+
else
|
|
267
|
+
log_warn "⚠️ CANARY 2 UNEXPECTED: State is $final_state (expected blocked)"
|
|
268
|
+
fi
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
# =============================================================================
|
|
272
|
+
# CANARY 3: YOLO Refusal
|
|
273
|
+
# =============================================================================
|
|
274
|
+
# Expected flow:
|
|
275
|
+
# executing → needs_human (YOLO refused due to empty allowlist)
|
|
276
|
+
#
|
|
277
|
+
# NOTE: This test requires yolo_mode: true in draft.yaml
|
|
278
|
+
# with an empty yolo_allowlist
|
|
279
|
+
# =============================================================================
|
|
280
|
+
canary_3() {
|
|
281
|
+
log_step "CANARY 3: YOLO Refusal (requires yolo_mode: true, empty allowlist)"
|
|
282
|
+
|
|
283
|
+
log_warn "This test requires modifying draft.yaml:"
|
|
284
|
+
log_warn " yolo_mode: true"
|
|
285
|
+
log_warn " yolo_allowlist: []"
|
|
286
|
+
log_warn ""
|
|
287
|
+
log_warn "If not configured, this test will behave like Canary 1."
|
|
288
|
+
|
|
289
|
+
log_info "Creating goal..."
|
|
290
|
+
local goal_id=$(create_goal "Canary Test Goal 3")
|
|
291
|
+
log_info "Goal ID: $goal_id"
|
|
292
|
+
|
|
293
|
+
log_info "Creating ticket..."
|
|
294
|
+
local ticket_id=$(create_ticket "$goal_id" "Test YOLO refusal" "This should be refused if YOLO is enabled with empty allowlist")
|
|
295
|
+
log_info "Ticket ID: $ticket_id"
|
|
296
|
+
|
|
297
|
+
log_info "Transitioning to planned then executing..."
|
|
298
|
+
curl -s -X POST "$API_BASE/tickets/$ticket_id/transition" \
|
|
299
|
+
-H "Content-Type: application/json" \
|
|
300
|
+
-d '{"to_state": "planned", "actor_type": "human", "reason": "Planning canary test"}'
|
|
301
|
+
|
|
302
|
+
curl -s -X POST "$API_BASE/tickets/$ticket_id/transition" \
|
|
303
|
+
-H "Content-Type: application/json" \
|
|
304
|
+
-d '{"to_state": "executing", "actor_type": "human", "reason": "Executing canary test"}'
|
|
305
|
+
|
|
306
|
+
log_info "Running execute job..."
|
|
307
|
+
local execute_response=$(run_execute "$ticket_id")
|
|
308
|
+
log_info "Execute job: $(echo "$execute_response" | jq -c '{id, status}')"
|
|
309
|
+
|
|
310
|
+
# Wait for needs_human (YOLO refused) or other states
|
|
311
|
+
wait_for_state "$ticket_id" "needs_human" "verifying" "blocked"
|
|
312
|
+
|
|
313
|
+
# Check final state
|
|
314
|
+
local final_state=$(get_ticket_state "$ticket_id")
|
|
315
|
+
log_info "Final state: $final_state"
|
|
316
|
+
|
|
317
|
+
# Get events to check reason
|
|
318
|
+
local events=$(curl -s "$API_BASE/tickets/$ticket_id/events")
|
|
319
|
+
local last_reason=$(echo "$events" | jq -r '.events[-1].reason')
|
|
320
|
+
log_info "Last transition reason: $last_reason"
|
|
321
|
+
|
|
322
|
+
if [ "$final_state" = "needs_human" ]; then
|
|
323
|
+
if echo "$last_reason" | grep -qi "yolo.*refused\|allowlist"; then
|
|
324
|
+
log_info "✅ CANARY 3 PASSED: YOLO refused with proper reason"
|
|
325
|
+
else
|
|
326
|
+
log_warn "⚠️ CANARY 3 UNCERTAIN: needs_human but reason doesn't mention YOLO"
|
|
327
|
+
log_info "Reason: $last_reason"
|
|
328
|
+
fi
|
|
329
|
+
else
|
|
330
|
+
log_warn "⚠️ CANARY 3 NOTE: YOLO mode may not be enabled ($final_state)"
|
|
331
|
+
fi
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
# =============================================================================
|
|
335
|
+
# CANARY 4: Cursor Interactive + Resume
|
|
336
|
+
# =============================================================================
|
|
337
|
+
# Expected flow:
|
|
338
|
+
# executing → needs_human (interactive executor)
|
|
339
|
+
# [human makes changes]
|
|
340
|
+
# resume → verifying → needs_human (or blocked if no changes)
|
|
341
|
+
#
|
|
342
|
+
# NOTE: This test requires preferred_executor: "cursor" in draft.yaml
|
|
343
|
+
# OR cursor CLI must be the only available executor
|
|
344
|
+
# =============================================================================
|
|
345
|
+
canary_4() {
|
|
346
|
+
log_step "CANARY 4: Cursor Interactive + Resume"
|
|
347
|
+
|
|
348
|
+
log_warn "This test requires either:"
|
|
349
|
+
log_warn " 1. preferred_executor: 'cursor' in draft.yaml"
|
|
350
|
+
log_warn " 2. OR: Only Cursor CLI available (no Claude CLI)"
|
|
351
|
+
log_warn ""
|
|
352
|
+
log_warn "If Claude is available and preferred, this test will behave like Canary 1."
|
|
353
|
+
|
|
354
|
+
log_info "Creating goal..."
|
|
355
|
+
local goal_id=$(create_goal "Canary Test Goal 4")
|
|
356
|
+
log_info "Goal ID: $goal_id"
|
|
357
|
+
|
|
358
|
+
log_info "Creating ticket..."
|
|
359
|
+
local ticket_id=$(create_ticket "$goal_id" "Interactive change test" "This requires human interaction if using Cursor")
|
|
360
|
+
log_info "Ticket ID: $ticket_id"
|
|
361
|
+
|
|
362
|
+
log_info "Transitioning to planned then executing..."
|
|
363
|
+
curl -s -X POST "$API_BASE/tickets/$ticket_id/transition" \
|
|
364
|
+
-H "Content-Type: application/json" \
|
|
365
|
+
-d '{"to_state": "planned", "actor_type": "human", "reason": "Planning canary test"}'
|
|
366
|
+
|
|
367
|
+
curl -s -X POST "$API_BASE/tickets/$ticket_id/transition" \
|
|
368
|
+
-H "Content-Type: application/json" \
|
|
369
|
+
-d '{"to_state": "executing", "actor_type": "human", "reason": "Executing canary test"}'
|
|
370
|
+
|
|
371
|
+
log_info "Running execute job..."
|
|
372
|
+
local execute_response=$(run_execute "$ticket_id")
|
|
373
|
+
log_info "Execute job: $(echo "$execute_response" | jq -c '{id, status}')"
|
|
374
|
+
|
|
375
|
+
# Wait for initial state
|
|
376
|
+
wait_for_state "$ticket_id" "needs_human" "verifying" "blocked"
|
|
377
|
+
|
|
378
|
+
local state_after_execute=$(get_ticket_state "$ticket_id")
|
|
379
|
+
log_info "State after execute: $state_after_execute"
|
|
380
|
+
|
|
381
|
+
if [ "$state_after_execute" = "needs_human" ]; then
|
|
382
|
+
log_info "Ticket is in needs_human state (interactive mode)"
|
|
383
|
+
|
|
384
|
+
# Get events to see why
|
|
385
|
+
local events=$(curl -s "$API_BASE/tickets/$ticket_id/events")
|
|
386
|
+
local last_reason=$(echo "$events" | jq -r '.events[-1].reason')
|
|
387
|
+
|
|
388
|
+
if echo "$last_reason" | grep -qi "interactive\|cursor"; then
|
|
389
|
+
log_info "Confirmed: Interactive executor (Cursor)"
|
|
390
|
+
log_info ""
|
|
391
|
+
log_info "=== MANUAL STEP REQUIRED ==="
|
|
392
|
+
log_info "1. Make some changes in the worktree"
|
|
393
|
+
log_info "2. Then call: curl -X POST $API_BASE/tickets/$ticket_id/resume"
|
|
394
|
+
log_info "==========================="
|
|
395
|
+
log_info ""
|
|
396
|
+
|
|
397
|
+
# Ask if user wants to test resume
|
|
398
|
+
read -p "Did you make changes? Run resume? (y/N): " do_resume
|
|
399
|
+
|
|
400
|
+
if [ "$do_resume" = "y" ] || [ "$do_resume" = "Y" ]; then
|
|
401
|
+
log_info "Running resume..."
|
|
402
|
+
local resume_response=$(run_resume "$ticket_id")
|
|
403
|
+
log_info "Resume job: $(echo "$resume_response" | jq -c '{id, status}')"
|
|
404
|
+
|
|
405
|
+
# Wait for final state
|
|
406
|
+
wait_for_state "$ticket_id" "verifying" "blocked" "needs_human"
|
|
407
|
+
|
|
408
|
+
local state_after_resume=$(get_ticket_state "$ticket_id")
|
|
409
|
+
if [ "$state_after_resume" = "verifying" ]; then
|
|
410
|
+
log_info "Resumed successfully, now in verifying"
|
|
411
|
+
wait_for_state "$ticket_id" "needs_human" "done" "blocked"
|
|
412
|
+
fi
|
|
413
|
+
|
|
414
|
+
local final_state=$(get_ticket_state "$ticket_id")
|
|
415
|
+
log_info "Final state: $final_state"
|
|
416
|
+
|
|
417
|
+
if [ "$final_state" = "needs_human" ] || [ "$final_state" = "done" ]; then
|
|
418
|
+
log_info "✅ CANARY 4 PASSED: Full interactive flow completed"
|
|
419
|
+
elif [ "$final_state" = "blocked" ]; then
|
|
420
|
+
log_warn "⚠️ CANARY 4 PARTIAL: Blocked (possibly no changes in worktree)"
|
|
421
|
+
fi
|
|
422
|
+
else
|
|
423
|
+
log_info "Skipping resume step"
|
|
424
|
+
log_info "✅ CANARY 4 PARTIAL: Interactive detection worked"
|
|
425
|
+
fi
|
|
426
|
+
else
|
|
427
|
+
log_warn "needs_human but not due to interactive executor"
|
|
428
|
+
log_info "Reason: $last_reason"
|
|
429
|
+
fi
|
|
430
|
+
else
|
|
431
|
+
log_warn "⚠️ CANARY 4 NOTE: Did not enter interactive mode"
|
|
432
|
+
log_info "This means Claude (headless) executor was used"
|
|
433
|
+
fi
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
# =============================================================================
|
|
437
|
+
# CANARY 5: Verification Failure (Syntax Error)
|
|
438
|
+
# =============================================================================
|
|
439
|
+
# Expected flow:
|
|
440
|
+
# executing → verifying → blocked (verification failed)
|
|
441
|
+
#
|
|
442
|
+
# This test proves the verification pipeline can actually fail.
|
|
443
|
+
# Creates a ticket that requests introducing a syntax error.
|
|
444
|
+
# =============================================================================
|
|
445
|
+
canary_5() {
|
|
446
|
+
log_step "CANARY 5: Verification Failure (Syntax Error)"
|
|
447
|
+
|
|
448
|
+
log_info "This test verifies that broken code is correctly blocked."
|
|
449
|
+
log_info "It requests a change that would break Python syntax."
|
|
450
|
+
log_info ""
|
|
451
|
+
|
|
452
|
+
log_info "Creating goal..."
|
|
453
|
+
local goal_id=$(create_goal "Canary Test Goal 5 - Break Things")
|
|
454
|
+
log_info "Goal ID: $goal_id"
|
|
455
|
+
|
|
456
|
+
log_info "Creating ticket that requests syntax error..."
|
|
457
|
+
local ticket_id=$(create_ticket "$goal_id" \
|
|
458
|
+
"Introduce syntax error in worker.py" \
|
|
459
|
+
"Add 'def broken_function(' (missing closing paren and body) to backend/app/worker.py. This should fail verification.")
|
|
460
|
+
log_info "Ticket ID: $ticket_id"
|
|
461
|
+
|
|
462
|
+
log_info "Transitioning to planned then executing..."
|
|
463
|
+
curl -s -X POST "$API_BASE/tickets/$ticket_id/transition" \
|
|
464
|
+
-H "Content-Type: application/json" \
|
|
465
|
+
-d '{"to_state": "planned", "actor_type": "human", "reason": "Planning canary test"}'
|
|
466
|
+
|
|
467
|
+
curl -s -X POST "$API_BASE/tickets/$ticket_id/transition" \
|
|
468
|
+
-H "Content-Type: application/json" \
|
|
469
|
+
-d '{"to_state": "executing", "actor_type": "human", "reason": "Executing canary test"}'
|
|
470
|
+
|
|
471
|
+
log_info "Running execute job..."
|
|
472
|
+
local execute_response=$(run_execute "$ticket_id")
|
|
473
|
+
log_info "Execute job: $(echo "$execute_response" | jq -c '{id, status}')"
|
|
474
|
+
|
|
475
|
+
# Wait for verifying (if executor makes changes) or blocked (if no changes)
|
|
476
|
+
wait_for_state "$ticket_id" "verifying" "blocked" "needs_human"
|
|
477
|
+
|
|
478
|
+
local state_after_execute=$(get_ticket_state "$ticket_id")
|
|
479
|
+
log_info "State after execute: $state_after_execute"
|
|
480
|
+
|
|
481
|
+
if [ "$state_after_execute" = "verifying" ]; then
|
|
482
|
+
log_info "Executor made changes, now in verifying..."
|
|
483
|
+
log_info "Waiting for verification to complete (should fail)..."
|
|
484
|
+
|
|
485
|
+
# Wait for blocked (verification should fail)
|
|
486
|
+
wait_for_state "$ticket_id" "blocked" "needs_human" "done"
|
|
487
|
+
|
|
488
|
+
local final_state=$(get_ticket_state "$ticket_id")
|
|
489
|
+
log_info "Final state: $final_state"
|
|
490
|
+
|
|
491
|
+
if [ "$final_state" = "blocked" ]; then
|
|
492
|
+
# Get events to check reason
|
|
493
|
+
local events=$(curl -s "$API_BASE/tickets/$ticket_id/events")
|
|
494
|
+
local last_reason=$(echo "$events" | jq -r '.events[-1].reason')
|
|
495
|
+
log_info "Block reason: $last_reason"
|
|
496
|
+
|
|
497
|
+
# Check evidence for verify_meta
|
|
498
|
+
local evidence=$(get_evidence "$ticket_id")
|
|
499
|
+
local verify_meta=$(echo "$evidence" | jq -r '.evidence[] | select(.kind == "verify_meta")')
|
|
500
|
+
|
|
501
|
+
if [ -n "$verify_meta" ]; then
|
|
502
|
+
log_info "Found verify_meta evidence"
|
|
503
|
+
local verify_exit=$(echo "$verify_meta" | jq -r '.exit_code')
|
|
504
|
+
log_info "Verify exit code: $verify_exit"
|
|
505
|
+
|
|
506
|
+
if [ "$verify_exit" != "0" ]; then
|
|
507
|
+
log_info "✅ CANARY 5 PASSED: Verification failed and blocked ticket"
|
|
508
|
+
log_info "Evidence shows failing verify command with exit code $verify_exit"
|
|
509
|
+
else
|
|
510
|
+
log_warn "⚠️ CANARY 5 UNEXPECTED: Blocked but verify_meta shows exit code 0"
|
|
511
|
+
fi
|
|
512
|
+
else
|
|
513
|
+
log_info "✅ CANARY 5 PASSED: Ticket blocked (verify_meta may not be captured yet)"
|
|
514
|
+
fi
|
|
515
|
+
elif [ "$final_state" = "needs_human" ] || [ "$final_state" = "done" ]; then
|
|
516
|
+
log_error "❌ CANARY 5 FAILED: Verification passed when it should have failed"
|
|
517
|
+
log_error "This means either:"
|
|
518
|
+
log_error " 1. Executor didn't actually introduce a syntax error"
|
|
519
|
+
log_error " 2. Verification commands aren't checking syntax (update draft.yaml)"
|
|
520
|
+
return 1
|
|
521
|
+
fi
|
|
522
|
+
elif [ "$state_after_execute" = "blocked" ]; then
|
|
523
|
+
# Could be blocked due to no changes
|
|
524
|
+
local events=$(curl -s "$API_BASE/tickets/$ticket_id/events")
|
|
525
|
+
local last_reason=$(echo "$events" | jq -r '.events[-1].reason')
|
|
526
|
+
|
|
527
|
+
if echo "$last_reason" | grep -qi "no.*change"; then
|
|
528
|
+
log_warn "⚠️ CANARY 5 SKIPPED: Executor produced no changes (can't test verification failure)"
|
|
529
|
+
log_info "This is expected if the executor refused to break things"
|
|
530
|
+
else
|
|
531
|
+
log_info "✅ CANARY 5 PASSED: Blocked during execution"
|
|
532
|
+
fi
|
|
533
|
+
else
|
|
534
|
+
log_warn "⚠️ CANARY 5 UNEXPECTED: State is $state_after_execute"
|
|
535
|
+
fi
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
# =============================================================================
|
|
539
|
+
# MAIN
|
|
540
|
+
# =============================================================================
|
|
541
|
+
|
|
542
|
+
main() {
|
|
543
|
+
echo "=============================================="
|
|
544
|
+
echo " ALMA KANBAN CANARY TESTS"
|
|
545
|
+
echo "=============================================="
|
|
546
|
+
echo ""
|
|
547
|
+
echo "API Base: $API_BASE"
|
|
548
|
+
echo ""
|
|
549
|
+
|
|
550
|
+
# Check if API is reachable
|
|
551
|
+
if ! curl -s "$API_BASE/board" > /dev/null 2>&1; then
|
|
552
|
+
log_error "Cannot reach API at $API_BASE"
|
|
553
|
+
log_error "Make sure the backend is running"
|
|
554
|
+
exit 1
|
|
555
|
+
fi
|
|
556
|
+
log_info "API is reachable"
|
|
557
|
+
|
|
558
|
+
local test_num="${1:-all}"
|
|
559
|
+
|
|
560
|
+
case "$test_num" in
|
|
561
|
+
1) canary_1 ;;
|
|
562
|
+
2) canary_2 ;;
|
|
563
|
+
3) canary_3 ;;
|
|
564
|
+
4) canary_4 ;;
|
|
565
|
+
5) canary_5 ;;
|
|
566
|
+
all)
|
|
567
|
+
canary_1
|
|
568
|
+
echo ""
|
|
569
|
+
canary_2
|
|
570
|
+
echo ""
|
|
571
|
+
canary_3
|
|
572
|
+
echo ""
|
|
573
|
+
canary_4
|
|
574
|
+
echo ""
|
|
575
|
+
canary_5
|
|
576
|
+
;;
|
|
577
|
+
*)
|
|
578
|
+
log_error "Unknown test: $test_num"
|
|
579
|
+
echo "Usage: $0 [1|2|3|4|5|all]"
|
|
580
|
+
exit 1
|
|
581
|
+
;;
|
|
582
|
+
esac
|
|
583
|
+
|
|
584
|
+
echo ""
|
|
585
|
+
echo "=============================================="
|
|
586
|
+
echo " CANARY TESTS COMPLETE"
|
|
587
|
+
echo "=============================================="
|
|
588
|
+
}
|
|
589
|
+
|
|
590
|
+
main "$@"
|
|
591
|
+
|
|
Binary file
|
|
Binary file
|
|
Binary file
|