agent-control-plane-core 0.11.1 → 0.11.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -127,7 +127,10 @@ Read `NATIVE_ASK_TIER` instead of keeping a list of which agents to escalate an
127
127
  where the flag is `false`, and leaves it alone where the host does the asking.
128
128
  `renderEscalated(adapter, verdict, event, { soleGate })` applies that rule: it
129
129
  re-renders as a `deny` any non-allow verdict that would render unenforced on a
130
- vetoable call, except an `ask` on a host whose flag is `true`.
130
+ vetoable call, except an `ask` on a host whose flag is `true`. A non-allow
131
+ verdict on a payload that names no event at all blocks through the host's exit
132
+ code and stderr, since `render` has no channel for it, unless the event parsed
133
+ observe-only (a Codex below its enforcing version), which cannot veto.
131
134
  It describes the HOST, not the verdict — `ask` renders as `enforced: false`
132
135
  everywhere — and it names the pre-tool gate, since a tool that already ran has
133
136
  nothing left to suspend. Conformance probes your real `render` against it, so a
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agent-control-plane-core",
3
- "version": "0.11.1",
3
+ "version": "0.11.2",
4
4
  "description": "Vendor-neutral control-plane contract for coding agents: a normalized ToolCallEvent/Verdict schema with per-agent adapters (Claude Code, Codex, Amp, Gemini CLI) and a conformance suite.",
5
5
  "type": "module",
6
6
  "repository": {
@@ -967,6 +967,13 @@ export function gatedResponse(
967
967
  * ({@link Adapter} `NATIVE_ASK_TIER`). Nothing changes for an allow, for a call
968
968
  * this guardrail cannot veto (a deny there blocks nothing either), or for a
969
969
  * render the host already enforces. The deny keeps the verdict's other fields.
970
+ *
971
+ * A payload that names no event at all (`meta.native_event` empty) is the one
972
+ * exception: `render` has no channel for it, so a non-allow verdict there takes
973
+ * the host's {@link BLOCK_TRANSPORT} block, which stops whatever call the hook
974
+ * was run for. An observe-only parse renders that block unenforced (exit 0),
975
+ * since its host cannot veto. An event the payload names but no adapter models keeps the rule
976
+ * above: on Claude Code's Stop, a block would keep the agent running.
970
977
  * @param {Adapter} adapter
971
978
  * @param {Verdict} verdict
972
979
  * @param {ToolCallEvent} event
@@ -974,7 +981,18 @@ export function gatedResponse(
974
981
  * @returns {NativeResponse}
975
982
  */
976
983
  export function renderEscalated(adapter, verdict, event, options = {}) {
977
- const { decision } = normalizeVerdict(verdict);
984
+ const { decision, reason } = normalizeVerdict(verdict);
985
+ // This block is what stops a guardrail's deny on an unreadable payload from
986
+ // rendering as an empty exit 0, which every host runs.
987
+ if (event.meta.native_event === "" && decision !== Decision.ALLOW)
988
+ return gatedResponse(adapter.AGENT, {
989
+ transport: event.meta.integration_mode,
990
+ // An observe-only parse (a Codex below its enforcing version) has no
991
+ // veto, so claiming the block was enforced would report a false block.
992
+ enforced: event.meta.integration_mode !== IntegrationMode.OBSERVE_ONLY,
993
+ unblocked_exit_code: 0,
994
+ reason,
995
+ });
978
996
  const response = adapter.render(verdict, event, options);
979
997
  if (
980
998
  response.enforced ||
@@ -296,6 +296,13 @@ export function gatedResponse(agent: string, { transport, enforced, unblocked_ex
296
296
  * ({@link Adapter} `NATIVE_ASK_TIER`). Nothing changes for an allow, for a call
297
297
  * this guardrail cannot veto (a deny there blocks nothing either), or for a
298
298
  * render the host already enforces. The deny keeps the verdict's other fields.
299
+ *
300
+ * A payload that names no event at all (`meta.native_event` empty) is the one
301
+ * exception: `render` has no channel for it, so a non-allow verdict there takes
302
+ * the host's {@link BLOCK_TRANSPORT} block, which stops whatever call the hook
303
+ * was run for. An observe-only parse renders that block unenforced (exit 0),
304
+ * since its host cannot veto. An event the payload names but no adapter models keeps the rule
305
+ * above: on Claude Code's Stop, a block would keep the agent running.
299
306
  * @param {Adapter} adapter
300
307
  * @param {Verdict} verdict
301
308
  * @param {ToolCallEvent} event