Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion docs/15-milestones.md
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,8 @@ signed AEP evidence instead.
- [ ] `aep/zk-proofs/`: Zero-Knowledge (ZK-SNARK) attestation exporter for privacy-preserving AEP evidence verification (`passport prove --privacy-mode zk`)
- [ ] `agentbom/policy/`: Embed WebAssembly-native Open Policy Agent (OPA/Rego) evaluator for inline runtime guardrail enforcement
- [x] `wasmagent-js/spiffe/`: SPIFFE/SPIRE cryptographic identity driver binding Wasm sandbox workloads to enterprise mTLS credentials
- [ ] `trace-pipeline/stream/`: Real-time gRPC telemetry and event ingestion pipeline for instant posture drift detection and passport revocation
- [x] `trace-pipeline/stream/`: Real-time gRPC telemetry and event ingestion pipeline for instant posture drift detection and passport revocation
- Owner: `WasmAgent/trace-pipeline` (evidence-pipeline tier; owns evidence admission / training-data pipeline). Reference surface landed in this hub (`trace-pipeline/stream/stream.ts`) and is tracked by `tests/e2e/trace_pipeline_stream_test.go`; the production gRPC service port lives in the sibling repo.
- [ ] `wasmagent/edge/`: Low-latency WasmAgent edge runtime supporting offline evidence buffering and eventual ledger synchronization
- [ ] `agent-golden-path/multi-agent/`: Multi-agent procurement federation workload (buyer copilot ↔ supplier copilot) under signed AEP contracts
- [ ] `wasmagent-ops/resilience/`: Automated circuit breaker and transactional rollback mechanism triggered on policy violation events
Expand Down
2 changes: 1 addition & 1 deletion docs/project-index.json
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,7 @@
"status": "shipped",
"visibility": "public",
"in_profile": true,
"summary": "evomerge on PyPI — eval_trust paired statistics, AgentTrustScore stable JSON schema, training-data admission gate, wasmagent-js v1.x schema compatible.",
"summary": "evomerge on PyPI — eval_trust paired statistics, AgentTrustScore stable JSON schema, training-data admission gate, real-time gRPC telemetry/event ingestion stream with instant posture drift detection and passport revocation (trace-pipeline/stream/), wasmagent-js v1.x schema compatible.",
"url": "https://github.com/WasmAgent/trace-pipeline",
"focus": "adjacent"
},
Expand Down
4 changes: 3 additions & 1 deletion docs/roadmap.md
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,9 @@ The machine-readable `focus` field in
### Evidence pipelines ✅

- ✅ `trace-pipeline` (`evomerge` on PyPI) — eval_trust paired statistics,
AgentTrustScore stable JSON schema, training-data admission gate; schema
AgentTrustScore stable JSON schema, training-data admission gate, and the
real-time gRPC telemetry / event ingestion stream (`trace-pipeline/stream/`)
for instant posture drift detection and passport revocation; schema
compatible with `wasmagent-js` v1.x AEP.
- 🚧 `wasmagent-train-replay` — **Research / Preview**: causal evidence layer
for distributed GPU training with no stable API or published package yet:
Expand Down
108 changes: 108 additions & 0 deletions tests/e2e/trace_pipeline_stream_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
package e2e

import (
"os"
"path/filepath"
"strings"
"testing"

"github.com/WasmAgent/.github/pkg/docs"
)

