Skip to content

Integration/v1 testing#1

Merged
bglusman merged 7 commits intomainfrom
integration/v1-testing
Apr 9, 2026
Merged

Integration/v1 testing#1
bglusman merged 7 commits intomainfrom
integration/v1-testing

Conversation

@bglusman
Copy link
Copy Markdown
Owner

@bglusman bglusman commented Apr 9, 2026

Description

Brief description of the changes in this PR.

Type of Change

  • Bug fix
  • New feature
  • Breaking change
  • Documentation update
  • Refactoring
  • CI/CD improvement

Testing

  • cargo test passes for all affected crates
  • cargo clippy passes with no warnings
  • cargo fmt is clean
  • New tests added for new functionality

Checklist

  • Code follows the project style guidelines
  • Self-review completed
  • Comments added for complex logic
  • Documentation updated (if applicable)
  • No new warnings introduced

Related Issues

Fixes # (issue number)

Breaking Changes

List any breaking changes and migration steps:

Additional Notes

Any additional context or screenshots.

Librarian and others added 2 commits April 8, 2026 23:06
Remove unused imports, dead code, and fix lint issues:
- Remove genuinely unused functions, methods, types, and modules
- Gate test-only code behind #[cfg(test)]
- Prefix unused struct fields with _ (with serde rename where needed)
- Fix derivable_impls, needless_borrows, collapsible_if, manual_strip
- Fix deprecated DateTime::from_utc and telegram msg.from() calls
- Fix unused_doc_comments on proptest macros

All 329+ tests pass. Zero clippy warnings with -D warnings.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings April 9, 2026 11:13
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds an integration-testing/policy-enforcement stack around OpenClaw tool calls by introducing a new clashd policy sidecar, an OpenClaw policy plugin, supporting scripts, and related e2e test cleanups.

Changes:

  • Introduce crates/clashd (Axum service) with /evaluate and /health endpoints plus Docker packaging.
  • Add crates/zeroclawed-policy-plugin TypeScript OpenClaw plugin + local compose setup and requirement-check script.
  • Add helper scripts for local mock LLM testing, syncing OpenClaw policy config, and deploying builds to a fixed host; minor formatting updates across existing Rust e2e tests and onecli-client.

Reviewed changes

Copilot reviewed 30 out of 32 changed files in this pull request and generated 10 comments.

Show a summary per file
File Description
scripts/sync-openclaw-policy.sh Installs the OpenClaw policy plugin and additively patches OpenClaw config + policy file sync
scripts/mock-llm.py Flask-based mock LLM server for integration tests
scripts/docker-compose.yml Switch mock-llm compose service to mounting mock-llm.py instead of inline heredoc
scripts/deploy-210.sh Deployment helper script targeting 192.168.1.210 with systemd setup and policy deploy
PR_STACK.md Documentation describing intended PR stacking/merge order and feature contents
crates/zeroclawed/tests/e2e/property_tests.rs Formatting-only changes to property tests
crates/zeroclawed/tests/e2e/onecli_proxy.rs Formatting + minor readability tweaks in proxy e2e tests
crates/zeroclawed/tests/e2e/mod.rs Reorder module declarations
crates/zeroclawed/tests/e2e/config_sanity.rs Formatting/readability tweaks in config sanity tests
crates/zeroclawed/Cargo.toml Add sled dependency
crates/zeroclawed-policy-plugin/tsconfig.json TypeScript compiler configuration for the policy plugin
crates/zeroclawed-policy-plugin/src/index.ts OpenClaw plugin entry: calls clashd in before_tool_call and blocks/requires approval
crates/zeroclawed-policy-plugin/README.md Plugin documentation (requirements, install, usage)
crates/zeroclawed-policy-plugin/package.json Plugin packaging metadata + OpenClaw extension/hook declarations
crates/zeroclawed-policy-plugin/openclaw.plugin.json Plugin manifest and config schema
crates/zeroclawed-policy-plugin/docker-compose.yml Compose stack for testing clashd + a mock OpenClaw service
crates/zeroclawed-policy-plugin/check-requirements.sh Checks OpenClaw version and clashd availability
crates/zeroclawed-policy-plugin/before_tool_call/tsconfig.json Separate TS config for standalone hook handler variant
crates/zeroclawed-policy-plugin/before_tool_call/index.ts Standalone before_tool_call hook handler implementation
crates/zeroclawed-policy-plugin/before_tool_call/index.js Committed compiled JS for the standalone hook handler
crates/zeroclawed-policy-plugin/before_tool_call/HOOK.md Documentation for the standalone hook handler
crates/onecli-client/src/vault.rs Minor formatting changes
crates/onecli-client/src/policy.rs Minor formatting changes
crates/onecli-client/src/main.rs Minor formatting/refactor for readability (imports/log formatting)
crates/onecli-client/src/config.rs Minor formatting changes
crates/clashd/src/main.rs New clashd server implementation with hardcoded policy rules
crates/clashd/README.md Documentation for clashd API and behavior
crates/clashd/Dockerfile Multi-stage build for clashd container image
crates/clashd/Cargo.toml New crate manifest for clashd
config/policy.star New Starlark policy file (intended for future/advanced policy engine)
Cargo.toml Add crates/clashd to the workspace members
Cargo.lock Lockfile updates for new crate/dependencies

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread scripts/sync-openclaw-policy.sh Outdated
Comment on lines +45 to +47
# Backup config
cp "$OPENCLAW_CONFIG" "$OPENCLAW_CONFIG.bak.$(date +%Y%m%d-%H%M%S)"

