chore: add missing source modules to version control
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:
Carsten Rehfeld
2026-05-14 15:49:03 +02:00
parent a9b3354bde
commit 46f32c2df2
87 changed files with 6657 additions and 34 deletions
@@ -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"
@@ -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"
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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"