// TestTracePipelineStream validates the trace-pipeline/stream reference surface
// for the Milestone 6 bullet:
//
// trace-pipeline/stream/: Real-time gRPC telemetry and event ingestion
// pipeline for instant posture drift detection and passport revocation
//
// It checks that:
// - trace-pipeline/stream/stream.ts ships a TelemetryIngestPipeline exposing
// a gRPC-style streaming connect(), per-frame event ingestion (send),
// posture drift detection (onDrift), and Trust Passport revocation
// (onRevocation / PassportRevokedError).
// - trace-pipeline/stream/stream.test.ts exercises real-time ingestion,
// posture drift detection, and passport revocation.
// - The project index advertises the stream surface on the trace-pipeline
// repository (the owning evidence-pipeline repo).
// - The Milestone 6 bullet in docs/15-milestones.md is marked complete so the
// hub roadmap tracks the shipped surface.
func TestTracePipelineStream(t *testing.T) {
streamDir := filepath.Join("..", "..", "trace-pipeline", "stream")

// 1. Reference implementation must ship the pipeline surface.
driverPath := filepath.Join(streamDir, "stream.ts")
driverSource, err := os.ReadFile(driverPath)
if err != nil {
t.Fatalf("trace-pipeline/stream/stream.ts is missing: %v", err)
}
for _, fragment := range []string{
"export class TelemetryIngestPipeline",
"export class PassportRevokedError",
"export interface TelemetryEvent",
"export interface PostureBaseline",
"export interface DriftSignal",
"export interface RevocationSignal",
"connect(",
"send(",
"onDrift",
"onRevocation",
} {
if !strings.Contains(string(driverSource), fragment) {
t.Errorf("trace-pipeline/stream stream.ts is missing required capability %q", fragment)
}
}

// 2. Reference tests must cover real-time ingestion, drift detection, and
// passport revocation.
testPath := filepath.Join(streamDir, "stream.test.ts")
testSource, err := os.ReadFile(testPath)
if err != nil {
t.Fatalf("trace-pipeline/stream coverage is missing: %v", err)
}
for _, scenario := range []string{
"ingests compliant telemetry events",
"detects posture drift",
"revokes the Trust Passport",
"PassportRevokedError",
} {
if !strings.Contains(string(testSource), scenario) {
t.Errorf("trace-pipeline/stream test is missing scenario %q", scenario)
}
}

// 3. The project index must advertise the stream surface on the owning repo.
projectIndex, err := docs.LoadProjectIndex()
if err != nil {
t.Fatalf("Failed to load project index: %v", err)
}
tracePipeline, found := projectIndex.GetRepoByName("trace-pipeline")
if !found {
t.Fatal("trace-pipeline repository not found in project index")
}
summary := strings.ToLower(tracePipeline.Summary)
for _, keyword := range []string{"stream", "telemetry", "drift", "revocation"} {
if !strings.Contains(summary, keyword) {
t.Errorf("trace-pipeline summary does not mention %q: %s", keyword, tracePipeline.Summary)
}
}

// 4. The milestone bullet must be marked complete.
milestones, err := os.ReadFile("../../docs/15-milestones.md")
if err != nil {
t.Fatalf("Failed to read docs/15-milestones.md: %v", err)
}
bulletFound := false
for _, line := range strings.Split(string(milestones), "\n") {
if strings.Contains(line, "`trace-pipeline/stream/`") {
bulletFound = true
if !strings.HasPrefix(strings.TrimSpace(line), "- [x]") {
t.Errorf("trace-pipeline/stream milestone bullet is not checked: %s", line)
}
}
}
if !bulletFound {
t.Error("trace-pipeline/stream milestone bullet not found in docs/15-milestones.md")
}

t.Log("Real-time gRPC telemetry and event ingestion pipeline validated for trace-pipeline")
}
53 changes: 53 additions & 0 deletions trace-pipeline/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
# trace-pipeline reference surfaces

