Skip to content

Commit 25dc67a

Browse files
chore(release): v1.1.0 (#39)
* chore(release): v1.1.0 Bumps plugin manifest + marketplace.json to v1.1.0 and dates the [Unreleased] CHANGELOG entry 2026-05-03 — covering the 4 read-side governance skills landed in #37 (explain-decision, list-overrides, create-override, revoke-override). Pure metadata change; no behavior or test surface modifications. Signed-off-by: Saurabh Jain <saurabhjain1592@gmail.com> * test(runtime-e2e): add audit-search runtime test (rule #1) Registers the AxonFlow MCP server in Codex's global config (the same step a real Codex user runs after installing this plugin), drives `codex exec` non-interactively with auto-approval, and asserts: 1. Codex's MCP layer logs `started` then `(completed)` for the call to <name>/search_audit_events (proves the agent dispatched through Codex's MCP runtime, not raw JSON-RPC). 2. The agent emits the SMOKE_RESULT: marker (full pipeline executed). 3. The response shape carries entries[] (validates audit/search nil-fix from axonflow-enterprise#1834 is in place too). Known product gap surfaced during testing: Codex's HTTP MCP support shows `Auth: Unsupported` for our Basic-auth MCP server. Calls work in community mode (permissive) but enterprise mode is a real gap. This satisfies rule #1 for the audit-search row of the W2 work. The other four W2 features (explain, list/create/revoke overrides) have wiring on main but no runtime-e2e test yet — they're explicitly ❌ in axonflow-internal-docs/engineering/FEATURE_RUNTIME_COVERAGE.md and slated for follow-up PRs per the runtime-e2e/<feature>/test.sh convention introduced in #38. Signed-off-by: Saurabh Jain <saurabhjain1592@gmail.com> * test(runtime-e2e): add 4 more per-feature tests + lifecycle integration Same shape as the Claude Code companion PR. Adds: - runtime-e2e/explain-decision/test.sh - runtime-e2e/list-overrides/test.sh - runtime-e2e/create-override/test.sh - runtime-e2e/revoke-override/test.sh - runtime-e2e/governance-lifecycle/test.sh - runtime-e2e/_lib/codex-runtime.sh — shared helpers; codex_register_mcp registers the AxonFlow MCP server in Codex's global config (the same step a real Codex user runs after installing this plugin) and codex_exec_capture drives `codex exec --skip-git-repo-check --dangerously-bypass-approvals-and-sandbox`. All 5 new tests verified PASS locally against a community-mode stack. Signed-off-by: Saurabh Jain <saurabhjain1592@gmail.com> * test(runtime-e2e): convert all 5 W2 tests + lifecycle to outcome assertions Following the rule-#1 spirit: dispatch is necessary but not sufficient. Each test now asserts a real state transition or platform fact, not just that the call dispatched. - audit-search seeds a unique marker via a SQLi block, drives the agent to find it via search_audit_events, asserts the agent reply CONTAINS the marker. - list-overrides creates a real override with a unique reason tag, asserts the agent's reply contains the tag AND the exact UUID, cleans up on EXIT. - create-override exercises the rejection path on sys_sqli_admin_bypass and asserts the agent surfaces the platform's 403 verdict to the user. - revoke-override seeds a real override, drives the agent to revoke via delete_override, then verifies SERVER-SIDE that revoked_at is populated. - explain-decision triggers a block to mint a real decision_id, drives the agent to explain it, asserts the reply NAMES the policy that fired (Authentication Bypass / sys_sqli_admin_bypass). - governance-lifecycle drives the full chain in one session (list → create → list → revoke → list), captures baseline/after_create/after_revoke counts, asserts state transitions match (count went up, then down) and server-side state confirms the revoke. Helper change: codex registers the MCP server unauthenticated (codex mcp add doesn't accept Basic auth), and the agent resolves that to a "community" tenant — different from the "demo-client" tenant a Basic-authed direct-curl seed lands in. To keep tenants consistent between seed and assertion, the helper grew three new functions (mcp_seed_override / mcp_seed_block / mcp_cleanup_override) that go through the same unauth /api/v1/mcp-server JSON-RPC path the codex agent uses. Tests that previously seeded via /api/v1/overrides switched to these helpers. CHANGELOG bumped to 2026-05-04. Signed-off-by: Saurabh Jain <saurabhjain1592@gmail.com> --------- Signed-off-by: Saurabh Jain <saurabhjain1592@gmail.com>
1 parent 0316d29 commit 25dc67a

16 files changed

Lines changed: 720 additions & 10 deletions

File tree

.codex-plugin/marketplace.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,14 +7,14 @@
77
},
88
"metadata": {
99
"description": "Runtime governance for OpenAI Codex. Policy enforcement on terminal commands, advisory governance via skills, PII detection, audit trails, and compliance-grade decision records.",
10-
"version": "1.0.0"
10+
"version": "1.1.0"
1111
},
1212
"plugins": [
1313
{
1414
"name": "axonflow",
1515
"source": "./",
1616
"description": "Policy enforcement, PII detection, and audit trails for OpenAI Codex. Hybrid governance — enforces policies on terminal commands (exec_command) via hooks, provides advisory governance for other tools via implicit-activation skills, and records compliance-grade audit trails. Self-hosted via Docker — all data stays on your infrastructure.",
17-
"version": "1.0.0",
17+
"version": "1.1.0",
1818
"author": {
1919
"name": "AxonFlow",
2020
"email": "hello@getaxonflow.com",

.codex-plugin/plugin.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
"name": "axonflow",
33
"displayName": "AxonFlow Governance",
44
"description": "Policy enforcement, PII detection, and audit trails for OpenAI Codex. Hybrid governance — enforces policies on terminal commands (exec_command) via hooks, provides advisory governance for other tools via implicit-activation skills, and records compliance-grade audit trails. Self-hosted via Docker — all data stays on your infrastructure.",
5-
"version": "1.0.0",
5+
"version": "1.1.0",
66
"author": {
77
"name": "AxonFlow",
88
"email": "hello@getaxonflow.com",

CHANGELOG.md

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,16 +2,15 @@
22

33
## [Unreleased]
44

5+
## [1.1.0] - 2026-05-04 — 4 read-side governance skills
6+
57
### Added
68

7-
- **4 new agent-callable governance skills.** Codex agents can now use the
9+
- **4 new agent-callable governance skills.** Codex agents can use the
810
AxonFlow read-side governance surface directly in conversation:
9-
`explain-decision` (full reasoning behind a previous decision),
10-
`list-overrides` (active session overrides), `create-override`
11-
(governed override with mandatory justification), and `revoke-override`.
12-
Joins the existing `audit-search` skill for full read-side parity. The
13-
underlying MCP tools are already exposed by the platform; these skills
14-
document when and how the agent should invoke them.
11+
`explain-decision`, `list-overrides`, `create-override`, and
12+
`revoke-override`. Joins the existing `audit-search` skill for full
13+
read-side parity.
1514

1615
## [1.0.0] - 2026-04-29 — Production, quality, and security hardening — upgrade encouraged
1716

runtime-e2e/_lib/codex-runtime.sh

Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,126 @@
1+
#!/usr/bin/env bash
2+
# Shared helpers for Codex runtime-e2e tests.
3+
#
4+
# Each per-feature test sources this file, calls codex_register_mcp,
5+
# runs codex_exec with a tool-bearing prompt, and checks for the MCP
6+
# `started` / `(completed)` / `(failed)` markers in Codex's output.
7+
#
8+
# Codex doesn't expose a structured event stream the way Claude Code's
9+
# stream-json does, so we parse the human-readable diagnostic lines
10+
# Codex prints. Brittle if Codex changes that format, but it's the
11+
# only signal available today.
12+
13+
set -uo pipefail
14+
15+
: "${AXONFLOW_ENDPOINT:=http://localhost:8080}"
16+
: "${MCP_SERVER_NAME:=axonflow_w2_e2e}"
17+
18+
runtime_e2e_skip_if_unavailable() {
19+
if ! command -v codex >/dev/null 2>&1; then
20+
echo "SKIP: codex CLI not on PATH"
21+
exit 0
22+
fi
23+
if ! command -v jq >/dev/null 2>&1; then
24+
echo "SKIP: jq not on PATH"
25+
exit 0
26+
fi
27+
if ! curl -sSf -o /dev/null --max-time 5 "$AXONFLOW_ENDPOINT/health"; then
28+
echo "SKIP: AxonFlow stack not reachable at $AXONFLOW_ENDPOINT/health"
29+
echo " Start one via axonflow-enterprise scripts/setup-e2e-testing.sh"
30+
exit 0
31+
fi
32+
}
33+
34+
codex_register_mcp() {
35+
codex mcp remove "$MCP_SERVER_NAME" >/dev/null 2>&1 || true
36+
codex mcp add "$MCP_SERVER_NAME" --url "$AXONFLOW_ENDPOINT/api/v1/mcp-server" >/dev/null
37+
}
38+
39+
codex_cleanup_mcp() {
40+
codex mcp remove "$MCP_SERVER_NAME" >/dev/null 2>&1 || true
41+
}
42+
43+
codex_exec_capture() {
44+
local prompt="$1"
45+
local output_file="$2"
46+
# Order matters: `>file 2>&1` first redirects stdout to file, then dups
47+
# stderr to the same fd (the file). The reverse order — `2>&1 >file` —
48+
# leaves stderr at the inherited terminal because the dup happens
49+
# against the pre-redirection stdout. We want both streams in the file
50+
# so the grep assertions can find Codex's `mcp: started/(completed)`
51+
# diagnostic lines.
52+
timeout 90 codex exec --skip-git-repo-check --dangerously-bypass-approvals-and-sandbox "$prompt" >"$output_file" 2>&1 || true
53+
}
54+
55+
assert_mcp_started() {
56+
local output_file="$1"
57+
local tool="$2"
58+
grep -qE "mcp: $MCP_SERVER_NAME/$tool started" "$output_file"
59+
}
60+
61+
assert_mcp_completed() {
62+
local output_file="$1"
63+
local tool="$2"
64+
grep -qE "mcp: $MCP_SERVER_NAME/$tool \(completed\)" "$output_file"
65+
}
66+
67+
assert_mcp_failed() {
68+
local output_file="$1"
69+
local tool="$2"
70+
grep -qE "mcp: $MCP_SERVER_NAME/$tool \(failed\)" "$output_file"
71+
}
72+
73+
assert_smoke_result() {
74+
local output_file="$1"
75+
grep -q "SMOKE_RESULT:" "$output_file"
76+
}
77+
78+
assert_output_contains() {
79+
local output_file="$1"
80+
local needle="$2"
81+
grep -q "$needle" "$output_file"
82+
}
83+
84+
# Seed an override via the SAME unauthenticated MCP path codex uses, so the
85+
# tenant resolves to the same value (community in community-mode docker).
86+
# Direct REST seeds via /api/v1/overrides resolve to a different tenant
87+
# (demo-client) under community-mode auth, which would invisibly break
88+
# tenant-scoped lookups (revoke / explain) the agent later issues.
89+
# Echoes the override id on stdout, or empty string on failure.
90+
mcp_seed_override() {
91+
local policy_id="${1:-sys_pii_email}"
92+
local reason="${2:-mcp-seed}"
93+
local ttl="${3:-300}"
94+
local payload
95+
payload=$(jq -n --arg pid "$policy_id" --arg r "$reason" --argjson ttl "$ttl" \
96+
'{jsonrpc:"2.0",id:"1",method:"tools/call",params:{name:"create_override",arguments:{policy_id:$pid,policy_type:"static",override_reason:$r,ttl_seconds:$ttl}}}')
97+
curl -s -X POST -H "Content-Type: application/json" -d "$payload" \
98+
"$AXONFLOW_ENDPOINT/api/v1/mcp-server" \
99+
| jq -r '.result.content[0].text // ""' \
100+
| jq -r '.id // ""' 2>/dev/null
101+
}
102+
103+
# Trigger a SQLi-block decision through the unauth MCP path so the resulting
104+
# decision_id + audit row land in the same tenant codex sees. Echoes the
105+
# decision_id on stdout.
106+
mcp_seed_block() {
107+
local marker="${1:-mcp-block-$(date +%s)}"
108+
local payload
109+
payload=$(jq -n --arg m "SELECT * FROM users WHERE id=1 OR 1=1; -- $marker" \
110+
'{jsonrpc:"2.0",id:"1",method:"tools/call",params:{name:"check_policy",arguments:{connector_type:"sql",statement:$m,operation:"query"}}}')
111+
curl -s -X POST -H "Content-Type: application/json" -d "$payload" \
112+
"$AXONFLOW_ENDPOINT/api/v1/mcp-server" \
113+
| jq -r '.result.content[0].text // ""' \
114+
| jq -r '.decision_id // ""' 2>/dev/null
115+
}
116+
117+
# Revoke-by-id via unauth MCP for cleanup. Quiet on failure.
118+
mcp_cleanup_override() {
119+
local id="$1"
120+
[ -z "$id" ] && return
121+
local payload
122+
payload=$(jq -n --arg id "$id" \
123+
'{jsonrpc:"2.0",id:"1",method:"tools/call",params:{name:"delete_override",arguments:{override_id:$id}}}')
124+
curl -s -X POST -H "Content-Type: application/json" -d "$payload" \
125+
"$AXONFLOW_ENDPOINT/api/v1/mcp-server" >/dev/null 2>&1 || true
126+
}

runtime-e2e/audit-search/README.md

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
# audit-search — runtime E2E
2+
3+
**Asserts:** Codex registers the AxonFlow MCP server via `codex mcp add` (the same step a user runs after installing this plugin), `codex exec` non-interactively dispatches the `mcp__<name>__search_audit_events` tool through Codex's MCP runtime against a live AxonFlow stack, the call completes (not failed/cancelled), and the agent emits a `SMOKE_RESULT:` marker carrying the `entries[]` response.
4+
5+
**Prereqs:** `codex` CLI on PATH and authenticated; `jq`; live AxonFlow stack reachable at `$AXONFLOW_ENDPOINT` (default `http://localhost:8080`). Note: Codex HTTP MCP support is bearer-token-only — Basic-auth AxonFlow endpoints work in community mode but enterprise mode is a known gap.
6+
7+
**Run:**
8+
```bash
9+
AXONFLOW_ENDPOINT=http://localhost:8080 \
10+
bash runtime-e2e/audit-search/test.sh
11+
```

runtime-e2e/audit-search/test.sh

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
#!/usr/bin/env bash
2+
# Codex runtime E2E: audit-search OUTCOME TEST (W2 — rule #1)
3+
#
4+
# Outcome verification, not just dispatch. Seeds a unique marker into
5+
# the platform's audit log via a real SQLi block, drives a real Codex
6+
# agent through search_audit_events, asserts the agent's reply
7+
# CONTAINS the marker.
8+
9+
set -uo pipefail
10+
11+
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
12+
# shellcheck source=../_lib/codex-runtime.sh
13+
source "$SCRIPT_DIR/../_lib/codex-runtime.sh"
14+
15+
runtime_e2e_skip_if_unavailable
16+
17+
trap codex_cleanup_mcp EXIT
18+
codex_register_mcp
19+
echo "--- Registered Codex MCP server: $MCP_SERVER_NAME -> $AXONFLOW_ENDPOINT/api/v1/mcp-server"
20+
21+
MARKER="w2-runtime-e2e-audit-marker-$(date +%s)-$RANDOM"
22+
echo "--- Seeding audit marker: $MARKER ---"
23+
curl -s -X POST \
24+
-H "Authorization: Basic $(printf 'demo-client:demo-secret' | base64)" \
25+
-H "Content-Type: application/json" \
26+
-d "{\"connector_type\":\"sql\",\"statement\":\"SELECT * FROM users WHERE id=1 OR 1=1; -- $MARKER\",\"operation\":\"query\"}" \
27+
"$AXONFLOW_ENDPOINT/api/v1/mcp/check-input" >/dev/null
28+
sleep 2
29+
30+
DIRECT_HITS=$(curl -s -X POST \
31+
-H "Authorization: Basic $(printf 'demo-client:demo-secret' | base64)" \
32+
-H "Content-Type: application/json" \
33+
-d '{"limit":50}' \
34+
"$AXONFLOW_ENDPOINT/api/v1/audit/search" \
35+
| jq --arg m "$MARKER" '[.entries[] | select((.query // "") | contains($m))] | length' 2>/dev/null)
36+
if [ "${DIRECT_HITS:-0}" -lt 1 ]; then
37+
echo "SKIP: marker did not land in audit log via direct seed"
38+
exit 0
39+
fi
40+
41+
PROMPT="Call the mcp__${MCP_SERVER_NAME}__search_audit_events tool with limit=50 to fetch recent audit events. Then find any entry whose query field contains the substring '$MARKER' and report it. Output exactly the literal text SMOKE_RESULT: followed by a single-line JSON like SMOKE_RESULT: {\"marker_found\":true,\"audit_id\":\"...\"} if found, or SMOKE_RESULT: {\"marker_found\":false} if not."
42+
43+
OUTPUT_FILE=$(mktemp -t axonflow-codex-audit.XXXXXX)
44+
trap 'codex_cleanup_mcp; rm -f "$OUTPUT_FILE"' EXIT
45+
46+
echo "--- Running codex exec ... ---"
47+
codex_exec_capture "$PROMPT" "$OUTPUT_FILE"
48+
49+
errors=0
50+
51+
if assert_mcp_started "$OUTPUT_FILE" "search_audit_events"; then
52+
echo "PASS: Codex started the MCP tool call"
53+
else
54+
echo "FAIL: Codex did not start the MCP tool call"
55+
errors=$((errors + 1))
56+
fi
57+
58+
if assert_mcp_completed "$OUTPUT_FILE" "search_audit_events"; then
59+
echo "PASS: Codex MCP tool call completed"
60+
elif assert_mcp_failed "$OUTPUT_FILE" "search_audit_events"; then
61+
echo "FAIL: Codex MCP tool call failed"
62+
errors=$((errors + 1))
63+
fi
64+
65+
if assert_smoke_result "$OUTPUT_FILE"; then
66+
echo "PASS: agent emitted SMOKE_RESULT marker"
67+
else
68+
echo "FAIL: agent did not emit SMOKE_RESULT marker"
69+
errors=$((errors + 1))
70+
fi
71+
72+
if assert_output_contains "$OUTPUT_FILE" '"marker_found":true'; then
73+
echo "PASS: agent's audit-search returned the marker we seeded — outcome verified"
74+
else
75+
tail -10 "$OUTPUT_FILE" | sed 's/^/ /'
76+
echo "FAIL: agent did NOT find the seeded marker"
77+
errors=$((errors + 1))
78+
fi
79+
80+
if [ "$errors" -gt 0 ]; then
81+
echo ""
82+
echo "FAIL: $errors outcome-test assertion(s) failed"
83+
exit 1
84+
fi
85+
echo ""
86+
echo "PASS: audit-search outcome — Codex agent found a real marker event end-to-end"
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
# create-override — runtime E2E
2+
3+
**Asserts:** Drives the runtime to dispatch create_override against a non-override-able policy; verifies dispatch (community-mode platform may accept or reject — both are runtime-path successes).
4+
5+
**Prereqs:** runtime CLI on PATH and authenticated; `jq`; live AxonFlow stack reachable at `$AXONFLOW_ENDPOINT` (default `http://localhost:8080`).
6+
7+
**Run:**
8+
```bash
9+
AXONFLOW_ENDPOINT=http://localhost:8080 \
10+
bash runtime-e2e/create-override/test.sh
11+
```
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
#!/usr/bin/env bash
2+
# Codex runtime E2E: create_override REJECTION OUTCOME (W2 — rule #1)
3+
#
4+
# Verifies that the platform's allow_override=FALSE enforcement is reachable
5+
# through the Codex MCP runtime path. Pre-platform-fix the create_override
6+
# call on sys_sqli_admin_bypass would silently succeed; post-fix the
7+
# platform returns 403 and the agent surfaces the rejection.
8+
9+
set -uo pipefail
10+
11+
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
12+
# shellcheck source=../_lib/codex-runtime.sh
13+
source "$SCRIPT_DIR/../_lib/codex-runtime.sh"
14+
15+
runtime_e2e_skip_if_unavailable
16+
17+
trap codex_cleanup_mcp EXIT
18+
codex_register_mcp
19+
20+
PROMPT="Call the mcp__${MCP_SERVER_NAME}__create_override tool with policy_id=\"sys_sqli_admin_bypass\", policy_type=\"static\", and override_reason=\"runtime-e2e rejection verification\". The platform should reject because the policy is severity=critical. After the tool result, output exactly the literal text SMOKE_RESULT: followed by a single-line JSON like SMOKE_RESULT: {\"dispatched\":true,\"server_rejected\":true,\"http_status\":403} or SMOKE_RESULT: {\"dispatched\":true,\"server_rejected\":false}."
21+
22+
OUTPUT_FILE=$(mktemp -t axonflow-codex-create.XXXXXX)
23+
trap 'codex_cleanup_mcp; rm -f "$OUTPUT_FILE"' EXIT
24+
25+
echo "--- Running codex exec ... ---"
26+
codex_exec_capture "$PROMPT" "$OUTPUT_FILE"
27+
28+
errors=0
29+
30+
if assert_mcp_started "$OUTPUT_FILE" "create_override"; then
31+
echo "PASS: Codex started the MCP tool call"
32+
else
33+
echo "FAIL: Codex did not start the MCP tool call"
34+
errors=$((errors + 1))
35+
fi
36+
37+
if assert_smoke_result "$OUTPUT_FILE"; then
38+
echo "PASS: agent emitted SMOKE_RESULT marker"
39+
else
40+
echo "FAIL: agent did not emit SMOKE_RESULT marker"
41+
errors=$((errors + 1))
42+
fi
43+
44+
# Outcome assertion — the agent reply must reflect the platform rejection.
45+
if assert_output_contains "$OUTPUT_FILE" '"server_rejected":true' \
46+
|| assert_output_contains "$OUTPUT_FILE" 'Critical-risk policies cannot be overridden' \
47+
|| assert_output_contains "$OUTPUT_FILE" 'allow_override' \
48+
|| assert_output_contains "$OUTPUT_FILE" '403'; then
49+
echo "PASS: agent surfaced the platform rejection — outcome verified"
50+
else
51+
tail -10 "$OUTPUT_FILE" | sed 's/^/ /'
52+
echo "FAIL: agent did NOT surface the rejection"
53+
errors=$((errors + 1))
54+
fi
55+
56+
if [ "$errors" -gt 0 ]; then
57+
echo ""
58+
echo "FAIL: $errors outcome-test assertion(s) failed"
59+
exit 1
60+
fi
61+
echo ""
62+
echo "PASS: create-override — agent dispatched + platform rejected + agent surfaced rejection"
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
# explain-decision — runtime E2E
2+
3+
**Asserts:** Drives the runtime to dispatch explain_decision against a fabricated decision_id; platform returns 404 and agent surfaces the not-found result via SMOKE_RESULT marker.
4+
5+
**Prereqs:** runtime CLI on PATH and authenticated; `jq`; live AxonFlow stack reachable at `$AXONFLOW_ENDPOINT` (default `http://localhost:8080`).
6+
7+
**Run:**
8+
```bash
9+
AXONFLOW_ENDPOINT=http://localhost:8080 \
10+
bash runtime-e2e/explain-decision/test.sh
11+
```

0 commit comments

Comments
 (0)