2005b8ef82
CI / push-validation (push) Successful in 18s
CI / build (push) Successful in 19s
CI / helm (push) Successful in 24s
CI / lint (push) Successful in 29s
CI / security (push) Successful in 1m11s
CI / e2e_tests (push) Successful in 2m56s
CI / quality (push) Successful in 3m40s
CI / typecheck (push) Successful in 3m59s
CI / integration_tests (push) Successful in 4m3s
CI / unit_tests (push) Successful in 4m55s
CI / docker (push) Successful in 10s
CI / coverage (push) Successful in 10m44s
CI / status-check (push) Successful in 1s
CI / benchmark-regression (push) Has been skipped
CI / benchmark-publish (push) Successful in 1h13m28s
## Summary Replaces all 234 bare `@skip` occurrences across 82 Behave feature files with the correct TDD issue-capture tagging system described in CONTRIBUTING.md § Bug Fix Workflow. Previously, the noxfile ran Behave with `--tags=not @skip`, silently excluding all `@skip`-tagged scenarios from every CI run. These tests never ran, never inverted results via the `@tdd_expected_fail` mechanism, and never contributed to coverage — defeating the purpose of TDD issue-capture testing. Every `@skip` occurrence had a commented-out hint line immediately above it showing the intended proper tags (e.g., `# @tdd_issue @tdd_issue_4272 @tdd_expected_fail @skip`), confirming they were all intended for conversion. ## Changes ### Mechanical conversion (234 replacements across 82 files) - Extracted the proper TDD tags from the comment hint above each `@skip` line, removed `@skip` from the tag set, and replaced the `@skip` line with those tags. - Removed the now-redundant comment hint lines alongside each replacement. ### Bug-fixed scenarios — `@tdd_expected_fail` removed (84 scenarios) - After conversion, ran `nox -s unit_tests` to identify which newly-enabled `@tdd_expected_fail` scenarios now **pass** (their referenced bugs have already been fixed). Removed `@tdd_expected_fail` from those 84 scenarios and their corresponding feature-level tags, leaving only the permanent `@tdd_issue @tdd_issue_<N>` regression-guard tags. - Affected features include: `tdd_tool_runner_env_precedence`, `tdd_automation_profile_session_leak`, `tls_certificate_check`, `project_create_persist`, `resource_type_bootstrap_*`, and 18 others. ### Noxfile cleanup - Removed all four `--tags=not @skip` arguments from `noxfile.py` (unit_tests and coverage sessions). With zero `@skip` tags remaining in the codebase, this filter was dead code and its presence would mislead future maintainers into thinking `@skip` is still a supported escape mechanism. ### Regression guard files - Split the regression guards into two focused files: - `tdd_regression_guards_exec_env.feature` for bug #4281 (exec-env precedence) - `tdd_regression_guards_session_list.feature` for bug #4271 (session list summary) - Each file carries only its own `@tdd_issue` tags at the feature level, avoiding cross-contamination via Behave tag inheritance. The `Background` step (`session-list-summary mock`) only appears in the session-list file where it is actually needed. ### Duplicate tag cleanup - Removed duplicate `@tdd_issue @tdd_issue_4287` tag lines in `tdd_skill_add_regression.feature` (lines 20 and 29). ### Inline comment for retained `@tdd_expected_fail` - Added inline comment in `ci_workflow_validation.feature:134` explaining why this specific #4227 scenario retains `@tdd_expected_fail` despite #4227 being closed (CI YAML does not encode threshold as a machine-readable value). ### Known edge cases — `@tdd_expected_fail` retained (closed issues, fix on master, scenarios still fail) The following issues are **closed** and their fixes **are on master**, but the specific test assertions still fail because the fixes address other aspects of the bugs. The `@tdd_expected_fail` tags are functionally correct and must remain until the specific scenario assertions pass: - `tdd_exec_env_resolution_precedence.feature` — bug #1080 (closed 2026-03-31). The precedence-level-2-vs-4 scenario still fails. - `session_list_summary_dedup.feature` — bug #3046 (closed 2026-04-05). The dedup-consistency scenarios still fail. - `actor_add_update_enforcement.feature` — bug #2609 (closed 2026-04-05). The enforcement scenarios still fail. - `ci_workflow_validation.feature:134` — #4227 (closed 2026-04-08). The CI YAML threshold assertion still fails. ## Verification - `grep -r "@skip" features/ --include="*.feature"` → **zero results** ✓ - `grep -n "tags=not @skip" noxfile.py` → **zero results** ✓ - `nox -s unit_tests` → **629 features passed, 0 failed** ✓ (up from ~545 before this PR) - CI all green (coverage ≥ 97%) ✓ - Integration tests (Robot Framework) do not use `@skip` — confirmed no action needed ✓ - E2E tests (Robot Framework) do not use `@skip` — confirmed no action needed ✓ - `CHANGELOG.md` updated with entry for this change ✓ - `CONTRIBUTORS.md` — Rui Hu already listed ✓ ## Issues Addressed Closes #7025 Co-authored-by: CleverThis <hal9000@cleverthis.com> Reviewed-on: #7221 Reviewed-by: HAL 9000 <HAL9000@cleverthis.com> Reviewed-by: HAL9001 <hal9001@cleverthis.com> Co-authored-by: Rui Hu <rui.hu@cleverthis.com> Co-committed-by: Rui Hu <rui.hu@cleverthis.com>
314 lines
14 KiB
Gherkin
314 lines
14 KiB
Gherkin
Feature: M6 autonomy acceptance smoke tests
|
|
As a developer working with the CleverAgents M6 milestone (v3.5.0)
|
|
I want to verify the A2A facade, event queue, guard enforcement, and
|
|
automation profile resolution end-to-end
|
|
So that all v3.5.0 acceptance criteria are satisfied before milestone closure
|
|
|
|
Background:
|
|
Given a m6 smoke test runner
|
|
And a m6 smoke A2A local facade
|
|
|
|
# --- Fixture loading ---
|
|
|
|
Scenario: M6 smoke load A2A facade flows fixture
|
|
When I m6 smoke load the A2A facade flows fixture
|
|
Then the m6 smoke facade fixture should have a session lifecycle entry
|
|
And the m6 smoke facade fixture should have a plan lifecycle entry
|
|
|
|
Scenario: M6 smoke load autonomy guardrails fixture
|
|
When I m6 smoke load the autonomy guardrails fixture
|
|
Then the m6 smoke guardrails fixture should have a denylist entry
|
|
And the m6 smoke guardrails fixture should have an allowlist entry
|
|
And the m6 smoke guardrails fixture should have a cost budget entry
|
|
|
|
Scenario: M6 smoke load automation profiles fixture
|
|
When I m6 smoke load the automation profiles fixture
|
|
Then the m6 smoke profiles fixture should list all 8 built-in names
|
|
And the m6 smoke profiles fixture should have a custom profile entry
|
|
|
|
# --- A2A facade session operations (AC-1: session lifecycle) ---
|
|
|
|
Scenario: M6 smoke A2A session create returns session id
|
|
When I m6 smoke dispatch "session.create" with params {}
|
|
Then the m6 smoke response status should be "ok"
|
|
And the m6 smoke response data should contain key "session_id"
|
|
And the m6 smoke response data should contain key "status"
|
|
|
|
Scenario: M6 smoke A2A session close returns closed status
|
|
When I m6 smoke dispatch "session.close" with params {}
|
|
Then the m6 smoke response status should be "ok"
|
|
And the m6 smoke response data "status" should equal "closed"
|
|
|
|
# --- A2A facade plan operations (AC-1: plan lifecycle) ---
|
|
|
|
Scenario: M6 smoke A2A plan create returns plan id
|
|
When I m6 smoke dispatch "plan.create" with params {}
|
|
Then the m6 smoke response status should be "ok"
|
|
And the m6 smoke response data should contain key "plan_id"
|
|
|
|
Scenario: M6 smoke A2A plan execute queues plan
|
|
When I m6 smoke dispatch "plan.execute" with params {"plan_id": "01M6SM0KE00000000000000001"}
|
|
Then the m6 smoke response status should be "ok"
|
|
And the m6 smoke response data "status" should equal "queued"
|
|
|
|
Scenario: M6 smoke A2A plan status returns phase
|
|
When I m6 smoke dispatch "plan.status" with params {"plan_id": "01M6SM0KE00000000000000001"}
|
|
Then the m6 smoke response status should be "ok"
|
|
And the m6 smoke response data should contain key "phase"
|
|
|
|
Scenario: M6 smoke A2A plan diff returns changes list
|
|
When I m6 smoke dispatch "plan.diff" with params {"plan_id": "01M6SM0KE00000000000000001"}
|
|
Then the m6 smoke response status should be "ok"
|
|
And the m6 smoke response data should contain key "changes"
|
|
|
|
Scenario: M6 smoke A2A plan apply returns applied status
|
|
When I m6 smoke dispatch "plan.apply" with params {"plan_id": "01M6SM0KE00000000000000001"}
|
|
Then the m6 smoke response status should be "ok"
|
|
And the m6 smoke response data "status" should equal "applied"
|
|
|
|
# --- A2A facade registry and context operations ---
|
|
|
|
Scenario: M6 smoke A2A registry list tools returns empty list
|
|
When I m6 smoke dispatch "registry.list_tools" with params {}
|
|
Then the m6 smoke response status should be "ok"
|
|
And the m6 smoke response data should contain key "tools"
|
|
|
|
Scenario: M6 smoke A2A registry list resources returns empty list
|
|
When I m6 smoke dispatch "registry.list_resources" with params {}
|
|
Then the m6 smoke response status should be "ok"
|
|
And the m6 smoke response data should contain key "resources"
|
|
|
|
Scenario: M6 smoke A2A context get returns context dict
|
|
When I m6 smoke dispatch "context.get" with params {}
|
|
Then the m6 smoke response status should be "ok"
|
|
And the m6 smoke response data should contain key "context"
|
|
|
|
Scenario: M6 smoke A2A event subscribe returns subscription id
|
|
When I m6 smoke dispatch "event.subscribe" with params {}
|
|
Then the m6 smoke response status should be "ok"
|
|
And the m6 smoke response data should contain key "subscription_id"
|
|
|
|
# --- A2A facade error handling ---
|
|
|
|
Scenario: M6 smoke A2A unknown operation raises error
|
|
When I m6 smoke dispatch unknown operation "nonexistent.op"
|
|
Then the m6 smoke facade should raise A2aOperationNotFoundError
|
|
|
|
Scenario: M6 smoke A2A dispatch with invalid request type raises TypeError
|
|
When I m6 smoke dispatch with a non-A2aRequest object
|
|
Then the m6 smoke facade should raise TypeError
|
|
|
|
# --- A2A facade service registration ---
|
|
|
|
Scenario: M6 smoke A2A register service stores service
|
|
When I m6 smoke register service "plan_service" on the facade
|
|
Then the m6 smoke facade should have service "plan_service"
|
|
|
|
Scenario: M6 smoke A2A list operations returns all supported
|
|
When I m6 smoke list facade operations
|
|
Then the m6 smoke operations should include "session.create"
|
|
And the m6 smoke operations should include "plan.execute"
|
|
And the m6 smoke operations should include "event.subscribe"
|
|
And the m6 smoke operations count should be 42
|
|
|
|
# --- A2A event queue (AC-2: event queue publish/subscribe) ---
|
|
|
|
Scenario: M6 smoke A2A event queue publish and retrieve
|
|
Given a m6 smoke A2A event queue
|
|
When I m6 smoke publish an event with type "plan.progress"
|
|
Then the m6 smoke event queue should have 1 event
|
|
And the m6 smoke last event type should be "plan.progress"
|
|
|
|
Scenario: M6 smoke A2A event queue subscribe local callback
|
|
Given a m6 smoke A2A event queue
|
|
When I m6 smoke subscribe a local callback
|
|
And I m6 smoke publish an event with type "plan.complete"
|
|
Then the m6 smoke callback should have been called once
|
|
|
|
Scenario: M6 smoke A2A event queue unsubscribe
|
|
Given a m6 smoke A2A event queue
|
|
When I m6 smoke subscribe a local callback
|
|
And I m6 smoke unsubscribe the callback
|
|
And I m6 smoke publish an event with type "plan.complete"
|
|
Then the m6 smoke callback should not have been called
|
|
|
|
Scenario: M6 smoke A2A event queue close prevents publish
|
|
Given a m6 smoke A2A event queue
|
|
When I m6 smoke close the event queue
|
|
Then the m6 smoke publishing should raise RuntimeError
|
|
|
|
Scenario: M6 smoke A2A event queue remote subscribe raises error
|
|
Given a m6 smoke A2A event queue
|
|
When I m6 smoke attempt remote subscribe to "https://example.com/events"
|
|
Then the m6 smoke facade should raise A2aNotAvailableError
|
|
|
|
# --- A2A HTTP transport stub ---
|
|
|
|
Scenario: M6 smoke A2A transport send raises not available
|
|
When I m6 smoke attempt transport send
|
|
Then the m6 smoke facade should raise A2aNotAvailableError
|
|
|
|
Scenario: M6 smoke A2A transport connect raises not available
|
|
When I m6 smoke attempt transport connect to "https://example.com/a2a"
|
|
Then the m6 smoke facade should raise A2aNotAvailableError
|
|
|
|
Scenario: M6 smoke A2A transport disconnect raises not available
|
|
When I m6 smoke attempt transport disconnect
|
|
Then the m6 smoke facade should raise A2aNotAvailableError
|
|
|
|
Scenario: M6 smoke A2A transport is_connected returns false
|
|
When I m6 smoke check transport is_connected
|
|
Then the m6 smoke transport should not be connected
|
|
|
|
# --- A2A version negotiation ---
|
|
|
|
@tdd_issue @tdd_issue_4249 @tdd_expected_fail
|
|
Scenario: M6 smoke A2A version negotiation accepts 1.0
|
|
When I m6 smoke negotiate A2A version "1.0"
|
|
Then the m6 smoke negotiated version should be "1.0"
|
|
|
|
@tdd_issue @tdd_issue_4365 @tdd_expected_fail
|
|
Scenario: M6 smoke A2A version negotiation rejects unsupported
|
|
When I m6 smoke negotiate A2A version "2.0"
|
|
Then the m6 smoke facade should raise A2aVersionMismatchError
|
|
|
|
@tdd_issue @tdd_issue_4249 @tdd_expected_fail
|
|
Scenario: M6 smoke A2A version is_supported returns correct result
|
|
When I m6 smoke check if version "1.0" is supported
|
|
Then the m6 smoke version support should be true
|
|
When I m6 smoke check if version "99.0" is supported
|
|
Then the m6 smoke version support should be false
|
|
|
|
# --- Automation profiles built-in (AC-4: profile resolution) ---
|
|
|
|
Scenario: M6 smoke all 8 built-in profiles exist
|
|
When I m6 smoke list all built-in profiles
|
|
Then the m6 smoke profile count should be 8
|
|
And the m6 smoke profiles should include "manual"
|
|
And the m6 smoke profiles should include "full-auto"
|
|
|
|
Scenario: M6 smoke manual profile has all thresholds at 1.0
|
|
When I m6 smoke load built-in profile "manual"
|
|
Then the m6 smoke profile decompose_task should be 1.0
|
|
And the m6 smoke profile create_tool should be 1.0
|
|
And the m6 smoke profile select_tool should be 1.0
|
|
And the m6 smoke profile require_sandbox should be true
|
|
|
|
Scenario: M6 smoke full-auto profile has no gates
|
|
When I m6 smoke load built-in profile "full-auto"
|
|
Then the m6 smoke profile decompose_task should be 0.0
|
|
And the m6 smoke profile create_tool should be 0.0
|
|
And the m6 smoke profile select_tool should be 0.0
|
|
And the m6 smoke profile require_sandbox should be false
|
|
And the m6 smoke profile allow_unsafe_tools should be true
|
|
|
|
# --- Automation profile creation and validation ---
|
|
|
|
Scenario: M6 smoke create custom namespaced profile
|
|
When I m6 smoke create a profile named "acme/strict" with select_tool 1.0
|
|
Then the m6 smoke created profile name should be "acme/strict"
|
|
And the m6 smoke created profile select_tool should be 1.0
|
|
|
|
Scenario: M6 smoke profile name validation rejects invalid
|
|
When I m6 smoke create a profile with invalid name "has spaces"
|
|
Then the m6 smoke creation should raise ValueError
|
|
|
|
Scenario: M6 smoke profile threshold validation rejects out of range
|
|
When I m6 smoke create a profile with decompose_task 1.5
|
|
Then the m6 smoke creation should raise ValueError
|
|
|
|
Scenario: M6 smoke profile from_yaml loads correctly
|
|
Given a m6 smoke temporary profile YAML file
|
|
When I m6 smoke load profile from the temp YAML
|
|
Then the m6 smoke loaded profile name should be "test-yaml-profile"
|
|
|
|
# --- Guard enforcement (AC-3: denylist, budget caps, tool call limits) ---
|
|
|
|
Scenario: M6 smoke guard denylist blocks denied tool
|
|
Given a m6 smoke profile with denylist guard for "rm_rf"
|
|
When I m6 smoke check guard for tool "rm_rf"
|
|
Then the m6 smoke guard result should not be allowed
|
|
And the m6 smoke guard reason should contain "denylist"
|
|
|
|
Scenario: M6 smoke guard denylist allows non-denied tool
|
|
Given a m6 smoke profile with denylist guard for "rm_rf"
|
|
When I m6 smoke check guard for tool "read_file"
|
|
Then the m6 smoke guard result should be allowed
|
|
|
|
Scenario: M6 smoke guard allowlist blocks unlisted tool
|
|
Given a m6 smoke profile with allowlist guard for "read_file" and "search"
|
|
When I m6 smoke check guard for tool "write_file"
|
|
Then the m6 smoke guard result should not be allowed
|
|
And the m6 smoke guard reason should contain "allowlist"
|
|
|
|
Scenario: M6 smoke guard max tool calls blocks at limit
|
|
Given a m6 smoke profile with max 5 tool calls per step
|
|
When I m6 smoke check guard for tool "llm_call" with 5 calls so far
|
|
Then the m6 smoke guard result should not be allowed
|
|
And the m6 smoke guard reason should contain "limit"
|
|
|
|
Scenario: M6 smoke guard cost budget blocks at cap
|
|
Given a m6 smoke profile with max cost 10.0
|
|
When I m6 smoke check guard for tool "llm_call" with cost 10.0
|
|
Then the m6 smoke guard result should not be allowed
|
|
And the m6 smoke guard reason should contain "budget"
|
|
|
|
Scenario: M6 smoke guard write approval blocks write operations
|
|
Given a m6 smoke profile with write approval required
|
|
When I m6 smoke check guard for tool "write_file" as a write operation
|
|
Then the m6 smoke guard result should not be allowed
|
|
And the m6 smoke guard reason should contain "Write operations"
|
|
|
|
Scenario: M6 smoke guard apply approval blocks apply phase
|
|
Given a m6 smoke profile with apply approval required
|
|
When I m6 smoke check guard for tool "__apply__"
|
|
Then the m6 smoke guard result should not be allowed
|
|
And the m6 smoke guard reason should contain "Apply phase"
|
|
|
|
Scenario: M6 smoke guard with no guards allows everything
|
|
Given a m6 smoke profile with no guards
|
|
When I m6 smoke check guard for tool "anything"
|
|
Then the m6 smoke guard result should be allowed
|
|
|
|
# --- Profile service resolution precedence (AC-4: plan > action > global) ---
|
|
|
|
Scenario: M6 smoke profile resolution plan takes precedence
|
|
Given a m6 smoke automation profile service
|
|
When I m6 smoke resolve profile with plan "ci" action "auto" project "manual"
|
|
Then the m6 smoke resolved profile name should be "ci"
|
|
|
|
Scenario: M6 smoke profile resolution action takes precedence over project
|
|
Given a m6 smoke automation profile service
|
|
When I m6 smoke resolve profile with plan null action "auto" project "manual"
|
|
Then the m6 smoke resolved profile name should be "auto"
|
|
|
|
Scenario: M6 smoke profile resolution falls back to global default
|
|
Given a m6 smoke automation profile service
|
|
When I m6 smoke resolve profile with plan null action null project null
|
|
Then the m6 smoke resolved profile name should be "manual"
|
|
|
|
# --- Profile service guard evaluation ---
|
|
|
|
Scenario: M6 smoke service evaluate guard delegates to profile
|
|
Given a m6 smoke automation profile service
|
|
When I m6 smoke evaluate guard for profile "manual" and tool "read_file"
|
|
Then the m6 smoke guard result should be allowed
|
|
|
|
# --- A2A model validation ---
|
|
|
|
Scenario: M6 smoke A2aRequest validates non-empty operation
|
|
When I m6 smoke create A2aRequest with empty operation
|
|
Then the m6 smoke creation should raise ValueError
|
|
|
|
Scenario: M6 smoke A2aResponse validates status values
|
|
When I m6 smoke create A2aResponse with invalid status "maybe"
|
|
Then the m6 smoke creation should raise ValueError
|
|
|
|
Scenario: M6 smoke A2aEvent validates non-empty event_type
|
|
When I m6 smoke create A2aEvent with empty event_type
|
|
Then the m6 smoke creation should raise ValueError
|
|
|
|
Scenario: M6 smoke A2aErrorDetail validates non-empty fields
|
|
When I m6 smoke create A2aErrorDetail with empty code
|
|
Then the m6 smoke creation should raise ValueError
|