From 4b6d05742c7615f2cf4f4121d9929790af96f297 Mon Sep 17 00:00:00 2001 From: Teryl Taylor Date: Thu, 21 May 2026 16:53:05 -0600 Subject: [PATCH] fix: missing cmf-demo main.go file and gitignore fix that missed it. --- .gitignore | 27 ++- examples/go-demo/.gitignore | 11 +- examples/go-demo/cmd/cmf-demo/main.go | 272 ++++++++++++++++++++++++++ 3 files changed, 297 insertions(+), 13 deletions(-) create mode 100644 examples/go-demo/cmd/cmf-demo/main.go diff --git a/.gitignore b/.gitignore index beb86085..f6a8c150 100644 --- a/.gitignore +++ b/.gitignore @@ -29,7 +29,6 @@ token.txt cpex.sbom.xml docs/docs/test/ docs/resources/ -tmp *.tgz *.gz *.bz @@ -48,8 +47,10 @@ node_modules/ mcp.db-journal mcp.db-shm mcp.db-wal -certs/ -jwt/ +# Anchored: matches only ./certs/ at the repo root, not nested +# `certs/` directories under source. Bare `certs/` would silently +# hide any cert-management module / test fixture at any depth. +/certs/ FIXMEs *.old logs/ @@ -62,19 +63,22 @@ corpus/ tests/fuzz/fuzzers/results/ .venv mcp.db -public/ +# Anchored to repo root — bare `public/` would shadow any nested +# `public/` directory in source (common in web/frontend code). +/public/ ica_integrations_host.sbom.json .pyre dictionary.dic pdm.lock .pdm-python temp/ -public/ *history.md htmlcov test_commands.md cover.md -build/ +# Anchored: bare `build/` would shadow any nested build-output dir +# anywhere in the source tree. +/build/ .icaenv commands_output.txt commands_output.md @@ -94,7 +98,6 @@ scribeflow.log coverage_re bin/flagged flagged/ -certs/ # VENV .python37/ .python39/ @@ -111,16 +114,20 @@ __pycache__/ # C extensions *.so -# Distribution / packaging +# Distribution / packaging — Python build artifacts. `build/` and +# `lib/` are anchored (root-only) so they don't silently hide +# nested source dirs of the same name. Other patterns (`dist/`, +# `downloads/`, `eggs/`, …) stay bare — they're less likely to +# collide with source-tree directory names. .wily/ .Python -build/ develop-eggs/ dist/ downloads/ eggs/ .eggs/ -lib/ +# Anchored: bare `lib/` would shadow any nested `lib/` source dir. +/lib/ lib64/ parts/ sdist/ diff --git a/examples/go-demo/.gitignore b/examples/go-demo/.gitignore index 8123b755..6d4b7557 100644 --- a/examples/go-demo/.gitignore +++ b/examples/go-demo/.gitignore @@ -1,3 +1,8 @@ -# Built demo binaries -cpex-demo -cmf-demo +# Built demo binaries. Patterns are anchored (leading slash) so they +# match *files* at their build-output locations, not arbitrary path +# components — the previous unanchored `cmf-demo` rule was silently +# ignoring the `cmd/cmf-demo/` source directory and everything under +# it. +/cpex-demo +/cmf-demo +/cmd/cmf-demo/cmf-demo diff --git a/examples/go-demo/cmd/cmf-demo/main.go b/examples/go-demo/cmd/cmf-demo/main.go new file mode 100644 index 00000000..c550d33a --- /dev/null +++ b/examples/go-demo/cmd/cmf-demo/main.go @@ -0,0 +1,272 @@ +// Location: ./examples/go-demo/cmd/cmf-demo/main.go +// Copyright 2025 +// SPDX-License-Identifier: Apache-2.0 +// Authors: Teryl Taylor +// +// CPEX CMF Demo — typed message processing with rich extensions. +// +// Demonstrates CMF (ContextForge Message Format) message processing +// through the CPEX plugin pipeline: +// +// 1. Build typed CMF messages (tool calls, tool results) +// 2. Attach security extensions (labels, subject), HTTP headers, +// and agent context +// 3. Invoke cmf.tool_pre_invoke — policy checks tool permissions +// against security labels and meta tags +// 4. Invoke cmf.tool_post_invoke — header injector adds response +// headers using capability-gated write access +// 5. Inspect modified extensions (injected headers) in results +// +// Build & run: +// +// cd examples/go-demo/ffi && cargo build --release +// cd examples/go-demo && go run ./cmd/cmf-demo + +package main + +/* +#cgo LDFLAGS: -L${SRCDIR}/../../../../target/release -lcpex_demo_ffi -lm -ldl -lpthread -framework CoreFoundation -framework Security +#include + +int cpex_demo_register_factories(void* mgr); +*/ +import "C" + +import ( + "fmt" + "os" + "unsafe" + + cpex "github.com/contextforge-org/contextforge-plugins-framework/go/cpex" +) + +func main() { + fmt.Println("=== CPEX CMF Demo ===") + fmt.Println() + + // --- Setup --- + mgr, err := cpex.NewPluginManagerDefault() + if err != nil { + fatal("create manager: %v", err) + } + defer mgr.Shutdown() + + err = mgr.RegisterFactories(func(handle unsafe.Pointer) error { + if C.cpex_demo_register_factories(handle) != 0 { + return fmt.Errorf("factory registration failed") + } + return nil + }) + if err != nil { + fatal("register factories: %v", err) + } + + yaml, err := os.ReadFile("../../cmf_plugins.yaml") + if err != nil { + // Try current directory too + yaml, err = os.ReadFile("cmf_plugins.yaml") + if err != nil { + fatal("read config: %v", err) + } + } + + if err := mgr.LoadConfig(string(yaml)); err != nil { + fatal("load config: %v", err) + } + if err := mgr.Initialize(); err != nil { + fatal("initialize: %v", err) + } + + fmt.Printf("Plugins loaded: %d\n", mgr.PluginCount()) + fmt.Printf("Hooks: cmf.tool_pre_invoke=%v cmf.tool_post_invoke=%v\n\n", + mgr.HasHooksFor("cmf.tool_pre_invoke"), + mgr.HasHooksFor("cmf.tool_post_invoke"), + ) + + // ----------------------------------------------------------------------- + // Scenario 1: PII tool call WITHOUT security label — DENIED + // ----------------------------------------------------------------------- + fmt.Println("=== Scenario 1: get_compensation tool call (no PII label) ===") + fmt.Println() + + msg := cpex.MessagePayload{ + Message: cpex.NewMessage("assistant", + cpex.NewTextPart("I'll look up the compensation data for you."), + cpex.NewToolCallPart(cpex.ToolCall{ + ToolCallID: "tc_001", + Name: "get_compensation", + Arguments: map[string]any{"employee_id": 42}, + Namespace: "hr", + }), + ), + } + + ext := &cpex.Extensions{ + Meta: &cpex.MetaExtension{ + EntityType: "tool", + EntityName: "get_compensation", + Tags: []string{"pii", "hr"}, + }, + Security: &cpex.SecurityExtension{ + Labels: []string{}, // no PII label — should be denied + Subject: &cpex.SubjectExtension{ + ID: "alice", + Roles: []string{"hr_analyst"}, + }, + }, + Http: &cpex.HttpExtension{ + RequestHeaders: map[string]string{ + "Authorization": "Bearer eyJ...", + "X-Request-ID": "req-001", + }, + }, + Agent: &cpex.AgentExtension{ + SessionID: "sess_abc123", + AgentID: "hr-assistant", + }, + } + + result, ct, bg, err := mgr.InvokeByName("cmf.tool_pre_invoke", + cpex.PayloadCMFMessage, msg, ext, nil) + if err != nil { + fatal("invoke: %v", err) + } + printResult(result) + bg.Close() + ct.Close() + + // ----------------------------------------------------------------------- + // Scenario 2: PII tool call WITH security label — ALLOWED + // ----------------------------------------------------------------------- + fmt.Println("=== Scenario 2: get_compensation tool call (with PII label) ===") + fmt.Println() + + ext.Security.Labels = []string{"PII", "HR"} // now has PII label + + result, ct, bg, err = mgr.InvokeByName("cmf.tool_pre_invoke", + cpex.PayloadCMFMessage, msg, ext, nil) + if err != nil { + fatal("invoke: %v", err) + } + printResult(result) + + // Check for modified extensions (header injector adds response headers) + // Check for modified extensions (header injector adds response headers) + if len(result.ModifiedExtensions) > 0 { + modExt, err := result.DeserializeExtensions() + if err != nil { + fmt.Printf(" (failed to deserialize modified extensions: %v)\n\n", err) + } else if modExt != nil && modExt.Http != nil && len(modExt.Http.ResponseHeaders) > 0 { + fmt.Println(" Modified response headers:") + for k, v := range modExt.Http.ResponseHeaders { + fmt.Printf(" %s: %s\n", k, v) + } + fmt.Println() + } + } + bg.Close() + + // ----------------------------------------------------------------------- + // Scenario 3: Post-invoke with tool result — header injection + // ----------------------------------------------------------------------- + fmt.Println("=== Scenario 3: tool result post-invoke (header injection) ===") + fmt.Println() + + resultMsg := cpex.MessagePayload{ + Message: cpex.NewMessage("tool", + cpex.NewToolResultPart(cpex.ToolResult{ + ToolCallID: "tc_001", + ToolName: "get_compensation", + Content: map[string]any{ + "employee_id": 42, + "salary": 125000, + "currency": "USD", + }, + IsError: false, + }), + ), + } + + postExt := &cpex.Extensions{ + Meta: &cpex.MetaExtension{ + EntityType: "tool", + EntityName: "get_compensation", + Tags: []string{"pii", "hr"}, + }, + Security: &cpex.SecurityExtension{ + Labels: []string{"PII", "HR"}, + }, + Http: &cpex.HttpExtension{ + RequestHeaders: map[string]string{ + "Authorization": "Bearer eyJ...", + "X-Request-ID": "req-001", + }, + }, + } + + result2, ct2, bg2, err := mgr.InvokeByName("cmf.tool_post_invoke", + cpex.PayloadCMFMessage, resultMsg, postExt, ct) + if err != nil { + fatal("post-invoke: %v", err) + } + printResult(result2) + + if len(result2.ModifiedExtensions) > 0 { + modExt, err := result2.DeserializeExtensions() + if err != nil { + fmt.Printf(" (failed to deserialize modified extensions: %v)\n\n", err) + } else if modExt != nil && modExt.Http != nil { + fmt.Println(" Modified response headers:") + for k, v := range modExt.Http.ResponseHeaders { + fmt.Printf(" %s: %s\n", k, v) + } + fmt.Println() + } + } + bg2.Close() + ct2.Close() + + // ----------------------------------------------------------------------- + // Scenario 4: Non-PII tool — allowed, no policy restriction + // ----------------------------------------------------------------------- + fmt.Println("=== Scenario 4: list_departments (non-PII, text message) ===") + fmt.Println() + + textMsg := cpex.MessagePayload{ + Message: cpex.NewMessage("user", + cpex.NewTextPart("Show me the list of departments"), + ), + } + + textExt := &cpex.Extensions{ + Meta: &cpex.MetaExtension{ + EntityType: "tool", + EntityName: "list_departments", + }, + } + + result, ct, bg, err = mgr.InvokeByName("cmf.tool_pre_invoke", + cpex.PayloadCMFMessage, textMsg, textExt, nil) + if err != nil { + fatal("invoke: %v", err) + } + printResult(result) + bg.Close() + ct.Close() + + fmt.Println("=== CMF Demo complete ===") +} + +func printResult(result *cpex.PipelineResult) { + if !result.IsDenied() { + fmt.Printf(" Result: ALLOWED\n\n") + } else { + v := result.Violation + fmt.Printf(" Result: DENIED — %s [%s]\n\n", v.Reason, v.Code) + } +} + +func fatal(format string, args ...any) { + fmt.Fprintf(os.Stderr, "ERROR: "+format+"\n", args...) + os.Exit(1) +}