Files
cleveragents-core/features/m6_autonomy_acceptance.feature
hurui200320 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
feat(tests): replace all @skip tags with proper @tdd_expected_fail tags or remove them across the entire codebase (#7221)
## 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>
2026-04-13 04:56:01 +00:00

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