chore: add missing source modules to version control
Deploy to Production / deploy (push) Failing after 7s
Deploy to Production / deploy (push) Failing after 7s
apix-demo, apix-portal/src, apix-spider/src, apix-registry/src, apix-common/src were never staged. Without them the CI build has no source to compile and the Docker images cannot be produced. Also adds docs/ (infrastructure notes) missed in prior commits. Co-Authored-By: Mira <noreply@anthropic.com>
This commit is contained in:
+56
@@ -0,0 +1,56 @@
|
||||
Feature: Device registry — dedicated entry point
|
||||
As an IoT device agent
|
||||
I want a dedicated /devices entry point with device-specific navigation
|
||||
So that I can discover compatible replacement services without touching the agent service world
|
||||
|
||||
Background:
|
||||
Given a production IoT service "SmartHub Service" with deviceClass "device.class.smart-home-hub" and protocol "MQTT_5_0"
|
||||
And a production IoT service "SensorBridge" with deviceClass "device.class.industrial-sensor" and protocol "WEBSOCKET"
|
||||
And a production service "PlainApi" with no IoT profile
|
||||
|
||||
Scenario: GET /devices returns a navigation document with templated links
|
||||
When GET /devices is called with no query params
|
||||
Then the response is HTTP 200
|
||||
And the device root _links.self href ends with "/devices"
|
||||
And the device root _links.search is templated
|
||||
And the device root _links.replacement is templated
|
||||
|
||||
Scenario: Registry root exposes a devices link
|
||||
When GET / is called
|
||||
Then the response is HTTP 200
|
||||
And the root _links contains a "devices" entry
|
||||
|
||||
Scenario: Device search by capability returns only IoT-ready services
|
||||
When GET /devices?capability=device.telemetry is called
|
||||
Then the response is HTTP 200
|
||||
And "SmartHub Service" is in the device results
|
||||
And "SensorBridge" is in the device results
|
||||
And "PlainApi" is not in the device results
|
||||
|
||||
Scenario: Device search by deviceClass narrows results
|
||||
When GET /devices?deviceClass=device.class.smart-home-hub is called
|
||||
Then the response is HTTP 200
|
||||
And "SmartHub Service" is in the device results
|
||||
And "SensorBridge" is not in the device results
|
||||
|
||||
Scenario: Device search by protocol narrows results
|
||||
When GET /devices?protocol=MQTT_5_0 is called
|
||||
Then the response is HTTP 200
|
||||
And "SmartHub Service" is in the device results
|
||||
And "SensorBridge" is not in the device results
|
||||
|
||||
Scenario: GET /devices/{id} returns the device service with its IoT profile
|
||||
When GET /devices/{smartHubServiceId} is called
|
||||
Then the response is HTTP 200
|
||||
And the response body contains an iotProfile
|
||||
|
||||
Scenario: GET /devices/{id} returns 404 for a service without an IoT profile
|
||||
When GET /devices/{plainApiId} is called
|
||||
Then the response is HTTP 404
|
||||
|
||||
Scenario: Device replacement discovery at /devices/{id}/replacements
|
||||
Given a deprecated device service "OldHub" with locked set to false
|
||||
And "SmartHub Service" has declared device compatibility with "OldHub"
|
||||
When GET /devices/{oldHubId}/replacements is called
|
||||
Then the response is HTTP 200
|
||||
And the replacement candidates contain "SmartHub Service"
|
||||
+2
-1
@@ -82,6 +82,7 @@ Feature: Organisation audit log
|
||||
|
||||
Scenario: Audit log returns events newest first
|
||||
Given an organisation is registered with target level "UNVERIFIED" for domain "example.com"
|
||||
When time advances by 2 minutes
|
||||
And the owner has initiated key rotation using the rotation secret
|
||||
When the owner requests the audit log
|
||||
And the owner requests the audit log
|
||||
Then the first audit event is "TAN_ISSUED"
|
||||
|
||||
+40
-8
@@ -12,10 +12,17 @@ Feature: BSF admin actions — temp grants, revocation, TAN-based key rotation
|
||||
And the effective O-level is "OPERATIONALLY_VERIFIED"
|
||||
And the earned O-level is "IDENTITY_VERIFIED"
|
||||
|
||||
Scenario: Effective O-level drops back to earned level after temp grant expires
|
||||
Scenario: Effective O-level still active 1 nanosecond before the temp grant expires
|
||||
Given an organisation has earned O-level "IDENTITY_VERIFIED" with target "IDENTITY_VERIFIED"
|
||||
And a BSF admin grants a temporary level "OPERATIONALLY_VERIFIED" expiring in 2 hours
|
||||
When time advances by 3 hours
|
||||
When time advances by 1 nanosecond short of 2 hours
|
||||
And the caller reads the organisation
|
||||
Then the effective O-level is "OPERATIONALLY_VERIFIED"
|
||||
|
||||
Scenario: Effective O-level drops to earned level at exactly 2 hours
|
||||
Given an organisation has earned O-level "IDENTITY_VERIFIED" with target "IDENTITY_VERIFIED"
|
||||
And a BSF admin grants a temporary level "OPERATIONALLY_VERIFIED" expiring in 2 hours
|
||||
When time advances by 2 hours
|
||||
And the caller reads the organisation
|
||||
Then the effective O-level is "IDENTITY_VERIFIED"
|
||||
|
||||
@@ -69,10 +76,19 @@ Feature: BSF admin actions — temp grants, revocation, TAN-based key rotation
|
||||
When the owner initiates key rotation using an invalid rotation secret
|
||||
Then the response status is 403
|
||||
|
||||
Scenario: Key rotation TAN expires before confirmation — rotation is rejected
|
||||
Scenario: Key rotation TAN still valid 1 nanosecond before the 5-minute expiry
|
||||
Given an organisation is registered with target level "UNVERIFIED" for domain "example.com"
|
||||
And the owner has initiated key rotation using the rotation secret
|
||||
When time advances by 6 minutes
|
||||
When time advances by 1 nanosecond short of 5 minutes
|
||||
And the owner confirms key rotation with the TAN
|
||||
Then the response status is 200
|
||||
And a new api key with prefix "apix_org_" is returned
|
||||
And a new rotation secret with prefix "apix_rot_" is returned
|
||||
|
||||
Scenario: Key rotation TAN expires at exactly 5 minutes
|
||||
Given an organisation is registered with target level "UNVERIFIED" for domain "example.com"
|
||||
And the owner has initiated key rotation using the rotation secret
|
||||
When time advances by 5 minutes
|
||||
And the owner confirms key rotation with the TAN
|
||||
Then the response status is 422
|
||||
|
||||
@@ -132,10 +148,19 @@ Feature: BSF admin actions — temp grants, revocation, TAN-based key rotation
|
||||
Then the response status is 200
|
||||
And the response message confirms TAN was sent
|
||||
|
||||
Scenario: TAN is rejected after its 5-minute validity window
|
||||
Scenario: Emergency TAN still valid 1 nanosecond before the 5-minute expiry
|
||||
Given an organisation is registered with target level "UNVERIFIED" for domain "example.com"
|
||||
And the owner has requested a TAN using the registered email
|
||||
When time advances by 6 minutes
|
||||
When time advances by 1 nanosecond short of 5 minutes
|
||||
And the owner uses the TAN to rotate keys
|
||||
Then the response status is 200
|
||||
And a new api key with prefix "apix_org_" is returned
|
||||
And a new rotation secret with prefix "apix_rot_" is returned
|
||||
|
||||
Scenario: Emergency TAN expires at exactly 5 minutes
|
||||
Given an organisation is registered with target level "UNVERIFIED" for domain "example.com"
|
||||
And the owner has requested a TAN using the registered email
|
||||
When time advances by 5 minutes
|
||||
And the owner uses the TAN to rotate keys
|
||||
Then the response status is 422
|
||||
|
||||
@@ -145,9 +170,16 @@ Feature: BSF admin actions — temp grants, revocation, TAN-based key rotation
|
||||
When the owner requests a TAN using the registered email
|
||||
Then the response status is 422
|
||||
|
||||
Scenario: TAN request counter resets after 24 hours
|
||||
Scenario: TAN request counter is still active at exactly 24 hours
|
||||
Given an organisation is registered with target level "UNVERIFIED" for domain "example.com"
|
||||
And the owner has requested a TAN 3 times within the last 24 hours
|
||||
When time advances by 25 hours
|
||||
When time advances by 24 hours
|
||||
And the owner requests a TAN using the registered email
|
||||
Then the response status is 422
|
||||
|
||||
Scenario: TAN request counter resets at 24 hours and 1 nanosecond
|
||||
Given an organisation is registered with target level "UNVERIFIED" for domain "example.com"
|
||||
And the owner has requested a TAN 3 times within the last 24 hours
|
||||
When time advances by 24 hours and 1 nanosecond
|
||||
And the owner requests a TAN using the registered email
|
||||
Then the response status is 200
|
||||
|
||||
+12
-2
@@ -42,11 +42,21 @@ Feature: DNS-challenge key rotation — bot-friendly ACME DNS-01 pattern
|
||||
When the agent confirms DNS-challenge key rotation
|
||||
Then the response status is 422
|
||||
|
||||
Scenario: Confirm fails when the 15-minute challenge window has expired
|
||||
Scenario: DNS challenge window still active 1 nanosecond before the 15-minute expiry
|
||||
Given an organisation is registered with target level "UNVERIFIED" for domain "example.com"
|
||||
And the agent has initiated DNS-challenge key rotation
|
||||
And the agent has published the rotation challenge to dns
|
||||
When time advances by 16 minutes
|
||||
When time advances by 1 nanosecond short of 15 minutes
|
||||
And the agent confirms DNS-challenge key rotation
|
||||
Then the response status is 200
|
||||
And a new api key with prefix "apix_org_" is returned
|
||||
And a new rotation secret with prefix "apix_rot_" is returned
|
||||
|
||||
Scenario: Confirm fails at exactly the 15-minute challenge window expiry
|
||||
Given an organisation is registered with target level "UNVERIFIED" for domain "example.com"
|
||||
And the agent has initiated DNS-challenge key rotation
|
||||
And the agent has published the rotation challenge to dns
|
||||
When time advances by 15 minutes
|
||||
And the agent confirms DNS-challenge key rotation
|
||||
Then the response status is 422
|
||||
|
||||
|
||||
+58
@@ -0,0 +1,58 @@
|
||||
Feature: Agent experience feedback
|
||||
|
||||
Background:
|
||||
Given a sandbox named "feedback-demo" exists
|
||||
|
||||
Scenario: Feedback schema is globally discoverable without authentication
|
||||
When GET /sandbox/feedback-schema is called without authentication
|
||||
Then the response code is 200
|
||||
And the schema contains at least 9 dimensions
|
||||
And the schema contains dimension key "hateoas_navigation"
|
||||
And the schema contains dimension key "liveness_signal_accuracy"
|
||||
And the schema contains dimension key "error_message_quality"
|
||||
And the schema contains dimension key "extension_property_coverage"
|
||||
And the schema scale minimum is 0 and maximum is 10
|
||||
|
||||
Scenario: Submit valid feedback returns 202
|
||||
When feedback is submitted to "feedback-demo" with scores hateoas_navigation=8 discovery_accuracy=7
|
||||
Then the response code is 202
|
||||
And the response message is "Feedback recorded. Thank you."
|
||||
And the response contains _links.schema
|
||||
And the response contains _links.sandbox
|
||||
|
||||
Scenario: Submit feedback with model identity
|
||||
When feedback is submitted to "feedback-demo" with scores hateoas_navigation=9 and model "claude-sonnet-4-6" provider "anthropic"
|
||||
Then the response code is 202
|
||||
|
||||
Scenario: Submit feedback with unknown dimension keys is accepted but ignored
|
||||
When feedback is submitted to "feedback-demo" with scores unknown_key=5
|
||||
Then the response code is 422
|
||||
|
||||
Scenario: Submit feedback with score out of range returns 422
|
||||
When feedback is submitted to "feedback-demo" with scores hateoas_navigation=11
|
||||
Then the response code is 422
|
||||
|
||||
Scenario: Submit feedback with empty scores returns 400
|
||||
When feedback is submitted to "feedback-demo" with empty scores
|
||||
Then the response code is 400
|
||||
|
||||
Scenario: Aggregate feedback requires sandbox API key
|
||||
When the feedback aggregate for "feedback-demo" is requested without an API key
|
||||
Then the response code is 401
|
||||
|
||||
Scenario: Aggregate feedback shows averages per dimension
|
||||
Given feedback has been submitted to "feedback-demo" with scores hateoas_navigation=6 discovery_accuracy=8
|
||||
And feedback has been submitted to "feedback-demo" with scores hateoas_navigation=10 discovery_accuracy=4
|
||||
When the feedback aggregate for "feedback-demo" is requested with the sandbox API key
|
||||
Then the response code is 200
|
||||
And the total submissions is 2
|
||||
And the dimension "hateoas_navigation" has average 8.0
|
||||
And the dimension "discovery_accuracy" has average 6.0
|
||||
|
||||
Scenario: Aggregate includes provider breakdown
|
||||
Given feedback has been submitted to "feedback-demo" with scores hateoas_navigation=7 and model "claude-sonnet-4-6" provider "anthropic"
|
||||
And feedback has been submitted to "feedback-demo" with scores hateoas_navigation=5 and model "gpt-4o" provider "openai"
|
||||
When the feedback aggregate for "feedback-demo" is requested with the sandbox API key
|
||||
Then the response code is 200
|
||||
And the submissionsByProvider contains "anthropic" with count 1
|
||||
And the submissionsByProvider contains "openai" with count 1
|
||||
+33
@@ -0,0 +1,33 @@
|
||||
Feature: HATEOAS navigation and root key resolution
|
||||
|
||||
Background:
|
||||
Given a sandbox named "nav-demo" exists
|
||||
|
||||
Scenario: Root resource without API key omits _links.sandbox
|
||||
When the root resource is requested without an API key
|
||||
Then the response code is 200
|
||||
And the response contains _links.registerSandbox
|
||||
And the response contains _links.feedbackSchema
|
||||
And the response does not contain _links.sandbox
|
||||
|
||||
Scenario: Root resource with valid sandbox API key includes _links.sandbox
|
||||
When the root resource is requested with the sandbox API key for "nav-demo"
|
||||
Then the response code is 200
|
||||
And the response contains _links.sandbox ending with "/sandbox/nav-demo"
|
||||
|
||||
Scenario: Root resource with unknown API key omits _links.sandbox
|
||||
When the root resource is requested with API key "apix_sb_unknownkey"
|
||||
Then the response code is 200
|
||||
And the response does not contain _links.sandbox
|
||||
|
||||
Scenario: Sandbox root endpoint returns sandbox metadata
|
||||
When the sandbox root for "nav-demo" is requested
|
||||
Then the response code is 200
|
||||
And the response contains sandbox name "nav-demo"
|
||||
And the response contains _links.services
|
||||
And the response contains _links.submitFeedback
|
||||
And the response contains _links.feedbackSchema
|
||||
|
||||
Scenario: Sandbox root for unknown name returns 404
|
||||
When the sandbox root for "does-not-exist" is requested
|
||||
Then the response code is 404
|
||||
+32
@@ -0,0 +1,32 @@
|
||||
Feature: Sandbox registration
|
||||
|
||||
Scenario: Create a sandbox and receive an API key exactly once
|
||||
When an agent registers a sandbox named "peter-demo" with email "peter@openclaw.io"
|
||||
Then the response code is 201
|
||||
And the response contains a sandbox id
|
||||
And the response contains an API key with prefix "apix_sb_"
|
||||
And the response contains tier "FREE"
|
||||
And the response contains a non-null expiresAt
|
||||
And the response contains _links.self ending with "/sandbox/peter-demo"
|
||||
And the response contains _links.services
|
||||
|
||||
Scenario: Registration fails when name contains uppercase letters
|
||||
When an agent registers a sandbox named "Peter-Demo" with email "peter@openclaw.io"
|
||||
Then the response code is 400
|
||||
|
||||
Scenario: Registration fails when name is too short
|
||||
When an agent registers a sandbox named "ab" with email "peter@openclaw.io"
|
||||
Then the response code is 400
|
||||
|
||||
Scenario: Registration fails when name starts with a hyphen
|
||||
When an agent registers a sandbox named "-demo" with email "peter@openclaw.io"
|
||||
Then the response code is 400
|
||||
|
||||
Scenario: Registration fails when email is invalid
|
||||
When an agent registers a sandbox named "valid-name" with email "not-an-email"
|
||||
Then the response code is 400
|
||||
|
||||
Scenario: Duplicate name is rejected with 409
|
||||
Given a sandbox named "duplicate-test" exists
|
||||
When an agent registers a sandbox named "duplicate-test" with email "other@example.com"
|
||||
Then the response code is 409
|
||||
+45
@@ -0,0 +1,45 @@
|
||||
Feature: Sandbox service isolation from production
|
||||
|
||||
Background:
|
||||
Given a sandbox named "isolation-test" exists
|
||||
And a production service "ProdService" with endpoint "https://prod.example.com" is registered
|
||||
|
||||
Scenario: Service registered in sandbox does not appear in production list
|
||||
Given a sandbox service with endpoint "https://sandbox.example.com" and capability "test.cap" is registered in "isolation-test"
|
||||
When GET /services is called without authentication
|
||||
Then the response code is 200
|
||||
And "https://sandbox.example.com" is not in the endpoint list
|
||||
|
||||
Scenario: Production service does not appear in sandbox service list
|
||||
Given a sandbox service with endpoint "https://sandbox.example.com" and capability "test.cap" is registered in "isolation-test"
|
||||
When the sandbox service list for "isolation-test" is requested
|
||||
Then the response code is 200
|
||||
And "https://prod.example.com" is not in the endpoint list
|
||||
|
||||
Scenario: Service registration in sandbox requires the sandbox API key
|
||||
When a service is registered in sandbox "isolation-test" without an API key
|
||||
Then the response code is 401
|
||||
|
||||
Scenario: Service registration in sandbox with wrong key returns 401
|
||||
When a service is registered in sandbox "isolation-test" with API key "apix_sb_wrongkey"
|
||||
Then the response code is 401
|
||||
|
||||
Scenario: Sandbox search is isolated from production results
|
||||
Given a sandbox service with endpoint "https://sb-search.example.com" and capability "search.cap" is registered in "isolation-test"
|
||||
When sandbox "isolation-test" services are searched by capability "search.cap"
|
||||
Then the response code is 200
|
||||
And "https://sb-search.example.com" is in the endpoint list
|
||||
|
||||
Scenario: Sandbox search does not return production services
|
||||
Given a production service "ProdSearchService" with endpoint "https://prod-search.example.com" is registered
|
||||
When sandbox "isolation-test" services are searched by capability "device.telemetry"
|
||||
Then the response code is 200
|
||||
And "https://prod-search.example.com" is not in the endpoint list
|
||||
|
||||
Scenario: Sandbox services can be registered with extension properties and queried by them
|
||||
Given a sandbox service with endpoint "https://ext-eu.example.com" capability "data.processing" and extension "region:eu" is registered in "isolation-test"
|
||||
And a sandbox service with endpoint "https://ext-us.example.com" capability "data.processing" and extension "region:us" is registered in "isolation-test"
|
||||
When sandbox "isolation-test" services are searched by capability "data.processing" and property "region:eu"
|
||||
Then the response code is 200
|
||||
And "https://ext-eu.example.com" is in the endpoint list
|
||||
And "https://ext-us.example.com" is not in the endpoint list
|
||||
+43
@@ -0,0 +1,43 @@
|
||||
Feature: Sandbox telemetry
|
||||
|
||||
Background:
|
||||
Given a sandbox named "telemetry-demo" exists
|
||||
|
||||
Scenario: Telemetry starts empty before any activity
|
||||
When the telemetry for "telemetry-demo" is requested with the sandbox API key
|
||||
Then the response code is 200
|
||||
And the usage map is empty or contains only zero counts
|
||||
|
||||
Scenario: Viewing the sandbox root increments the SANDBOX_VIEWED counter
|
||||
Given the sandbox root for "telemetry-demo" has been viewed once
|
||||
When the telemetry for "telemetry-demo" is requested with the sandbox API key
|
||||
Then the response code is 200
|
||||
And the usage counter "SANDBOX_VIEWED" is at least 1
|
||||
|
||||
Scenario: Registering a service increments the SERVICE_REGISTERED counter
|
||||
Given a sandbox service with endpoint "https://tel.example.com" and capability "tel.cap" is registered in "telemetry-demo"
|
||||
When the telemetry for "telemetry-demo" is requested with the sandbox API key
|
||||
Then the response code is 200
|
||||
And the usage counter "SERVICE_REGISTERED" is at least 1
|
||||
|
||||
Scenario: Listing services increments the SERVICE_LISTED counter
|
||||
Given the sandbox service list for "telemetry-demo" has been requested once
|
||||
When the telemetry for "telemetry-demo" is requested with the sandbox API key
|
||||
Then the response code is 200
|
||||
And the usage counter "SERVICE_LISTED" is at least 1
|
||||
|
||||
Scenario: Telemetry requires the sandbox API key
|
||||
When the telemetry for "telemetry-demo" is requested without an API key
|
||||
Then the response code is 401
|
||||
|
||||
Scenario: Telemetry with wrong key returns 401
|
||||
When the telemetry for "telemetry-demo" is requested with API key "apix_sb_wrongkey"
|
||||
Then the response code is 401
|
||||
|
||||
Scenario: Telemetry response includes tier metadata
|
||||
When the telemetry for "telemetry-demo" is requested with the sandbox API key
|
||||
Then the response code is 200
|
||||
And the telemetry contains tier "FREE"
|
||||
And the telemetry contains ratePerMinute 60
|
||||
And the telemetry contains maxServices 10
|
||||
And the telemetry contains maxOrgs 3
|
||||
+8
@@ -0,0 +1,8 @@
|
||||
Feature: Sandbox tier caps
|
||||
|
||||
Scenario: FREE sandbox allows up to 10 services
|
||||
Given a sandbox named "cap-test" exists
|
||||
And 10 services have been registered in sandbox "cap-test"
|
||||
When a service is registered in sandbox "cap-test" with the sandbox API key
|
||||
Then the response code is 429
|
||||
And the sandbox error contains "Service limit reached"
|
||||
Reference in New Issue
Block a user