From 338141d62223805833c3f27e60aa3d8a3f0700f9 Mon Sep 17 00:00:00 2001 From: David Crowe Date: Thu, 27 Aug 2026 11:17:50 -0700 Subject: [PATCH 1/2] Render gateway upgrade notices at SessionStart; /acp-upgrade skill (0.13.0) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The attest response may now carry a notice when this plugin version is behind the gateway registry's latest. Surface it as SessionStart additionalContext so the MODEL sees it and can drive the upgrade — behind a human approval; the canonical installer is step_up-gated at the gateway (gatewaystack-connect#849). Canonical contract lives in lib/attestation.mjs (attestNoticeOutput) with tests. New /acp-upgrade skill walks the agent through the approval-gated installer run, the retry-after-approval, and the restart+re-attest verification. Companion to gatewaystack-connect#849; closes #20. --- bin/govern.mjs | 20 +++++++++++++-- lib/attestation.mjs | 18 +++++++++++++ plugin.json | 2 +- skills/acp-upgrade/SKILL.md | 50 +++++++++++++++++++++++++++++++++++++ test/attestation.test.mjs | 17 +++++++++++++ 5 files changed, 104 insertions(+), 3 deletions(-) create mode 100644 skills/acp-upgrade/SKILL.md diff --git a/bin/govern.mjs b/bin/govern.mjs index 636feb1..5fca9e8 100644 --- a/bin/govern.mjs +++ b/bin/govern.mjs @@ -63,7 +63,7 @@ const ACP_GOVERN = process.env.ACP_API_BASE || "https://govern.agenticcontrolplane.com"; -const PLUGIN_VERSION = "0.12.0"; +const PLUGIN_VERSION = "0.13.0"; // Console base for user-facing deep links (session receipt, #606). const ACP_CONSOLE = @@ -842,7 +842,7 @@ async function handleSessionStart() { const hookHash = sha256FileHex(fileURLToPath(import.meta.url)); if (!hookHash) process.exit(0); const grantsHash = sha256FileHex(join(homedir(), ".acp", "harness-grants.json")); - await fetch(`${ACP_GOVERN}/govern/attest`, { + const res = await fetch(`${ACP_GOVERN}/govern/attest`, { method: "POST", headers, body: JSON.stringify({ @@ -857,6 +857,22 @@ async function handleSessionStart() { }), signal: controller.signal, }); + // Upgrade notice (gatewaystack-connect#849): the attest response may + // carry a `notice` when this plugin version is behind the registry's + // latest — surfaced as SessionStart additionalContext so the MODEL + // sees it and can drive the upgrade (behind a human approval; the + // canonical installer is step_up-gated at the gateway). Once per + // session by construction: attest runs at SessionStart only. + // Canonical logic in lib/attestation.mjs (attestNoticeOutput). + const data = await res.json().catch(() => null); + if (data && typeof data.notice === "string" && data.notice.trim()) { + process.stdout.write(JSON.stringify({ + hookSpecificOutput: { + hookEventName: "SessionStart", + additionalContext: data.notice.trim(), + }, + })); + } } catch { // silent — absence of attestation is visible server-side by design } finally { diff --git a/lib/attestation.mjs b/lib/attestation.mjs index f29eeb5..aa88a00 100644 --- a/lib/attestation.mjs +++ b/lib/attestation.mjs @@ -34,3 +34,21 @@ export function buildAttestationPayload({ harness, }; } + +/** Upgrade-notice contract (gatewaystack-connect#849): the /govern/attest + * response may carry `notice` when the reporting plugin version is behind + * the registry's latest. Returns the ONE stdout JSON object SessionStart + * may write, or null. additionalContext reaches the model; a stale hook + * that can't render this is exactly the population the server-side + * console badge and founder-alert enrichment exist for. */ +export function attestNoticeOutput(responseBody) { + if (!responseBody || typeof responseBody.notice !== "string") return null; + const notice = responseBody.notice.trim(); + if (!notice) return null; + return { + hookSpecificOutput: { + hookEventName: "SessionStart", + additionalContext: notice, + }, + }; +} diff --git a/plugin.json b/plugin.json index 7ab13bf..3d89acc 100644 --- a/plugin.json +++ b/plugin.json @@ -1,6 +1,6 @@ { "name": "agentic-control-plane", - "version": "0.12.0", + "version": "0.13.0", "description": "Identity, governance, and audit for every Claude Code tool call. Logs all tool usage, enforces policies, and gives teams full visibility \u2014 without changing how you use Claude.", "author": { "name": "GatewayStack", diff --git a/skills/acp-upgrade/SKILL.md b/skills/acp-upgrade/SKILL.md new file mode 100644 index 0000000..928d32b --- /dev/null +++ b/skills/acp-upgrade/SKILL.md @@ -0,0 +1,50 @@ +--- +name: acp-upgrade +description: Upgrade the ACP governance plugin and hook to the latest version via the canonical installer (human-approved) +user-invocable: true +--- + +# ACP upgrade + +Use this when ACP reports that a newer plugin version is available (an +`[ACP]` upgrade notice at session start), or when the user asks to upgrade +ACP. + +## Why upgrades matter + +The govern hook is the enforcement point on this machine. Old versions can +carry enforcement gaps — for example, pre-0.11.0 hooks could time out under +gateway latency, and a timed-out PreToolUse hook is non-blocking: the call +runs even though the server issued a deny. Upgrading closes gaps that are +already fixed upstream. + +## How to upgrade + +1. Run the canonical installer, exactly: + + ```bash + curl -fsSL https://agenticcontrolplane.com/install.sh | sh + ``` + + Expect an approval requirement: refreshing the governance hook is + step_up-gated at unattended tiers, and the deny reason carries the + approval link. Ask your human to approve it in the ACP console, then + run the SAME command again — the consumed approval lets it through. + Do not modify the command, add flags, or chain other commands onto it: + only the exact canonical form is recognized. + +2. After the installer finishes, tell the user to **restart their session** + (the running session keeps the old hook in memory). + +3. On the next session start the hook re-attests: the console session view + shows the new version and hash. If the badge still shows the old + version, the installer did not take — check `~/.acp/govern.mjs` was + rewritten and `claude plugin list` shows the current plugin version. + +## Never + +- Never edit `~/.acp/govern.mjs`, `~/.claude/settings.json` hooks, or the + plugin files directly — governance machinery is human-only; the + canonical installer behind a human approval is the ONE sanctioned path. +- Never work around a denied installer run — the approval link in the + deny reason is the path forward. diff --git a/test/attestation.test.mjs b/test/attestation.test.mjs index 5a4eca6..d5f5b06 100644 --- a/test/attestation.test.mjs +++ b/test/attestation.test.mjs @@ -50,3 +50,20 @@ test("an attestation without a hook hash is no attestation at all", () => { null, ); }); + +import { attestNoticeOutput } from "../lib/attestation.mjs"; + +test("upgrade notice from the attest response becomes SessionStart additionalContext", () => { + const out = attestNoticeOutput({ ok: true, verdict: "attested", notice: " [ACP] v0.13.0 available " }); + assert.deepEqual(out, { + hookSpecificOutput: { hookEventName: "SessionStart", additionalContext: "[ACP] v0.13.0 available" }, + }); +}); + +test("no notice, empty notice, or malformed response → no stdout object", () => { + assert.equal(attestNoticeOutput({ ok: true, verdict: "attested" }), null); + assert.equal(attestNoticeOutput({ notice: " " }), null); + assert.equal(attestNoticeOutput({ notice: 42 }), null); + assert.equal(attestNoticeOutput(null), null); + assert.equal(attestNoticeOutput(undefined), null); +}); From 7d85536e63e323d51cb5aa3f94e08de70e739098 Mon Sep 17 00:00:00 2001 From: David Crowe Date: Thu, 27 Aug 2026 11:18:39 -0700 Subject: [PATCH 2/2] marketplace.json: sync version to 0.13.0 --- .claude-plugin/marketplace.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.claude-plugin/marketplace.json b/.claude-plugin/marketplace.json index 86d7e7a..6b3dc92 100644 --- a/.claude-plugin/marketplace.json +++ b/.claude-plugin/marketplace.json @@ -9,7 +9,7 @@ "name": "agentic-control-plane", "source": "./", "description": "Control, audit, and cost-optimize every Claude Code tool call. Governance hook + bundled ACP MCP (cost X-ray, run traces, policy checks) + /cost-xray pre-ship report.", - "version": "0.12.0", + "version": "0.13.0", "author": { "name": "GatewayStack" },