fix(compliance): emit stripped field notices#2257
Conversation
* fix(compliance): emit stripped field notices * fix(compliance): preserve strip notices through polling
There was a problem hiding this comment.
Clean backport of the input_schema_field_stripped surfacing fix from main (b4defa56) to the 7.11.x / AdCP 3.0 branch. Right shape: it promotes a console.warn-only signal to structured debug_logs + storyboard notices without touching the strip behavior itself, so the runner becomes a witness to what it already does rather than a translator of it.
Things I checked
- Backport parity. All three source files match the merged
mainfix;mainis at9.0.0-beta.31/3.1.0-rc.14, this branch carries the 7.11.5 generatedversion.ts. Theversion.tsbump is the branch's own build output, not a hand-edit racing changesets —package.jsonis untouched and apatchchangeset is present. Not MUST FIX #6. - Witness, not translator.
collectInputSchemaFieldStripNotices(runner.ts:1444) rebuilds the message from structureddetails.task/details.fields, not by re-parsing the warning string. No fabricated fields, no silent normalization. - executeStep return-site coverage.
inputSchemaStripNoticesis computed post-dispatch and attached to the three reachable post-dispatch returns — force_scenario_unsupported skip (runner.ts:3595), unsupported/unknown-tool skip (3624), and the main success return. The earlier returns (≤3582) fire before any task dispatch, so there's nothing to carry. No silent drop. - Pre-polling preservation. task-map.ts:135 snapshots
prePollingDebugLogsbefore thewaitForCompletionreplacement and merges only whenreplacedByPolling.code-reviewerconfirmedpollTaskCompletionbuilds freshTaskResults with no carrieddebug_logs, so the merge can't double-count; theseenset is belt-and-suspenders on top. - Pass/fail isolation.
overall_passedisfailedCount === 0 && requiredPhasesPassed && !assertionsFailed(runner.ts:2613) — no notice term. Aninfonotice provably can't flip a passing run, and both new tests assert exactly that. - Param threading.
debugLogs?is optional (no caller breakage); new positionalstoryboardIdon the module-privateexecuteStepis updated at both internal callers (runner.ts:2167, 2960). No public surface removed or renamed.
Follow-ups (non-blocking — file as issues)
- Storyboard-level notice rollup is lossy for variable payloads.
mergeRunnerNotices(runner.ts:~1483) dedups bycodealone, keeping the first notice'smessageand only unioningstoryboard_ids. Fine for the static-message codes (request_signing.required, etc.), butinput_schema_field_strippedencodes per-task/per-field detail in its message — two steps stripping different fields on different tasks collapse to the first step's message atStoryboardResult.notices. Full witness survives atstep.notices, so it's a partial rollup, not data loss. Either merge bycode + taskfor this notice or fold the distinct field sets into the aggregated message. (code-reviewer: Medium.) - Severity direction.
ad-tech-protocol-expert(sound-with-caveats):infois the correct safe default for a backport that must not break passing runs, but it collapses two different events — buyer sent an unsupported field (info is right) vs. an agent that under-declares itsinputSchemaand gets a real field silently dropped (a genuine interop defect). Confirm adcp#5495 tracks the open strip-vs-pass-through design question rather than treating "we emit a notice now" as closure. Not this PR's job to resolve.
Minor nits (non-blocking)
- Changeset type. New
NoticeCodeenum value + new optionalnotices?onStoryboardStepResultis additive public-type surface, which leansminorby the strict rule.patchis defensible here — it's diagnostic-only and restores intended visibility — but worth a glance next time the rule bites. docs_urlis an issue, not a spec anchor.https://github.com/adcontextprotocol/adcp/issues/5495is weaker than a published spec section; fine for now, upgrade for GA.any[]oninputSchemaStripLogs(SingleAgentClient.ts:1267, 2274) matches the adjacentv25DriftLogs: any[]convention — consistent, but aDebugLogEntrytype would be stronger.
LGTM. Follow-ups noted below.
There was a problem hiding this comment.
LGTM. Clean backport of b4defa5 from main. Follow-ups noted below.
The fix is the right shape: stripped-field signals already existed via console.warn, so this is witness, not translator — surfaces what is happening into structured debug_logs and runner notices so compliance JSON output stops hiding the signal. Pre-polling preservation in task-map.ts is the load-bearing piece — waitForCompletion replacement was the path that silently dropped them.
Things I checked:
- Diff equivalence vs upstream b4defa5 on main — same fix; only delta is src/lib/version.ts LIBRARY_VERSION 7.11.2 -> 7.11.5, which is the auto-generated file (header @generated by scripts/sync-schemas.ts) syncing to the branch package.json (already at 7.11.5). Not a manual package.json edit.
- Changeset present (patch) and type is correct — additive notice surfacing, no API breakage.
- collectInputSchemaFieldStripNotices at src/lib/testing/storyboard/runner.ts:1444 — defensive type-narrowing on debug_logs is correct, fields filtered to strings, empty-fields entries dropped.
- replacedByPolling flag in src/lib/testing/storyboard/task-map.ts:135-150 — pre-polling logs only merged when polling actually replaced the result. Happy path (sync completion) is unchanged.
- New NoticeCode input_schema_field_stripped added to the union in types.ts:1636 — StoryboardStepResult.notices? is optional and will not break existing readers.
- No src/lib/protocols/** changes, no adapter/wire behavior changes, no fabrication. The stripping behavior itself is unchanged.
- Tests cover sync and async polling paths; request-validation.test.js asserts the structured debug_logs shape (code, task, fields).
Follow-ups (non-blocking — file as issues):
- Multi-pass notice aggregation (runner.ts:2779). Pre-existing: noticesDedup keeps the first pass storyboard_ids and drops the rest. For this PR it is a no-op (multi-pass runs one storyboard.id), but the comment "notices are identical across passes" was written for capability-derived notices and is now load-bearing in a way it was not before. Worth a follow-up to switch to mergeRunnerNotices(passResults.flatMap(r => r.notices)) so step-derived notices accumulate cleanly if multi-pass ever spans storyboards.
- debug_logs duplication in task-map.ts:151. The spread [...prePollingDebugLogs, ...debugLogs] will double-list any log the completion frame echoes back. The notice collector dedups by (task, fields) so the strip notice is safe, but other adopters iterating raw debug_logs would see duplicates. Cheap fix: dedup by (type, message, timestamp).
- Async test does not exercise the merge. test/lib/storyboard-notices.test.js async case verifies the pre-polling log survives, but the polled result has no competing debug_logs. A regression that silently drops prePollingDebugLogs would pass the test. Add a strip log on the completion frame and assert exactly one notice.
Minor nits (non-blocking):
- Dedup key is order-sensitive. Joining task and fields produces different keys for [a,b] vs [b,a]. stripped is populated by Object.entries(adapted) iteration order so it is stable in practice, but sorting fields before joining is a one-liner that removes the assumption.
- debugLogs?: any[] out-param at SingleAgentClient.ts:1332. Matches the surrounding v25DriftLogs pattern, so consistent — but a typed DebugLogEntry[] (or returning { adapted, debugLogs }) would tighten the surface. Pre-existing shape, not blocking.
- Notice message could name the remediation path. docs_url points at adcp#5495, but the message itself does not tell the adopter what to fix beyond "fix the tool schema declaration." Naming inputSchema explicitly would close the loop.
Approving.
Summary
Backports the structured
input_schema_field_strippedcompliance notice fix from main to the AdCP 3.0 / 7.11.x release branch.Why
adcontextprotocol/adcp#5495 remains open because
@adcp/sdk@7.11.5only prints stripped input-schema fields as console warnings. CI JSON reports still miss the signal, so storyboard scenarios can appear to pass without exercising fields the runner sent.Changes
debug_logsentries whenSingleAgentClientstrips fields missing from an agent toolinputSchema.waitForCompletionoutput.noticesand aggregates them intoStoryboardResult.notices.src/lib/version.tswith the current 7.11.5 package version, matching the branch build output.Validation
npm run buildNODE_ENV=test node --test-timeout=60000 --test-force-exit --test-name-pattern "input_schema_field_stripped|v3 partial-schema field stripping" --test test/lib/request-validation.test.js test/lib/storyboard-notices.test.jsecho "fix(compliance): emit stripped field notices" | npx commitlint --config commitlint.config.jsnpx commitlint --from origin/backport-2114-adcp-3.0 --to HEAD --verbosenpm pack --silent --pack-destination .tmp-pack, then tarball grep confirmedinput_schema_field_strippedappears in packeddist/lib/core/SingleAgentClient.jsanddist/lib/testing/storyboard/runner.js.