feat: add release and plugin lifecycle scenarios
This commit is contained in:
parent
ef0250519d
commit
f69ccf1803
12
README.md
12
README.md
@ -14,7 +14,7 @@ but Kova reports on OpenClaw behavior.
|
||||
- bundled plugin/runtime dependency behavior
|
||||
- plugin lifecycle paths
|
||||
- model/provider discovery paths
|
||||
- dashboard/TUI/API responsiveness
|
||||
- dashboard, TUI, and API responsiveness
|
||||
- memory, CPU, latency, and startup regressions
|
||||
|
||||
Kova is not a unit test runner. It should exercise OpenClaw the way users and
|
||||
@ -66,6 +66,8 @@ Real execution is explicit:
|
||||
node bin/kova.mjs run --target npm:2026.4.27 --scenario fresh-install --execute
|
||||
node bin/kova.mjs run --target npm:2026.4.27 --scenario fresh-install --state stale-runtime-deps --execute
|
||||
node bin/kova.mjs run --target npm:2026.4.27 --scenario gateway-performance --execute --node-profile
|
||||
node bin/kova.mjs run --target local-build:/path/to/openclaw --scenario release-runtime-startup --execute
|
||||
node bin/kova.mjs run --target npm:2026.4.27 --scenario plugin-external-install --execute
|
||||
node bin/kova.mjs matrix run --profile smoke --target npm:2026.4.27 --execute
|
||||
node bin/kova.mjs matrix run --profile release --target npm:2026.4.27 --include tag:plugins --exclude state:broken-plugin-deps --parallel 2 --execute
|
||||
```
|
||||
@ -73,9 +75,10 @@ node bin/kova.mjs matrix run --profile release --target npm:2026.4.27 --include
|
||||
Matrix filters accept `scenario:<id>`, `state:<id>`, `tag:<tag>`, or a bare
|
||||
scenario/state/tag value. Matrix runs bundle their report automatically.
|
||||
|
||||
Gateway readiness is deadline-based. Kova polls TCP listening and `/health`
|
||||
until the scenario readiness threshold expires, records every failed attempt in
|
||||
JSON, and reports time-to-listening plus time-to-health-ready.
|
||||
Gateway readiness is classified. Kova polls TCP listening and `/health` until a
|
||||
hard deadline, while separately enforcing the scenario readiness threshold.
|
||||
Reports distinguish hard failures, unhealthy gateways, slow startup, and ready
|
||||
gateways, with time-to-listening and time-to-health-ready evidence.
|
||||
|
||||
Kova destroys temporary envs by default after execution. Keep an env for
|
||||
debugging only when needed:
|
||||
@ -164,6 +167,7 @@ The repo has the first production skeleton:
|
||||
- gateway service snapshots
|
||||
- gateway health snapshots
|
||||
- gateway health latency samples
|
||||
- readiness classification for hard failure, unhealthy, slow startup, and ready
|
||||
- gateway log diagnostic counts
|
||||
- gateway PID/RSS/CPU metrics on executed scenarios
|
||||
- continuous resource sampling during commands
|
||||
|
||||
@ -9,6 +9,7 @@
|
||||
{ "scenario": "fresh-install", "state": "old-config-keys" },
|
||||
{ "scenario": "fresh-install", "state": "large-memory-session" },
|
||||
{ "scenario": "fresh-install", "state": "corrupted-config" },
|
||||
{ "scenario": "release-runtime-startup", "state": "fresh" },
|
||||
{ "scenario": "upgrade-existing-user", "state": "old-release-user", "timeoutMs": 180000 },
|
||||
{ "scenario": "upgrade-existing-user", "state": "failed-upgrade", "timeoutMs": 180000 },
|
||||
{ "scenario": "bundled-runtime-deps", "state": "missing-plugin-index" },
|
||||
@ -16,10 +17,17 @@
|
||||
{ "scenario": "plugin-lifecycle", "state": "plugin-index" },
|
||||
{ "scenario": "plugin-lifecycle", "state": "external-plugin" },
|
||||
{ "scenario": "plugin-lifecycle", "state": "broken-plugin-deps" },
|
||||
{ "scenario": "plugin-external-install", "state": "fresh" },
|
||||
{ "scenario": "plugin-remove", "state": "fresh" },
|
||||
{ "scenario": "plugin-update", "state": "fresh" },
|
||||
{ "scenario": "plugin-bad-manifest", "state": "fresh" },
|
||||
{ "scenario": "plugin-missing-runtime-deps", "state": "fresh" },
|
||||
{ "scenario": "bundled-plugin-startup", "state": "fresh" },
|
||||
{ "scenario": "provider-models", "state": "model-auth-configured" },
|
||||
{ "scenario": "provider-models", "state": "model-auth-missing" },
|
||||
{ "scenario": "agent-message-latency", "state": "mock-openai-provider", "timeoutMs": 180000 },
|
||||
{ "scenario": "dashboard-tui", "state": "fresh" },
|
||||
{ "scenario": "dashboard-readiness", "state": "fresh" },
|
||||
{ "scenario": "tui-responsiveness", "state": "fresh" },
|
||||
{ "scenario": "gateway-performance", "state": "many-bundled-plugins" },
|
||||
{ "scenario": "gateway-performance", "state": "gateway-already-running" },
|
||||
{ "scenario": "gateway-performance", "state": "stale-service-state" },
|
||||
|
||||
@ -3,6 +3,10 @@
|
||||
"title": "Release Matrix",
|
||||
"objective": "Broad OpenClaw release confidence across install, upgrade, bundled plugins, model/provider, UI, failure, soak, and platform smoke scenarios.",
|
||||
"entries": [
|
||||
{
|
||||
"scenario": "release-runtime-startup",
|
||||
"state": "fresh"
|
||||
},
|
||||
{
|
||||
"scenario": "fresh-install",
|
||||
"state": "fresh"
|
||||
@ -24,6 +28,30 @@
|
||||
"scenario": "plugin-lifecycle",
|
||||
"state": "plugin-index"
|
||||
},
|
||||
{
|
||||
"scenario": "plugin-external-install",
|
||||
"state": "fresh"
|
||||
},
|
||||
{
|
||||
"scenario": "plugin-remove",
|
||||
"state": "fresh"
|
||||
},
|
||||
{
|
||||
"scenario": "plugin-update",
|
||||
"state": "fresh"
|
||||
},
|
||||
{
|
||||
"scenario": "plugin-bad-manifest",
|
||||
"state": "fresh"
|
||||
},
|
||||
{
|
||||
"scenario": "plugin-missing-runtime-deps",
|
||||
"state": "fresh"
|
||||
},
|
||||
{
|
||||
"scenario": "bundled-plugin-startup",
|
||||
"state": "fresh"
|
||||
},
|
||||
{
|
||||
"scenario": "plugin-lifecycle",
|
||||
"state": "external-plugin"
|
||||
@ -38,7 +66,11 @@
|
||||
"timeoutMs": 180000
|
||||
},
|
||||
{
|
||||
"scenario": "dashboard-tui",
|
||||
"scenario": "dashboard-readiness",
|
||||
"state": "fresh"
|
||||
},
|
||||
{
|
||||
"scenario": "tui-responsiveness",
|
||||
"state": "fresh"
|
||||
},
|
||||
{
|
||||
|
||||
@ -3,6 +3,10 @@
|
||||
"title": "Smoke Matrix",
|
||||
"objective": "Fast OpenClaw coverage across fresh install, plugin dependency, plugin lifecycle, and gateway performance states.",
|
||||
"entries": [
|
||||
{
|
||||
"scenario": "release-runtime-startup",
|
||||
"state": "fresh"
|
||||
},
|
||||
{
|
||||
"scenario": "fresh-install",
|
||||
"state": "fresh"
|
||||
@ -15,6 +19,10 @@
|
||||
"scenario": "plugin-lifecycle",
|
||||
"state": "stale-runtime-deps"
|
||||
},
|
||||
{
|
||||
"scenario": "bundled-plugin-startup",
|
||||
"state": "fresh"
|
||||
},
|
||||
{
|
||||
"scenario": "gateway-performance",
|
||||
"state": "many-bundled-plugins"
|
||||
|
||||
38
scenarios/bundled-plugin-startup.json
Normal file
38
scenarios/bundled-plugin-startup.json
Normal file
@ -0,0 +1,38 @@
|
||||
{
|
||||
"id": "bundled-plugin-startup",
|
||||
"title": "Bundled Plugin Startup",
|
||||
"objective": "Validate that OpenClaw's bundled plugins load during gateway startup without missing package/module errors or degraded plugin services.",
|
||||
"tags": ["plugins", "bundled", "runtime-deps", "startup"],
|
||||
"timeoutMs": 180000,
|
||||
"thresholds": {
|
||||
"gatewayReadyMs": 30000,
|
||||
"gatewayReadyHardTimeoutMs": 120000,
|
||||
"pluginsListMs": 10000,
|
||||
"missingDependencyErrors": 0,
|
||||
"pluginLoadFailures": 0,
|
||||
"runtimeDepsStagingMs": 30000
|
||||
},
|
||||
"phases": [
|
||||
{
|
||||
"id": "startup",
|
||||
"title": "Start Bundled Plugin Gateway",
|
||||
"intent": "Start OpenClaw and let bundled plugin bootstrap run in the same shape users get from the target runtime.",
|
||||
"commands": ["ocm start {env} {startSelector} --json"],
|
||||
"evidence": ["bundled plugin count", "readiness classification", "dependency staging"]
|
||||
},
|
||||
{
|
||||
"id": "inspect",
|
||||
"title": "Inspect Bundled Plugins",
|
||||
"intent": "List and inspect plugin registry state after startup.",
|
||||
"commands": ["ocm @{env} -- plugins list", "ocm @{env} -- plugins registry --refresh --json", "ocm logs {env} --tail 400 --raw"],
|
||||
"evidence": ["plugin list", "registry refresh", "missing package/module errors", "plugin service failures"]
|
||||
},
|
||||
{
|
||||
"id": "restart",
|
||||
"title": "Warm Restart Bundled Plugins",
|
||||
"intent": "Restart after dependency staging should be warm and verify bundled plugin services remain healthy.",
|
||||
"commands": ["ocm service restart {env}", "ocm service status {env} --json", "ocm logs {env} --tail 400 --raw"],
|
||||
"evidence": ["warm readiness", "bundled plugin reload", "runtime dependency reuse"]
|
||||
}
|
||||
]
|
||||
}
|
||||
38
scenarios/dashboard-readiness.json
Normal file
38
scenarios/dashboard-readiness.json
Normal file
@ -0,0 +1,38 @@
|
||||
{
|
||||
"id": "dashboard-readiness",
|
||||
"title": "Dashboard Readiness",
|
||||
"objective": "Verify OpenClaw can produce a usable dashboard URL and keep the gateway responsive after dashboard entry.",
|
||||
"tags": ["dashboard", "control-ui", "gateway", "websocket"],
|
||||
"timeoutMs": 120000,
|
||||
"thresholds": {
|
||||
"gatewayReadyMs": 30000,
|
||||
"gatewayReadyHardTimeoutMs": 120000,
|
||||
"statusMs": 10000,
|
||||
"dashboardConnectMs": 10000,
|
||||
"missingDependencyErrors": 0,
|
||||
"pluginLoadFailures": 0
|
||||
},
|
||||
"phases": [
|
||||
{
|
||||
"id": "gateway",
|
||||
"title": "Gateway Start",
|
||||
"intent": "Start the gateway and classify readiness before dashboard checks.",
|
||||
"commands": ["ocm start {env} {startSelector} --json", "ocm @{env} -- status"],
|
||||
"evidence": ["gateway status", "gateway port", "readiness classification"]
|
||||
},
|
||||
{
|
||||
"id": "dashboard",
|
||||
"title": "Dashboard Link",
|
||||
"intent": "Use OpenClaw's dashboard command without opening a browser and require it to return promptly.",
|
||||
"commands": ["ocm @{env} -- dashboard --no-open"],
|
||||
"evidence": ["dashboard URL", "token handling", "command latency"]
|
||||
},
|
||||
{
|
||||
"id": "post-dashboard-health",
|
||||
"title": "Post-Dashboard Health",
|
||||
"intent": "Verify dashboard entry did not make the gateway or websocket layer unstable.",
|
||||
"commands": ["ocm @{env} -- status", "ocm logs {env} --tail 300 --raw"],
|
||||
"evidence": ["status after dashboard command", "websocket disconnect logs", "gateway errors"]
|
||||
}
|
||||
]
|
||||
}
|
||||
@ -1,27 +0,0 @@
|
||||
{
|
||||
"id": "dashboard-tui",
|
||||
"title": "Dashboard And TUI Responsiveness",
|
||||
"objective": "Verify user-facing dashboard and TUI entry paths connect to the gateway without making the gateway unresponsive.",
|
||||
"tags": ["dashboard", "tui", "websocket", "gateway"],
|
||||
"thresholds": {
|
||||
"statusMs": 10000,
|
||||
"tuiSmokeMs": 15000,
|
||||
"dashboardConnectMs": 10000
|
||||
},
|
||||
"phases": [
|
||||
{
|
||||
"id": "gateway",
|
||||
"title": "Gateway Start",
|
||||
"intent": "Start the gateway and confirm status before UI checks.",
|
||||
"commands": ["ocm start {env} {startSelector} --json", "ocm @{env} -- status"],
|
||||
"evidence": ["gateway status", "dashboard URL", "gateway port"]
|
||||
},
|
||||
{
|
||||
"id": "ui-entry",
|
||||
"title": "UI Entry Smoke",
|
||||
"intent": "Exercise non-interactive UI entry points where available and capture connection failures.",
|
||||
"commands": ["ocm logs {env} --tail 250 --raw"],
|
||||
"evidence": ["websocket disconnect logs", "TUI startup logs", "gateway responsiveness after UI entry"]
|
||||
}
|
||||
]
|
||||
}
|
||||
36
scenarios/plugin-bad-manifest.json
Normal file
36
scenarios/plugin-bad-manifest.json
Normal file
@ -0,0 +1,36 @@
|
||||
{
|
||||
"id": "plugin-bad-manifest",
|
||||
"title": "Bad Plugin Manifest",
|
||||
"objective": "Attempt to install an invalid plugin fixture, require OpenClaw to reject it cleanly, and verify the gateway remains healthy.",
|
||||
"tags": ["plugins", "manifest", "failure", "lifecycle"],
|
||||
"timeoutMs": 180000,
|
||||
"thresholds": {
|
||||
"gatewayReadyMs": 30000,
|
||||
"gatewayReadyHardTimeoutMs": 120000,
|
||||
"statusMs": 10000,
|
||||
"pluginLoadFailures": 0
|
||||
},
|
||||
"phases": [
|
||||
{
|
||||
"id": "provision",
|
||||
"title": "Provision Plugin Env",
|
||||
"intent": "Start a clean gateway before attempting the invalid plugin install.",
|
||||
"commands": ["ocm start {env} {startSelector} --json", "ocm @{env} -- status"],
|
||||
"evidence": ["baseline gateway status", "readiness classification"]
|
||||
},
|
||||
{
|
||||
"id": "reject-invalid-plugin",
|
||||
"title": "Reject Invalid Plugin",
|
||||
"intent": "Run the real install command and require it to fail without corrupting plugin state.",
|
||||
"commands": ["node {kovaRoot}/support/expect-command-fails.mjs -- ocm @{env} -- plugins install {kovaRoot}/support/plugins/kova-bad-manifest"],
|
||||
"evidence": ["install command rejected", "validation error", "no install record committed"]
|
||||
},
|
||||
{
|
||||
"id": "post-failure-health",
|
||||
"title": "Post-Failure Health",
|
||||
"intent": "Verify OpenClaw still answers status and plugin list after rejecting the bad manifest.",
|
||||
"commands": ["ocm @{env} -- status", "ocm @{env} -- plugins list", "ocm logs {env} --tail 300 --raw"],
|
||||
"evidence": ["gateway status", "plugin list", "logs after invalid install"]
|
||||
}
|
||||
]
|
||||
}
|
||||
37
scenarios/plugin-external-install.json
Normal file
37
scenarios/plugin-external-install.json
Normal file
@ -0,0 +1,37 @@
|
||||
{
|
||||
"id": "plugin-external-install",
|
||||
"title": "External Plugin Install",
|
||||
"objective": "Install a real local external plugin fixture, refresh OpenClaw plugin state, and verify the gateway remains healthy without dependency or registry errors.",
|
||||
"tags": ["plugins", "external-plugin", "install", "lifecycle"],
|
||||
"timeoutMs": 180000,
|
||||
"thresholds": {
|
||||
"gatewayReadyMs": 30000,
|
||||
"gatewayReadyHardTimeoutMs": 120000,
|
||||
"pluginsListMs": 10000,
|
||||
"missingDependencyErrors": 0,
|
||||
"pluginLoadFailures": 0
|
||||
},
|
||||
"phases": [
|
||||
{
|
||||
"id": "provision",
|
||||
"title": "Provision Plugin Env",
|
||||
"intent": "Start a clean OpenClaw env before installing an external plugin.",
|
||||
"commands": ["ocm start {env} {startSelector} --json", "ocm @{env} -- plugins list"],
|
||||
"evidence": ["baseline plugin list", "gateway readiness"]
|
||||
},
|
||||
{
|
||||
"id": "install",
|
||||
"title": "Install Local Plugin",
|
||||
"intent": "Use OpenClaw's real plugin install command against a local external plugin fixture.",
|
||||
"commands": ["ocm @{env} -- plugins install {kovaRoot}/support/plugins/kova-basic --force", "ocm @{env} -- plugins list", "ocm @{env} -- plugins registry --refresh --json"],
|
||||
"evidence": ["install output", "plugin index update", "registry refresh", "plugin appears in list"]
|
||||
},
|
||||
{
|
||||
"id": "restart",
|
||||
"title": "Restart After Install",
|
||||
"intent": "Restart the gateway so OpenClaw loads the installed external plugin from persisted state.",
|
||||
"commands": ["ocm service restart {env}", "ocm service status {env} --json", "ocm logs {env} --tail 300 --raw"],
|
||||
"evidence": ["restart readiness", "plugin load logs", "missing dependency scan"]
|
||||
}
|
||||
]
|
||||
}
|
||||
37
scenarios/plugin-missing-runtime-deps.json
Normal file
37
scenarios/plugin-missing-runtime-deps.json
Normal file
@ -0,0 +1,37 @@
|
||||
{
|
||||
"id": "plugin-missing-runtime-deps",
|
||||
"title": "Plugin Missing Runtime Deps",
|
||||
"objective": "Install a plugin that imports an undeclared runtime dependency and verify OpenClaw reports the dependency failure without taking down the gateway.",
|
||||
"tags": ["plugins", "runtime-deps", "failure", "lifecycle"],
|
||||
"timeoutMs": 180000,
|
||||
"thresholds": {
|
||||
"gatewayReadyMs": 30000,
|
||||
"gatewayReadyHardTimeoutMs": 120000,
|
||||
"statusMs": 10000,
|
||||
"missingDependencyErrors": 1,
|
||||
"pluginLoadFailures": 1
|
||||
},
|
||||
"phases": [
|
||||
{
|
||||
"id": "install",
|
||||
"title": "Install Missing-Dependency Plugin",
|
||||
"intent": "Install a fixture whose runtime entry imports a package it does not declare.",
|
||||
"commands": ["ocm start {env} {startSelector} --json", "ocm @{env} -- plugins install {kovaRoot}/support/plugins/kova-missing-runtime-dep --force", "ocm @{env} -- plugins list"],
|
||||
"evidence": ["install result", "plugin entry registered", "gateway readiness before load"]
|
||||
},
|
||||
{
|
||||
"id": "restart",
|
||||
"title": "Restart And Capture Failure",
|
||||
"intent": "Restart so OpenClaw attempts to load the plugin and emits missing dependency diagnostics.",
|
||||
"commands": ["ocm service restart {env}", "ocm service status {env} --json", "ocm logs {env} --tail 400 --raw"],
|
||||
"evidence": ["missing dependency diagnostic", "plugin load failure", "gateway remains supervised"]
|
||||
},
|
||||
{
|
||||
"id": "survival",
|
||||
"title": "Gateway Survival Check",
|
||||
"intent": "Confirm one bad plugin does not make the user-facing gateway unusable.",
|
||||
"commands": ["ocm @{env} -- status", "ocm @{env} -- plugins list"],
|
||||
"evidence": ["status after plugin failure", "plugin list after failure"]
|
||||
}
|
||||
]
|
||||
}
|
||||
37
scenarios/plugin-remove.json
Normal file
37
scenarios/plugin-remove.json
Normal file
@ -0,0 +1,37 @@
|
||||
{
|
||||
"id": "plugin-remove",
|
||||
"title": "Plugin Remove",
|
||||
"objective": "Install and uninstall a local external plugin, then verify OpenClaw removes plugin install records cleanly and survives restart.",
|
||||
"tags": ["plugins", "external-plugin", "remove", "uninstall", "lifecycle"],
|
||||
"timeoutMs": 180000,
|
||||
"thresholds": {
|
||||
"gatewayReadyMs": 30000,
|
||||
"gatewayReadyHardTimeoutMs": 120000,
|
||||
"pluginsListMs": 10000,
|
||||
"missingDependencyErrors": 0,
|
||||
"pluginLoadFailures": 0
|
||||
},
|
||||
"phases": [
|
||||
{
|
||||
"id": "install",
|
||||
"title": "Install Plugin To Remove",
|
||||
"intent": "Install the local fixture so uninstall exercises a real managed plugin record.",
|
||||
"commands": ["ocm start {env} {startSelector} --json", "ocm @{env} -- plugins install {kovaRoot}/support/plugins/kova-basic --force", "ocm @{env} -- plugins list"],
|
||||
"evidence": ["install record", "plugin appears before uninstall"]
|
||||
},
|
||||
{
|
||||
"id": "remove",
|
||||
"title": "Uninstall Plugin",
|
||||
"intent": "Use OpenClaw's real uninstall path and force promptless removal for automation.",
|
||||
"commands": ["ocm @{env} -- plugins uninstall kova-basic --force", "ocm @{env} -- plugins list", "ocm @{env} -- plugins registry --refresh --json"],
|
||||
"evidence": ["uninstall output", "install index cleanup", "registry after removal"]
|
||||
},
|
||||
{
|
||||
"id": "restart",
|
||||
"title": "Restart After Removal",
|
||||
"intent": "Restart after uninstall to verify removed plugin state does not break gateway startup.",
|
||||
"commands": ["ocm service restart {env}", "ocm service status {env} --json", "ocm logs {env} --tail 300 --raw"],
|
||||
"evidence": ["restart readiness", "removed plugin not loaded", "missing dependency scan"]
|
||||
}
|
||||
]
|
||||
}
|
||||
38
scenarios/plugin-update.json
Normal file
38
scenarios/plugin-update.json
Normal file
@ -0,0 +1,38 @@
|
||||
{
|
||||
"id": "plugin-update",
|
||||
"title": "Plugin Update",
|
||||
"objective": "Install a managed external plugin and run OpenClaw plugin update paths to verify tracked plugin metadata and dry-run update diagnostics.",
|
||||
"tags": ["plugins", "external-plugin", "update", "lifecycle"],
|
||||
"timeoutMs": 180000,
|
||||
"thresholds": {
|
||||
"gatewayReadyMs": 30000,
|
||||
"gatewayReadyHardTimeoutMs": 120000,
|
||||
"pluginsListMs": 10000,
|
||||
"pluginUpdateDryRunMs": 20000,
|
||||
"missingDependencyErrors": 0,
|
||||
"pluginLoadFailures": 0
|
||||
},
|
||||
"phases": [
|
||||
{
|
||||
"id": "install",
|
||||
"title": "Install Managed Plugin",
|
||||
"intent": "Create a tracked plugin install record for update checks.",
|
||||
"commands": ["ocm start {env} {startSelector} --json", "ocm @{env} -- plugins install {kovaRoot}/support/plugins/kova-basic --force", "ocm @{env} -- plugins list"],
|
||||
"evidence": ["plugin install record", "plugin appears in list"]
|
||||
},
|
||||
{
|
||||
"id": "update",
|
||||
"title": "Update Plugin Metadata",
|
||||
"intent": "Run individual and all-plugin update dry-runs so OpenClaw reports update plans without mutating the fixture.",
|
||||
"commands": ["ocm @{env} -- plugins update kova-basic --dry-run", "ocm @{env} -- plugins update --all --dry-run", "ocm @{env} -- plugins registry --refresh --json"],
|
||||
"evidence": ["plugin update dry-run output", "tracked plugin metadata", "registry refresh"]
|
||||
},
|
||||
{
|
||||
"id": "post-update-health",
|
||||
"title": "Post-Update Health",
|
||||
"intent": "Verify the gateway is still usable after update planning.",
|
||||
"commands": ["ocm @{env} -- status", "ocm logs {env} --tail 300 --raw"],
|
||||
"evidence": ["status after update", "plugin lifecycle logs", "dependency errors"]
|
||||
}
|
||||
]
|
||||
}
|
||||
40
scenarios/release-runtime-startup.json
Normal file
40
scenarios/release-runtime-startup.json
Normal file
@ -0,0 +1,40 @@
|
||||
{
|
||||
"id": "release-runtime-startup",
|
||||
"title": "Release Runtime Startup",
|
||||
"objective": "Start a release-shaped OpenClaw runtime, measure cold readiness and bundled runtime dependency staging, and fail on degraded plugin startup.",
|
||||
"tags": ["release-runtime", "local-build", "gateway", "plugins", "runtime-deps", "startup"],
|
||||
"timeoutMs": 180000,
|
||||
"thresholds": {
|
||||
"gatewayReadyMs": 30000,
|
||||
"gatewayReadyHardTimeoutMs": 120000,
|
||||
"statusMs": 10000,
|
||||
"pluginsListMs": 10000,
|
||||
"peakRssMb": 900,
|
||||
"missingDependencyErrors": 0,
|
||||
"pluginLoadFailures": 0,
|
||||
"runtimeDepsStagingMs": 30000
|
||||
},
|
||||
"phases": [
|
||||
{
|
||||
"id": "provision",
|
||||
"title": "Provision Release Runtime Env",
|
||||
"intent": "Start OpenClaw from the selected release-shaped runtime and keep probing beyond the expected threshold so slow startups are classified.",
|
||||
"commands": ["ocm start {env} {startSelector} --json"],
|
||||
"evidence": ["runtime binding", "gateway port", "time to listening", "time to health ready", "readiness classification"]
|
||||
},
|
||||
{
|
||||
"id": "post-start",
|
||||
"title": "Post-Start Runtime Checks",
|
||||
"intent": "Verify the gateway is usable after cold startup and capture plugin registry behavior.",
|
||||
"commands": ["ocm service status {env} --json", "ocm @{env} -- status", "ocm @{env} -- plugins list"],
|
||||
"evidence": ["gateway state", "status command latency", "plugin list", "plugin startup health"]
|
||||
},
|
||||
{
|
||||
"id": "startup-logs",
|
||||
"title": "Startup Logs And Runtime Deps",
|
||||
"intent": "Capture dependency staging, missing package/module errors, plugin service failures, and startup timing logs.",
|
||||
"commands": ["ocm logs {env} --tail 400 --raw"],
|
||||
"evidence": ["runtime dependency staging duration", "missing dependency errors", "plugin service failures", "startup phase logs"]
|
||||
}
|
||||
]
|
||||
}
|
||||
38
scenarios/tui-responsiveness.json
Normal file
38
scenarios/tui-responsiveness.json
Normal file
@ -0,0 +1,38 @@
|
||||
{
|
||||
"id": "tui-responsiveness",
|
||||
"title": "TUI Responsiveness",
|
||||
"objective": "Verify OpenClaw's terminal UI can attach to a running gateway, render a connected screen promptly, and shut down cleanly.",
|
||||
"tags": ["tui", "terminal", "gateway", "input"],
|
||||
"timeoutMs": 120000,
|
||||
"thresholds": {
|
||||
"gatewayReadyMs": 30000,
|
||||
"gatewayReadyHardTimeoutMs": 120000,
|
||||
"statusMs": 10000,
|
||||
"tuiSmokeMs": 15000,
|
||||
"missingDependencyErrors": 0,
|
||||
"pluginLoadFailures": 0
|
||||
},
|
||||
"phases": [
|
||||
{
|
||||
"id": "gateway",
|
||||
"title": "Gateway Start",
|
||||
"intent": "Start the gateway and confirm it is healthy before opening TUI.",
|
||||
"commands": ["ocm start {env} {startSelector} --json", "ocm @{env} -- status"],
|
||||
"evidence": ["gateway status", "readiness classification"]
|
||||
},
|
||||
{
|
||||
"id": "tui-smoke",
|
||||
"title": "TUI Attach Smoke",
|
||||
"intent": "Spawn the real TUI, wait for a recognizable connected screen, then interrupt it cleanly.",
|
||||
"commands": ["node {kovaRoot}/support/tui-smoke.mjs {env} 15000"],
|
||||
"evidence": ["TUI render time", "connected screen", "clean interrupt"]
|
||||
},
|
||||
{
|
||||
"id": "post-tui-health",
|
||||
"title": "Post-TUI Health",
|
||||
"intent": "Verify the gateway remains responsive after a terminal UI session starts and exits.",
|
||||
"commands": ["ocm @{env} -- status", "ocm logs {env} --tail 300 --raw"],
|
||||
"evidence": ["status after TUI", "TUI disconnect logs", "gateway errors"]
|
||||
}
|
||||
]
|
||||
}
|
||||
@ -4,6 +4,7 @@ import { quoteShell } from "./commands.mjs";
|
||||
import { collectEnvMetrics } from "./metrics.mjs";
|
||||
import { evaluateRecord } from "./evaluator.mjs";
|
||||
import { artifactsDir } from "./paths.mjs";
|
||||
import { repoRoot } from "./paths.mjs";
|
||||
import { mkdir } from "node:fs/promises";
|
||||
import { join } from "node:path";
|
||||
|
||||
@ -456,6 +457,7 @@ function commandValues(context, envName, artifactDir = "") {
|
||||
from: context.from ?? "",
|
||||
sourceEnv: quoteShell(context.sourceEnv ?? ""),
|
||||
artifactDir,
|
||||
kovaRoot: quoteShell(repoRoot),
|
||||
startSelector: context.targetPlan.startSelector,
|
||||
upgradeSelector: context.targetPlan.upgradeSelector,
|
||||
fromUpgradeSelector: context.fromPlan?.upgradeSelector ?? ""
|
||||
|
||||
@ -158,6 +158,7 @@ export function materializeCommands(commands, values) {
|
||||
.replaceAll("{from}", values.from)
|
||||
.replaceAll("{sourceEnv}", values.sourceEnv)
|
||||
.replaceAll("{artifactDir}", values.artifactDir ?? "")
|
||||
.replaceAll("{kovaRoot}", values.kovaRoot ?? "")
|
||||
.replaceAll("{startSelector}", values.startSelector)
|
||||
.replaceAll("{upgradeSelector}", values.upgradeSelector)
|
||||
.replaceAll("{fromUpgradeSelector}", values.fromUpgradeSelector)
|
||||
|
||||
@ -104,7 +104,7 @@ export async function runSelfCheck(flags = {}) {
|
||||
if (!data.bundlePath.startsWith(tmp)) {
|
||||
throw new Error(`matrix bundle path should use report dir: ${data.bundlePath}`);
|
||||
}
|
||||
assertEqual(data.summary?.statuses?.["DRY-RUN"], 3, "filtered matrix dry-run count");
|
||||
assertEqual(data.summary?.statuses?.["DRY-RUN"], 5, "filtered matrix dry-run count");
|
||||
}
|
||||
));
|
||||
|
||||
|
||||
33
support/expect-command-fails.mjs
Normal file
33
support/expect-command-fails.mjs
Normal file
@ -0,0 +1,33 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
import { spawn } from "node:child_process";
|
||||
|
||||
const separator = process.argv.indexOf("--");
|
||||
const command = separator >= 0 ? process.argv.slice(separator + 1) : process.argv.slice(2);
|
||||
|
||||
if (command.length === 0) {
|
||||
console.error("usage: expect-command-fails.mjs -- <command> [args...]");
|
||||
process.exit(2);
|
||||
}
|
||||
|
||||
const child = spawn(command[0], command.slice(1), {
|
||||
stdio: "inherit",
|
||||
shell: false
|
||||
});
|
||||
|
||||
child.on("exit", (code, signal) => {
|
||||
if (signal) {
|
||||
console.error(`expected command to fail normally, but it exited by signal ${signal}`);
|
||||
process.exit(1);
|
||||
}
|
||||
if (code === 0) {
|
||||
console.error("expected command to fail, but it exited 0");
|
||||
process.exit(1);
|
||||
}
|
||||
process.exit(0);
|
||||
});
|
||||
|
||||
child.on("error", (error) => {
|
||||
console.error(error.message);
|
||||
process.exit(1);
|
||||
});
|
||||
9
support/plugins/kova-bad-manifest/package.json
Normal file
9
support/plugins/kova-bad-manifest/package.json
Normal file
@ -0,0 +1,9 @@
|
||||
{
|
||||
"name": "kova-bad-manifest-plugin",
|
||||
"version": "1.0.0",
|
||||
"description": "Invalid Kova plugin fixture used to verify manifest validation",
|
||||
"type": "module",
|
||||
"openclaw": {
|
||||
"extensions": []
|
||||
}
|
||||
}
|
||||
8
support/plugins/kova-basic/index.js
Normal file
8
support/plugins/kova-basic/index.js
Normal file
@ -0,0 +1,8 @@
|
||||
import { definePluginEntry } from "openclaw/plugin-sdk/core";
|
||||
|
||||
export default definePluginEntry({
|
||||
id: "kova-basic",
|
||||
name: "Kova Basic",
|
||||
description: "External plugin fixture used by Kova lifecycle scenarios.",
|
||||
register() {}
|
||||
});
|
||||
26
support/plugins/kova-basic/package.json
Normal file
26
support/plugins/kova-basic/package.json
Normal file
@ -0,0 +1,26 @@
|
||||
{
|
||||
"name": "kova-basic-plugin",
|
||||
"version": "1.0.0",
|
||||
"description": "Kova external plugin lifecycle fixture",
|
||||
"type": "module",
|
||||
"exports": {
|
||||
".": "./index.js"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"openclaw": ">=2026.4.25"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"openclaw": {
|
||||
"optional": true
|
||||
}
|
||||
},
|
||||
"openclaw": {
|
||||
"extensions": [
|
||||
"./index.js"
|
||||
],
|
||||
"install": {
|
||||
"defaultChoice": "local",
|
||||
"minHostVersion": ">=2026.4.25"
|
||||
}
|
||||
}
|
||||
}
|
||||
9
support/plugins/kova-missing-runtime-dep/index.js
Normal file
9
support/plugins/kova-missing-runtime-dep/index.js
Normal file
@ -0,0 +1,9 @@
|
||||
import "kova-intentionally-missing-runtime-dep";
|
||||
import { definePluginEntry } from "openclaw/plugin-sdk/core";
|
||||
|
||||
export default definePluginEntry({
|
||||
id: "kova-missing-runtime-dep",
|
||||
name: "Kova Missing Runtime Dep",
|
||||
description: "External plugin fixture that should fail with a missing dependency diagnostic.",
|
||||
register() {}
|
||||
});
|
||||
26
support/plugins/kova-missing-runtime-dep/package.json
Normal file
26
support/plugins/kova-missing-runtime-dep/package.json
Normal file
@ -0,0 +1,26 @@
|
||||
{
|
||||
"name": "kova-missing-runtime-dep-plugin",
|
||||
"version": "1.0.0",
|
||||
"description": "Kova plugin fixture that imports an undeclared runtime dependency",
|
||||
"type": "module",
|
||||
"exports": {
|
||||
".": "./index.js"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"openclaw": ">=2026.4.25"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"openclaw": {
|
||||
"optional": true
|
||||
}
|
||||
},
|
||||
"openclaw": {
|
||||
"extensions": [
|
||||
"./index.js"
|
||||
],
|
||||
"install": {
|
||||
"defaultChoice": "local",
|
||||
"minHostVersion": ">=2026.4.25"
|
||||
}
|
||||
}
|
||||
}
|
||||
68
support/tui-smoke.mjs
Normal file
68
support/tui-smoke.mjs
Normal file
@ -0,0 +1,68 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
import { spawn } from "node:child_process";
|
||||
|
||||
const envName = process.argv[2];
|
||||
const timeoutMs = Number(process.argv[3] ?? 15000);
|
||||
|
||||
if (!envName || !Number.isInteger(timeoutMs) || timeoutMs <= 0) {
|
||||
console.error("usage: tui-smoke.mjs <ocm-env> <timeout-ms>");
|
||||
process.exit(2);
|
||||
}
|
||||
|
||||
const child = spawn("ocm", [
|
||||
`@${envName}`,
|
||||
"--",
|
||||
"tui",
|
||||
"--session",
|
||||
"kova-tui-smoke",
|
||||
"--history-limit",
|
||||
"5"
|
||||
], {
|
||||
stdio: ["pipe", "pipe", "pipe"],
|
||||
shell: false
|
||||
});
|
||||
|
||||
let output = "";
|
||||
let settled = false;
|
||||
|
||||
const timer = setTimeout(() => {
|
||||
finish(false, `TUI did not render a recognizable connected screen within ${timeoutMs}ms`);
|
||||
}, timeoutMs);
|
||||
|
||||
child.stdout.on("data", onData);
|
||||
child.stderr.on("data", onData);
|
||||
|
||||
child.on("exit", (code, signal) => {
|
||||
if (settled) {
|
||||
return;
|
||||
}
|
||||
finish(false, `TUI exited before smoke check passed (code=${code}, signal=${signal ?? "none"})`);
|
||||
});
|
||||
|
||||
child.on("error", (error) => {
|
||||
if (settled) {
|
||||
return;
|
||||
}
|
||||
finish(false, error.message);
|
||||
});
|
||||
|
||||
function onData(chunk) {
|
||||
output += chunk.toString("utf8");
|
||||
if (/openclaw tui|session\s+main|session\s+kova-tui-smoke|local ready|agent\s+main/i.test(output)) {
|
||||
finish(true, "TUI rendered a recognizable connected screen");
|
||||
}
|
||||
}
|
||||
|
||||
function finish(ok, message) {
|
||||
settled = true;
|
||||
clearTimeout(timer);
|
||||
if (child.exitCode === null && child.signalCode === null) {
|
||||
child.kill("SIGINT");
|
||||
}
|
||||
console.log(message);
|
||||
if (!ok && output.trim()) {
|
||||
console.log(output.slice(-4000));
|
||||
}
|
||||
process.exit(ok ? 0 : 1);
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user