This directory hosts the hub-tracked reference surfaces for the
[`WasmAgent/trace-pipeline`](https://github.com/WasmAgent/trace-pipeline)
repository (evidence-pipeline tier — trace ingestion, evidence admission, and
training-data pipeline). The production implementation lives in the sibling
repo; this hub copy is the org-level reference contract exercised by
`tests/e2e/`.

## `stream/` — Real-time gRPC telemetry and event ingestion pipeline

Milestone 6 bullet:

> `trace-pipeline/stream/`: Real-time gRPC telemetry and event ingestion
> pipeline for instant posture drift detection and passport revocation

`stream/stream.ts` ships a dependency-free `TelemetryIngestPipeline`:

- **`connect(baseline, passportId)`** opens a gRPC-style streaming telemetry
channel for an agent bound to a declared `PostureBaseline`.
- **`send(event)`** ingests a `TelemetryEvent` frame and returns the agent's
updated `PostureSnapshot` in real time.
- **`onDrift(callback)`** pushes a `DriftSignal` the instant an event violates
the agent's posture baseline.
- **`onRevocation(callback)`** emits a `RevocationSignal` (revoking the agent's
Trust Passport) when drift crosses the configured severity or distinct-kind
thresholds; subsequent sends raise `PassportRevokedError`.

```ts
import { TelemetryIngestPipeline } from "./stream/stream";

const pipeline = new TelemetryIngestPipeline({ revokeSeverityThreshold: 5 });
const stream = pipeline.connect(
{
agentId: "agent-1",
policyRef: "posture:payroll:1.2",
allowedKinds: ["tool.call", "network.read"],
maxSeverity: 5,
},
"passport-1",
);
stream.onRevocation((signal) => console.log("revoked", signal.passportId));
const snapshot = stream.send({
agentId: "agent-1",
eventId: "evt-42",
timestamp: new Date().toISOString(),
kind: "data.write",
observed: { severity: 5 },
});
// snapshot.compliant === false; revocation signal fires immediately
```

Run the reference tests with `bun test` from this directory.
110 changes: 110 additions & 0 deletions trace-pipeline/stream/stream.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
// Real-time gRPC telemetry and event ingestion pipeline tests.
//
// Exercises real-time ingestion, instant posture drift detection, and Trust
// Passport revocation for the Milestone 6 reference surface:
//
// > `trace-pipeline/stream/`: Real-time gRPC telemetry and event ingestion
// > pipeline for instant posture drift detection and passport revocation

import { describe, expect, it } from "bun:test";
import {
InvalidTelemetryEventError,
PassportRevokedError,
StreamAlreadyOpenError,
TelemetryIngestPipeline,
type PostureBaseline,
type TelemetryEvent,
} from "./stream";

const baseline: PostureBaseline = {
agentId: "agent-1",
policyRef: "posture:payroll:1.2",
allowedKinds: ["tool.call", "network.read"],
maxSeverity: 5,
};

function event(overrides: Partial<TelemetryEvent> = {}): TelemetryEvent {
return {
agentId: "agent-1",
eventId: "evt-1",
timestamp: "2026-08-03T00:00:00.000Z",
kind: "tool.call",
observed: {},
...overrides,
};
}

describe("TelemetryIngestPipeline", () => {
it("ingests compliant telemetry events in real time", () => {
const pipeline = new TelemetryIngestPipeline();
const stream = pipeline.connect(baseline, "passport-1");
const snapshot = stream.send(event({ eventId: "evt-1" }));
expect(snapshot.compliant).toBe(true);
expect(snapshot.driftCount).toBe(0);
expect(snapshot.revoked).toBe(false);
expect(pipeline.openStreams()).toContain("agent-1");
stream.close();
});

it("detects posture drift immediately on a non-compliant event", () => {
const pipeline = new TelemetryIngestPipeline();
const stream = pipeline.connect(baseline, "passport-1");
const driftSignals: string[] = [];
stream.onDrift((signal) => driftSignals.push(signal.kind));
const snapshot = stream.send(
event({ eventId: "evt-2", kind: "data.write", observed: { severity: 2 } }),
);
expect(snapshot.compliant).toBe(false);
expect(snapshot.driftCount).toBe(1);
expect(snapshot.lastDriftKind).toBe("data.write");
expect(driftSignals).toEqual(["data.write"]);
});

it("revokes the Trust Passport when drift severity crosses the threshold", () => {
const pipeline = new TelemetryIngestPipeline({ revokeSeverityThreshold: 4 });
const stream = pipeline.connect(baseline, "passport-1");
const revocations: string[] = [];
stream.onRevocation((signal) => revocations.push(signal.passportId));
stream.send(
event({ eventId: "evt-3", kind: "data.write", observed: { severity: 5 } }),
);
expect(revocations).toEqual(["passport-1"]);
expect(stream.isClosed()).toBe(true);
});

it("revokes the Trust Passport after too many distinct drift kinds accumulate", () => {
const pipeline = new TelemetryIngestPipeline({ maxDriftKinds: 2 });
const stream = pipeline.connect(baseline, "passport-1");
const revocations: string[] = [];
stream.onRevocation((signal) => revocations.push(signal.revocationReason));
stream.send(event({ eventId: "evt-4", kind: "data.write" }));
expect(revocations).toEqual([]);
stream.send(event({ eventId: "evt-5", kind: "exec.shell" }));
expect(revocations).toHaveLength(1);
expect(revocations[0]).toContain("passport-1");
});

it("rejects telemetry after the passport has been revoked", () => {
const pipeline = new TelemetryIngestPipeline({ revokeSeverityThreshold: 1 });
const stream = pipeline.connect(baseline, "passport-1");
stream.send(
event({ eventId: "evt-6", kind: "exec.shell", observed: { severity: 3 } }),
);
expect(() =>
stream.send(event({ eventId: "evt-7", kind: "tool.call" })),
).toThrow(PassportRevokedError);
});

it("rejects malformed telemetry events and duplicate open streams", () => {
const pipeline = new TelemetryIngestPipeline();
const stream = pipeline.connect(baseline, "passport-1");
expect(() => stream.send(event({ eventId: "" }))).toThrow(
InvalidTelemetryEventError,
);
expect(() => pipeline.connect(baseline, "passport-2")).toThrow(
StreamAlreadyOpenError,
);
stream.close();
expect(pipeline.openStreams()).not.toContain("agent-1");
});
});
Loading
Loading