agent-control-plane-core 0.4.0 → 0.4.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.
Files changed (2) hide show
  1. package/package.json +1 -1
  2. package/src/conformance.mjs +33 -17
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agent-control-plane-core",
3
- "version": "0.4.0",
3
+ "version": "0.4.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": {
@@ -124,23 +124,34 @@ export function assertToolAliasesCovered(
124
124
  * @param {any} assert node:assert/strict (injected)
125
125
  */
126
126
  export function assertAliasedInputsCanonical(fixturesList, assert) {
127
- for (const fixtures of fixturesList) {
128
- for (const testCase of fixtures.cases) {
129
- const nativeTool = testCase.event?.meta?.native_tool;
130
- const canon = testCase.event?.tool;
131
- if (typeof nativeTool !== "string" || nativeTool === canon) continue;
132
- const key = lookup(
133
- /** @type {Record<string, string>} */ (MODELED_TOOL_INPUT_KEYS),
134
- canon,
135
- );
136
- if (key === undefined) continue;
137
- assert.ok(
138
- testCase.event.input !== undefined &&
139
- Object.hasOwn(testCase.event.input, key),
140
- `fixture '${testCase.name}' (${fixtures.agent}): ${JSON.stringify(nativeTool)} was canonicalized to ${JSON.stringify(canon)}, which advertises input.${key}, but the input carries ${JSON.stringify(Object.keys(testCase.event.input ?? {}))} — rename the input keys or drop the alias`,
141
- );
142
- }
143
- }
127
+ for (const fixtures of fixturesList)
128
+ for (const testCase of fixtures.cases)
129
+ assertAliasedInput(fixtures.agent, testCase.name, testCase.event, assert);
130
+ }
131
+
132
+ /**
133
+ * The per-event half of {@link assertAliasedInputsCanonical}, shared with
134
+ * {@link runAdapterConformance} so the rule holds against a LIVE parse and not
135
+ * only against the fixture file.
136
+ * @param {string} agent
137
+ * @param {string} caseName
138
+ * @param {any} event a normalized ToolCallEvent
139
+ * @param {any} assert node:assert/strict (injected)
140
+ */
141
+ function assertAliasedInput(agent, caseName, event, assert) {
142
+ const nativeTool = event?.meta?.native_tool;
143
+ const canon = event?.tool;
144
+ // Only a RENAME makes the promise; a native-named tool advertises nothing.
145
+ if (typeof nativeTool !== "string" || nativeTool === canon) return;
146
+ const key = lookup(
147
+ /** @type {Record<string, string>} */ (MODELED_TOOL_INPUT_KEYS),
148
+ canon,
149
+ );
150
+ if (key === undefined) return;
151
+ assert.ok(
152
+ event.input !== undefined && Object.hasOwn(event.input, key),
153
+ `'${caseName}' (${agent}): ${JSON.stringify(nativeTool)} was canonicalized to ${JSON.stringify(canon)}, which advertises input.${key}, but the input carries ${JSON.stringify(Object.keys(event.input ?? {}))} — rename the input keys or drop the alias`,
154
+ );
144
155
  }
145
156
 
146
157
  /**
@@ -217,6 +228,11 @@ export function runAdapterConformance({ adapter, fixtures, assert }) {
217
228
  testCase.event,
218
229
  `parse mismatch: ${testCase.name}`,
219
230
  );
231
+ // Against what parse ACTUALLY produced, not the fixture's copy of it. The
232
+ // deepEqual above ties the two today, but it is the fixture that gets
233
+ // updated when an adapter changes — so checking the fixture alone would let
234
+ // a rename-without-its-input land by editing the expectation.
235
+ assertAliasedInput(adapter.AGENT, testCase.name, parsed, assert);
220
236
 
221
237
  if (testCase.call_class !== undefined) {
222
238
  assert.ok(