open-standards/jouleclaw/jouleclaw-rs/crates/jouleclaw-policy-ipc/tests/end_to_end.rs
Transaction Science 406ce18302 agent infrastructure: privacy filter + workspace + IPC transport (waves 19-21)
Closes the last three outstanding gaps from the post-wave-11
research + policy-bridge follow-ups. JouleClaw now has full parity
with NVIDIA OpenShell's agent-infrastructure primitives, with every
piece kept all-Rust, deterministic, and dep-light.

## Wave-19 — jouleclaw-routing::privacy (proposal #3)

Orthogonal sensitivity axis on top of the joules-first cascade.
NVIDIA OpenShell's Privacy Router primitive, JouleClaw-native.

Surface:
  - SensitivityLevel: Public / Internal / Confidential / Restricted
  - PrivacyClassifier trait — deterministic, pluggable
  - HeuristicPrivacyClassifier — dep-free reference impl detecting
    email / phone / SSN / credential markers / a tunable
    custom-keyword list
  - TierSensitivityPolicy — per JouleClass ceiling, default
    locks Wire to Public-only and Model to Internal
  - PrivacyRouter — wraps any inner Router, filters its plan to
    permitted tiers, prepends a reasoning line carrying the
    classifier verdict

17 new tests: classifier (6) + policy (4) + router (5) + the
fallback / coord-aware path (2).

## Wave-20 — jouleclaw-workspace (proposal #4)

Persistent structured workspace for long-running L6 agents.
NVIDIA-OpenShell File-System parity, all-Rust + signed.

Surface:
  - Workspace<B: WorkspaceBackend> handle with put/get/has/remove
  - WorkspaceBackend trait — content-addressed bytes (BLAKE3)
  - MemoryWorkspaceBackend / FileWorkspaceBackend reference impls
  - WorkspaceManifest — sorted entries, agent-owned, BLAKE3
    content_hash over the canonical bytes
  - SignedWorkspaceManifest + sign_manifest / verify_signed_manifest
    using the AgentIdentity from jouleclaw-attest
  - Path normalisation rejects absolute paths, `..` segments,
    empty segments, NUL bytes

20 new tests: path normalisation (4) + put/get round-trip (3) +
manifest determinism (3) + sign/verify (3) + resume across
sessions (1) + file backend persistence + content-address dedupe
(2) + bad-input rejection (3) + EntryKind wire (1).

## Wave-21 — jouleclaw-policy-ipc (final follow-up)

Out-of-process transport for PolicyEngine. NVIDIA-OpenShell's
"agent process cannot in-process patch the policy" primitive.

Surface:
  - PolicyServer<E: PolicyEngine> — wraps an engine, serves LD-JSON
    requests on any (BufRead, Write) pair
  - PolicyClient — owns a spawned child process, implements
    PolicyEngine over the wire (evaluate / audit / audit_since /
    policy_version / install_policy)
  - jclaw-policyd binary — wires PolicyServer to stdin/stdout,
    with optional --policy <path> startup
  - Request / Response wire envelopes with stable LD-JSON shapes

Required one trait extension: install_policy is now a defaulted
method on PolicyEngine (defaulted to EnginePoisoned for backends
that don't support live updates), so the wire path and the
in-process path share a single trait method. InMemoryPolicyEngine
delegates the trait method to its inherent impl — same code path
for both transports.

5 unit tests (in-memory wire round-trip) + 3 integration tests
(child-process spawn → real wire round-trip → install + evaluate +
audit through the kernel boundary).

## Workspace

104 crates (+2: workspace + policy-ipc), 2,574 lib tests pass
(+42 across waves 19-21), 0 failures. The full original adoption
plan + the policy-bridge follow-ups are now shipped.
2026-06-02 06:20:59 -04:00

73 lines
2.4 KiB
Rust

//! End-to-end integration test: spawn `jclaw-policyd` as a child
//! process and drive it through PolicyClient. The binary is built by
//! Cargo as a sibling of the test artefact under `target/<profile>/`.
use jouleclaw_policy::{
Action, AgentId, AgentScope, FsMode, Policy, PolicyEngine, Rule,
};
use jouleclaw_policy_ipc::PolicyClient;
use std::path::PathBuf;
fn binary_path() -> PathBuf {
// CARGO_BIN_EXE_<name> is set by Cargo when running integration
// tests of a crate that declares a [[bin]] target.
let p = env!("CARGO_BIN_EXE_jclaw-policyd");
PathBuf::from(p)
}
#[test]
fn evaluate_round_trips_through_a_child_process() {
let client = PolicyClient::spawn(binary_path()).expect("spawn jclaw-policyd");
// Server boots with the empty deny-by-default policy.
let decision = client.evaluate(
&AgentId::new("did:key:zA"),
&Action::filesystem("/x", FsMode::Read),
);
assert!(decision.is_deny(), "empty policy → deny-by-default");
}
#[test]
fn install_policy_over_ipc_then_evaluate_uses_new_rules() {
let client = PolicyClient::spawn(binary_path()).expect("spawn jclaw-policyd");
let new = Policy::new_at_version(
vec![Rule::allow("read-workspace", AgentScope::Any)
.filesystem("/workspace/**", [FsMode::Read])],
2,
);
let v = client.install_policy(new).expect("install");
assert_eq!(v, 2);
assert_eq!(client.policy_version(), 2);
let allowed = client.evaluate(
&AgentId::new("did:key:zA"),
&Action::filesystem("/workspace/notes.md", FsMode::Read),
);
assert!(allowed.is_allow());
let denied = client.evaluate(
&AgentId::new("did:key:zA"),
&Action::filesystem("/etc/passwd", FsMode::Read),
);
assert!(denied.is_deny());
}
#[test]
fn audit_log_is_visible_over_ipc() {
let client = PolicyClient::spawn(binary_path()).expect("spawn jclaw-policyd");
let _ = client.evaluate(
&AgentId::new("did:key:zA"),
&Action::filesystem("/x", FsMode::Read),
);
let _ = client.evaluate(
&AgentId::new("did:key:zA"),
&Action::filesystem("/y", FsMode::Read),
);
let log = client.audit();
assert_eq!(log.len(), 2);
assert_eq!(log[0].sequence, 1);
assert_eq!(log[1].sequence, 2);
let after = client.audit_since(1);
assert_eq!(after.len(), 1);
assert_eq!(after[0].sequence, 2);
}