Copy link

Copilot AI Apr 9, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The script backs up $OPENCLAW_CONFIG unconditionally, but if the config file doesn’t exist yet cp will fail and (with set -e) the script aborts before the Python block can emit the intended "config not found" error. Add an existence check (or move backup into the Python block after successfully reading the config) to keep the script idempotent.

Copilot uses AI. Check for mistakes.
Comment thread scripts/mock-llm.py Outdated
Comment on lines +12 to +17
data = request.get_json()
last_request = data

# Check if tools were sent (this is what we want to verify)
has_tools = 'tools' in data

Copy link

Copilot AI Apr 9, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

request.get_json() can return None (e.g., invalid/missing JSON or wrong Content-Type). The subsequent 'tools' in data will then raise a TypeError and the mock server will 500. Handle None by defaulting to {} or returning a 400 with a clear message.

Copilot uses AI. Check for mistakes.
Comment thread scripts/deploy-210.sh Outdated
Comment on lines +19 to +21
# Build locally first
echo "📦 Building locally..."
cargo build --release --workspace 2>&1 | tail -5
Copy link

Copilot AI Apr 9, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

cargo build ... | tail -5 masks build failures unless pipefail is enabled (the pipeline exit code is from tail). This can lead to deploying stale or missing binaries. Either remove the pipe, or add set -o pipefail (or set -euo pipefail) and avoid truncating error output.

Copilot uses AI. Check for mistakes.
Comment thread scripts/deploy-210.sh Outdated
Comment on lines +2 to +10
# deploy-210.sh - Deploy ZeroClawed integration build to 192.168.1.210
# Usage: ./scripts/deploy-210.sh [--branch BRANCH] [--build]

set -e

HOST="192.168.1.210"
USER="root"
SSH_KEY="${SSH_KEY:-$HOME/.ssh/id_ed25519}"
BRANCH="${1:-integration/v1-testing}"
Copy link

Copilot AI Apr 9, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The usage header advertises --branch / --build, but the script currently treats $1 as the branch name and doesn’t parse flags at all. This is misleading and will break callers who pass --branch. Either implement basic flag parsing or update the usage comment + variable initialization to match actual behavior.

Copilot uses AI. Check for mistakes.
Comment thread config/policy.star Outdated
agent = context.get("agent", "unknown")

# Get target from args (for tools like gateway config edits)
target = args.get("path", "") or args.get("config", {{}}).get("agentId", "")
Copy link

Copilot AI Apr 9, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This Starlark policy has args.get("config", {{}}) which is not valid syntax (double-braced dict literal). It will prevent the policy from loading/executing. Replace {{}} with {} (and consider a minimal smoke test/CI check that the policy file parses).

Suggested change
target = args.get("path", "") or args.get("config", {{}}).get("agentId", "")
target = args.get("path", "") or args.get("config", {}).get("agentId", "")

Copilot uses AI. Check for mistakes.
Comment on lines +41 to +46
register(api) {
const config: PolicyConfig = {
...DEFAULT_CONFIG,
// Could load from plugin config store in future
};

Copy link

Copilot AI Apr 9, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This plugin defines a config schema (clashdEndpoint/timeoutMs/fallbackOnError) but the runtime code only reads environment variables and ignores any per-plugin configuration provided by OpenClaw. Either wire the OpenClaw plugin config into config here, or remove/update the schema/docs to avoid a non-functional configuration surface.

Copilot uses AI. Check for mistakes.
Comment on lines +1 to +5
"use strict";
// before_tool_call hook handler
// This is invoked by OpenClaw before each tool execution
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
Copy link

Copilot AI Apr 9, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This before_tool_call/index.js appears to be a compiled artifact (TS helper boilerplate) and the package manifest doesn’t reference it (the plugin loads ./dist/index.js). Keeping generated JS in-repo alongside a second TS implementation is confusing and risks divergence. Consider removing this folder, or clearly wiring it into the build/package and adding the generated output to .gitignore instead.

Copilot uses AI. Check for mistakes.
Comment thread crates/zeroclawed/Cargo.toml Outdated
harness = true

[dependencies]
sled = "0.34"
Copy link

Copilot AI Apr 9, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

sled is added as a dependency but there are no uses of it in the repository. If it’s not needed for this PR, please remove it to avoid pulling in an extra storage engine (compile time, audit surface). If it is needed, include the corresponding code changes so the dependency is justified.

Suggested change
sled = "0.34"

Copilot uses AI. Check for mistakes.
Comment thread crates/clashd/src/main.rs Outdated
Comment on lines +66 to +77
// Check for destructive operations
let destructive_patterns = ["rm -rf", "rm -fr", "mkfs", "wipefs", "dd if=/dev/"];
for pattern in &destructive_patterns {
if args_str.contains(pattern) {
warn!(pattern, "Destructive command detected");
return Ok(Json(EvaluateResponse {
verdict: "deny".to_string(),
reason: Some(format!(
"Destructive command '{}' blocked by policy",
pattern
)),
}));
Copy link

Copilot AI Apr 9, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The destructive-command detection denies any request whose serialized args contain patterns like rm -rf, regardless of tool. The README describes these as "destructive shell commands"; as written, non-shell tools could be denied if their args happen to include these substrings. If the intent is shell-only blocking, gate this block on the tool (e.g., tool == "shell").

Suggested change
// Check for destructive operations
let destructive_patterns = ["rm -rf", "rm -fr", "mkfs", "wipefs", "dd if=/dev/"];
for pattern in &destructive_patterns {
if args_str.contains(pattern) {
warn!(pattern, "Destructive command detected");
return Ok(Json(EvaluateResponse {
verdict: "deny".to_string(),
reason: Some(format!(
"Destructive command '{}' blocked by policy",
pattern
)),
}));
// Check for destructive shell operations
if tool_lower == "shell" {
let destructive_patterns = ["rm -rf", "rm -fr", "mkfs", "wipefs", "dd if=/dev/"];
for pattern in &destructive_patterns {
if args_str.contains(pattern) {
warn!(tool = %req.tool, pattern, "Destructive command detected");
return Ok(Json(EvaluateResponse {
verdict: "deny".to_string(),
reason: Some(format!(
"Destructive command '{}' blocked by policy",
pattern
)),
}));
}

Copilot uses AI. Check for mistakes.
Comment on lines +85 to +93
## Version Check During Installation

The plugin checks OpenClaw version on load and logs a warning if requirements aren't met:

```
[zeroclawed-policy] WARNING: OpenClaw version 2026.3.20 detected.
[zeroclawed-policy] This plugin requires >=2026.3.24-beta.2 for requireApproval support.
[zeroclawed-policy] Policy enforcement will not work correctly.
```
Copy link

Copilot AI Apr 9, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This section claims the plugin checks the OpenClaw version on load and logs a warning when requirements aren’t met, but the current implementation doesn’t perform any version detection/checking. Either implement the runtime version check or update/remove this documentation to match actual behavior.

Suggested change
## Version Check During Installation
The plugin checks OpenClaw version on load and logs a warning if requirements aren't met:
```
[zeroclawed-policy] WARNING: OpenClaw version 2026.3.20 detected.
[zeroclawed-policy] This plugin requires >=2026.3.24-beta.2 for requireApproval support.
[zeroclawed-policy] Policy enforcement will not work correctly.
```
## Version Compatibility
This plugin requires OpenClaw `>=2026.3.24-beta.2` because the approval flow depends on `requireApproval` support in hook results.
The current plugin implementation does not automatically detect or warn about the OpenClaw version at load time, so compatibility must be verified during installation or troubleshooting.

Copilot uses AI. Check for mistakes.
@bglusman bglusman force-pushed the integration/v1-testing branch from 77c14bc to b8af38a Compare April 9, 2026 15:57
@bglusman bglusman merged commit a6baf69 into main Apr 9, 2026
9 checks passed
bglusman added a commit that referenced this pull request Apr 24, 2026
Security-proxy now shares onecli-client's vault resolver rather than
its own parallel startup-time env scan. Per-request, on cache miss,
it consults env → fnox → vaultwarden and caches the result. This
fixes the silently-broken rotation behaviour documented in
`docs/rfcs/consolidation-findings.md` finding #5: previously, a
rotated key added to env or fnox after security-proxy started was
invisible until restart.

Changes:
- Add `onecli-client = { path = "../onecli-client" }` as a path dep.
- New `CredentialInjector::ensure_cached(provider) -> bool` that
  returns fast when the DashMap already has the provider, else calls
  `onecli_client::vault::get_secret(provider)` and inserts on success.
- Public wrapper `detect_provider_pub(host)` so the proxy hot path can
  determine the provider name without duplicating the pattern table.
- Proxy.rs resolves + caches before calling inject() on each request.
- Two behavior tests covering cache-hit (no resolver call) and
  nothing-resolves (cache stays clean, no `Bearer ""` footgun).
  Written in given/when/then doc form as per today's test-quality
  review discussion.

Left for follow-up commits on this branch:
- Merge onecli-client's `/vault/:secret` + `/proxy/:provider` routes
  into security-proxy (move the handlers, then delete main.rs).
- Reconcile env-var conventions (ZEROGATE_KEY_* vs <NAME>_API_KEY —
  finding #1).
- Per-provider auth header schemes (finding #3 — onecli only does
  Bearer; security-proxy has anthropic's x-api-key path).
- Remove hardcoded `vault.enjyn.com` fallback (finding #6).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
bglusman added a commit that referenced this pull request Apr 24, 2026
…icy docs

Two small cleanups driven by the test audit and consolidation findings:

1. Finding #1: `load_from_env` now emits a tracing::warn log whenever a
   `ZEROGATE_KEY_*` env var is encountered, nudging operators to the
   standard `<NAME>_API_KEY` convention that the shared resolver
   understands. The old path still populates the cache so existing
   deployments don't break; a future PR removes it.

2. Audit finding: `ensure_cached`'s doc-comment claimed "first-write-wins"
   but the covered `test_add_overwrites` test proves `add()` is
   last-write-wins. Both are true — `ensure_cached` itself never
   overwrites, `add` always does. The doc is rewritten to state the
   contract precisely: rotation via direct `add()` takes effect
   immediately, rotation via env/fnox/vault does NOT until the cache
   entry is cleared or the service restarts. TTL/invalidation flagged
   as follow-up.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants